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