diff --git a/rust/crates/mnote-web/browser/tree-shell-filetree-menu-runtime.js b/rust/crates/mnote-web/browser/tree-shell-filetree-menu-runtime.js new file mode 100644 index 00000000..6004a2ad --- /dev/null +++ b/rust/crates/mnote-web/browser/tree-shell-filetree-menu-runtime.js @@ -0,0 +1,354 @@ +// MNote debug /tree shell filetree context menu runtime. + +import { + getFileTreeRowAssetId, + getFileTreeRowDocumentId, +} from "./tree-shell-filetree-runtime.js"; +import { normalizeTreeShellText as normalizeText } from "./tree-shell-state-runtime.js"; + +const createFileTreeMenuItem = (kind, label, options = {}) => ({ + kind, + label, + disabled: options.disabled === true, + reason: normalizeText(options.reason), + separatorBefore: options.separatorBefore === true, +}); + +function buildFileTreeMenuTarget(context, rowId, rowKind, documentId, assetId) { + const fileTreeRowById = context.getFileTreeRowById(); + const item = rowId ? fileTreeRowById.get(rowId) : null; + return { + item, + rowId: rowId || null, + rowKind: rowKind || item?.rowKind || "root", + documentId: documentId || getFileTreeRowDocumentId(item) || null, + assetId: assetId || getFileTreeRowAssetId(item) || null, + }; +} + +export function buildTreeShellFileTreeContextMenuProfile(context, target) { + const selectedFileTreeRowIds = context.getSelectedFileTreeRowIds(); + const fileTreeClipboard = context.getFileTreeClipboard(); + const selectedCount = selectedFileTreeRowIds.size; + const isMulti = selectedCount > 1 && target.rowId && selectedFileTreeRowIds.has(target.rowId); + const item = target.item; + const rowKind = target.rowKind || "root"; + const canPaste = Boolean(fileTreeClipboard.action && fileTreeClipboard.rowIds.length > 0); + const hasDocument = Boolean(getFileTreeRowDocumentId(item)); + const hasAsset = Boolean(getFileTreeRowAssetId(item)); + const localSource = context.sourceKind === "local_folder"; + const convexSource = context.sourceKind === "convex_workspace"; + const canCreateFolder = localSource; + const canCreatePage = rowKind === "root" || rowKind === "folder" || rowKind === "document"; + const canRename = + rowKind === "markdown" || + rowKind === "document" || + rowKind === "folder" || + rowKind === "index"; + const canCopyCut = + rowKind === "markdown" || + rowKind === "document" || + rowKind === "index"; + const canDelete = + rowKind === "markdown" || + rowKind === "document" || + rowKind === "index" || + (localSource && rowKind === "asset"); + + if (rowKind === "root") { + return [ + createFileTreeMenuItem("newPage", "新建页面", { + disabled: !canCreatePage, + reason: "当前 root 不支持新建页面", + }), + createFileTreeMenuItem("newFolder", "新建文件夹", { + disabled: !canCreateFolder, + reason: convexSource ? "Convex workspace 当前未暴露 folder capability" : "当前 source 不支持新建文件夹", + }), + createFileTreeMenuItem("upload", "上传/导入", { + disabled: true, + reason: localSource ? "外部文件请拖入 Explorer" : "Convex 上传 executor 尚未接入", + }), + createFileTreeMenuItem("refresh", "刷新", { separatorBefore: true }), + createFileTreeMenuItem("collapseAll", "全部折叠"), + ]; + } + + if (isMulti) { + return [ + createFileTreeMenuItem("copy", "复制"), + createFileTreeMenuItem("cut", "剪切"), + createFileTreeMenuItem("delete", "删除", { disabled: false }), + createFileTreeMenuItem("moveTo", "移动到", { + separatorBefore: true, + disabled: true, + reason: "目标选择器尚未接入 filetree 菜单", + }), + ]; + } + + const items = []; + if (rowKind === "folder") { + items.push(createFileTreeMenuItem("newPage", "新建页面", { + disabled: !canCreatePage, + reason: "当前文件夹不支持新建页面", + })); + items.push(createFileTreeMenuItem("newFolder", "新建文件夹", { + disabled: !canCreateFolder, + reason: convexSource ? "Convex workspace 当前未暴露 folder capability" : "当前 source 不支持新建文件夹", + })); + items.push(createFileTreeMenuItem("paste", "粘贴", { + disabled: !canPaste, + reason: "剪贴板为空", + })); + items.push(createFileTreeMenuItem("rename", "重命名", { + separatorBefore: true, + disabled: !canRename, + reason: "当前文件夹不支持重命名", + })); + items.push(createFileTreeMenuItem("delete", "删除", { + disabled: true, + reason: "目录删除尚未收口到统一 executor", + })); + return items; + } + + items.push(createFileTreeMenuItem("open", "打开", { + disabled: !hasDocument && !hasAsset, + reason: "当前行没有可打开资源", + })); + items.push(createFileTreeMenuItem("rename", "重命名", { + disabled: !canRename, + reason: "当前资源暂不支持重命名", + })); + items.push(createFileTreeMenuItem("copy", "复制", { + disabled: !canCopyCut, + reason: "当前资源暂不支持复制", + })); + items.push(createFileTreeMenuItem("cut", "剪切", { + disabled: !canCopyCut, + reason: "当前资源暂不支持剪切", + })); + items.push(createFileTreeMenuItem("paste", "粘贴", { + disabled: !canPaste, + reason: "剪贴板为空", + })); + items.push(createFileTreeMenuItem("moveTo", "移动到", { + disabled: true, + reason: "目标选择器尚未接入 filetree 菜单", + })); + items.push(createFileTreeMenuItem("delete", "删除", { + disabled: !canDelete, + reason: "当前资源暂不支持删除", + })); + items.push(createFileTreeMenuItem("reveal", "Reveal", { separatorBefore: true })); + if (hasAsset) { + items.push(createFileTreeMenuItem("download", "下载", { + disabled: true, + reason: "下载 executor 尚未接入", + })); + } + if (item?.capabilities?.includes("share")) { + items.push(createFileTreeMenuItem("share", "分享", { separatorBefore: true })); + } + if (item?.capabilities?.includes("publish")) { + items.push(createFileTreeMenuItem("publish", "发布")); + } + return items; +} + +export function createTreeShellFileTreeMenuRuntime(context) { + let activeFileTreeMenuElement = null; + + const closeFileTreeContextMenu = () => { + if (!activeFileTreeMenuElement) return; + activeFileTreeMenuElement.remove(); + activeFileTreeMenuElement = null; + }; + + const executeFileTreeContextMenuAction = async (kind, target) => { + closeFileTreeContextMenu(); + const item = target.item; + if (kind === "open") { + if (item) context.openHydratedFileTreeItem(item); + return; + } + if (kind === "rename") { + if (target.rowId && getFileTreeRowDocumentId(item)) { + context.beginInlineRename("filetree", target.rowId); + } + return; + } + if (kind === "copy" || kind === "cut") { + const selectedFileTreeRowIds = context.getSelectedFileTreeRowIds(); + if (target.rowId && !selectedFileTreeRowIds.has(target.rowId)) { + context.commitFileTreeSelection({ + selectedRowIds: [target.rowId], + anchorRowId: target.rowId, + focusedRowId: target.rowId, + }); + context.syncFileTreeSelectionDom(); + } + context.setFileTreeClipboard(kind); + return; + } + if (kind === "paste") { + await context.pasteFileTreeClipboardInto(target.rowId); + return; + } + if (kind === "delete") { + await context.runFileTreeDelete(target.rowId); + return; + } + if (kind === "newPage") { + const parentId = item && item.rowKind === "folder" + ? item.nodeId + : item && item.rowKind === "document" + ? item.nodeId + : null; + await context.handleCreate(parentId); + return; + } + if (kind === "newFolder") { + const parentId = item && item.rowKind === "folder" ? item.nodeId : null; + const result = await context.sendCommand({ + action: "createFolder", + workspaceId: context.workspaceId, + documentId: "", + parentId, + title: "新建文件夹", + }); + context.setStatus("创建文件夹成功"); + context.setLastAction("已创建新建文件夹"); + context.scheduleRefresh({ + renameRowId: context.localCreatedRowIdFromCommandResult(result, "folder"), + }); + return result; + } + if (kind === "refresh") { + await context.refreshLocalFolderSnapshot(); + return; + } + if (kind === "collapseAll") { + context.expanded.clear(); + context.renderTree(); + return; + } + if (kind === "reveal") { + context.setLastAction(`Reveal ${target.documentId || target.assetId || target.rowId || "root"}`); + context.postToHost("tree.filetree.reveal", { + documentId: target.documentId, + assetId: target.assetId, + rowId: target.rowId, + payload: target, + }); + return; + } + context.postToHost(`tree.filetree.${kind}`, { + documentId: target.documentId, + assetId: target.assetId, + rowId: target.rowId, + payload: target, + }); + }; + + const openFileTreeContextMenu = ({ + documentId, + assetId, + rowId, + rowKind, + clientX, + clientY, + }) => { + closeFileTreeContextMenu(); + const target = buildFileTreeMenuTarget(context, rowId, rowKind, documentId, assetId); + const profile = buildTreeShellFileTreeContextMenuProfile(context, target); + const menu = document.createElement("div"); + menu.className = "tree-context-menu"; + menu.dataset.testid = "filetree-context-menu"; + menu.dataset.sourceKind = context.sourceKind; + menu.dataset.rowKind = target.rowKind; + menu.setAttribute("role", "menu"); + + profile.forEach((entry) => { + if (entry.separatorBefore) { + const separator = document.createElement("div"); + separator.className = "tree-menu-separator"; + separator.setAttribute("role", "separator"); + menu.appendChild(separator); + } + const button = document.createElement("button"); + button.type = "button"; + button.className = "tree-menu-item"; + button.dataset.menuAction = entry.kind; + button.setAttribute("role", "menuitem"); + button.textContent = entry.label; + if (entry.disabled) { + button.disabled = true; + if (entry.reason) { + button.title = entry.reason; + button.dataset.disabledReason = entry.reason; + } + } else { + button.addEventListener("click", () => { + void executeFileTreeContextMenuAction(entry.kind, target); + }); + } + menu.appendChild(button); + }); + + const closeOnOutside = (event) => { + if (activeFileTreeMenuElement && !activeFileTreeMenuElement.contains(event.target)) { + closeFileTreeContextMenu(); + document.removeEventListener("mousedown", closeOnOutside, true); + document.removeEventListener("keydown", closeOnKeyDown, true); + } + }; + const closeOnKeyDown = (event) => { + if (event.key === "Escape") { + closeFileTreeContextMenu(); + document.removeEventListener("mousedown", closeOnOutside, true); + document.removeEventListener("keydown", closeOnKeyDown, true); + } + }; + + document.body.appendChild(menu); + const x = Number.isFinite(clientX) ? clientX : 16; + const y = Number.isFinite(clientY) ? clientY : 16; + const rect = menu.getBoundingClientRect(); + menu.style.left = `${Math.max(4, Math.min(x, window.innerWidth - rect.width - 4))}px`; + menu.style.top = `${Math.max(4, Math.min(y, window.innerHeight - rect.height - 4))}px`; + activeFileTreeMenuElement = menu; + window.setTimeout(() => { + document.addEventListener("mousedown", closeOnOutside, true); + document.addEventListener("keydown", closeOnKeyDown, true); + }, 0); + context.setLastAction( + assetId + ? `已打开资源 ${assetId} 的更多操作` + : `已打开文件树节点 ${documentId || rowId || "unknown"} 的更多操作`, + ); + context.postToHost("tree.filetree.context-menu", { + documentId, + assetId, + rowId, + rowKind, + payload: { + documentId, + assetId, + rowId, + rowKind, + x: clientX, + y: clientY, + }, + x: clientX, + y: clientY, + }); + }; + + return { + closeFileTreeContextMenu, + executeFileTreeContextMenuAction, + openFileTreeContextMenu, + }; +} diff --git a/rust/crates/mnote-web/browser/tree-shell-runtime.js b/rust/crates/mnote-web/browser/tree-shell-runtime.js index 741fa4e1..05aaeea5 100644 --- a/rust/crates/mnote-web/browser/tree-shell-runtime.js +++ b/rust/crates/mnote-web/browser/tree-shell-runtime.js @@ -20,6 +20,9 @@ import { getFileTreeRowOwnerDocumentId, normalizeTreeShellTreeItems, } from "./tree-shell-filetree-runtime.js"; +import { + createTreeShellFileTreeMenuRuntime, +} from "./tree-shell-filetree-menu-runtime.js"; import { focusTreeShellPickerRowElement, hydrateTreeShellInitialPageTree, @@ -295,7 +298,6 @@ function startTreeShellRuntime() { let fileTreeHoverExpandTimer = 0; let fileTreeClipboard = { action: null, rowIds: [] }; let inlineRenameState = { mode: null, id: null, committing: false }; - let activeFileTreeMenuElement = null; let activeFileTreePreflightElement = null; let activeCursor = itemById.get(currentActiveDocumentId) || null; @@ -2195,160 +2197,6 @@ function startTreeShellRuntime() { applyPickerStateAction({ kind: normalizedCommand }); }; - const closeFileTreeContextMenu = () => { - if (!activeFileTreeMenuElement) return; - activeFileTreeMenuElement.remove(); - activeFileTreeMenuElement = null; - }; - - const buildFileTreeMenuTarget = (rowId, rowKind, documentId, assetId) => { - const item = rowId ? fileTreeRowById.get(rowId) : null; - return { - item, - rowId: rowId || null, - rowKind: rowKind || item?.rowKind || "root", - documentId: documentId || getFileTreeRowDocumentId(item) || null, - assetId: assetId || getFileTreeRowAssetId(item) || null, - }; - }; - - const createFileTreeMenuItem = (kind, label, options = {}) => ({ - kind, - label, - disabled: options.disabled === true, - reason: normalizeText(options.reason), - separatorBefore: options.separatorBefore === true, - }); - - const buildFileTreeContextMenuProfile = (target) => { - const selectedCount = selectedFileTreeRowIds.size; - const isMulti = selectedCount > 1 && target.rowId && selectedFileTreeRowIds.has(target.rowId); - const item = target.item; - const rowKind = target.rowKind || "root"; - const canPaste = Boolean(fileTreeClipboard.action && fileTreeClipboard.rowIds.length > 0); - const hasDocument = Boolean(getFileTreeRowDocumentId(item)); - const hasAsset = Boolean(getFileTreeRowAssetId(item)); - const localSource = sourceKind === "local_folder"; - const convexSource = sourceKind === "convex_workspace"; - const canCreateFolder = localSource; - const canCreatePage = rowKind === "root" || rowKind === "folder" || rowKind === "document"; - const canRename = - rowKind === "markdown" || - rowKind === "document" || - rowKind === "folder" || - rowKind === "index"; - const canCopyCut = - rowKind === "markdown" || - rowKind === "document" || - rowKind === "index"; - const canDelete = - rowKind === "markdown" || - rowKind === "document" || - rowKind === "index" || - (localSource && rowKind === "asset"); - - if (rowKind === "root") { - return [ - createFileTreeMenuItem("newPage", "新建页面", { - disabled: !canCreatePage, - reason: "当前 root 不支持新建页面", - }), - createFileTreeMenuItem("newFolder", "新建文件夹", { - disabled: !canCreateFolder, - reason: convexSource ? "Convex workspace 当前未暴露 folder capability" : "当前 source 不支持新建文件夹", - }), - createFileTreeMenuItem("upload", "上传/导入", { - disabled: true, - reason: localSource ? "外部文件请拖入 Explorer" : "Convex 上传 executor 尚未接入", - }), - createFileTreeMenuItem("refresh", "刷新", { separatorBefore: true }), - createFileTreeMenuItem("collapseAll", "全部折叠"), - ]; - } - - if (isMulti) { - return [ - createFileTreeMenuItem("copy", "复制"), - createFileTreeMenuItem("cut", "剪切"), - createFileTreeMenuItem("delete", "删除", { disabled: false }), - createFileTreeMenuItem("moveTo", "移动到", { - separatorBefore: true, - disabled: true, - reason: "目标选择器尚未接入 filetree 菜单", - }), - ]; - } - - const items = []; - if (rowKind === "folder") { - items.push(createFileTreeMenuItem("newPage", "新建页面", { - disabled: !canCreatePage, - reason: "当前文件夹不支持新建页面", - })); - items.push(createFileTreeMenuItem("newFolder", "新建文件夹", { - disabled: !canCreateFolder, - reason: convexSource ? "Convex workspace 当前未暴露 folder capability" : "当前 source 不支持新建文件夹", - })); - items.push(createFileTreeMenuItem("paste", "粘贴", { - disabled: !canPaste, - reason: "剪贴板为空", - })); - items.push(createFileTreeMenuItem("rename", "重命名", { - separatorBefore: true, - disabled: !canRename, - reason: "当前文件夹不支持重命名", - })); - items.push(createFileTreeMenuItem("delete", "删除", { - disabled: true, - reason: "目录删除尚未收口到统一 executor", - })); - return items; - } - - items.push(createFileTreeMenuItem("open", "打开", { - disabled: !hasDocument && !hasAsset, - reason: "当前行没有可打开资源", - })); - items.push(createFileTreeMenuItem("rename", "重命名", { - disabled: !canRename, - reason: "当前资源暂不支持重命名", - })); - items.push(createFileTreeMenuItem("copy", "复制", { - disabled: !canCopyCut, - reason: "当前资源暂不支持复制", - })); - items.push(createFileTreeMenuItem("cut", "剪切", { - disabled: !canCopyCut, - reason: "当前资源暂不支持剪切", - })); - items.push(createFileTreeMenuItem("paste", "粘贴", { - disabled: !canPaste, - reason: "剪贴板为空", - })); - items.push(createFileTreeMenuItem("moveTo", "移动到", { - disabled: true, - reason: "目标选择器尚未接入 filetree 菜单", - })); - items.push(createFileTreeMenuItem("delete", "删除", { - disabled: !canDelete, - reason: "当前资源暂不支持删除", - })); - items.push(createFileTreeMenuItem("reveal", "Reveal", { separatorBefore: true })); - if (hasAsset) { - items.push(createFileTreeMenuItem("download", "下载", { - disabled: true, - reason: "下载 executor 尚未接入", - })); - } - if (item?.capabilities?.includes("share")) { - items.push(createFileTreeMenuItem("share", "分享", { separatorBefore: true })); - } - if (item?.capabilities?.includes("publish")) { - items.push(createFileTreeMenuItem("publish", "发布")); - } - return items; - }; - const localCreatedRowIdFromCommandResult = (result, rowKind) => { const relativePath = typeof result?.execution?.relativePath === "string" && result.execution.relativePath.trim() @@ -2360,184 +2208,31 @@ function startTreeShellRuntime() { return `local:${rowKind}:${relativePath}`; }; - const executeFileTreeContextMenuAction = async (kind, target) => { - closeFileTreeContextMenu(); - const item = target.item; - if (kind === "open") { - if (item) openHydratedFileTreeItem(item); - return; - } - if (kind === "rename") { - if (target.rowId && getFileTreeRowDocumentId(item)) { - beginInlineRename("filetree", target.rowId); - } - return; - } - if (kind === "copy" || kind === "cut") { - if (target.rowId && !selectedFileTreeRowIds.has(target.rowId)) { - commitFileTreeSelection({ - selectedRowIds: [target.rowId], - anchorRowId: target.rowId, - focusedRowId: target.rowId, - }); - syncFileTreeSelectionDom(); - } - setFileTreeClipboard(kind); - return; - } - if (kind === "paste") { - await pasteFileTreeClipboardInto(target.rowId); - return; - } - if (kind === "delete") { - await runFileTreeDelete(target.rowId); - return; - } - if (kind === "newPage") { - const parentId = item && item.rowKind === "folder" - ? item.nodeId - : item && item.rowKind === "document" - ? item.nodeId - : null; - await handleCreate(parentId); - return; - } - if (kind === "newFolder") { - const parentId = item && item.rowKind === "folder" ? item.nodeId : null; - const result = await sendCommand({ - action: "createFolder", - workspaceId, - documentId: "", - parentId, - title: "新建文件夹", - }); - setStatus("创建文件夹成功"); - setLastAction("已创建新建文件夹"); - scheduleRefresh({ - renameRowId: localCreatedRowIdFromCommandResult(result, "folder"), - }); - return result; - } - if (kind === "refresh") { - await refreshLocalFolderSnapshot(); - return; - } - if (kind === "collapseAll") { - expanded.clear(); - renderTree(); - return; - } - if (kind === "reveal") { - setLastAction(`Reveal ${target.documentId || target.assetId || target.rowId || "root"}`); - postToHost("tree.filetree.reveal", { - documentId: target.documentId, - assetId: target.assetId, - rowId: target.rowId, - payload: target, - }); - return; - } - postToHost(`tree.filetree.${kind}`, { - documentId: target.documentId, - assetId: target.assetId, - rowId: target.rowId, - payload: target, - }); - }; - - const openFileTreeContextMenu = ({ - documentId, - assetId, - rowId, - rowKind, - clientX, - clientY, - }) => { - closeFileTreeContextMenu(); - const target = buildFileTreeMenuTarget(rowId, rowKind, documentId, assetId); - const profile = buildFileTreeContextMenuProfile(target); - const menu = document.createElement("div"); - menu.className = "tree-context-menu"; - menu.dataset.testid = "filetree-context-menu"; - menu.dataset.sourceKind = sourceKind; - menu.dataset.rowKind = target.rowKind; - menu.setAttribute("role", "menu"); - - profile.forEach((entry) => { - if (entry.separatorBefore) { - const separator = document.createElement("div"); - separator.className = "tree-menu-separator"; - separator.setAttribute("role", "separator"); - menu.appendChild(separator); - } - const button = document.createElement("button"); - button.type = "button"; - button.className = "tree-menu-item"; - button.dataset.menuAction = entry.kind; - button.setAttribute("role", "menuitem"); - button.textContent = entry.label; - if (entry.disabled) { - button.disabled = true; - if (entry.reason) { - button.title = entry.reason; - button.dataset.disabledReason = entry.reason; - } - } else { - button.addEventListener("click", () => { - void executeFileTreeContextMenuAction(entry.kind, target); - }); - } - menu.appendChild(button); - }); - - const closeOnOutside = (event) => { - if (activeFileTreeMenuElement && !activeFileTreeMenuElement.contains(event.target)) { - closeFileTreeContextMenu(); - document.removeEventListener("mousedown", closeOnOutside, true); - document.removeEventListener("keydown", closeOnKeyDown, true); - } - }; - const closeOnKeyDown = (event) => { - if (event.key === "Escape") { - closeFileTreeContextMenu(); - document.removeEventListener("mousedown", closeOnOutside, true); - document.removeEventListener("keydown", closeOnKeyDown, true); - } - }; - - document.body.appendChild(menu); - const x = Number.isFinite(clientX) ? clientX : 16; - const y = Number.isFinite(clientY) ? clientY : 16; - const rect = menu.getBoundingClientRect(); - menu.style.left = `${Math.max(4, Math.min(x, window.innerWidth - rect.width - 4))}px`; - menu.style.top = `${Math.max(4, Math.min(y, window.innerHeight - rect.height - 4))}px`; - activeFileTreeMenuElement = menu; - window.setTimeout(() => { - document.addEventListener("mousedown", closeOnOutside, true); - document.addEventListener("keydown", closeOnKeyDown, true); - }, 0); - setLastAction( - assetId - ? `已打开资源 ${assetId} 的更多操作` - : `已打开文件树节点 ${documentId || rowId || "unknown"} 的更多操作`, - ); - postToHost("tree.filetree.context-menu", { - documentId, - assetId, - rowId, - rowKind, - payload: { - documentId, - assetId, - rowId, - rowKind, - x: clientX, - y: clientY, - }, - x: clientX, - y: clientY, - }); - }; + const fileTreeMenuRuntime = createTreeShellFileTreeMenuRuntime({ + sourceKind, + workspaceId, + getFileTreeRowById: () => fileTreeRowById, + getSelectedFileTreeRowIds: () => selectedFileTreeRowIds, + getFileTreeClipboard: () => fileTreeClipboard, + setFileTreeClipboard, + commitFileTreeSelection, + syncFileTreeSelectionDom, + pasteFileTreeClipboardInto, + runFileTreeDelete, + handleCreate: (...args) => handleCreate(...args), + sendCommand, + setStatus, + setLastAction, + scheduleRefresh, + refreshLocalFolderSnapshot, + expanded, + renderTree: () => renderTree(), + postToHost, + openHydratedFileTreeItem: (...args) => openHydratedFileTreeItem(...args), + beginInlineRename: (...args) => beginInlineRename(...args), + localCreatedRowIdFromCommandResult, + }); + const openFileTreeContextMenu = fileTreeMenuRuntime.openFileTreeContextMenu; const openContextMenu = (nodeId, clientX, clientY) => { if (!nodeId || mode !== "page") return; diff --git a/rust/crates/mnote-web/src/routes/mod.rs b/rust/crates/mnote-web/src/routes/mod.rs index b761bd8c..d7b63da1 100644 --- a/rust/crates/mnote-web/src/routes/mod.rs +++ b/rust/crates/mnote-web/src/routes/mod.rs @@ -182,6 +182,10 @@ pub fn build_router(state: AppState) -> Router { "/api/mnote-browser-runtime/tree-shell-filetree-runtime.js", get(web_shell::tree_shell_filetree_runtime_asset), ) + .route( + "/api/mnote-browser-runtime/tree-shell-filetree-menu-runtime.js", + get(web_shell::tree_shell_filetree_menu_runtime_asset), + ) .route( "/api/mnote-browser-runtime/tree-shell-picker-runtime.js", get(web_shell::tree_shell_picker_runtime_asset), @@ -645,6 +649,7 @@ mod tests { "/api/mnote-browser-runtime/tree-shell-state-runtime.js", "/api/mnote-browser-runtime/tree-shell-icons-runtime.js", "/api/mnote-browser-runtime/tree-shell-filetree-runtime.js", + "/api/mnote-browser-runtime/tree-shell-filetree-menu-runtime.js", "/api/mnote-browser-runtime/tree-shell-picker-runtime.js", "/api/mnote-browser-runtime/tree-shell-dom-runtime.js", "/api/mnote-browser-runtime/document-conflict-panel-runtime.js", diff --git a/rust/crates/mnote-web/src/routes/tree.rs b/rust/crates/mnote-web/src/routes/tree.rs index 35f29ded..c9497eef 100644 --- a/rust/crates/mnote-web/src/routes/tree.rs +++ b/rust/crates/mnote-web/src/routes/tree.rs @@ -2395,6 +2395,8 @@ mod tests { const TREE_SHELL_RUNTIME_JS: &str = include_str!("../../browser/tree-shell-runtime.js"); const TREE_SHELL_FILETREE_RUNTIME_JS: &str = include_str!("../../browser/tree-shell-filetree-runtime.js"); + const TREE_SHELL_FILETREE_MENU_RUNTIME_JS: &str = + include_str!("../../browser/tree-shell-filetree-menu-runtime.js"); use super::{ collect_filetree_render_rows, create_command_wire, TreeCommandEnvelopeContext, @@ -2676,9 +2678,12 @@ mod tests { assert!(html.contains("\"rowKind\":\"asset_folder\"")); assert!(html.contains("\"resourceMeta\"")); assert!(TREE_SHELL_RUNTIME_JS.contains("tree-shell-filetree-runtime.js")); + assert!(TREE_SHELL_RUNTIME_JS.contains("tree-shell-filetree-menu-runtime.js")); assert!( TREE_SHELL_FILETREE_RUNTIME_JS.contains("function getFileTreeRowOwnerDocumentId(item)") ); + assert!(TREE_SHELL_FILETREE_MENU_RUNTIME_JS + .contains("function buildFileTreeMenuTarget(context")); assert!(TREE_SHELL_RUNTIME_JS .contains("row.dataset.ownerDocumentId = ownerDocumentId || \"\";")); assert!( diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs index 8211bd2d..6c8d8f8b 100644 --- a/rust/crates/mnote-web/src/routes/web_shell.rs +++ b/rust/crates/mnote-web/src/routes/web_shell.rs @@ -994,6 +994,20 @@ pub async fn tree_shell_filetree_runtime_asset() -> Response { .unwrap_or_else(|_| Response::new(Body::empty())) } +pub async fn tree_shell_filetree_menu_runtime_asset() -> Response { + const JS: &str = include_str!("../../browser/tree-shell-filetree-menu-runtime.js"); + Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + "application/javascript; charset=utf-8", + ) + .header(header::CACHE_CONTROL, "no-store") + .header(HEADER_MNOTE_WEB_OWNER, "mnote-web") + .body(Body::from(JS)) + .unwrap_or_else(|_| Response::new(Body::empty())) +} + pub async fn tree_shell_picker_runtime_asset() -> Response { const JS: &str = include_str!("../../browser/tree-shell-picker-runtime.js"); Response::builder()