From 289a44acea55794397c78fd7268fc0e322a728af Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Sun, 24 May 2026 13:59:54 +0800 Subject: [PATCH] fix: drop files on markdown row into same folder --- ...filetree-drag-upload-target-parent-folder-v1.md | 8 ++++++++ rust/crates/mnote-web/src/ssr/pages/layout.rs | 14 +++++++++++--- ...k163-local-folder-unified-tree-browser-smoke.js | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/bugs/04-tree-domain/done/4-49-filetree-drag-upload-target-parent-folder-v1.md b/bugs/04-tree-domain/done/4-49-filetree-drag-upload-target-parent-folder-v1.md index 8a843115..6761d9ba 100644 --- a/bugs/04-tree-domain/done/4-49-filetree-drag-upload-target-parent-folder-v1.md +++ b/bugs/04-tree-domain/done/4-49-filetree-drag-upload-target-parent-folder-v1.md @@ -326,3 +326,11 @@ PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium node scripts/task479-local-folde - 2.8 页面设置中“隐藏本地 Markdown 文件标题”的可切换 UI / 持久化已实现:新增 `hideTitleHeader` page option,本地 Markdown 默认开启;关闭后页头立即显示,刷新后从 `.mnote/page-options.json` 恢复。`task479` Check 6 通过。 - 2.2 watcher 缺失状态刷新已收口:新增本地文件 `stat` 端点,编辑器附件增强逻辑在 watcher / projection 刷新后重新检查本地链接存在性;删除真实附件后不刷新页面即标记 `data-mnote-attachment-missing="true"`,并保持 Markdown 正文不变。`task479` Check 3 通过。 - 2.6 滚动边界已补齐显式 wheel 拦截:Workbench 根容器固定视口高度,内部滚动容器隔离滚动,resize handle 的 wheel 事件不再穿透到 body。`task479` Check 5 通过。 + +### 6.1 2026-05-24 追加:拖到 Markdown 行的外部文件落盘修复 + +- 复现范围已进一步确认:问题不是普通 folder row,而是把外部文件拖到 file tree 的 `.md` 行时出现。 +- 根因:`layout.rs` 里 file tree 外部 drop 只在 folder/directory row 才传入目录路径,`.md` 行会被序列化成 `targetRelativePath = null/空值`,后续上传分流又把“字段存在”当成 `filetree.folder.drop`,最终把文件落到 root。 +- 修复:新增 `fileTreeRowLocalUploadTargetRelativePath()`,`.md` 行改为解析为“该 md 所在目录”;外部 drop 只在有真实目录路径时才走 folder upload,不再回退写 root。 +- 回归:`scripts/task163-local-folder-unified-tree-browser-smoke.js` 增加断言,确保拖到 Markdown 行不会落到 root。 +- 验证:用户已手动确认该 bug 修复完成。 diff --git a/rust/crates/mnote-web/src/ssr/pages/layout.rs b/rust/crates/mnote-web/src/ssr/pages/layout.rs index c9cb7501..1a89c43b 100644 --- a/rust/crates/mnote-web/src/ssr/pages/layout.rs +++ b/rust/crates/mnote-web/src/ssr/pages/layout.rs @@ -5073,6 +5073,16 @@ const SIDEBAR_TREE_JS: &str = r##" return ''; } + function fileTreeRowLocalUploadTargetRelativePath(row) { + if (!(row instanceof HTMLElement)) return ''; + var kind = fileTreeRowKind(row); + var relativePath = fileTreeRowLocalRelativePath(row); + if (!relativePath) return ''; + if (kind === 'folder' || kind === 'directory') return relativePath; + var lastSlash = relativePath.lastIndexOf('/'); + return lastSlash >= 0 ? relativePath.slice(0, lastSlash) : ''; + } + function fileTreeRowKind(row) { if (!(row instanceof HTMLElement)) return ''; return String(row.getAttribute('data-row-kind') || '').trim(); @@ -10111,9 +10121,7 @@ const SIDEBAR_TREE_JS: &str = r##" targetRowKind: targetRow ? targetRow.getAttribute('data-row-kind') : 'root', documentId: targetRow ? targetRow.getAttribute('data-document-id') || targetRow.getAttribute('data-doc-id') : null, assetId: targetRow ? targetRow.getAttribute('data-asset-id') : null, - targetRelativePath: targetRow && (targetRow.getAttribute('data-row-kind') === 'folder' || targetRow.getAttribute('data-row-kind') === 'directory') - ? fileTreeRowLocalRelativePath(targetRow) - : null, + targetRelativePath: targetRow ? fileTreeRowLocalUploadTargetRelativePath(targetRow) : '', uploadIntent: 'filetree.folder.drop' }; if (activeFileTreeDropRow instanceof HTMLElement) activeFileTreeDropRow.setAttribute('data-drop-target', 'false'); diff --git a/scripts/task163-local-folder-unified-tree-browser-smoke.js b/scripts/task163-local-folder-unified-tree-browser-smoke.js index 1cf221b5..0af31083 100644 --- a/scripts/task163-local-folder-unified-tree-browser-smoke.js +++ b/scripts/task163-local-folder-unified-tree-browser-smoke.js @@ -727,6 +727,7 @@ async function run() { await confirmPreflight(page); await page.waitForTimeout(800); assert(fs.existsSync(path.join(root, "docs", "drop-on-md.txt")), "drop 到 md 文件时应写入该 md 的父目录"); + assert(!fs.existsSync(path.join(root, "drop-on-md.txt")), "drop 到 Markdown 行时不应回退写入 root"); const readonlyDropRequestCount = requests.length; await dispatchExternalFileDrop(page, readonlyRow, "readonly-drop.txt", "readonly"); await waitForPreflight(page, "操作预检失败");