mirror of
https://github.com/delibae/claude-prism.git
synced 2026-10-07 02:58:00 +00:00
feat: implement clipboard file path reading and Cmd+V paste functionality in sidebar
This commit is contained in:
parent
6b705ee447
commit
10ac562d38
3 changed files with 87 additions and 2 deletions
|
|
@ -135,6 +135,52 @@ fn create_new_window(app: tauri::AppHandle) -> Result<(), String> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// --- Clipboard file paths (for Cmd+V paste in file tree) ---
|
||||
|
||||
#[tauri::command]
|
||||
async fn read_clipboard_file_paths() -> Result<Vec<String>, String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
tokio::task::spawn_blocking(|| {
|
||||
let script = concat!(
|
||||
"set thePaths to \"\"\n",
|
||||
"try\n",
|
||||
"\tset theFiles to the clipboard as \u{00ab}class furl\u{00bb}\n",
|
||||
"\tset thePaths to POSIX path of theFiles\n",
|
||||
"on error\n",
|
||||
"\ttry\n",
|
||||
"\t\trepeat with f in (the clipboard as list)\n",
|
||||
"\t\t\ttry\n",
|
||||
"\t\t\t\tset thePaths to thePaths & POSIX path of (f as \u{00ab}class furl\u{00bb}) & linefeed\n",
|
||||
"\t\t\tend try\n",
|
||||
"\t\tend repeat\n",
|
||||
"\tend try\n",
|
||||
"end try\n",
|
||||
"return thePaths",
|
||||
);
|
||||
|
||||
let output = std::process::Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg(script)
|
||||
.output()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if stdout.is_empty() {
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Ok(stdout.lines().filter(|l| !l.is_empty()).map(|s| s.to_string()).collect())
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
// Load .env file (walks up from cwd to find it)
|
||||
|
|
@ -158,6 +204,7 @@ pub fn run() {
|
|||
create_new_window,
|
||||
detect_editors,
|
||||
open_in_editor,
|
||||
read_clipboard_file_paths,
|
||||
latex::compile_latex,
|
||||
latex::synctex_edit,
|
||||
claude::check_claude_status,
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export function ProjectPicker() {
|
|||
<ClockIcon className="size-4" />
|
||||
<span>Recent Projects</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="max-h-64 space-y-1 overflow-y-auto">
|
||||
{recentProjects.map((project) => (
|
||||
<div
|
||||
key={project.path}
|
||||
|
|
|
|||
|
|
@ -298,6 +298,39 @@ export function Sidebar() {
|
|||
};
|
||||
}, [importFiles]);
|
||||
|
||||
// Track selected folder for paste target
|
||||
const [pasteTargetFolder, setPasteTargetFolder] = useState<string | undefined>();
|
||||
|
||||
// ─── Cmd+V paste files from OS clipboard ───
|
||||
useEffect(() => {
|
||||
const handleKeyDown = async (e: KeyboardEvent) => {
|
||||
if (!(e.metaKey || e.ctrlKey) || e.key !== "v") return;
|
||||
|
||||
// Don't intercept paste in text inputs / editor (contentEditable)
|
||||
const active = document.activeElement;
|
||||
if (
|
||||
active &&
|
||||
(active.tagName === "INPUT" ||
|
||||
active.tagName === "TEXTAREA" ||
|
||||
(active as HTMLElement).isContentEditable)
|
||||
)
|
||||
return;
|
||||
|
||||
try {
|
||||
const paths = await invoke<string[]>("read_clipboard_file_paths");
|
||||
if (paths.length > 0) {
|
||||
e.preventDefault();
|
||||
await importFiles(paths, pasteTargetFolder);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[paste] read clipboard failed:", err);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [importFiles, pasteTargetFolder]);
|
||||
|
||||
// dnd-kit drag-and-drop (uses PointerSensor — works in Tauri WKWebView)
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
||||
|
|
@ -370,6 +403,7 @@ export function Sidebar() {
|
|||
}, [activeFileId]);
|
||||
|
||||
const toggleFolder = useCallback((path: string) => {
|
||||
setPasteTargetFolder(path);
|
||||
setExpandedFolders((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(path)) next.delete(path);
|
||||
|
|
@ -579,7 +613,11 @@ export function Sidebar() {
|
|||
activeFileId={activeFileId}
|
||||
expandedFolders={expandedFolders}
|
||||
onToggleFolder={toggleFolder}
|
||||
onSelectFile={setActiveFile}
|
||||
onSelectFile={(id: string) => {
|
||||
const parent = id.includes("/") ? id.substring(0, id.lastIndexOf("/")) : undefined;
|
||||
setPasteTargetFolder(parent);
|
||||
setActiveFile(id);
|
||||
}}
|
||||
onNewFile={openNewFileDialog}
|
||||
onNewFolder={openNewFolderDialog}
|
||||
onImport={handleImport}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue