Files
mnote/rust/crates/mnote-web/browser/sidebar-filetree-command-runtime.js
T
Agent Board bc6f8488ee feat: vault core/CLI/workbench, vaultd token path, filetree view-state cleanup
Land password-vault dedicated workbench and mnote-vault-core/CLI, agent token
read path design, vault transport split, and retire obsolete filetree smokes.
Ignore local vault reimport scripts that trip secret scanners.
2026-07-24 11:36:06 +08:00

2337 lines
107 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
const {
applyRemoveDocumentDelta,
createFileTreeFolder,
createPage,
cssEscape,
currentDocumentId,
currentRootUri,
currentSourceKind,
deleteSingleFileTreeAsset,
dispatchSidebarEvent,
dispatchTreeCommand,
ensureFileTreeWritableTarget,
fileTreeRuntimeDeps,
fileTreeRuntimeFunction,
fileTreeSelectionRuntimeDeps,
fileTreeSelectionRuntimeFunction,
isFileTreePageRow,
localFilePathFromAssetId,
normalizeFileTreePageRenameTitle,
openConvexAssetFromFileTree,
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(function(error) {
committing = false;
input.disabled = false;
window.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();
}
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) {
window.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(/^\/+/, '');
if (!rootPath || !normalized) return rootPath || normalized;
return rootPath.replace(/\/+$/g, '') + '/' + normalized;
}
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;
}
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(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 {
window.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 openConvexAssetFromFileTree({ ...detail, openTarget: 'new-window' });
return;
}
if (isAsset && action === 'open-edit-mode') {
withOfficeEditModeGuard(function() {
recordFileTreeAction('open-edit-mode', detail);
void openConvexAssetFromFileTree({ ...detail, openTarget: 'edit-mode' });
});
return;
}
if (isAsset && action === 'open-right') {
recordFileTreeAction('open-right', detail);
void openConvexAssetFromFileTree({ ...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 (!window.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(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.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(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.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(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.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(function(){ updateTitleEverywhere(documentId, nextTitle.trim()); });
}
return;
}
if (action === 'create-child') {
void createPage(trigger || document.body, documentId);
return;
}
if (action === 'convert-child') {
convertToPreviousSiblingChild(trigger, detail);
return;
}
var deleteTargetId = documentId || String(detail.rowId || '').trim();
if (action === 'delete-trash' && deleteTargetId) {
if (!window.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(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.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,避免前端直连 WeKnora 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 });
window.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 (!window.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 });
window.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,
};
};