Files
mnote/rust/crates/mnote-web/browser/sidebar-filetree-command-runtime.js
T

1978 lines
90 KiB
JavaScript
Raw Normal View History

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,
2026-06-01 10:07:42 +08:00
openLocalResourceInActiveTab,
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;
}
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();
}
2026-06-01 10:07:42 +08:00
function isLocalOcrSourceFileName(fileName) {
return /\.(png|jpe?g|webp|gif|bmp|tiff?|pdf)$/i.test(String(fileName || '').trim());
}
function localOcrSourceRelativePath(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 localOcrRootUri(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 supportsLocalOcr(detail) {
var path = localOcrSourceRelativePath(detail);
var title = String(detail && (detail.title || detail.fileName) || '').trim() || path.split('/').pop() || '';
return Boolean(path && localOcrRootUri(detail, null) && isLocalOcrSourceFileName(title || path));
}
function localOcrProvider() {
var override = String(window.__MNOTE_LOCAL_OCR_PROVIDER || '').trim().toLowerCase();
return override === 'mock' ? 'mock' : 'mineru';
}
async function runLocalOcrForDetail(detail, trigger) {
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', 'running');
var payload = await ingestKnowledgeRagForDetail(detail, trigger).catch(function(error) {
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', 'failed');
throw error;
});
var sourceRootRelativePath = localOcrSourceRelativePath(detail)
|| String(detail && (detail.localRelativePath || detail.path || detail.title) || '').trim();
var rootUri = localOcrRootUri(detail, trigger);
var status = payload && payload.retryRequired ? 'retry' : 'done';
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', status);
document.documentElement.setAttribute('data-mnote-local-ocr-menu-path', sourceRootRelativePath);
window.dispatchEvent(new CustomEvent('mnote:local-ocr-job-updated', {
detail: { status: status, job: payload, rootUri: rootUri, sourceRootRelativePath: sourceRootRelativePath }
}));
return payload;
}
async function ingestKnowledgeRagForDetail(detail, trigger) {
var sourceRootRelativePath = localOcrSourceRelativePath(detail)
|| String(detail && (detail.localRelativePath || detail.path || detail.title) || '').trim();
var rootUri = localOcrRootUri(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', {
2026-06-01 10:07:42 +08:00
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: workspaceId,
rootUri: rootUri,
sources: [{ sourcePath: sourceRootRelativePath }]
})
2026-06-01 10:07:42 +08:00
});
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');
2026-06-01 10:07:42 +08:00
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 }
2026-06-01 10:07:42 +08:00
}));
return payload;
2026-06-01 10:07:42 +08:00
}
function handleTreeContextMenuAction(action, detail, trigger) {
closeTreeContextMenu();
detail = detail || {};
2026-06-01 10:07:42 +08:00
if (action === 'local-ocr') {
recordFileTreeAction('knowledge-rag-index', detail);
2026-06-01 10:07:42 +08:00
recordFileTreeActionStatus('pending', detail);
void runLocalOcrForDetail(detail, trigger).then(function(job) {
recordFileTreeActionStatus(job && job.retryRequired ? 'retry' : 'done', detail);
2026-06-01 10:07:42 +08:00
}).catch(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.alert(error && error.message ? error.message : '资料库索引失败');
});
return;
}
if (action === 'knowledge-rag-index') {
recordFileTreeAction('knowledge-rag-index', detail);
recordFileTreeActionStatus('pending', detail);
void ingestKnowledgeRagForDetail(detail, trigger).then(function() {
recordFileTreeActionStatus('done', detail);
}).catch(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.alert(error && error.message ? error.message : '资料库索引失败');
2026-06-01 10:07:42 +08:00
});
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 === '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;
}
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);
}
// ── 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));
2026-06-01 10:07:42 +08:00
var localOcrSupported = supportsLocalOcr(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 文件夹支持星标置顶' },
{ action: 'paste-into', icon: 'content_paste', label: '粘贴到此处', disabled: !runtimeState.sidebarFileTreeClipboard, title: runtimeState.sidebarFileTreeClipboard ? '粘贴到当前文件树目标' : '剪贴板为空', 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') {
var ragItem = { action: 'knowledge-rag-index', icon: 'travel_explore', label: '加入资料库索引', when: '!workspace.readonly' };
var insertAt = isAttachment ? 11 : isAsset ? 3 : 3;
if (insertAt >= 0) items.splice(insertAt, 0, ragItem);
2026-06-01 10:07:42 +08:00
}
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;
2026-05-29 11:13:05 +08:00
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) + '"]');
2026-05-29 11:13:05 +08:00
}
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
2026-05-29 11:13:05 +08:00
}).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;
}
2026-05-29 11:13:05 +08:00
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');
2026-05-29 11:13:05 +08:00
clearPendingLocalFolderRestoreRowId();
return true;
}
2026-05-29 11:13:05 +08:00
function schedulePendingLocalFolderRestoreFocus(options) {
if (!pendingLocalFolderRestoreRowId()) return false;
2026-05-29 11:13:05 +08:00
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) + '"]');
var node = row ? row.closest('.tree-node') : null;
var children = node ? node.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="document"], :scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="doc"]') : [];
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 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 targetParentId = fileTreeMoveTargetParentId(targetRow);
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: targetParentId ? fileTreeChildCount(targetParentId) + i : 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) {
if (!runtimeState.sidebarFileTreeClipboard || !Array.isArray(runtimeState.sidebarFileTreeClipboard.rowIds) || runtimeState.sidebarFileTreeClipboard.rowIds.length === 0) 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 = runtimeState.sidebarFileTreeClipboard.action === 'cut' ? 'move' : 'copy';
recordFileTreeAction('paste', {
rowId: targetRow ? targetRow.getAttribute('data-row-id') || '' : '',
documentId: targetDocumentId,
sourceRowIds: runtimeState.sidebarFileTreeClipboard.rowIds,
clipboardAction: runtimeState.sidebarFileTreeClipboard.action
});
var ok = await moveSidebarFileTreeRows(runtimeState.sidebarFileTreeClipboard.rowIds, targetRow, { copy: action === 'copy' });
if (ok && action === 'move') runtimeState.sidebarFileTreeClipboard = 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,
moveSidebarFileTreeRows,
pasteSidebarFileTreeClipboard,
deleteSelectedSidebarFileTreeRows,
};
};