export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => { const { applyRemoveDocumentDelta, createFileTreeFolder, createPage, cssEscape, currentDocumentId, currentRootUri, currentSourceKind, deleteSingleFileTreeAsset, dispatchSidebarEvent, dispatchTreeCommand, ensureFileTreeWritableTarget, fileTreeRuntimeDeps, fileTreeRuntimeFunction, fileTreeSelectionRuntimeDeps, fileTreeSelectionRuntimeFunction, isFileTreePageRow, localFilePathFromAssetId, normalizeFileTreePageRenameTitle, openLocalAssetFromFileTree, openEditorAttachmentDetail, openEditorAttachmentDownload, openEditorAttachmentEditTab, openEditorAttachmentNewWindow, openLocalResourceInActiveTab, knowledgeHostUrl, refreshLocalFolderSidebarSnapshot, removeFileTreeAssetRow, revealFileTreeResource, resolveWorkspaceId, runtimeState, selectedSidebarFileTreeSelection, updateTitleEverywhere, validateFileTreeRename, } = dependencies; const sidebarFileTreeSelection = selectedSidebarFileTreeSelection; var fileTreeOperationBatchCounter = 0; function nextFileTreeOperationBatchId(action) { fileTreeOperationBatchCounter += 1; return 'filetree-' + String(action || 'operation') + '-' + Date.now() + '-' + fileTreeOperationBatchCounter; } function rowTitle(row) { var title = row ? row.querySelector(':scope > .tree-link > .tree-link-title') : null; return title && title.textContent ? title.textContent.trim() : '无标题'; } function rowCenter(row) { var rect = row.getBoundingClientRect(); return { x: rect.left + Math.min(rect.width - 12, 180), y: rect.top + Math.min(rect.height, 22) }; } async function renameFileTreeAsset(assetId, title) { var response = await fetch('/api/media/batch', { method: 'POST', credentials: 'include', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ action: 'rename', assetIds: [assetId], newName: title }) }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error((payload && payload.error) || '重命名附件失败'); return payload; } function beginFileTreeInlineRename(row) { if (!(row instanceof HTMLElement)) return false; if (row.querySelector('.tree-rename-input')) return true; var rowKind = row.getAttribute('data-row-kind') || ''; var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || ''; var assetId = row.getAttribute('data-asset-id') || ''; var rowId = row.getAttribute('data-row-id') || ''; var localFolderSource = currentSourceKind() === 'local_folder'; var localFolderDirectory = localFolderSource && (rowKind === 'folder' || rowKind === 'directory'); var commandTargetId = documentId || (localFolderSource ? (assetId || rowId) : ''); if (!commandTargetId && !assetId) return false; if (rowKind && ['document', 'doc', 'index', 'markdown', 'asset'].indexOf(rowKind) < 0 && !assetId && !localFolderDirectory) return false; var link = row.querySelector(':scope > .tree-link'); var title = rowTitle(row); if (!(link instanceof HTMLElement)) return false; link.hidden = true; var input = document.createElement('input'); input.type = 'text'; input.className = 'tree-rename-input'; input.setAttribute('data-rename-id', rowId); input.value = title; input.style.minWidth = '0'; input.style.flex = '1 1 auto'; input.style.height = '22px'; input.style.border = '1px solid #93c5fd'; input.style.borderRadius = '3px'; input.style.padding = '0 4px'; input.style.font = 'inherit'; input.style.background = '#fff'; input.style.color = '#1f2937'; var validation = document.createElement('div'); validation.setAttribute('data-testid', 'tree-rename-validation'); validation.setAttribute('data-mnote-rename-validation', 'true'); validation.style.fontSize = '12px'; validation.style.color = '#dc2626'; validation.style.padding = '2px 4px'; validation.hidden = true; var setValidation = function(message) { var text = String(message || '').trim(); validation.textContent = text; validation.hidden = !text; input.setAttribute('aria-invalid', text ? 'true' : 'false'); }; var closed = false; var committing = false; var close = function() { if (closed) return; closed = true; if (input.parentElement) input.parentElement.removeChild(input); if (validation.parentElement) validation.parentElement.removeChild(validation); link.hidden = false; }; var commit = function() { if (closed || committing) return; var nextTitle = input.value.trim(); if (!nextTitle || nextTitle === title) { close(); return; } var validationMessage = validateFileTreeRename(row, nextTitle); if (validationMessage) { setValidation(validationMessage); return; } setValidation(''); var commandTitle = isFileTreePageRow(row) ? normalizeFileTreePageRenameTitle(nextTitle) : nextTitle; committing = true; input.disabled = true; var work = assetId && !localFolderSource ? Promise.resolve().then(function(){ document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-asset-id="' + cssEscape(assetId) + '"] .tree-link-title').forEach(function(titleNode) { titleNode.textContent = commandTitle; }); close(); return renameFileTreeAsset(assetId, commandTitle); }).then(function(){ return null; }) : dispatchTreeCommand(row, { action: 'rename', workspaceId: resolveWorkspaceId(row), documentId: commandTargetId, title: commandTitle }).then(function(){ if (!localFolderSource && documentId) updateTitleEverywhere(documentId, commandTitle); }); void work.then(close).catch(async function(error) { committing = false; input.disabled = false; await window.mnote.alert(error && error.message ? error.message : '重命名失败'); }); }; input.addEventListener('click', function(event) { event.stopPropagation(); }); input.addEventListener('dblclick', function(event) { event.stopPropagation(); }); input.addEventListener('keydown', function(event) { event.stopPropagation(); if (event.key === 'Enter') { event.preventDefault(); commit(); } else if (event.key === 'Escape') { event.preventDefault(); close(); } }); input.addEventListener('blur', commit); link.parentElement.insertBefore(input, link.nextSibling); link.parentElement.insertBefore(validation, input.nextSibling); window.requestAnimationFrame(function() { input.focus(); input.select(); }); return true; } function closeTreeContextMenu() { if (runtimeState.activeTreeContextMenu && runtimeState.activeTreeContextMenu.parentElement) { runtimeState.activeTreeContextMenu.parentElement.removeChild(runtimeState.activeTreeContextMenu); } runtimeState.activeTreeContextMenu = null; } /** * Product clipboard used by menu paste + keyboard paste. * Keep keyboard-module clipboard and runtimeState in lockstep so * 「粘贴到此处」enables after either Ctrl+C/X or menu cut/copy. */ function currentSidebarFileTreeClipboard() { if (runtimeState.sidebarFileTreeClipboard && Array.isArray(runtimeState.sidebarFileTreeClipboard.rowIds) && runtimeState.sidebarFileTreeClipboard.rowIds.length) { return runtimeState.sidebarFileTreeClipboard; } var kb = window.__mnoteFileTreeKeyboardRuntime; var fromKb = kb && typeof kb.getSidebarFileTreeClipboard === 'function' ? kb.getSidebarFileTreeClipboard() : (kb && kb.sidebarFileTreeClipboard) || null; if (fromKb && Array.isArray(fromKb.rowIds) && fromKb.rowIds.length) { // Heal dual-clipboard drift (keyboard module wrote first). runtimeState.sidebarFileTreeClipboard = fromKb; return fromKb; } return null; } function setSidebarFileTreeClipboard(action, rowIds) { var next = action && Array.isArray(rowIds) && rowIds.length ? { action: action, rowIds: rowIds.slice() } : null; runtimeState.sidebarFileTreeClipboard = next; var kb = window.__mnoteFileTreeKeyboardRuntime; if (kb && typeof kb.setSidebarFileTreeClipboard === 'function') { if (next) { kb.setSidebarFileTreeClipboard(next.action, next.rowIds); } else if (typeof kb.clearSidebarFileTreeClipboard === 'function') { kb.clearSidebarFileTreeClipboard(); } } else if (next) { document.documentElement.setAttribute('data-mnote-filetree-clipboard-action', next.action); } else { document.documentElement.removeAttribute('data-mnote-filetree-clipboard-action'); } return next; } function copyTreeContextValue(value, actionName) { var text = String(value || ''); var done = function() { document.documentElement.setAttribute('data-mnote-tree-context-last-copy', actionName || 'copy'); }; if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { return navigator.clipboard.writeText(text).then(done).catch(function(){}); } var textarea = document.createElement('textarea'); textarea.value = text; textarea.setAttribute('readonly', 'readonly'); textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; document.body.appendChild(textarea); textarea.select(); try { document.execCommand('copy'); } catch (_) {} document.body.removeChild(textarea); done(); return Promise.resolve(); } function triggerBrowserDownload(url) { if (!url) return false; var link = document.createElement('a'); link.href = url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.download = ''; link.style.position = 'fixed'; link.style.left = '-9999px'; link.style.top = '0'; document.body.appendChild(link); try { link.click(); } catch (_) { if (typeof window.open === 'function') window.open(url, '_blank', 'noopener,noreferrer'); } window.setTimeout(function() { if (link.parentElement) link.parentElement.removeChild(link); }, 1000); return true; } function recordFileTreeAction(action, detail) { var normalized = String(action || '').trim() || 'unknown'; var rowId = String(detail && detail.rowId || '').trim(); var documentId = String(detail && detail.documentId || '').trim(); var assetId = String(detail && detail.assetId || '').trim(); document.documentElement.setAttribute('data-mnote-filetree-last-action', normalized); if (rowId) document.documentElement.setAttribute('data-mnote-filetree-last-action-row-id', rowId); if (documentId) document.documentElement.setAttribute('data-mnote-filetree-last-action-document-id', documentId); if (assetId) document.documentElement.setAttribute('data-mnote-filetree-last-action-asset-id', assetId); window.dispatchEvent(new CustomEvent('tree.filetree.action', { detail: Object.assign({}, detail || {}, { action: normalized }) })); } function recordFileTreeActionStatus(status, detail) { var normalized = String(status || '').trim() || 'unknown'; document.documentElement.setAttribute('data-mnote-filetree-last-action-status', normalized); window.dispatchEvent(new CustomEvent('tree.filetree.action.status', { detail: Object.assign({}, detail || {}, { status: normalized }) })); } function documentHref(documentId, workspaceId) { var url = new URL('/documents/' + encodeURIComponent(documentId), window.location.origin); if (workspaceId) url.searchParams.set('workspaceId', workspaceId); copyWorkspaceSourceParams(url); return url.toString(); } async function convertToPreviousSiblingChild(trigger, detail) { var documentId = detail.documentId || ''; var row = document.querySelector('#sidebar-tree-root .tree-row[data-node-id="' + cssEscape(documentId) + '"]'); if (!(row instanceof HTMLElement)) return; var parentId = row.getAttribute('data-parent-id') || ''; var siblings = Array.from(document.querySelectorAll('#sidebar-tree-root .tree-row[data-shell-mode="page"]')).filter(function(candidate) { return (candidate.getAttribute('data-parent-id') || '') === parentId; }); var index = siblings.indexOf(row); if (index <= 0) { await window.mnote.alert('当前页面前面没有同级页面。'); return; } var previous = siblings[index - 1]; var previousId = previous.getAttribute('data-node-id') || ''; var children = previous.parentElement ? previous.parentElement.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="page"]') : []; void dispatchTreeCommand(trigger || row, { action: 'move', workspaceId: detail.workspaceId || resolveWorkspaceId(row), documentId: documentId, parentId: previousId, sortOrder: children.length }); } function fileTreeCopyPath(detail, trigger) { // 对 local_folder 复制真实相对路径,不复制标题 if (currentSourceKind() === 'local_folder') { if (detail.assetId) { var path = localFilePathFromAssetId(detail.assetId); if (path) { try { return decodeURIComponent(path.replace(/~2F/g, '/')); } catch (_) { return path.replace(/~2F/g, '/'); } } } if (detail.documentId) { var docPath = String(detail.documentId || '') .replace(/^local-md:/, '') .replace(/^local-dir:/, '') .replace(/~2F/g, '/'); if (docPath) { try { return decodeURIComponent(docPath); } catch (_) { return docPath; } } } } return detail.title || ''; } function localRootPathFromRootUri(rootUri) { var value = String(rootUri || '').trim(); if (!value) return ''; if (!/^file:\/\//i.test(value)) return value.charAt(0) === '/' ? value : ''; var pathPart = value.replace(/^file:\/\//i, ''); if (pathPart.indexOf('localhost/') === 0) pathPart = pathPart.slice('localhost'.length); if (pathPart.charAt(0) !== '/') pathPart = '/' + pathPart; try { return decodeURIComponent(pathPart); } catch (_) { return pathPart; } } function joinLocalAbsolutePath(rootUri, relativePath) { var rootPath = localRootPathFromRootUri(rootUri); var normalized = decodeLocalEncodedPath(relativePath).replace(/^\/+/, '').replace(/\\/g, '/'); if (!rootPath || !normalized) return rootPath || normalized; // Collapse . / empty and resolve .. without climbing above workspace root. var parts = []; normalized.split('/').forEach(function (seg) { if (!seg || seg === '.') return; if (seg === '..') { if (parts.length) parts.pop(); return; } parts.push(seg); }); var safeRel = parts.join('/'); if (!safeRel) return rootPath.replace(/\/+$/g, ''); return rootPath.replace(/\/+$/g, '') + '/' + safeRel; } function fileTreeCopyLocalAbsolutePath(detail, trigger) { var workspacePath = detail && detail.workspacePath && typeof detail.workspacePath === 'object' ? detail.workspacePath : null; var rootUri = String(workspacePath && workspacePath.rootUri || '').trim() || (typeof currentRootUri === 'function' ? currentRootUri() : ''); if (!rootUri && trigger && typeof trigger.closest === 'function') { var row = trigger.closest('.tree-row[data-shell-mode="filetree"]'); if (row instanceof HTMLElement) rootUri = row.getAttribute('data-root-uri') || ''; } if (!rootUri && document.body) rootUri = document.body.getAttribute('data-mnote-root-uri') || ''; var relativePath = String( workspacePath && (workspacePath.relativePath || workspacePath.localRelativePath) || detail && detail.localRelativePath || '' ).trim(); if (!relativePath && detail && detail.assetId) relativePath = localFilePathFromAssetId(detail.assetId); if (!relativePath && detail && detail.documentId) { relativePath = String(detail.documentId || '') .replace(/^local-md:/, '') .replace(/^local-dir:/, ''); } if (!relativePath && detail && detail.rowId) { relativePath = String(detail.rowId || '') .replace(/^local:asset:/, '') .replace(/^local:markdown:/, '') .replace(/^local:folder:/, '') .replace(/^local:node:/, ''); } return joinLocalAbsolutePath(rootUri, relativePath); } function fileTreeMenuTargetParentId(detail, trigger) { var rowKind = String(detail && detail.rowKind || '').trim(); var rowId = String(detail && detail.rowId || '').trim(); var documentId = String(detail && detail.documentId || '').trim(); if (rowKind === 'folder' && rowId) return rowId; if (rowKind === 'directory' && rowId) return rowId; if (documentId) return documentId; if (trigger && typeof trigger.getAttribute === 'function') { var triggerKind = String(trigger.getAttribute('data-row-kind') || '').trim(); var triggerRowId = String(trigger.getAttribute('data-row-id') || '').trim(); if ((triggerKind === 'folder' || triggerKind === 'directory') && triggerRowId) return triggerRowId; } return ''; } function withOfficeEditModeGuard(callback) { document.documentElement.setAttribute('data-mnote-last-office-edit-mode-requested', 'true'); document.documentElement.setAttribute('data-mnote-last-office-edit-mode-guard', 'silent'); callback(); } function knowledgeRagSourceRelativePath(detail) { var workspacePath = detail && detail.workspacePath && typeof detail.workspacePath === 'object' ? detail.workspacePath : null; var relativePath = String( detail && detail.localRelativePath || workspacePath && (workspacePath.relativePath || workspacePath.localRelativePath) || '' ).trim(); if (!relativePath && detail && detail.assetId) relativePath = localFilePathFromAssetId(detail.assetId); return relativePath.replace(/^\/+/, ''); } function knowledgeRagRootUri(detail, trigger) { var workspacePath = detail && detail.workspacePath && typeof detail.workspacePath === 'object' ? detail.workspacePath : null; var rootUri = String( detail && (detail.localRootUri || detail.rootUri) || workspacePath && workspacePath.rootUri || '' ).trim(); if (!rootUri && trigger && typeof trigger.closest === 'function') { var row = trigger.closest('.tree-row[data-shell-mode="filetree"]'); if (row instanceof HTMLElement) rootUri = String(row.getAttribute('data-root-uri') || '').trim(); } return rootUri || currentRootUri() || ''; } function supportsKnowledgeRagSource(detail) { var path = knowledgeRagSourceRelativePath(detail); return Boolean(path && knowledgeRagRootUri(detail, null)); } async function ingestKnowledgeRagForDetail(detail, trigger) { var sourceRootRelativePath = knowledgeRagSourceRelativePath(detail) || String(detail && (detail.localRelativePath || detail.path || detail.title) || '').trim(); var rootUri = knowledgeRagRootUri(detail, trigger); if (!sourceRootRelativePath || !rootUri) { throw new Error('缺少知识库来源或 rootUri'); } var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim(); document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', 'running'); document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-path', sourceRootRelativePath); var response = await fetch('/api/knowledge-rag/ingest', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ workspaceId: workspaceId, rootUri: rootUri, sources: [{ sourcePath: sourceRootRelativePath }], knowledgeBaseId: String(detail && detail.kbId || '').trim() || undefined, providerKnowledgeBaseId: String(detail && (detail.providerKbId || detail.kbId) || '').trim() || undefined }) }); var payload = await response.json().catch(function() { return null; }); if (!response.ok || !payload || payload.ok === false) { var message = payload && payload.message ? payload.message : '知识库索引失败'; document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', 'failed'); throw new Error(message); } document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', payload.retryRequired ? 'retry' : 'done'); window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', { detail: { rootUri: rootUri, workspaceId: workspaceId, sourceRootRelativePath: sourceRootRelativePath, result: payload } })); return payload; } async function handleTreeContextMenuAction(action, detail, trigger) { closeTreeContextMenu(); detail = detail || {}; if (action === 'knowledge-rag-index') { recordFileTreeAction('knowledge-rag-index', detail); recordFileTreeActionStatus('pending', detail); void ingestKnowledgeRagForDetail(detail, trigger).then(function() { recordFileTreeActionStatus('done', detail); if (window.mnote && window.mnote.toast) { window.mnote.toast('已加入知识库', { kind: 'success' }); } }).catch(async function(error) { var msg = error && error.message ? error.message : '知识库索引失败'; recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' })); if (window.mnote && window.mnote.toast) { window.mnote.toast(msg, { kind: 'error' }); } else { await window.mnote.alert(msg); } }); return; } if (action === 'open-knowledge-page') { var knowledgeUrl = typeof knowledgeHostUrl === 'function' ? knowledgeHostUrl({ workspaceId: String(detail && detail.workspaceId || resolveWorkspaceId(trigger || document.body) || '').trim(), sourceKind: currentSourceKind() || 'local_folder', rootUri: knowledgeRagRootUri(detail, trigger) }) : '/knowledge'; if (typeof window.open === 'function') { window.open(knowledgeUrl, '_blank', 'noopener,noreferrer'); } else { window.location.href = knowledgeUrl; } return; } if (detail.contextKind === 'attachment') { if (action === 'copy-link') { void copyTreeContextValue(detail.href || '', 'attachment-copy-link'); return; } if (action === 'download') { openEditorAttachmentDownload(detail); return; } if (action === 'popup-preview') { openEditorAttachmentDetail(detail); return; } if (action === 'new-window') { openEditorAttachmentNewWindow(detail); return; } if (action === 'new-window-edit') { withOfficeEditModeGuard(function() { openEditorAttachmentNewWindow(detail, 'edit'); }); return; } if (action === 'open-edit-mode') { withOfficeEditModeGuard(function() { void openEditorAttachmentEditTab(detail); }); return; } if (action === 'right-preview') { dispatchSidebarEvent('tree.attachment.open-right', detail); return; } if (action === 'copy-id') { void copyTreeContextValue(detail.assetId || '', 'attachment-copy-id'); return; } dispatchSidebarEvent('tree.attachment.action', { action: action, attachment: detail }); return; } var documentId = detail.documentId || ''; var workspaceId = detail.workspaceId || resolveWorkspaceId(trigger || document.body); var title = detail.title || '无标题'; var isAsset = detail.contextKind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index'; if (detail.contextKind === 'filetree' && action === 'toggle-sidebar-folder-shortcut') { dispatchSidebarEvent('tree.sidebarShortcut.toggleFolder', detail); return; } if (detail.contextKind === 'filetree' && action === 'download') { downloadSelectedFileTreeAssetRows(detail, trigger); return; } if (isAsset && action === 'new-window') { recordFileTreeAction('new-window', detail); void openLocalAssetFromFileTree({ ...detail, openTarget: 'new-window' }); return; } if (isAsset && action === 'open-edit-mode') { withOfficeEditModeGuard(async function() { recordFileTreeAction('open-edit-mode', detail); void openLocalAssetFromFileTree({ ...detail, openTarget: 'edit-mode' }); }); return; } if (isAsset && action === 'open-right') { recordFileTreeAction('open-right', detail); void openLocalAssetFromFileTree({ ...detail, openTarget: 'side' }); return; } if (action === 'open-right') { recordFileTreeAction('open-right', detail); dispatchSidebarEvent('tree.page.open-right', detail); return; } if (action === 'share') { dispatchSidebarEvent('tree.page.share', detail); return; } if (action === 'copy-link') { void copyTreeContextValue(documentHref(documentId, workspaceId), 'copy-link'); return; } if (action === 'copy-link-title') { void copyTreeContextValue(title + ' ' + documentHref(documentId, workspaceId), 'copy-link-title'); return; } if (action === 'copy-reference-inline') { void copyTreeContextValue('((' + title + ' ' + documentId + '))', 'copy-reference-inline'); return; } if (action === 'copy-reference-embed') { void copyTreeContextValue('{{' + title + ' ' + documentId + '}}', 'copy-reference-embed'); return; } if (action === 'copy-id') { var copyIdValue = currentSourceKind() === 'local_folder' ? fileTreeCopyLocalAbsolutePath(detail, trigger) : ''; void copyTreeContextValue(copyIdValue || documentId || detail.assetId || detail.rowId || '', 'copy-id'); return; } if (action === 'delete-trash' && detail.contextKind === 'filetree') { var selectedFileTreeRows = selectedSidebarFileTreeRows(); if (selectedFileTreeRows.length > 1) { void deleteSelectedSidebarFileTreeRows(trigger || document.body); return; } } if (action === 'delete-trash' && isAsset) { if (!(await window.mnote.confirm('确定要将“' + title + '”删除到垃圾桶吗?'))) return; recordFileTreeAction('delete-trash', detail); recordFileTreeActionStatus('pending', detail); void deleteSingleFileTreeAsset(detail, trigger).then(function() { recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' })); if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot(); }).catch(async function(error) { recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' })); await window.mnote.alert(error && error.message ? error.message : '资源删除失败'); }); return; } if (action === 'new-file') { void createPage(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null); return; } if (action === 'new-folder') { recordFileTreeAction('new-folder', detail); void createFileTreeFolder(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null).then(function(ok) { recordFileTreeActionStatus(ok ? 'created' : 'skipped', detail); }).catch(async function(error) { recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' })); await window.mnote.alert(error && error.message ? error.message : '新建文件夹失败'); }); return; } if (action === 'cut' || action === 'copy') { var clipboardRows = selectedSidebarFileTreeRows(); if (!clipboardRows.length && trigger && trigger.closest) { var single = trigger.closest('.tree-row[data-shell-mode="filetree"]'); if (single instanceof HTMLElement) clipboardRows = [single]; } var clipboardRowIds = clipboardRows.map(function(row) { return String(row.getAttribute('data-row-id') || '').trim(); }).filter(Boolean); if (!clipboardRowIds.length) { recordFileTreeActionStatus('skipped', Object.assign({}, detail, { reason: 'empty-selection' })); return; } setSidebarFileTreeClipboard(action, clipboardRowIds); recordFileTreeAction(action, Object.assign({}, detail, { sourceRowIds: clipboardRowIds, clipboardAction: action, })); recordFileTreeActionStatus('applied', Object.assign({}, detail, { sourceRowIds: clipboardRowIds, clipboardAction: action, })); return; } if (action === 'paste-into') { var pasteRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger; recordFileTreeAction('paste-into', detail); void pasteSidebarFileTreeClipboard(pasteRow).then(function(ok) { recordFileTreeActionStatus(ok ? 'applied' : 'skipped', detail); }).catch(async function(error) { recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' })); await window.mnote.alert(error && error.message ? error.message : '粘贴失败'); }); return; } if (action === 'copy-path') { void copyTreeContextValue(fileTreeCopyPath(detail, trigger), 'copy-path'); return; } if (action === 'refresh') { document.documentElement.setAttribute('data-mnote-filetree-refresh-requested', 'true'); dispatchSidebarEvent('tree.filetree.refresh', detail); return; } if (action === 'collapse-all') { document.querySelectorAll('#sidebar-file-tree-root .tree-row[aria-expanded="true"]').forEach(function(row) { if (!(row instanceof HTMLElement)) return; row.setAttribute('aria-expanded', 'false'); var node = row.closest('.tree-node'); var children = node ? node.querySelector(':scope > .tree-children') : null; if (children) children.classList.add('tree-children--collapsed'); }); return; } if (action === 'reveal') { if (trigger && typeof trigger.scrollIntoView === 'function') { trigger.scrollIntoView({ block: 'nearest' }); trigger.focus && trigger.focus(); } return; } if (action === 'duplicate') { dispatchSidebarEvent('tree.page.duplicate', detail); return; } if (action === 'rename') { if (detail.contextKind === 'filetree' && trigger) { var renameRow = trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger; if (beginFileTreeInlineRename(renameRow)) return; } var nextTitle = window.prompt('重命名页面', title); if (nextTitle && nextTitle.trim() && documentId) { void dispatchTreeCommand(trigger || document.body, { action: 'rename', workspaceId: workspaceId, documentId: documentId, title: nextTitle.trim() }).then(async function(){ updateTitleEverywhere(documentId, nextTitle.trim()); }); } return; } if (action === 'create-child') { void createPage(trigger || document.body, documentId); return; } if (action === 'convert-child') { void convertToPreviousSiblingChild(trigger, detail); return; } var deleteTargetId = documentId || String(detail.rowId || '').trim(); if (action === 'delete-trash' && deleteTargetId) { if (!(await window.mnote.confirm('确定要将“' + title + '”删除到垃圾桶吗?'))) return; recordFileTreeAction('delete-trash', detail); recordFileTreeActionStatus('pending', detail); void dispatchTreeCommand(trigger || document.body, { action: 'archive', workspaceId: workspaceId, documentId: deleteTargetId }).then(function() { recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' })); if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot(); }).catch(async function(error) { recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' })); await window.mnote.alert(error && error.message ? error.message : '删除失败'); }); } } function appendTreeContextMenuButton(menu, item, detail, trigger) { if (item.separator) { var sep = document.createElement('div'); sep.className = 'mnote-tree-context-menu__separator'; sep.setAttribute('role', 'separator'); menu.appendChild(sep); return; } // Submenu root: render parent button with child flyout if (item.children && Array.isArray(item.children) && item.children.length > 0) { if (typeof injectKnowledgeSubmenuStyles === 'function') injectKnowledgeSubmenuStyles(); var rootBtn = document.createElement('button'); rootBtn.type = 'button'; rootBtn.className = 'mnote-tree-context-menu__item mnote-tree-context-menu__item--submenu-root'; rootBtn.setAttribute('role', 'menuitem'); rootBtn.setAttribute('data-action', item.action || 'knowledge-rag-root'); rootBtn.setAttribute('data-command-id', item.commandId || item.action || 'knowledge-rag-root'); if (item.when) rootBtn.setAttribute('data-command-when', item.when); if (item.danger || item.destructive) rootBtn.setAttribute('data-destructive', 'true'); if (item.requiresApproval) rootBtn.setAttribute('data-requires-approval', 'true'); rootBtn.disabled = item.disabled === true; if (item.title) rootBtn.title = item.title; if (item.disabled === true && item.title) rootBtn.setAttribute('data-disabled-reason', item.title); var rootIcon = document.createElement('span'); rootIcon.className = 'material-symbols-outlined mnote-tree-context-menu__icon'; rootIcon.setAttribute('aria-hidden', 'true'); rootIcon.setAttribute('data-icon', item.icon || 'radio_button_unchecked'); var rootLabel = document.createElement('span'); rootLabel.className = 'mnote-tree-context-menu__label'; rootLabel.textContent = item.label; var arrow = document.createElement('span'); arrow.className = 'mnote-tree-context-menu__submenu-arrow'; arrow.textContent = '▶'; arrow.setAttribute('aria-hidden', 'true'); rootBtn.appendChild(rootIcon); rootBtn.appendChild(rootLabel); rootBtn.appendChild(arrow); var submenu = document.createElement('div'); submenu.className = 'mnote-tree-context-menu__submenu'; submenu.setAttribute('data-testid', 'mnote-knowledge-submenu'); submenu.setAttribute('role', 'menu'); function renderSubmenuChildren(children) { submenu.textContent = ''; children.forEach(function(child) { if (child.separator) { var childSep = document.createElement('div'); childSep.className = 'mnote-tree-context-menu__separator'; childSep.setAttribute('role', 'separator'); submenu.appendChild(childSep); return; } var childBtn = document.createElement('button'); childBtn.type = 'button'; childBtn.className = child.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item'; if (child.heading) childBtn.className += ' mnote-tree-context-menu__item--heading'; childBtn.setAttribute('role', 'menuitem'); childBtn.setAttribute('data-action', child.action || ''); childBtn.setAttribute('data-command-id', child.commandId || child.action || ''); if (child.when) childBtn.setAttribute('data-command-when', child.when); childBtn.disabled = child.disabled === true || child.heading === true; if (child.title) childBtn.title = child.title; if (child.disabled === true && child.title) childBtn.setAttribute('data-disabled-reason', child.title); var childIcon = document.createElement('span'); childIcon.className = 'material-symbols-outlined mnote-tree-context-menu__icon'; childIcon.setAttribute('aria-hidden', 'true'); childIcon.setAttribute('data-icon', child.icon || 'radio_button_unchecked'); var childLabel = document.createElement('span'); childLabel.className = 'mnote-tree-context-menu__label'; childLabel.textContent = child.label; childBtn.appendChild(childIcon); childBtn.appendChild(childLabel); if (child.shortcut) { var childShortcut = document.createElement('span'); childShortcut.className = 'mnote-tree-context-menu__shortcut'; childShortcut.textContent = child.shortcut; childBtn.appendChild(childShortcut); } var childDetail = child.kbId ? Object.assign({}, detail, { kbId: child.kbId, providerKbId: child.providerKbId || child.kbId }) : detail; (function(act, actDetail, actTrigger) { childBtn.addEventListener('click', function(event) { event.preventDefault(); event.stopPropagation(); if (!act || childBtn.disabled) return; handleTreeContextMenuAction(act, actDetail, actTrigger); }); })(child.action, childDetail, trigger); submenu.appendChild(childBtn); }); } renderSubmenuChildren(item.children); rootBtn.appendChild(submenu); var submenuShown = false; var submenuHideTimer = null; var lastSubmenuPointerY = 0; function knowledgeSubmenuPosition(pointerY) { var btnRect = rootBtn.getBoundingClientRect(); submenu.style.display = 'block'; submenu.style.visibility = 'hidden'; var smRect = submenu.getBoundingClientRect(); var leftPos = btnRect.right + 4; var anchorY = Number(pointerY || lastSubmenuPointerY || 0); var topPos = anchorY > 0 ? anchorY - 18 : btnRect.top; if (leftPos + smRect.width > window.innerWidth - 8) { leftPos = Math.max(8, btnRect.left - smRect.width - 4); } if (topPos + smRect.height > window.innerHeight - 8) { topPos = Math.max(8, window.innerHeight - smRect.height - 8); } submenu.style.left = leftPos + 'px'; submenu.style.top = topPos + 'px'; submenu.style.visibility = 'visible'; } function knowledgeSubmenuShow(pointerY) { if (Number(pointerY || 0) > 0) lastSubmenuPointerY = pointerY; if (submenuHideTimer) { window.clearTimeout(submenuHideTimer); submenuHideTimer = null; } if (!submenuShown) { knowledgeSubmenuPosition(pointerY); submenuShown = true; rootBtn.classList.add('mnote-tree-context-menu__item--submenu-open'); } } function knowledgeSubmenuScheduleHide() { if (submenuHideTimer) window.clearTimeout(submenuHideTimer); submenuHideTimer = window.setTimeout(function() { if (submenuShown) { submenu.style.display = 'none'; submenuShown = false; rootBtn.classList.remove('mnote-tree-context-menu__item--submenu-open'); } submenuHideTimer = null; }, 150); } rootBtn.addEventListener('mouseenter', function(event) { if (!rootBtn.disabled) knowledgeSubmenuShow(event.clientY); }); rootBtn.addEventListener('mousemove', function(event) { if (rootBtn.disabled || !submenuShown) return; lastSubmenuPointerY = event.clientY; }); rootBtn.addEventListener('mouseleave', function(event) { if (event.relatedTarget && (submenu.contains(event.relatedTarget) || rootBtn.contains(event.relatedTarget))) return; knowledgeSubmenuScheduleHide(); }); submenu.addEventListener('mouseenter', function() { if (submenuHideTimer) { window.clearTimeout(submenuHideTimer); submenuHideTimer = null; } }); submenu.addEventListener('mouseleave', function(event) { if (event.relatedTarget && (rootBtn.contains(event.relatedTarget) || submenu.contains(event.relatedTarget))) return; knowledgeSubmenuScheduleHide(); }); if (typeof item.loadChildren === 'function') { item.loadChildren().then(function(children) { if (!Array.isArray(children) || children.length === 0) return; renderSubmenuChildren(children); if (submenuShown) knowledgeSubmenuPosition(lastSubmenuPointerY); }).catch(function() {}); } menu.appendChild(rootBtn); return; } var button = document.createElement('button'); button.type = 'button'; button.className = item.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item'; button.setAttribute('role', 'menuitem'); button.setAttribute('data-action', item.action); button.setAttribute('data-command-id', item.commandId || item.action || ''); if (item.when) button.setAttribute('data-command-when', item.when); if (item.danger || item.destructive) button.setAttribute('data-destructive', 'true'); if (item.requiresApproval) button.setAttribute('data-requires-approval', 'true'); button.disabled = item.disabled === true; if (item.title) button.title = item.title; if (item.disabled === true && item.title) button.setAttribute('data-disabled-reason', item.title); var icon = document.createElement('span'); icon.className = 'material-symbols-outlined mnote-tree-context-menu__icon'; icon.setAttribute('aria-hidden', 'true'); icon.setAttribute('data-icon', item.icon || 'radio_button_unchecked'); var label = document.createElement('span'); label.className = 'mnote-tree-context-menu__label'; label.textContent = item.label; button.appendChild(icon); button.appendChild(label); if (item.shortcut) { var shortcut = document.createElement('span'); shortcut.className = 'mnote-tree-context-menu__shortcut'; shortcut.textContent = item.shortcut; button.appendChild(shortcut); } button.addEventListener('click', function(event) { event.preventDefault(); event.stopPropagation(); handleTreeContextMenuAction(item.action, detail, trigger); }); menu.appendChild(button); } // ── 知识库二级菜单 CSS 注入(仅一次) ── var __mnoteKnowledgeSubmenuStylesInjected = false; function injectKnowledgeSubmenuStyles() { if (__mnoteKnowledgeSubmenuStylesInjected) return; __mnoteKnowledgeSubmenuStylesInjected = true; var style = document.createElement('style'); style.id = 'mnote-knowledge-submenu-styles'; style.textContent = '.mnote-tree-context-menu__item--submenu-root{position:relative}.mnote-tree-context-menu__submenu{position:fixed;z-index:calc(var(--wolai-z-context-menu,9999) + 1);width:240px;min-width:220px;max-width:min(340px,calc(100vw - 16px));padding:6px;border:1px solid rgba(27,28,28,.08);border-radius:6px;background:#FFF;box-shadow:0 8px 20px rgba(27,28,28,.12);backdrop-filter:blur(24px);color:var(--atelier-text);display:none}.mnote-tree-context-menu__submenu-arrow{margin-left:auto;font-size:11px;color:#6D6A65;padding-left:12px}.mnote-tree-context-menu__item--submenu-open{background:#F4F3F3}.mnote-tree-context-menu__item--heading{font-size:12px;color:#6D6A65;background:transparent!important;cursor:default}'; document.head.appendChild(style); } // ── 知识库列表获取:只读 MNote 后端 registry/status,不直连外部知识库 service key。 ── function fetchKnowledgeBaseList(detail) { var rootUri = knowledgeRagRootUri(detail, null); if (!rootUri) return Promise.resolve([]); var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim(); var sourcePath = knowledgeRagSourceRelativePath(detail); var statusUrl = '/api/knowledge-rag/status?rootUri=' + encodeURIComponent(rootUri); if (workspaceId) statusUrl += '&workspaceId=' + encodeURIComponent(workspaceId); return fetch(statusUrl, { credentials: 'include', headers: { accept: 'application/json' } }) .then(function(response) { return response.ok ? response.json() : null; }) .then(function(payload) { // 构建 knowledgeBase → 已索引 sourcePath 快速查找 var indexedByKb = {}; var registry = payload && payload.registry; if (registry && Array.isArray(registry.entries) && sourcePath) { registry.entries.forEach(function(entry) { var entryKbId = String(entry.providerKnowledgeBaseId || entry.provider_kb_id || '').trim(); var entryPath = String(entry.sourceRootRelativePath || entry.source_root_relative_path || '').trim(); if (entryKbId && entryPath && entry.indexedAtMs && entryPath === sourcePath) { if (!indexedByKb[entryKbId]) indexedByKb[entryKbId] = {}; indexedByKb[entryKbId][entryPath] = true; } }); } var bases = payload && payload.knowledgeBases && Array.isArray(payload.knowledgeBases.bases) ? payload.knowledgeBases.bases : []; return bases.filter(function(base) { return base && base.canWrite !== false && (base.providerKbId || base.provider_kb_id || base.baseId || base.base_id); }).map(function(base) { var providerKbId = String(base.providerKbId || base.provider_kb_id || '').trim(); var baseId = String(base.baseId || base.base_id || providerKbId).trim(); var kbId = providerKbId || baseId; return { id: baseId, providerKbId: kbId, name: String(base.name || baseId || providerKbId || '未命名知识库').trim(), sourceCount: Number(base.sourceCount || base.source_count || 0) || 0, sourceAlreadyIndexed: Boolean(sourcePath && indexedByKb[kbId] && indexedByKb[kbId][sourcePath]) }; }); }).catch(function() { return []; }); } function buildKnowledgeRagSubmenuChildren(detail, writableBases) { var children = [ { action: 'knowledge-rag-index', icon: 'travel_explore', label: '索引到默认知识库', when: '!workspace.readonly' }, { separator: true }, { heading: true, icon: 'history', label: '最近使用知识库' } ]; if (writableBases && writableBases.length > 0) { writableBases.slice(0, 3).forEach(function(base) { var indexed = base.sourceAlreadyIndexed; children.push({ action: 'knowledge-rag-index', icon: indexed ? 'check_circle' : 'database', label: (indexed ? '✓ ' : '') + base.name, kbId: base.id, providerKbId: base.providerKbId, title: indexed ? '重新索引' : (base.sourceCount ? ('已有 ' + base.sourceCount + ' 个来源') : '加入此知识库'), when: '!workspace.readonly' }); }); } else { children.push({ action: '', icon: 'database', label: '暂无可写知识库', disabled: true }); } children.push({ heading: true, icon: 'folder_managed', label: '当前 workspace 知识库' }); (writableBases || []).slice(3).forEach(function(base) { var indexed = base.sourceAlreadyIndexed; children.push({ action: 'knowledge-rag-index', icon: indexed ? 'check_circle' : 'database', label: (indexed ? '✓ ' : '') + base.name, kbId: base.id, providerKbId: base.providerKbId, title: indexed ? '重新索引' : (base.sourceCount ? ('已有 ' + base.sourceCount + ' 个来源') : '加入此知识库'), when: '!workspace.readonly' }); }); children.push( { separator: true }, { action: 'open-knowledge-page', icon: 'add', label: '新建知识库...' }, { action: 'open-knowledge-page', icon: 'settings', label: '管理知识库...' } ); return children; } // ── CommandContext 启用态辅助:与 Rust core-protocol CommandContext 保持同一口径 ── function currentOpenEditorsSnapshotForCommandContext() { try { if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function') { return window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot(); } } catch (_) {} return window.__mnoteOpenEditorsSnapshot || null; } function commandContextTargetRow(detail) { if (detail && detail.targetRow instanceof HTMLElement) return detail.targetRow; var rowId = String(detail && detail.rowId || '').trim(); if (rowId) { var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]'); if (row instanceof HTMLElement) return row; } return null; } function buildSidebarFileTreeContext(kind, detail) { var runtime = window.__mnoteFileTreeContextMenuRuntime; if (runtime && typeof runtime.buildSidebarFileTreeContext === 'function') { try { var targetRow = commandContextTargetRow(detail); return runtime.buildSidebarFileTreeContext(kind, { selection: sidebarFileTreeSelection, currentSourceKind: currentSourceKind, workspaceReadonly: function() { return document.documentElement.getAttribute('data-mnote-workspace-readonly') === 'true'; }, queryRowById: function(rowId) { return document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]'); }, targetRow: targetRow, openEditorsSnapshot: currentOpenEditorsSnapshotForCommandContext(), bufferState: detail && detail.bufferState, }); } catch (_) {} } var s = sidebarFileTreeSelection; var rowIds = Array.from(s.selectedRowIds || []); var rows = []; for (var i = 0; i < rowIds.length; i += 1) { var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowIds[i]) + '"]'); if (row instanceof HTMLElement) rows.push(row); } var resourceKinds = {}; for (var i = 0; i < rows.length; i += 1) { var rk = rows[i].getAttribute('data-row-kind') || 'unknown'; resourceKinds[rk] = true; } var rkKeys = Object.keys(resourceKinds); return { 'workspace.sourceKind': currentSourceKind() || '', 'workspace.readonly': document.documentElement.getAttribute('data-mnote-workspace-readonly') === 'true', 'tree.focusKind': kind === 'filetree' ? 'file_tree' : kind === 'page' ? 'page_tree' : kind, 'tree.selectionCount': rows.length, 'tree.selectionResourceKind': rkKeys.length === 1 ? rkKeys[0] : 'mixed' }; } function evaluateSidebarFileTreeWhen(ctx, expr) { var runtime = window.__mnoteFileTreeContextMenuRuntime; if (runtime && typeof runtime.evaluateSidebarFileTreeWhen === 'function') { return runtime.evaluateSidebarFileTreeWhen(ctx || {}, expr || ''); } if (!expr) return true; try { expr = expr.trim(); // 词法切分:支持 key、!key、==、!=、&&、|| 和括号分组 var tokens = []; var i = 0; while (i < expr.length) { var ch = expr[i]; if (ch === ' ' || ch === '\t') { i++; continue; } if (ch === '(') { tokens.push({ t: '(' }); i++; continue; } if (ch === ')') { tokens.push({ t: ')' }); i++; continue; } if (ch === '&' && expr[i+1] === '&') { tokens.push({ t: '&&' }); i += 2; continue; } if (ch === '|' && expr[i+1] === '|') { tokens.push({ t: '||' }); i += 2; continue; } if (ch === '=' && expr[i+1] === '=') { tokens.push({ t: '==' }); i += 2; continue; } if (ch === '!' && expr[i+1] === '=') { tokens.push({ t: '!=' }); i += 2; continue; } if (ch === '!') { tokens.push({ t: '!' }); i++; continue; } if (ch === '"' || ch === "'") { i++; var str = ''; while (i < expr.length && expr[i] !== ch) { str += expr[i]; i++; } if (i < expr.length) i++; tokens.push({ t: 'str', v: str }); continue; } if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_' || ch === '.' || (ch >= '0' && ch <= '9')) { var id = ''; while (i < expr.length && ((expr[i] >= 'a' && expr[i] <= 'z') || (expr[i] >= 'A' && expr[i] <= 'Z') || (expr[i] >= '0' && expr[i] <= '9') || expr[i] === '_' || expr[i] === '.')) { id += expr[i]; i++; } tokens.push({ t: 'id', v: id }); continue; } throw new Error('Unexpected char: ' + ch); } var pos = 0; function peek() { return tokens[pos]; } function consume() { return tokens[pos++]; } function expect(t) { var tok = consume(); if (!tok || tok.t !== t) throw new Error('Expected ' + t + ' got ' + (tok ? tok.t : 'EOF')); return tok; } function ctxVal(key) { var v = ctx[key]; if (v === undefined || v === null) return ''; return v; } function parseOr() { var left = parseAnd(); while (peek() && peek().t === '||') { consume(); var right = parseAnd(); left = left || right; } return left; } function parseAnd() { var left = parsePrimary(); while (peek() && peek().t === '&&') { consume(); var right = parsePrimary(); left = left && right; } return left; } function parsePrimary() { if (!peek()) throw new Error('Unexpected EOF'); if (peek().t === '(') { consume(); var val = parseOr(); expect(')'); return val; } if (peek().t === '!') { consume(); var keyTok = consume(); if (!keyTok || keyTok.t !== 'id') throw new Error('Expected key after !'); return !ctxVal(keyTok.v); } if (peek().t === 'id') { var keyTok = consume(); if (peek() && peek().t === '==') { consume(); var valTok = consume(); if (!valTok) throw new Error('Expected value after =='); var val = valTok.t === 'str' ? valTok.v : (valTok.v || ''); // 与 Rust evaluate_when 保持一致:按 string / bool / number 比较。 var cv = ctxVal(keyTok.v); if (val === 'true') return cv === true || cv === 'true'; if (val === 'false') return cv === false || cv === '' || cv === 'false'; var num = Number(val); if (!isNaN(num) && String(num) === val) return Number(cv) === num; return String(cv) === val; } if (peek() && peek().t === '!=') { consume(); var valTok = consume(); if (!valTok) throw new Error('Expected value after !='); var val = valTok.t === 'str' ? valTok.v : (valTok.v || ''); var cv = ctxVal(keyTok.v); if (val === 'true') return !(cv === true || cv === 'true'); if (val === 'false') return !(cv === false || cv === '' || cv === 'false'); var num = Number(val); if (!isNaN(num) && String(num) === val) return Number(cv) !== num; return String(cv) !== val; } // 单独 key:按 truthy 规则判断。 var cv = ctxVal(keyTok.v); if (typeof cv === 'boolean') return cv; if (typeof cv === 'number') return cv !== 0; return cv !== '' && cv !== undefined && cv !== null; } throw new Error('Unexpected token: ' + peek().t); } return parseOr(); } catch(e) { return true; } } function openTreeContextMenu(kind, detail, x, y, trigger) { closeTreeContextMenu(); detail = Object.assign({}, detail || {}, { contextKind: kind }); var menu = document.createElement('div'); menu.className = 'mnote-tree-context-menu'; menu.setAttribute('role', 'menu'); menu.setAttribute('data-testid', 'mnote-tree-context-menu'); menu.setAttribute('data-kind', kind); var isAttachment = kind === 'attachment'; var isAsset = kind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index'; var isFileTreeDownload = kind === 'filetree' && detail.downloadable; var ctx = buildSidebarFileTreeContext(kind, detail); menu.setAttribute('data-command-context-source-kind', String(ctx['workspace.sourceKind'] || '')); menu.setAttribute('data-command-context-readonly', String(ctx['workspace.readonly'] === true)); menu.setAttribute('data-command-context-selection-count', String(ctx['tree.selectionCount'] || 0)); menu.setAttribute('data-command-context-resource-kind', String(ctx['tree.selectionResourceKind'] || '')); menu.setAttribute('data-command-context-target-kind', String(ctx['tree.targetResourceKind'] || '')); menu.setAttribute('data-command-context-editor-dirty', String(ctx['editor.dirty'] === true)); menu.setAttribute('data-command-context-ai-can-write', String(ctx['ai.canWrite'] === true)); var knowledgeRagSupported = supportsKnowledgeRagSource(detail); var items = isAttachment ? [ { action: 'duplicate', icon: 'file_copy', label: '拷贝副本', shortcut: 'Ctrl + D' }, { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly' }, { separator: true }, { action: 'copy-link', icon: 'link', label: '复制链接' }, { action: 'move-embed', icon: 'subdirectory_arrow_right', label: '移动/嵌入到...', shortcut: 'Alt+Shift+M/G' }, { action: 'history', icon: 'history', label: '块历史...' }, { separator: true }, { action: 'popup-preview', icon: 'preview', label: '弹窗预览' }, { action: 'right-preview', icon: 'right_panel_open', label: '右侧预览' }, { action: 'open-edit-mode', icon: 'edit_note', label: '弹窗编辑' }, { action: 'new-window-edit', icon: 'open_in_new', label: '新窗口编辑' }, { action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' }, { action: 'download', icon: 'download', label: '下载' }, { action: 'replace-file', icon: 'sync', label: '更换文件' }, { action: 'rename-attachment', icon: 'drive_file_rename_outline', label: '重命名' }, { action: 'comment', icon: 'mode_comment', label: '评论', shortcut: 'Ctrl+Alt+M' }, { action: 'caption', icon: 'notes', label: '添加说明文字' }, { separator: true }, { action: 'color', icon: 'format_paint', label: '颜色' } ] : isAsset ? [ { action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' }, { action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' }, { action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) + ' 个项目' : '下载' }, { action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly && !editor.dirty' }, { action: 'copy-id', icon: 'tag', label: '复制资源 ID' }, { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' }, { separator: true }, { action: 'copy-path', icon: 'content_copy', label: 'Copy Path' }, { action: 'refresh', icon: 'refresh', label: 'Refresh' }, { action: 'reveal', icon: 'my_location', label: 'Reveal' } ] : kind === 'filetree' ? [ { action: 'open-right', icon: 'right_panel_open', label: '在右侧边栏打开', shortcut: 'Alt+' }, { action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || 0) + ' 个项目' : '下载', disabled: !isFileTreeDownload, title: isFileTreeDownload ? '下载当前本地文件或文件夹' : '当前项目没有可下载的本地路径' }, { separator: true }, { action: 'copy-link', icon: 'link', label: '复制访问链接' }, { action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' }, { action: 'copy-id', icon: 'tag', label: '复制页面ID' }, { action: 'copy-path', icon: 'content_copy', label: 'Copy Path' }, { separator: true }, { action: 'new-file', icon: 'note_add', label: 'New File', when: '!workspace.readonly' }, { action: 'new-folder', icon: 'create_new_folder', label: 'New Folder', disabled: currentSourceKind() !== 'local_folder', title: currentSourceKind() === 'local_folder' ? '在当前目录下创建子文件夹' : '仅 local folder 支持创建文件夹', when: '!workspace.readonly' }, { action: 'toggle-sidebar-folder-shortcut', icon: 'star', label: '加入/取消星标置顶', disabled: currentSourceKind() !== 'local_folder' || (detail.rowKind !== 'folder' && detail.rowKind !== 'directory'), title: currentSourceKind() === 'local_folder' ? '把当前文件夹加入或移出星标置顶' : '仅 local folder 文件夹支持星标置顶' }, { separator: true }, { action: 'cut', icon: 'content_cut', label: '剪切', shortcut: 'Ctrl+X', when: '!workspace.readonly' }, { action: 'copy', icon: 'content_copy', label: '复制', shortcut: 'Ctrl+C' }, { action: 'paste-into', icon: 'content_paste', label: '粘贴到此处', shortcut: 'Ctrl+V', disabled: !currentSidebarFileTreeClipboard(), title: currentSidebarFileTreeClipboard() ? '粘贴到当前文件树目标' : '剪贴板为空', when: '!workspace.readonly' }, { action: 'refresh', icon: 'refresh', label: 'Refresh' }, { action: 'collapse-all', icon: 'unfold_less', label: 'Collapse All' }, { action: 'reveal', icon: 'my_location', label: 'Reveal' }, { separator: true }, { action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly && !editor.dirty' }, { separator: true }, { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' } ] : [ { action: 'open-right', icon: 'right_panel_open', label: '在右侧边栏打开', shortcut: 'Alt+' }, { separator: true }, { action: 'copy-link', icon: 'link', label: '复制访问链接' }, { action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' }, { action: 'copy-id', icon: 'tag', label: '复制页面ID' }, { separator: true }, { action: 'rename', icon: 'edit', label: '重命名', when: '!workspace.readonly && !editor.dirty' }, { separator: true }, { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' } ]; if (currentSourceKind() === 'local_folder' && detail.rowKind !== 'index' && knowledgeRagSupported) { var ragChildren = buildKnowledgeRagSubmenuChildren(detail, []); var ragItem = { action: 'knowledge-rag-root', icon: 'travel_explore', label: '加入知识库', children: ragChildren, when: '!workspace.readonly' }; ragItem.loadChildren = function() { return fetchKnowledgeBaseList(detail).then(function(bases) { return buildKnowledgeRagSubmenuChildren(detail, bases); }); }; var insertAt = isAttachment ? 11 : isAsset ? 3 : 3; if (insertAt >= 0) items.splice(insertAt, 0, ragItem); } items.forEach(function(item) { if (item.when !== undefined && !evaluateSidebarFileTreeWhen(ctx, item.when)) { var reason = ctx['workspace.readonly'] === true ? '当前工作区只读' : ctx['editor.dirty'] === true ? '当前目标有未保存修改' : '当前上下文不支持此操作'; item = Object.assign({}, item, { disabled: true, title: item.title || reason }); } appendTreeContextMenuButton(menu, item, detail, trigger); }); document.body.appendChild(menu); var rect = menu.getBoundingClientRect(); var left = Math.min(Math.max(8, x || 8), Math.max(8, window.innerWidth - rect.width - 8)); var top = Math.min(Math.max(8, y || 8), Math.max(8, window.innerHeight - rect.height - 8)); menu.style.left = left + 'px'; menu.style.top = top + 'px'; runtimeState.activeTreeContextMenu = menu; } function openPageTreeContextMenu(row, x, y, trigger) { if (!(row instanceof HTMLElement)) return; var documentId = row.getAttribute('data-node-id') || ''; openTreeContextMenu('page', { documentId: documentId, rowId: documentId, rowKind: 'document', title: rowTitle(row), workspaceId: resolveWorkspaceId(row) }, x, y, trigger || row); } function openFileTreeContextMenu(row, x, y, trigger) { if (!(row instanceof HTMLElement)) return; var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || ''; var selectedDownloadRows = selectedSidebarFileTreeRowsForDownload(row); var selectedAssetRows = selectedDownloadRows.filter(isFileTreeDownloadableAssetRow); var workspacePath = readWorkspacePathFromRow(row); openTreeContextMenu('filetree', { documentId: documentId, rowId: row.getAttribute('data-row-id') || '', rowKind: row.getAttribute('data-row-kind') || '', assetId: row.getAttribute('data-asset-id') || '', localRelativePath: fileTreeRowLocalRelativePath(row), workspacePath: workspacePath, targetRow: row, title: rowTitle(row), workspaceId: resolveWorkspaceId(row), downloadable: isFileTreeDownloadableRow(row), selectedDownloadCount: selectedDownloadRows.length, selectedDownloadRowIds: selectedDownloadRows.map(function(downloadRow) { return downloadRow.getAttribute('data-row-id') || ''; }).filter(Boolean), selectedAssetCount: selectedAssetRows.length, selectedAssetRowIds: selectedAssetRows.map(function(assetRow) { return assetRow.getAttribute('data-row-id') || ''; }).filter(Boolean) }, x, y, trigger || row); } function visibleFileTreeRows() { var runtimeFn = fileTreeSelectionRuntimeFunction('visibleFileTreeRows'); if (runtimeFn) return runtimeFn(fileTreeSelectionRuntimeDeps()); return Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]')) .filter(function(row) { if (!(row instanceof HTMLElement)) return false; if (row.closest('.tree-children--collapsed')) return false; return row.offsetParent !== null || row.getClientRects().length > 0; }); } function syncSidebarFileTreeSelection() { var runtimeFn = fileTreeSelectionRuntimeFunction('syncSidebarFileTreeSelection'); if (runtimeFn) { runtimeFn(sidebarFileTreeSelection, fileTreeSelectionRuntimeDeps()); return; } var rows = visibleFileTreeRows(); rows.forEach(function(row) { var rowId = row.getAttribute('data-row-id') || ''; row.setAttribute('data-selected', String(Boolean(rowId && sidebarFileTreeSelection.selectedRowIds.has(rowId)))); row.setAttribute('data-focused', String(rowId === sidebarFileTreeSelection.focusedRowId)); }); window.dispatchEvent(new CustomEvent('tree.filetree.selection.changed', { detail: { selectedRowIds: Array.from(sidebarFileTreeSelection.selectedRowIds), anchorRowId: sidebarFileTreeSelection.anchorRowId, focusedRowId: sidebarFileTreeSelection.focusedRowId } })); } function selectSidebarFileTreeRow(row, modifiers) { var runtimeFn = fileTreeSelectionRuntimeFunction('selectSidebarFileTreeRow'); if (runtimeFn) return runtimeFn(row, sidebarFileTreeSelection, modifiers, fileTreeSelectionRuntimeDeps()); if (!(row instanceof HTMLElement)) return []; var rowId = row.getAttribute('data-row-id') || ''; if (!rowId) return []; var rows = visibleFileTreeRows(); var visibleRowIds = rows.map(function(item) { return item.getAttribute('data-row-id') || ''; }).filter(Boolean); var selected = new Set(sidebarFileTreeSelection.selectedRowIds); var shiftKey = Boolean(modifiers && modifiers.shiftKey); var ctrlKey = Boolean(modifiers && (modifiers.ctrlKey || modifiers.metaKey)); if (shiftKey && sidebarFileTreeSelection.anchorRowId) { var anchorIndex = visibleRowIds.indexOf(sidebarFileTreeSelection.anchorRowId); var targetIndex = visibleRowIds.indexOf(rowId); if (anchorIndex >= 0 && targetIndex >= 0) { selected = new Set(visibleRowIds.slice(Math.min(anchorIndex, targetIndex), Math.max(anchorIndex, targetIndex) + 1)); } else { selected = new Set([rowId]); sidebarFileTreeSelection.anchorRowId = rowId; } } else if (ctrlKey) { if (selected.has(rowId) && selected.size > 1) selected.delete(rowId); else selected.add(rowId); sidebarFileTreeSelection.anchorRowId = rowId; } else { selected = new Set([rowId]); sidebarFileTreeSelection.anchorRowId = rowId; } sidebarFileTreeSelection.selectedRowIds = selected; sidebarFileTreeSelection.focusedRowId = rowId; syncSidebarFileTreeSelection(); return Array.from(selected); } function selectSidebarFileTreeDocument(documentId, options) { var id = String(documentId || '').trim(); if (!id) return false; var relativePath = options && options.relativePath ? String(options.relativePath || '').trim() : id.indexOf('local-md:') === 0 ? decodeLocalEncodedPath(id.slice('local-md:'.length)) : ''; var targetRowId = options && options.rowId ? String(options.rowId || '').trim() : ''; var row = targetRowId ? document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(targetRowId) + '"]') : null; if (!(row instanceof HTMLElement)) { row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + id) + '"]') || document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-document-id="' + cssEscape(id) + '"]') || document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-doc-id="' + cssEscape(id) + '"]'); } if (row instanceof HTMLElement && relativePath && fileTreeRowLocalRelativePath(row) !== relativePath) { row = null; } if (row instanceof HTMLElement && row.closest('.tree-children--collapsed') && relativePath && typeof revealFileTreeResource === 'function') { void revealFileTreeResource({ rootUri: options && options.rootUri, relativePath: relativePath, rowId: options && options.rowId, select: true, focus: true, scroll: options ? options.scrollIntoView !== false : true }); return true; } if (!(row instanceof HTMLElement)) { if (relativePath && typeof revealFileTreeResource === 'function') { void revealFileTreeResource({ rootUri: options && options.rootUri, relativePath: relativePath, rowId: options && options.rowId, select: true, focus: true, scroll: options ? options.scrollIntoView !== false : true }).then(function(revealed) { if (revealed) return; var fallbackParentPath = localMarkdownBundleParentPath(relativePath); var fallbackBundleRow = fallbackParentPath ? visibleFileTreeRowByRelativePath(fallbackParentPath) : null; if (fallbackBundleRow instanceof HTMLElement) activateSidebarFileTreeRow(fallbackBundleRow, options); }).catch(function() {}); return true; } var bundleParentPath = localMarkdownBundleParentPath(relativePath); var bundleRow = bundleParentPath ? visibleFileTreeRowByRelativePath(bundleParentPath) : null; if (bundleRow instanceof HTMLElement) { return activateSidebarFileTreeRow(bundleRow, options); } return false; } return activateSidebarFileTreeRow(row, options); } function selectSidebarFileTreeRowById(rowId, options) { var id = String(rowId || '').trim(); if (!id) return false; var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(id) + '"]'); if (!(row instanceof HTMLElement)) return false; selectSidebarFileTreeRow(row, { ctrlKey: false, metaKey: false, shiftKey: false }); document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"]').forEach(function(activeRow) { if (activeRow instanceof HTMLElement) activeRow.setAttribute('data-active', 'false'); }); row.setAttribute('data-active', 'true'); if (options && options.scrollIntoView !== false) { try { row.scrollIntoView({ block: 'nearest' }); } catch (_) { row.scrollIntoView(); } } return true; } function pendingLocalFolderRestoreRowId() { if (window.__mnotePendingLocalFolderRestoreFiletreeRowId) { return String(window.__mnotePendingLocalFolderRestoreFiletreeRowId || '').trim(); } if (!window.localStorage) return ''; var rowId = ''; try { rowId = String(window.localStorage.getItem('mnote.pendingLocalFolderRestoreFiletreeRowId') || '').trim(); } catch (_) { rowId = ''; } if (!rowId && typeof window.name === 'string' && window.name.indexOf('mnote.pendingLocalFolderRestoreFiletreeRowId=') === 0) { rowId = decodeURIComponent(window.name.slice('mnote.pendingLocalFolderRestoreFiletreeRowId='.length)); } if (rowId) window.__mnotePendingLocalFolderRestoreFiletreeRowId = rowId; return rowId; } function clearPendingLocalFolderRestoreRowId() { window.__mnotePendingLocalFolderRestoreFiletreeRowId = ''; try { window.localStorage.removeItem('mnote.pendingLocalFolderRestoreFiletreeRowId'); } catch (_) {} if (typeof window.name === 'string' && window.name.indexOf('mnote.pendingLocalFolderRestoreFiletreeRowId=') === 0) { window.name = ''; } } function applyPendingLocalFolderRestoreFocusOnce() { var rowId = pendingLocalFolderRestoreRowId(); if (!rowId) return false; document.documentElement.setAttribute('data-mnote-local-folder-restore-pending-row-id', rowId); if (!selectSidebarFileTreeRowById(rowId, { scrollIntoView: true })) { document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'row-missing'); return false; } document.documentElement.setAttribute('data-mnote-local-folder-restore-focused-row-id', rowId); document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'focused'); clearPendingLocalFolderRestoreRowId(); return true; } function schedulePendingLocalFolderRestoreFocus(options) { if (!pendingLocalFolderRestoreRowId()) return false; var reason = options && options.reason ? String(options.reason) : ''; var attempt = Math.max(0, Number(options && options.attempt || 0)); var focused = applyPendingLocalFolderRestoreFocusOnce(); if (focused) return true; if (attempt >= 4) { document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'timeout'); return false; } if (window.__mnotePendingLocalFolderRestoreFocusFrame) { window.cancelAnimationFrame(window.__mnotePendingLocalFolderRestoreFocusFrame); window.__mnotePendingLocalFolderRestoreFocusFrame = 0; } window.__mnotePendingLocalFolderRestoreFocusFrame = window.requestAnimationFrame(function() { window.__mnotePendingLocalFolderRestoreFocusFrame = 0; window.setTimeout(function() { schedulePendingLocalFolderRestoreFocus({ reason: reason || 'retry', attempt: attempt + 1, }); }, 60); }); return focused; } function selectedSidebarFileTreeRowIdsForDrag(row) { var runtimeFn = fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRowIdsForDrag'); if (runtimeFn) return runtimeFn(row, sidebarFileTreeSelection); var rowId = row instanceof HTMLElement ? row.getAttribute('data-row-id') || '' : ''; if (rowId && sidebarFileTreeSelection.selectedRowIds.has(rowId)) { return Array.from(sidebarFileTreeSelection.selectedRowIds); } return rowId ? [rowId] : []; } function selectedSidebarFileTreeRows() { var runtimeFn = fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRows'); if (runtimeFn) return runtimeFn(sidebarFileTreeSelection, fileTreeSelectionRuntimeDeps()); var selectedIds = sidebarFileTreeSelection.selectedRowIds; var rows = visibleFileTreeRows().filter(function(row) { var rowId = row.getAttribute('data-row-id') || ''; return rowId && selectedIds.has(rowId); }); if (rows.length > 0) return rows; if (sidebarFileTreeSelection.focusedRowId) { var focused = document.querySelector( '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(sidebarFileTreeSelection.focusedRowId) + '"]' ); if (focused instanceof HTMLElement) return [focused]; } return []; } function fileTreeRowDocumentId(row) { var runtimeFn = fileTreeRuntimeFunction('fileTreeRowDocumentId'); if (runtimeFn) return runtimeFn(row); if (!(row instanceof HTMLElement)) return ''; return String(row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '').trim(); } function fileTreeRowAssetId(row) { var runtimeFn = fileTreeRuntimeFunction('fileTreeRowAssetId'); if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps()); if (!(row instanceof HTMLElement)) return ''; var assetId = String(row.getAttribute('data-asset-id') || '').trim(); if (assetId) return assetId; if (currentSourceKind() === 'local_folder' && fileTreeRowKind(row) === 'asset') { return String(row.getAttribute('data-row-id') || '').trim(); } return ''; } function decodeLocalEncodedPath(value) { var runtimeFn = fileTreeRuntimeFunction('decodeLocalEncodedPath'); if (runtimeFn) return runtimeFn(value); var path = String(value || '').trim().replace(/~([0-9A-Fa-f]{2})/g, '%$1'); if (!path) return ''; try { return decodeURIComponent(path); } catch (_) { return path; } } function normalizeFileTreeRelativePath(value) { var normalized = String(value || '').trim().replace(/^\/+|\/+$/g, ''); return normalized === '.' ? '' : normalized; } function parentRelativePathForFileTreePath(relativePath) { var normalized = normalizeFileTreeRelativePath(relativePath); if (!normalized) return ''; var index = normalized.lastIndexOf('/'); return index > 0 ? normalized.slice(0, index) : ''; } function fileNameStem(value) { var name = String(value || '').trim(); var dot = name.lastIndexOf('.'); return dot > 0 ? name.slice(0, dot) : name; } function localMarkdownBundleParentPath(relativePath) { var normalized = normalizeFileTreeRelativePath(relativePath); if (!/\.md$/i.test(normalized)) return ''; var parent = parentRelativePathForFileTreePath(normalized); if (!parent) return ''; var fileName = normalized.slice(normalized.lastIndexOf('/') + 1); var parentName = parent.slice(parent.lastIndexOf('/') + 1); return fileNameStem(fileName) === parentName ? parent : ''; } function visibleFileTreeRowByRelativePath(relativePath) { var normalized = normalizeFileTreeRelativePath(relativePath); if (!normalized) return null; var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="' + cssEscape(normalized) + '"]'); if (!(row instanceof HTMLElement)) return null; if (row.closest('.tree-children--collapsed')) return null; return row; } function activateSidebarFileTreeRow(row, options) { if (!(row instanceof HTMLElement)) return false; var rowId = row.getAttribute('data-row-id') || ''; if (!rowId) return false; selectSidebarFileTreeRow(row, { ctrlKey: false, metaKey: false, shiftKey: false }); document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"]').forEach(function(activeRow) { if (activeRow instanceof HTMLElement) activeRow.setAttribute('data-active', 'false'); }); row.setAttribute('data-active', 'true'); if (options && options.scrollIntoView !== false) { try { row.scrollIntoView({ block: 'nearest' }); } catch (_) { row.scrollIntoView(); } } return true; } function fileTreeRowLocalRelativePath(row) { var runtimeFn = fileTreeRuntimeFunction('fileTreeRowLocalRelativePath'); if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps()); if (!(row instanceof HTMLElement)) return ''; var direct = String(row.getAttribute('data-local-relative-path') || '').trim(); if (direct) return direct; var assetPath = localFilePathFromAssetId(fileTreeRowAssetId(row)); if (assetPath) return assetPath; var rowId = String(row.getAttribute('data-row-id') || '').trim(); var nodeId = String(row.getAttribute('data-node-id') || '').trim(); var documentId = fileTreeRowDocumentId(row); var kind = fileTreeRowKind(row); if (rowId.indexOf('local:asset:') === 0) return rowId.slice('local:asset:'.length); if (rowId.indexOf('local:markdown:') === 0) return rowId.slice('local:markdown:'.length); if (rowId.indexOf('local:folder:') === 0) return rowId.slice('local:folder:'.length); if (rowId.indexOf('local:node:') === 0) return rowId.slice('local:node:'.length); if (nodeId.indexOf('local:node:') === 0) return nodeId.slice('local:node:'.length); if ((kind === 'document' || kind === 'doc' || kind === 'markdown') && documentId.indexOf('local-md:') === 0) { return decodeLocalEncodedPath(documentId.slice('local-md:'.length)); } if ((kind === 'folder' || kind === 'directory' || kind === 'index') && documentId.indexOf('local-dir:') === 0) { return decodeLocalEncodedPath(documentId.slice('local-dir:'.length)); } return ''; } function fileTreeRowLocalUploadTargetRelativePath(row) { var runtimeFn = fileTreeRuntimeFunction('fileTreeRowLocalUploadTargetRelativePath'); if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps()); 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 readWorkspacePathFromRow(row) { var runtimeFn = fileTreeRuntimeFunction('readWorkspacePathFromRow'); if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps()); if (!(row instanceof HTMLElement)) return null; var relativePath = fileTreeRowLocalRelativePath(row); return { schema: 'mnote.workspace_path.v1', workspaceId: resolveWorkspaceId(row), sourceKind: currentSourceKind(), rootUri: currentRootUri(), relativePath: relativePath, localRelativePath: relativePath, objectIdentity: null, objectIdentityRaw: row.getAttribute('data-object-identity') || '', objectKind: row.getAttribute('data-object-kind') || '', resourceKind: row.getAttribute('data-object-kind') || fileTreeRowKind(row), rowId: row.getAttribute('data-row-id') || '', rowKind: fileTreeRowKind(row), documentId: fileTreeRowDocumentId(row), assetId: fileTreeRowAssetId(row), title: rowTitle(row), href: '', isLocalFolder: currentSourceKind() === 'local_folder' }; } function fileTreeRowKind(row) { var runtimeFn = fileTreeRuntimeFunction('fileTreeRowKind'); if (runtimeFn) return runtimeFn(row); if (!(row instanceof HTMLElement)) return ''; return String(row.getAttribute('data-row-kind') || '').trim(); } function isFileTreeDownloadableAssetRow(row) { var runtimeFn = fileTreeRuntimeFunction('isFileTreeDownloadableAssetRow'); if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps()); if (!(row instanceof HTMLElement)) return false; var kind = fileTreeRowKind(row); if (kind === 'document' || kind === 'doc' || kind === 'index' || kind === 'markdown' || kind === 'folder' || kind === 'directory') return false; return Boolean(fileTreeRowAssetId(row)); } function isFileTreeDownloadableRow(row) { var runtimeFn = fileTreeRuntimeFunction('isFileTreeDownloadableRow'); if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps()); if (!(row instanceof HTMLElement)) return false; if (currentSourceKind() !== 'local_folder') return isFileTreeDownloadableAssetRow(row); return Boolean(fileTreeRowLocalRelativePath(row) || isFileTreeDownloadableAssetRow(row)); } function fileTreeAssetDownloadDetail(row) { var runtimeFn = fileTreeRuntimeFunction('fileTreeAssetDownloadDetail'); if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps()); if (!isFileTreeDownloadableRow(row)) return null; var title = rowTitle(row); var relativePath = fileTreeRowLocalRelativePath(row); var assetId = fileTreeRowAssetId(row) || (relativePath ? 'local-file:' + relativePath : ''); return { contextKind: 'filetree', documentId: fileTreeRowDocumentId(row), rowId: row.getAttribute('data-row-id') || '', rowKind: fileTreeRowKind(row), assetId: assetId, localRelativePath: relativePath, workspacePath: readWorkspacePathFromRow(row), title: title, fileName: title, workspaceId: resolveWorkspaceId(row) }; } function selectedSidebarFileTreeRowsForDownload(contextRow) { var contextRowId = contextRow instanceof HTMLElement ? contextRow.getAttribute('data-row-id') || '' : ''; var selectedRows = selectedSidebarFileTreeRows().filter(isFileTreeDownloadableRow); if (contextRowId && sidebarFileTreeSelection.selectedRowIds.has(contextRowId) && selectedRows.length > 0) return selectedRows; return isFileTreeDownloadableRow(contextRow) ? [contextRow] : selectedRows; } function selectedSidebarFileTreeAssetRowsForDownload(contextRow) { return selectedSidebarFileTreeRowsForDownload(contextRow).filter(isFileTreeDownloadableAssetRow); } function downloadSelectedFileTreeAssetRows(detail, trigger) { var contextRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger; if (!(contextRow instanceof HTMLElement) && detail && detail.rowId) { contextRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(detail.rowId) + '"]'); } var rows = selectedSidebarFileTreeRowsForDownload(contextRow); var seen = new Set(); var downloads = []; rows.forEach(function(row) { var item = fileTreeAssetDownloadDetail(row); var key = item && (item.localRelativePath || item.assetId || item.rowId); if (!item || !key || seen.has(key)) return; seen.add(key); downloads.push(item); }); if (downloads.length === 0 && detail && detail.assetId) { downloads.push(Object.assign({}, detail, { fileName: detail.fileName || detail.title || '附件' })); } else if (downloads.length === 0 && detail && detail.localRelativePath) { downloads.push(Object.assign({}, detail, { assetId: detail.assetId || 'local-file:' + detail.localRelativePath, fileName: detail.fileName || detail.title || '附件' })); } if (downloads.length === 0) { recordFileTreeActionStatus('blocked', Object.assign({}, detail || {}, { reason: 'no-downloadable-assets' })); return false; } var primary = Object.assign({}, downloads[0], { count: downloads.length, assetIds: downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean), localRelativePaths: downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean) }); recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary); recordFileTreeActionStatus('requested', primary); document.documentElement.setAttribute('data-mnote-filetree-download-count', String(downloads.length)); document.documentElement.setAttribute('data-mnote-filetree-download-asset-ids', downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean).join(',')); document.documentElement.setAttribute('data-mnote-filetree-download-paths', downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean).join(',')); downloads.forEach(function(item) { void openEditorAttachmentDownload(item); }); return true; } function hasSelectedDocumentAncestor(row, selectedDocRowIds) { var node = row instanceof HTMLElement ? row.closest('.tree-node') : null; while (node && node.parentElement) { var parentChildren = node.parentElement.closest('.tree-children'); var parentNode = parentChildren ? parentChildren.closest('.tree-node') : null; var parentRow = parentNode ? parentNode.querySelector(':scope > .tree-row[data-shell-mode="filetree"]') : null; if (parentRow instanceof HTMLElement) { var parentRowId = parentRow.getAttribute('data-row-id') || ''; if (selectedDocRowIds.has(parentRowId)) return true; } node = parentNode; } return false; } function classifySidebarFileTreeAsset(row) { var badge = row instanceof HTMLElement ? row.querySelector('.tree-kind-badge') : null; var iconKind = badge instanceof HTMLElement ? String(badge.getAttribute('data-kind') || '').trim() : ''; var objectKind = row instanceof HTMLElement ? String(row.getAttribute('data-object-kind') || '').trim() : ''; var title = rowTitle(row).toLowerCase(); if (objectKind === 'mindmap' || iconKind === 'mindmap') return 'mindmap'; if (objectKind === 'table' || iconKind === 'table') return 'table'; return 'file'; } function buildSidebarFileTreeDeletePlan(rows) { var docRows = []; var folderRows = []; var assetRows = []; rows.forEach(function(row) { var kind = fileTreeRowKind(row); var documentId = fileTreeRowDocumentId(row); if ((kind === 'document' || kind === 'doc' || kind === 'index' || kind === 'markdown' || documentId.indexOf('local-md:') === 0) && documentId) { docRows.push(row); return; } if (kind === 'folder' && currentSourceKind() === 'local_folder') { folderRows.push(row); return; } if (fileTreeRowAssetId(row)) assetRows.push(row); }); var selectedContainerRowIds = new Set(docRows.concat(folderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean)); var topDocRows = []; var topFolderRows = []; var seenDocs = new Set(); var seenFolders = new Set(); docRows.forEach(function(row) { var documentId = fileTreeRowDocumentId(row); if (!documentId || seenDocs.has(documentId)) return; if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return; seenDocs.add(documentId); topDocRows.push(row); }); folderRows.forEach(function(row) { var rowId = row.getAttribute('data-row-id') || ''; if (!rowId || seenFolders.has(rowId)) return; if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return; seenFolders.add(rowId); topFolderRows.push(row); }); var selectedTopContainerRows = new Set(topDocRows.concat(topFolderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean)); var fileAssetRows = []; var mindmapRows = []; var tableRows = []; var seenAssets = new Set(); assetRows.forEach(function(row) { var assetId = fileTreeRowAssetId(row); if (!assetId || seenAssets.has(assetId)) return; if (hasSelectedDocumentAncestor(row, selectedTopContainerRows)) return; seenAssets.add(assetId); var assetKind = classifySidebarFileTreeAsset(row); if (assetKind === 'mindmap') mindmapRows.push(row); else if (assetKind === 'table') tableRows.push(row); else fileAssetRows.push(row); }); return { docRows: topDocRows, folderRows: topFolderRows, fileAssetRows: fileAssetRows, mindmapRows: mindmapRows, tableRows: tableRows }; } function sidebarFileTreeDeleteConfirmText(plan) { var docCount = plan.docRows.length; var folderCount = plan.folderRows.length; var fileCount = plan.fileAssetRows.length; var mindmapCount = plan.mindmapRows.length; var tableCount = plan.tableRows.length; var parts = []; if (docCount > 0) parts.push(docCount + ' 个页面(删除到垃圾桶)'); if (folderCount > 0) parts.push(folderCount + ' 个文件夹(删除到垃圾桶)'); if (fileCount > 0) parts.push(fileCount + ' 个附件(删除,10 分钟内可撤销)'); if (mindmapCount > 0) parts.push(mindmapCount + ' 个思维导图(移入垃圾桶,10 分钟内可恢复)'); if (tableCount > 0) parts.push(tableCount + (currentSourceKind() === 'local_folder' ? ' 个在线表格(移入垃圾桶,10 分钟内可恢复)' : ' 个在线表格(删除)')); return '确认删除选中的 ' + parts.join(' + ') + ' 吗?'; } async function postSidebarFileTreeJson(url, body) { var response = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body || {}) }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) { throw new Error((payload && (payload.error || payload.message)) || (url + ' failed: ' + response.status)); } return payload; } function fileTreeRowsByRowIds(rowIds) { var runtimeFn = fileTreeRuntimeFunction('fileTreeRowsByRowIds'); if (runtimeFn) return runtimeFn(rowIds, fileTreeRuntimeDeps()); return (rowIds || []).map(function(rowId) { return document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]'); }).filter(function(row) { return row instanceof HTMLElement; }); } function fileTreeChildCount(documentId) { var runtimeFn = fileTreeRuntimeFunction('fileTreeChildCount'); if (runtimeFn) return runtimeFn(documentId, fileTreeRuntimeDeps()); if (!documentId) return 0; var row = document.querySelector( '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + documentId) + '"],' + '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(documentId) + '"],' + '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-document-id="' + cssEscape(documentId) + '"],' + '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-node-id="' + cssEscape(documentId) + '"]' ); var node = row ? row.closest('.tree-node') : null; var children = node ? node.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"]') : []; return children.length; } function fileTreeMoveTargetParentId(targetRow) { if (!(targetRow instanceof HTMLElement)) return null; return fileTreeMenuTargetParentId({ rowKind: fileTreeRowKind(targetRow), rowId: targetRow.getAttribute('data-row-id') || '', documentId: fileTreeRowDocumentId(targetRow) }, targetRow) || null; } function fileTreeParentIdFromAttr(parentAttr) { var raw = String(parentAttr || '').trim(); if (!raw) return null; if (raw.indexOf('doc:') === 0) return raw.slice(4); return raw; } /** * Wolai-style move target: * - inside → nest under target (folder/dir/doc) * - before/after → same parent as target, sortOrder = sibling index (+1 for after) */ function resolveFileTreeMoveTarget(targetRow, position) { if (!(targetRow instanceof HTMLElement)) { return { parentId: null, sortOrder: 0 }; } var pos = position || 'inside'; if (pos === 'inside') { var nestParentId = fileTreeMoveTargetParentId(targetRow); return { parentId: nestParentId, sortOrder: nestParentId ? fileTreeChildCount(nestParentId) : 0, }; } var parentAttr = targetRow.getAttribute('data-parent-id') || ''; var parentId = fileTreeParentIdFromAttr(parentAttr); var siblings = Array.from( document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]') ).filter(function(row) { return (row.getAttribute('data-parent-id') || '') === parentAttr; }); var index = siblings.indexOf(targetRow); if (index < 0) index = 0; return { parentId: parentId, sortOrder: Math.max(0, index + (pos === 'after' ? 1 : 0)), }; } function fileTreeMoveSourceId(row) { if (!(row instanceof HTMLElement)) return ''; return fileTreeRowDocumentId(row) || (currentSourceKind() === 'local_folder' ? (fileTreeRowAssetId(row) || row.getAttribute('data-row-id') || '') : fileTreeRowAssetId(row)); } async function moveSidebarFileTreeRows(rowIds, targetRow, options) { var rows = fileTreeRowsByRowIds(rowIds || []); if (rows.length === 0) return false; var copy = Boolean(options && options.copy); var dropPosition = (options && options.dropPosition) || 'inside'; var resolved = resolveFileTreeMoveTarget(targetRow, dropPosition); var targetParentId = resolved.parentId; var baseSortOrder = typeof resolved.sortOrder === 'number' ? resolved.sortOrder : 0; var workspaceId = resolveWorkspaceId(targetRow || document.body); var writable = await ensureFileTreeWritableTarget('move', targetRow, rowIds || [], copy); if (!writable) return false; var batchId = nextFileTreeOperationBatchId(copy ? 'copy' : 'move'); var plan = buildSidebarFileTreeDeletePlan(rows); var failures = []; if (copy) { dispatchSidebarEvent('tree.filetree.copy-requested', { rowIds: rowIds || [], targetDocumentId: targetParentId }); recordFileTreeActionStatus('copy-requested', { documentId: targetParentId, batchId: batchId }); return true; } var movableRows = plan.docRows.concat(plan.folderRows); if (currentSourceKind() === 'local_folder') { movableRows = movableRows.concat(plan.fileAssetRows, plan.mindmapRows, plan.tableRows); } for (var i = 0; i < movableRows.length; i += 1) { var sourceRow = movableRows[i]; var sourceId = fileTreeMoveSourceId(sourceRow); if (!sourceId || sourceId === targetParentId) continue; try { await dispatchTreeCommand(targetRow || sourceRow, { action: 'move', batchId: batchId, workspaceId: workspaceId, documentId: sourceId, parentId: targetParentId, sortOrder: baseSortOrder + i }); } catch (error) { failures.push(sourceId + ': ' + (error && error.message ? error.message : '移动失败')); } } if (currentSourceKind() !== 'local_folder') { var assetIds = plan.fileAssetRows.map(fileTreeRowAssetId).filter(Boolean); if (assetIds.length > 0) { try { await postSidebarFileTreeJson('/api/media/batch', { action: 'move', assetIds: assetIds, targetDocumentId: targetParentId }); } catch (error) { failures.push(assetIds.join(',') + ': ' + (error && error.message ? error.message : '移动附件失败')); } } } window.dispatchEvent(new CustomEvent('tree:local-command-batch-complete', { detail: { batchId: batchId, action: 'move', failed: failures.length, count: movableRows.length } })); if (failures.length > 0) { recordFileTreeActionStatus('failed', { documentId: targetParentId, fallback: 'alert', batchId: batchId }); await window.mnote.alert('部分对象移动失败:' + failures.join(';')); return false; } recordFileTreeActionStatus('applied', { documentId: targetParentId, batchId: batchId }); return true; } async function pasteSidebarFileTreeClipboard(trigger) { var clipboard = currentSidebarFileTreeClipboard(); if (!clipboard) return false; var targetRow = trigger instanceof HTMLElement ? trigger : null; if (!targetRow && sidebarFileTreeSelection.focusedRowId) { targetRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(sidebarFileTreeSelection.focusedRowId) + '"]'); } var targetDocumentId = fileTreeMoveTargetParentId(targetRow) || currentDocumentId(); var action = clipboard.action === 'cut' ? 'move' : 'copy'; recordFileTreeAction('paste', { rowId: targetRow ? targetRow.getAttribute('data-row-id') || '' : '', documentId: targetDocumentId, sourceRowIds: clipboard.rowIds, clipboardAction: clipboard.action }); var ok = await moveSidebarFileTreeRows(clipboard.rowIds, targetRow, { copy: action === 'copy' }); if (ok && action === 'move') { setSidebarFileTreeClipboard(null, []); } return ok; } async function deleteSelectedSidebarFileTreeRows(trigger) { var rows = selectedSidebarFileTreeRows(); var plan = buildSidebarFileTreeDeletePlan(rows); var total = plan.docRows.length + plan.folderRows.length + plan.fileAssetRows.length + plan.mindmapRows.length + plan.tableRows.length; if (total === 0) return false; if (!(await window.mnote.confirm(sidebarFileTreeDeleteConfirmText(plan)))) return false; var batchId = nextFileTreeOperationBatchId('bulk-delete'); document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-batch-id', batchId); recordFileTreeAction('bulk-delete', { rowId: trigger instanceof HTMLElement ? trigger.getAttribute('data-row-id') || '' : '', count: total, batchId: batchId }); recordFileTreeActionStatus('pending', { count: total, batchId: batchId }); var failures = []; for (var i = 0; i < plan.docRows.length; i += 1) { var docRow = plan.docRows[i]; var documentId = fileTreeRowDocumentId(docRow); try { await dispatchTreeCommand(trigger || docRow, { action: 'archive', batchId: batchId, workspaceId: resolveWorkspaceId(docRow), documentId: documentId }); applyRemoveDocumentDelta({ documentId: documentId }); } catch (error) { failures.push(documentId); } } for (var fd = 0; fd < plan.folderRows.length; fd += 1) { var folderRow = plan.folderRows[fd]; var folderId = folderRow.getAttribute('data-row-id') || folderRow.getAttribute('data-node-id') || ''; try { await dispatchTreeCommand(trigger || folderRow, { action: 'archive', batchId: batchId, workspaceId: resolveWorkspaceId(folderRow), documentId: folderId }); } catch (error) { failures.push(folderId); } } if (plan.fileAssetRows.length > 0) { var fileAssetIds = plan.fileAssetRows.map(fileTreeRowAssetId).filter(Boolean); try { if (currentSourceKind() === 'local_folder') { for (var lf = 0; lf < fileAssetIds.length; lf += 1) { await dispatchTreeCommand(trigger || plan.fileAssetRows[lf], { action: 'archive', batchId: batchId, workspaceId: resolveWorkspaceId(plan.fileAssetRows[lf]), documentId: fileAssetIds[lf] }); } } else { await postSidebarFileTreeJson('/api/media/batch', { action: 'delete', assetIds: fileAssetIds }); } fileAssetIds.forEach(removeFileTreeAssetRow); } catch (error) { failures = failures.concat(fileAssetIds.map(function(assetId) { return assetId + ': ' + (error && error.message ? error.message : '删除附件失败'); })); } } for (var m = 0; m < plan.mindmapRows.length; m += 1) { var mindmapRow = plan.mindmapRows[m]; var mindmapId = fileTreeRowAssetId(mindmapRow); try { if (currentSourceKind() === 'local_folder') { await dispatchTreeCommand(trigger || mindmapRow, { action: 'archive', batchId: batchId, workspaceId: resolveWorkspaceId(mindmapRow), documentId: mindmapId }); } else { var mindmapDocId = fileTreeRowDocumentId(mindmapRow); var mindmapResponse = await fetch('/api/mindmap/' + encodeURIComponent(mindmapDocId) + '/' + encodeURIComponent(mindmapId), { method: 'DELETE' }); if (!mindmapResponse.ok) throw new Error('mindmap_delete_failed_' + mindmapResponse.status); } removeFileTreeAssetRow(mindmapId); } catch (error) { failures.push(mindmapId); } } for (var t = 0; t < plan.tableRows.length; t += 1) { var tableRow = plan.tableRows[t]; var tableId = fileTreeRowAssetId(tableRow); try { if (currentSourceKind() === 'local_folder') { await dispatchTreeCommand(trigger || tableRow, { action: 'archive', batchId: batchId, workspaceId: resolveWorkspaceId(tableRow), documentId: tableId }); } else { var tableResponse = await fetch('/api/tables/' + encodeURIComponent(tableId), { method: 'DELETE' }); if (!tableResponse.ok) throw new Error('table_delete_failed_' + tableResponse.status); window.dispatchEvent(new CustomEvent('online-table-deleted', { detail: { tableId: tableId } })); } removeFileTreeAssetRow(tableId); } catch (error) { failures.push(tableId); } } sidebarFileTreeSelection.selectedRowIds = new Set(); sidebarFileTreeSelection.focusedRowId = null; syncSidebarFileTreeSelection(); if (failures.length > 0) { window.dispatchEvent(new CustomEvent('tree:local-command-batch-complete', { detail: { batchId: batchId, action: 'bulk-delete', failed: failures.length, count: total } })); recordFileTreeActionStatus('failed', { count: total, failures: failures.slice(0, 20), fallback: 'alert', batchId: batchId }); await window.mnote.alert('部分对象删除失败:' + failures.slice(0, 5).join(', ') + (failures.length > 5 ? '…' : '')); return false; } document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-applied', 'true'); window.dispatchEvent(new CustomEvent('tree:local-command-batch-complete', { detail: { batchId: batchId, action: 'bulk-delete', failed: 0, count: total } })); recordFileTreeActionStatus('archived', { count: total, undo: 'trash-modal', batchId: batchId }); if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot(); return true; } return { rowTitle, rowCenter, renameFileTreeAsset, beginFileTreeInlineRename, closeTreeContextMenu, copyTreeContextValue, triggerBrowserDownload, recordFileTreeAction, recordFileTreeActionStatus, documentHref, convertToPreviousSiblingChild, fileTreeCopyPath, fileTreeMenuTargetParentId, withOfficeEditModeGuard, handleTreeContextMenuAction, appendTreeContextMenuButton, buildSidebarFileTreeContext, evaluateSidebarFileTreeWhen, openTreeContextMenu, openPageTreeContextMenu, openFileTreeContextMenu, visibleFileTreeRows, syncSidebarFileTreeSelection, selectSidebarFileTreeRow, selectSidebarFileTreeDocument, selectSidebarFileTreeRowById, pendingLocalFolderRestoreRowId, clearPendingLocalFolderRestoreRowId, applyPendingLocalFolderRestoreFocusOnce, schedulePendingLocalFolderRestoreFocus, selectedSidebarFileTreeRowIdsForDrag, selectedSidebarFileTreeRows, fileTreeRowDocumentId, fileTreeRowAssetId, decodeLocalEncodedPath, fileTreeRowLocalRelativePath, fileTreeRowLocalUploadTargetRelativePath, readWorkspacePathFromRow, fileTreeRowKind, isFileTreeDownloadableAssetRow, isFileTreeDownloadableRow, fileTreeAssetDownloadDetail, selectedSidebarFileTreeRowsForDownload, selectedSidebarFileTreeAssetRowsForDownload, downloadSelectedFileTreeAssetRows, hasSelectedDocumentAncestor, classifySidebarFileTreeAsset, buildSidebarFileTreeDeletePlan, sidebarFileTreeDeleteConfirmText, postSidebarFileTreeJson, fileTreeRowsByRowIds, fileTreeChildCount, resolveFileTreeMoveTarget, moveSidebarFileTreeRows, pasteSidebarFileTreeClipboard, deleteSelectedSidebarFileTreeRows, }; };