From d56b7d52bf8edb43e73499a284e2e09cd958444f Mon Sep 17 00:00:00 2001 From: cirry <812852553@qq.com> Date: Fri, 10 Jul 2026 11:27:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=B0=E5=AF=8C=E7=9B=AE=E5=BD=95=E6=A0=91?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .reasonix/desktop-topic-auto-title-meta.json | 12 + .reasonix/desktop-topic-created-at.json | 4 +- .reasonix/desktop-topic-title-sources.json | 4 +- .reasonix/desktop-topic-titles.json | 4 +- src-tauri/src/lib.rs | 42 ++- src/App.vue | 49 ++++ src/components/ConfirmDialog.vue | 174 ++++++++++++ src/components/DirectorySidebar.vue | 10 +- src/components/TreeEntry.vue | 280 ++++++++++++++++++- 9 files changed, 567 insertions(+), 12 deletions(-) create mode 100644 src/components/ConfirmDialog.vue diff --git a/.reasonix/desktop-topic-auto-title-meta.json b/.reasonix/desktop-topic-auto-title-meta.json index e9896b9..d29b632 100644 --- a/.reasonix/desktop-topic-auto-title-meta.json +++ b/.reasonix/desktop-topic-auto-title-meta.json @@ -16,5 +16,17 @@ "userTurns": 1, "basisHash": "a29f890c9742dd7d", "updatedAt": 1783651532738 + }, + "topic_20260710-024855_5c4b1833149d66bb": { + "stage": 3, + "userTurns": 3, + "basisHash": "531788785cf8bacd", + "updatedAt": 1783652640849 + }, + "topic_20260710-030936_cedad2907c3954fe": { + "stage": 1, + "userTurns": 1, + "basisHash": "2066e5673ac2f507", + "updatedAt": 1783653328367 } } \ No newline at end of file diff --git a/.reasonix/desktop-topic-created-at.json b/.reasonix/desktop-topic-created-at.json index 911c88d..69c7d30 100644 --- a/.reasonix/desktop-topic-created-at.json +++ b/.reasonix/desktop-topic-created-at.json @@ -2,5 +2,7 @@ "topic_20260630-144952_fd0584fe8f266d6a": 1782830992641, "topic_20260703-022850_50330b4eb93f116d": 1783045730616, "topic_20260706-145507_076a32df5f4276b5": 1783349707873, - "topic_20260706-153317_238f65a7a470703c": 1783351997029 + "topic_20260706-153317_238f65a7a470703c": 1783351997029, + "topic_20260710-024855_5c4b1833149d66bb": 1783651735446, + "topic_20260710-030936_cedad2907c3954fe": 1783652976310 } \ No newline at end of file diff --git a/.reasonix/desktop-topic-title-sources.json b/.reasonix/desktop-topic-title-sources.json index b3d25e0..869dc70 100644 --- a/.reasonix/desktop-topic-title-sources.json +++ b/.reasonix/desktop-topic-title-sources.json @@ -40,5 +40,7 @@ "topic_20260707-095503_bf879b65e4666516": "auto", "topic_20260708-020616_9ab54d98daf65053": "auto", "topic_20260708-093419_71d1fea2e4b5a40f": "auto", - "topic_20260708-095428_990617acd4168b40": "auto" + "topic_20260708-095428_990617acd4168b40": "auto", + "topic_20260710-024855_5c4b1833149d66bb": "auto", + "topic_20260710-030936_cedad2907c3954fe": "auto" } \ No newline at end of file diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json index 970780a..834f765 100644 --- a/.reasonix/desktop-topic-titles.json +++ b/.reasonix/desktop-topic-titles.json @@ -40,5 +40,7 @@ "topic_20260707-095503_bf879b65e4666516": "帮我移除markdownEditor…", "topic_20260708-020616_9ab54d98daf65053": "帮我分析一下现在的markdownE…", "topic_20260708-093419_71d1fea2e4b5a40f": "src/components/Dir…", - "topic_20260708-095428_990617acd4168b40": "帮我检查一下这个应用在进行放大缩小的…" + "topic_20260708-095428_990617acd4168b40": "帮我检查一下这个应用在进行放大缩小的…", + "topic_20260710-024855_5c4b1833149d66bb": "帮我给右侧的目录树添加一个功能,当用…", + "topic_20260710-030936_cedad2907c3954fe": "鼠标放在tree上的时候,只有hov…" } \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5a32317..1d1597e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -190,6 +190,42 @@ fn pick_save_file(app: tauri::AppHandle) -> Option { .map(|p| p.to_string()) } +#[tauri::command] +fn create_folder(path: String) -> Result<(), String> { + fs::create_dir_all(&path).map_err(|e| format!("无法创建文件夹: {}", e)) +} + +#[tauri::command] +fn create_file(path: String) -> Result<(), String> { + if let Some(parent) = PathBuf::from(&path).parent() { + fs::create_dir_all(parent).map_err(|e| format!("无法创建父目录: {}", e))?; + } + fs::write(&path, "").map_err(|e| format!("无法创建文件: {}", e)) +} + +#[tauri::command] +fn delete_entry(path: String) -> Result<(), String> { + let p = PathBuf::from(&path); + if p.is_dir() { + fs::remove_dir_all(&p).map_err(|e| format!("无法删除文件夹: {}", e)) + } else if p.is_file() { + fs::remove_file(&p).map_err(|e| format!("无法删除文件: {}", e)) + } else { + Err("路径不存在".to_string()) + } +} + +#[tauri::command] +fn rename_entry(path: String, new_name: String) -> Result { + let p = PathBuf::from(&path); + let parent = p + .parent() + .ok_or_else(|| "无法获取父目录".to_string())?; + let new_path = parent.join(&new_name); + fs::rename(&p, &new_path).map_err(|e| format!("无法重命名: {}", e))?; + Ok(new_path.to_string_lossy().to_string()) +} + #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) @@ -209,7 +245,11 @@ pub fn run() { write_file, watch_workspace, unwatch_workspace, - pick_save_file + pick_save_file, + create_folder, + create_file, + delete_entry, + rename_entry ]) .setup(|app| { // ---- File menu ---- diff --git a/src/App.vue b/src/App.vue index 8946b37..6b7722b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -805,6 +805,51 @@ async function onToggleFolder(path: string) { } } +async function onCreateFolder(path: string) { + try { + await invoke("create_folder", { path }); + } catch (e) { + console.error("Failed to create folder:", e); + } + scheduleWorkspaceRefresh(); +} + +async function onCreateFile(path: string) { + try { + await invoke("create_file", { path }); + } catch (e) { + console.error("Failed to create file:", e); + } + scheduleWorkspaceRefresh(); +} + +async function onDeleteEntry(path: string) { + try { + await invoke("delete_entry", { path }); + } catch (e) { + console.error("Failed to delete entry:", e); + } + // Close the file if it's the one being deleted + if (currentFilePath.value != null && getWorkspaceIdentity(currentFilePath.value) === getWorkspaceIdentity(path)) { + resetMarkdownEditorState(); + currentFilePath.value = null; + } + scheduleWorkspaceRefresh(); +} + +async function onRenameEntry(path: string, newName: string) { + try { + const newPath = await invoke("rename_entry", { path, newName }); + // Update current file path if it was renamed + if (currentFilePath.value != null && getWorkspaceIdentity(currentFilePath.value) === getWorkspaceIdentity(path)) { + currentFilePath.value = newPath; + } + } catch (e) { + console.error("Failed to rename entry:", e); + } + scheduleWorkspaceRefresh(); +} + async function onOpenFile(path: string) { const fileType = getWorkspaceFileType(path); if (fileType === "image" || fileType === "video") { @@ -960,6 +1005,10 @@ registerTauriListener("menu-event", (event) => { @remove-workspace="removeRecentWorkspace" @toggle-folder="onToggleFolder" @open-file="onOpenFile" + @create-folder="onCreateFolder" + @create-file="onCreateFile" + @delete-entry="onDeleteEntry" + @rename-entry="onRenameEntry" @update-default-document-mode="updateDefaultDocumentMode" @update-app-theme="updateAppTheme" /> diff --git a/src/components/ConfirmDialog.vue b/src/components/ConfirmDialog.vue new file mode 100644 index 0000000..f93aa5e --- /dev/null +++ b/src/components/ConfirmDialog.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/src/components/DirectorySidebar.vue b/src/components/DirectorySidebar.vue index 2e6c8f8..5cf27e0 100644 --- a/src/components/DirectorySidebar.vue +++ b/src/components/DirectorySidebar.vue @@ -46,8 +46,10 @@ const emit = defineEmits<{ removeWorkspace: [path: string]; toggleFolder: [path: string]; openFile: [path: string]; - createFile: []; - createFolder: []; + createFile: [path: string]; + createFolder: [path: string]; + deleteEntry: [path: string]; + renameEntry: [path: string, newName: string]; updateDefaultDocumentMode: [mode: DocumentMode]; updateAppTheme: [theme: AppTheme]; }>(); @@ -371,6 +373,10 @@ function openSettings(page: SettingsPage) { :currentFilePath="currentFilePath" @toggle-folder="(path: string) => emit('toggleFolder', path)" @open-file="(path: string) => emit('openFile', path)" + @create-folder="(path: string) => emit('createFolder', path)" + @create-file="(path: string) => emit('createFile', path)" + @delete-entry="(path: string) => emit('deleteEntry', path)" + @rename-entry="(path: string, name: string) => emit('renameEntry', path, name)" /> diff --git a/src/components/TreeEntry.vue b/src/components/TreeEntry.vue index a7877cb..81a9dd4 100644 --- a/src/components/TreeEntry.vue +++ b/src/components/TreeEntry.vue @@ -4,7 +4,8 @@ export default { name: "TreeEntry" }; @@ -86,8 +241,9 @@ function onClick() {
- {{ entry.name }} + {{ entry.name }} + + + + + + +
+ +
+ +
+
@@ -130,4 +318,84 @@ function onClick() { background: var(--app-hover); color: var(--app-accent); } + +/* ── More button ────────────────────────────── */ +.tree-more-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border: none; + border-radius: 4px; + background: transparent; + color: var(--app-muted); + font-size: 14px; + cursor: pointer; + transition: + background-color 0.12s ease, + color 0.12s ease; + flex-shrink: 0; +} + +.tree-more-btn:hover { + background: var(--app-border); + color: var(--app-text); +} + +/* ── Dropdown ───────────────────────────────── */ +.tree-dropdown { + position: fixed; + z-index: 9999; + background: var(--app-surface); + border: 1px solid var(--app-border-strong); + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + min-width: 140px; + padding: 4px; +} + +.tree-dropdown-item { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 6px 10px; + border-radius: 6px; + font-size: 13px; + color: var(--app-text); + background: transparent; + border: none; + cursor: pointer; + text-align: left; + white-space: nowrap; +} + +.tree-dropdown-item:hover { + background-color: var(--app-hover); +} + +.tree-dropdown-item i { + font-size: 15px; + color: var(--app-muted); + flex-shrink: 0; +} + +.tree-dropdown-item-danger { + color: #e53e3e; +} + +.tree-dropdown-item-danger i { + color: #e53e3e; +} + +.tree-dropdown-item-danger:hover { + background-color: #fff5f5; +} + +.tree-dropdown-sep { + height: 1px; + margin: 4px 6px; + background: var(--app-border); +}