1572 lines
70 KiB
JavaScript
1572 lines
70 KiB
JavaScript
export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||
const {
|
||
applyRemoveDocumentDelta,
|
||
createFileTreeFolder,
|
||
createPage,
|
||
cssEscape,
|
||
currentDocumentId,
|
||
currentSourceKind,
|
||
deleteSingleFileTreeAsset,
|
||
dispatchSidebarEvent,
|
||
dispatchTreeCommand,
|
||
ensureFileTreeWritableTarget,
|
||
fileTreeRuntimeDeps,
|
||
fileTreeRuntimeFunction,
|
||
fileTreeSelectionRuntimeDeps,
|
||
fileTreeSelectionRuntimeFunction,
|
||
isFileTreePageRow,
|
||
localFilePathFromAssetId,
|
||
normalizeFileTreePageRenameTitle,
|
||
openConvexAssetFromFileTree,
|
||
openEditorAttachmentDetail,
|
||
openEditorAttachmentDownload,
|
||
openEditorAttachmentEditTab,
|
||
openEditorAttachmentNewWindow,
|
||
refreshLocalFolderSidebarSnapshot,
|
||
resolveWorkspaceId,
|
||
runtimeState,
|
||
selectedSidebarFileTreeSelection,
|
||
updateTitleEverywhere,
|
||
validateFileTreeRename,
|
||
} = dependencies;
|
||
const sidebarFileTreeSelection = selectedSidebarFileTreeSelection;
|
||
|
||
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') || '';
|
||
if (!documentId && !assetId) return false;
|
||
if (rowKind && ['document', 'doc', 'index', 'markdown', 'asset'].indexOf(rowKind) < 0 && !assetId) 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', row.getAttribute('data-row-id') || '');
|
||
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
|
||
? 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: documentId,
|
||
title: commandTitle
|
||
}).then(function(){ updateTitleEverywhere(documentId, commandTitle); });
|
||
void work.then(close).catch(function(error) {
|
||
committing = false;
|
||
input.disabled = false;
|
||
window.alert(error && error.message ? error.message : '重命名失败');
|
||
});
|
||
};
|
||
input.addEventListener('click', function(event) { event.stopPropagation(); });
|
||
input.addEventListener('dblclick', function(event) { event.stopPropagation(); });
|
||
input.addEventListener('keydown', function(event) {
|
||
event.stopPropagation();
|
||
if (event.key === 'Enter') {
|
||
event.preventDefault();
|
||
commit();
|
||
} else if (event.key === 'Escape') {
|
||
event.preventDefault();
|
||
close();
|
||
}
|
||
});
|
||
input.addEventListener('blur', commit);
|
||
link.parentElement.insertBefore(input, link.nextSibling);
|
||
link.parentElement.insertBefore(validation, input.nextSibling);
|
||
window.requestAnimationFrame(function() {
|
||
input.focus();
|
||
input.select();
|
||
});
|
||
return true;
|
||
}
|
||
|
||
function closeTreeContextMenu() {
|
||
if (runtimeState.activeTreeContextMenu && runtimeState.activeTreeContextMenu.parentElement) {
|
||
runtimeState.activeTreeContextMenu.parentElement.removeChild(runtimeState.activeTreeContextMenu);
|
||
}
|
||
runtimeState.activeTreeContextMenu = null;
|
||
}
|
||
|
||
function copyTreeContextValue(value, actionName) {
|
||
var text = String(value || '');
|
||
var done = function() {
|
||
document.documentElement.setAttribute('data-mnote-tree-context-last-copy', actionName || 'copy');
|
||
};
|
||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
||
return navigator.clipboard.writeText(text).then(done).catch(function(){});
|
||
}
|
||
var textarea = document.createElement('textarea');
|
||
textarea.value = text;
|
||
textarea.setAttribute('readonly', 'readonly');
|
||
textarea.style.position = 'fixed';
|
||
textarea.style.left = '-9999px';
|
||
document.body.appendChild(textarea);
|
||
textarea.select();
|
||
try { document.execCommand('copy'); } catch (_) {}
|
||
document.body.removeChild(textarea);
|
||
done();
|
||
return Promise.resolve();
|
||
}
|
||
|
||
function triggerBrowserDownload(url) {
|
||
if (!url) return false;
|
||
var link = document.createElement('a');
|
||
link.href = url;
|
||
link.target = '_blank';
|
||
link.rel = 'noopener noreferrer';
|
||
link.download = '';
|
||
link.style.position = 'fixed';
|
||
link.style.left = '-9999px';
|
||
link.style.top = '0';
|
||
document.body.appendChild(link);
|
||
try {
|
||
link.click();
|
||
} catch (_) {
|
||
if (typeof window.open === 'function') window.open(url, '_blank', 'noopener,noreferrer');
|
||
}
|
||
window.setTimeout(function() {
|
||
if (link.parentElement) link.parentElement.removeChild(link);
|
||
}, 1000);
|
||
return true;
|
||
}
|
||
|
||
function recordFileTreeAction(action, detail) {
|
||
var normalized = String(action || '').trim() || 'unknown';
|
||
var rowId = String(detail && detail.rowId || '').trim();
|
||
var documentId = String(detail && detail.documentId || '').trim();
|
||
var assetId = String(detail && detail.assetId || '').trim();
|
||
document.documentElement.setAttribute('data-mnote-filetree-last-action', normalized);
|
||
if (rowId) document.documentElement.setAttribute('data-mnote-filetree-last-action-row-id', rowId);
|
||
if (documentId) document.documentElement.setAttribute('data-mnote-filetree-last-action-document-id', documentId);
|
||
if (assetId) document.documentElement.setAttribute('data-mnote-filetree-last-action-asset-id', assetId);
|
||
window.dispatchEvent(new CustomEvent('tree.filetree.action', {
|
||
detail: Object.assign({}, detail || {}, { action: normalized })
|
||
}));
|
||
}
|
||
|
||
function recordFileTreeActionStatus(status, detail) {
|
||
var normalized = String(status || '').trim() || 'unknown';
|
||
document.documentElement.setAttribute('data-mnote-filetree-last-action-status', normalized);
|
||
window.dispatchEvent(new CustomEvent('tree.filetree.action.status', {
|
||
detail: Object.assign({}, detail || {}, { status: normalized })
|
||
}));
|
||
}
|
||
|
||
function documentHref(documentId, workspaceId) {
|
||
var url = new URL('/documents/' + encodeURIComponent(documentId), window.location.origin);
|
||
if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
|
||
copyWorkspaceSourceParams(url);
|
||
return url.toString();
|
||
}
|
||
|
||
function convertToPreviousSiblingChild(trigger, detail) {
|
||
var documentId = detail.documentId || '';
|
||
var row = document.querySelector('#sidebar-tree-root .tree-row[data-node-id="' + cssEscape(documentId) + '"]');
|
||
if (!(row instanceof HTMLElement)) return;
|
||
var parentId = row.getAttribute('data-parent-id') || '';
|
||
var siblings = Array.from(document.querySelectorAll('#sidebar-tree-root .tree-row[data-shell-mode="page"]')).filter(function(candidate) {
|
||
return (candidate.getAttribute('data-parent-id') || '') === parentId;
|
||
});
|
||
var index = siblings.indexOf(row);
|
||
if (index <= 0) {
|
||
window.alert('当前页面前面没有同级页面。');
|
||
return;
|
||
}
|
||
var previous = siblings[index - 1];
|
||
var previousId = previous.getAttribute('data-node-id') || '';
|
||
var children = previous.parentElement ? previous.parentElement.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="page"]') : [];
|
||
void dispatchTreeCommand(trigger || row, {
|
||
action: 'move',
|
||
workspaceId: detail.workspaceId || resolveWorkspaceId(row),
|
||
documentId: documentId,
|
||
parentId: previousId,
|
||
sortOrder: children.length
|
||
});
|
||
}
|
||
|
||
function fileTreeCopyPath(detail, trigger) {
|
||
// 对 local_folder 复制真实相对路径,不复制标题
|
||
if (currentSourceKind() === 'local_folder') {
|
||
if (detail.assetId) {
|
||
var path = localFilePathFromAssetId(detail.assetId);
|
||
if (path) {
|
||
try {
|
||
return decodeURIComponent(path.replace(/~2F/g, '/'));
|
||
} catch (_) {
|
||
return path.replace(/~2F/g, '/');
|
||
}
|
||
}
|
||
}
|
||
if (detail.documentId) {
|
||
var docPath = String(detail.documentId || '')
|
||
.replace(/^local-md:/, '')
|
||
.replace(/^local-dir:/, '')
|
||
.replace(/~2F/g, '/');
|
||
if (docPath) {
|
||
try {
|
||
return decodeURIComponent(docPath);
|
||
} catch (_) {
|
||
return docPath;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return detail.title || '';
|
||
}
|
||
|
||
function 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 handleTreeContextMenuAction(action, detail, trigger) {
|
||
closeTreeContextMenu();
|
||
detail = detail || {};
|
||
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 === 'download') {
|
||
downloadSelectedFileTreeAssetRows(detail, trigger);
|
||
return;
|
||
}
|
||
if (isAsset && action === 'new-window') {
|
||
recordFileTreeAction('new-window', detail);
|
||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'new-window' });
|
||
return;
|
||
}
|
||
if (isAsset && action === 'open-edit-mode') {
|
||
withOfficeEditModeGuard(function() {
|
||
recordFileTreeAction('open-edit-mode', detail);
|
||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'edit-mode' });
|
||
});
|
||
return;
|
||
}
|
||
if (isAsset && action === 'open-right') {
|
||
recordFileTreeAction('open-right', detail);
|
||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'side' });
|
||
return;
|
||
}
|
||
if (action === 'open-right') {
|
||
recordFileTreeAction('open-right', detail);
|
||
dispatchSidebarEvent('tree.page.open-right', detail);
|
||
return;
|
||
}
|
||
if (action === 'share') {
|
||
dispatchSidebarEvent('tree.page.share', detail);
|
||
return;
|
||
}
|
||
if (action === 'copy-link') {
|
||
void copyTreeContextValue(documentHref(documentId, workspaceId), 'copy-link');
|
||
return;
|
||
}
|
||
if (action === 'copy-link-title') {
|
||
void copyTreeContextValue(title + ' ' + documentHref(documentId, workspaceId), 'copy-link-title');
|
||
return;
|
||
}
|
||
if (action === 'copy-reference-inline') {
|
||
void copyTreeContextValue('((' + title + ' ' + documentId + '))', 'copy-reference-inline');
|
||
return;
|
||
}
|
||
if (action === 'copy-reference-embed') {
|
||
void copyTreeContextValue('{{' + title + ' ' + documentId + '}}', 'copy-reference-embed');
|
||
return;
|
||
}
|
||
if (action === 'copy-id') {
|
||
void copyTreeContextValue(documentId || detail.assetId || detail.rowId || '', 'copy-id');
|
||
return;
|
||
}
|
||
if (action === 'delete-trash' && detail.contextKind === 'filetree') {
|
||
var selectedFileTreeRows = selectedSidebarFileTreeRows();
|
||
if (selectedFileTreeRows.length > 1) {
|
||
void deleteSelectedSidebarFileTreeRows(trigger || document.body);
|
||
return;
|
||
}
|
||
}
|
||
if (action === 'delete-trash' && isAsset) {
|
||
if (!window.confirm('确定要将“' + title + '”删除到垃圾桶吗?')) return;
|
||
recordFileTreeAction('delete-trash', detail);
|
||
recordFileTreeActionStatus('pending', detail);
|
||
void deleteSingleFileTreeAsset(detail, trigger).then(function() {
|
||
recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' }));
|
||
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
||
}).catch(function(error) {
|
||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||
window.alert(error && error.message ? error.message : '资源删除失败');
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'new-file') {
|
||
void createPage(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null);
|
||
return;
|
||
}
|
||
if (action === 'new-folder') {
|
||
recordFileTreeAction('new-folder', detail);
|
||
void createFileTreeFolder(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null).then(function(ok) {
|
||
recordFileTreeActionStatus(ok ? 'created' : 'skipped', detail);
|
||
}).catch(function(error) {
|
||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||
window.alert(error && error.message ? error.message : '新建文件夹失败');
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'paste-into') {
|
||
var pasteRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
|
||
recordFileTreeAction('paste-into', detail);
|
||
void pasteSidebarFileTreeClipboard(pasteRow).then(function(ok) {
|
||
recordFileTreeActionStatus(ok ? 'applied' : 'skipped', detail);
|
||
}).catch(function(error) {
|
||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||
window.alert(error && error.message ? error.message : '粘贴失败');
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'copy-path') {
|
||
void copyTreeContextValue(fileTreeCopyPath(detail, trigger), 'copy-path');
|
||
return;
|
||
}
|
||
if (action === 'refresh') {
|
||
document.documentElement.setAttribute('data-mnote-filetree-refresh-requested', 'true');
|
||
dispatchSidebarEvent('tree.filetree.refresh', detail);
|
||
return;
|
||
}
|
||
if (action === 'collapse-all') {
|
||
document.querySelectorAll('#sidebar-file-tree-root .tree-row[aria-expanded="true"]').forEach(function(row) {
|
||
if (!(row instanceof HTMLElement)) return;
|
||
row.setAttribute('aria-expanded', 'false');
|
||
var node = row.closest('.tree-node');
|
||
var children = node ? node.querySelector(':scope > .tree-children') : null;
|
||
if (children) children.classList.add('tree-children--collapsed');
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'reveal') {
|
||
if (trigger && typeof trigger.scrollIntoView === 'function') {
|
||
trigger.scrollIntoView({ block: 'nearest' });
|
||
trigger.focus && trigger.focus();
|
||
}
|
||
return;
|
||
}
|
||
if (action === 'duplicate') {
|
||
dispatchSidebarEvent('tree.page.duplicate', detail);
|
||
return;
|
||
}
|
||
if (action === 'rename') {
|
||
if (detail.contextKind === 'filetree' && trigger) {
|
||
var renameRow = trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
|
||
if (beginFileTreeInlineRename(renameRow)) return;
|
||
}
|
||
var nextTitle = window.prompt('重命名页面', title);
|
||
if (nextTitle && nextTitle.trim() && documentId) {
|
||
void dispatchTreeCommand(trigger || document.body, {
|
||
action: 'rename',
|
||
workspaceId: workspaceId,
|
||
documentId: documentId,
|
||
title: nextTitle.trim()
|
||
}).then(function(){ updateTitleEverywhere(documentId, nextTitle.trim()); });
|
||
}
|
||
return;
|
||
}
|
||
if (action === 'create-child') {
|
||
void createPage(trigger || document.body, documentId);
|
||
return;
|
||
}
|
||
if (action === 'convert-child') {
|
||
convertToPreviousSiblingChild(trigger, detail);
|
||
return;
|
||
}
|
||
var deleteTargetId = documentId || String(detail.rowId || '').trim();
|
||
if (action === 'delete-trash' && deleteTargetId) {
|
||
if (!window.confirm('确定要将“' + title + '”删除到垃圾桶吗?')) return;
|
||
recordFileTreeAction('delete-trash', detail);
|
||
recordFileTreeActionStatus('pending', detail);
|
||
void dispatchTreeCommand(trigger || document.body, {
|
||
action: 'archive',
|
||
workspaceId: workspaceId,
|
||
documentId: deleteTargetId
|
||
}).then(function() {
|
||
recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' }));
|
||
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
||
}).catch(function(error) {
|
||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||
window.alert(error && error.message ? error.message : '删除失败');
|
||
});
|
||
}
|
||
}
|
||
|
||
function appendTreeContextMenuButton(menu, item, detail, trigger) {
|
||
if (item.separator) {
|
||
var sep = document.createElement('div');
|
||
sep.className = 'mnote-tree-context-menu__separator';
|
||
sep.setAttribute('role', 'separator');
|
||
menu.appendChild(sep);
|
||
return;
|
||
}
|
||
var button = document.createElement('button');
|
||
button.type = 'button';
|
||
button.className = item.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item';
|
||
button.setAttribute('role', 'menuitem');
|
||
button.setAttribute('data-action', item.action);
|
||
button.disabled = item.disabled === true;
|
||
if (item.title) button.title = item.title;
|
||
var icon = document.createElement('span');
|
||
icon.className = 'material-symbols-outlined mnote-tree-context-menu__icon';
|
||
icon.setAttribute('aria-hidden', 'true');
|
||
icon.setAttribute('data-icon', item.icon || 'radio_button_unchecked');
|
||
var label = document.createElement('span');
|
||
label.className = 'mnote-tree-context-menu__label';
|
||
label.textContent = item.label;
|
||
button.appendChild(icon);
|
||
button.appendChild(label);
|
||
if (item.shortcut) {
|
||
var shortcut = document.createElement('span');
|
||
shortcut.className = 'mnote-tree-context-menu__shortcut';
|
||
shortcut.textContent = item.shortcut;
|
||
button.appendChild(shortcut);
|
||
}
|
||
button.addEventListener('click', function(event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
handleTreeContextMenuAction(item.action, detail, trigger);
|
||
});
|
||
menu.appendChild(button);
|
||
}
|
||
|
||
// ── CommandContext 启用态辅助:与 Rust core-protocol CommandContext 保持同一口径 ──
|
||
|
||
function buildSidebarFileTreeContext(kind) {
|
||
var runtime = window.__mnoteFileTreeContextMenuRuntime;
|
||
if (runtime && typeof runtime.buildSidebarFileTreeContext === 'function') {
|
||
try {
|
||
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) + '"]');
|
||
}
|
||
});
|
||
} 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);
|
||
var items = isAttachment ? [
|
||
{ action: 'duplicate', icon: 'file_copy', label: '拷贝副本', shortcut: 'Ctrl + D' },
|
||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: 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' },
|
||
{ action: 'copy-id', icon: 'tag', label: '复制资源 ID' },
|
||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' },
|
||
{ 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: 'paste-into', icon: 'content_paste', label: '粘贴到此处', disabled: !runtimeState.sidebarFileTreeClipboard, title: runtimeState.sidebarFileTreeClipboard ? '粘贴到当前文件树目标' : '剪贴板为空', when: '!workspace.readonly' },
|
||
{ action: 'refresh', icon: 'refresh', label: 'Refresh' },
|
||
{ action: 'collapse-all', icon: 'unfold_less', label: 'Collapse All' },
|
||
{ action: 'reveal', icon: 'my_location', label: 'Reveal' },
|
||
{ separator: true },
|
||
{ action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly' },
|
||
{ separator: true },
|
||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' }
|
||
] : [
|
||
{ 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' },
|
||
{ separator: true },
|
||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' }
|
||
];
|
||
items.forEach(function(item) {
|
||
if (item.when !== undefined && !evaluateSidebarFileTreeWhen(ctx, item.when)) {
|
||
item = Object.assign({}, item, { disabled: true, title: item.title || '当前上下文不支持此操作' });
|
||
}
|
||
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);
|
||
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),
|
||
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 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)) 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 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');
|
||
return true;
|
||
}
|
||
|
||
function schedulePendingLocalFolderRestoreFocus() {
|
||
if (!pendingLocalFolderRestoreRowId()) return false;
|
||
if (window.__mnotePendingLocalFolderRestoreFocusTimer) return false;
|
||
var attempts = 0;
|
||
var focused = false;
|
||
focused = applyPendingLocalFolderRestoreFocusOnce() || focused;
|
||
window.__mnotePendingLocalFolderRestoreFocusTimer = window.setInterval(function() {
|
||
attempts += 1;
|
||
focused = applyPendingLocalFolderRestoreFocusOnce() || focused;
|
||
if (attempts >= 20) {
|
||
if (focused) clearPendingLocalFolderRestoreRowId();
|
||
else document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'timeout');
|
||
window.clearInterval(window.__mnotePendingLocalFolderRestoreFocusTimer);
|
||
window.__mnotePendingLocalFolderRestoreFocusTimer = 0;
|
||
}
|
||
}, 250);
|
||
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(/~2F/g, '/');
|
||
if (!path) return '';
|
||
try {
|
||
return decodeURIComponent(path);
|
||
} catch (_) {
|
||
return path;
|
||
}
|
||
}
|
||
|
||
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 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,
|
||
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' || iconKind === 'luckysheet' || title.indexOf('.luckysheet') >= 0) return 'table';
|
||
return 'file';
|
||
}
|
||
|
||
function buildSidebarFileTreeDeletePlan(rows) {
|
||
var docRows = [];
|
||
var folderRows = [];
|
||
var assetRows = [];
|
||
rows.forEach(function(row) {
|
||
var kind = fileTreeRowKind(row);
|
||
if ((kind === 'document' || kind === 'doc' || kind === 'index' || kind === 'markdown') && fileTreeRowDocumentId(row)) {
|
||
docRows.push(row);
|
||
return;
|
||
}
|
||
if (kind === 'folder' && currentSourceKind() === 'local_folder') {
|
||
folderRows.push(row);
|
||
return;
|
||
}
|
||
if (fileTreeRowAssetId(row)) assetRows.push(row);
|
||
});
|
||
var selectedContainerRowIds = new Set(docRows.concat(folderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
|
||
var topDocRows = [];
|
||
var topFolderRows = [];
|
||
var seenDocs = new Set();
|
||
var seenFolders = new Set();
|
||
docRows.forEach(function(row) {
|
||
var documentId = fileTreeRowDocumentId(row);
|
||
if (!documentId || seenDocs.has(documentId)) return;
|
||
if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return;
|
||
seenDocs.add(documentId);
|
||
topDocRows.push(row);
|
||
});
|
||
folderRows.forEach(function(row) {
|
||
var rowId = row.getAttribute('data-row-id') || '';
|
||
if (!rowId || seenFolders.has(rowId)) return;
|
||
if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return;
|
||
seenFolders.add(rowId);
|
||
topFolderRows.push(row);
|
||
});
|
||
var selectedTopContainerRows = new Set(topDocRows.concat(topFolderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
|
||
var fileAssetRows = [];
|
||
var mindmapRows = [];
|
||
var tableRows = [];
|
||
var seenAssets = new Set();
|
||
assetRows.forEach(function(row) {
|
||
var assetId = fileTreeRowAssetId(row);
|
||
if (!assetId || seenAssets.has(assetId)) return;
|
||
if (hasSelectedDocumentAncestor(row, selectedTopContainerRows)) return;
|
||
seenAssets.add(assetId);
|
||
var assetKind = classifySidebarFileTreeAsset(row);
|
||
if (assetKind === 'mindmap') mindmapRows.push(row);
|
||
else if (assetKind === 'table') tableRows.push(row);
|
||
else fileAssetRows.push(row);
|
||
});
|
||
return {
|
||
docRows: topDocRows,
|
||
folderRows: topFolderRows,
|
||
fileAssetRows: fileAssetRows,
|
||
mindmapRows: mindmapRows,
|
||
tableRows: tableRows
|
||
};
|
||
}
|
||
|
||
function sidebarFileTreeDeleteConfirmText(plan) {
|
||
var docCount = plan.docRows.length;
|
||
var folderCount = plan.folderRows.length;
|
||
var fileCount = plan.fileAssetRows.length;
|
||
var mindmapCount = plan.mindmapRows.length;
|
||
var tableCount = plan.tableRows.length;
|
||
var parts = [];
|
||
if (docCount > 0) parts.push(docCount + ' 个页面(删除到垃圾桶)');
|
||
if (folderCount > 0) parts.push(folderCount + ' 个文件夹(删除到垃圾桶)');
|
||
if (fileCount > 0) parts.push(fileCount + ' 个附件(删除,10 分钟内可撤销)');
|
||
if (mindmapCount > 0) parts.push(mindmapCount + ' 个思维导图(移入垃圾桶,10 分钟内可恢复)');
|
||
if (tableCount > 0) parts.push(tableCount + (currentSourceKind() === 'local_folder' ? ' 个在线表格(移入垃圾桶,10 分钟内可恢复)' : ' 个在线表格(删除)'));
|
||
return '确认删除选中的 ' + parts.join(' + ') + ' 吗?';
|
||
}
|
||
|
||
async function postSidebarFileTreeJson(url, body) {
|
||
var response = await fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify(body || {})
|
||
});
|
||
var payload = await response.json().catch(function() { return null; });
|
||
if (!response.ok) {
|
||
throw new Error((payload && (payload.error || payload.message)) || (url + ' failed: ' + response.status));
|
||
}
|
||
return payload;
|
||
}
|
||
|
||
function fileTreeRowsByRowIds(rowIds) {
|
||
var runtimeFn = fileTreeRuntimeFunction('fileTreeRowsByRowIds');
|
||
if (runtimeFn) return runtimeFn(rowIds, fileTreeRuntimeDeps());
|
||
return (rowIds || []).map(function(rowId) {
|
||
return document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]');
|
||
}).filter(function(row) { return row instanceof HTMLElement; });
|
||
}
|
||
|
||
function fileTreeChildCount(documentId) {
|
||
var runtimeFn = fileTreeRuntimeFunction('fileTreeChildCount');
|
||
if (runtimeFn) return runtimeFn(documentId, fileTreeRuntimeDeps());
|
||
if (!documentId) return 0;
|
||
var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + documentId) + '"]');
|
||
var node = row ? row.closest('.tree-node') : null;
|
||
var children = node ? node.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="document"], :scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="doc"]') : [];
|
||
return children.length;
|
||
}
|
||
|
||
async function pasteSidebarFileTreeClipboard(trigger) {
|
||
if (!runtimeState.sidebarFileTreeClipboard || !Array.isArray(runtimeState.sidebarFileTreeClipboard.rowIds) || runtimeState.sidebarFileTreeClipboard.rowIds.length === 0) return false;
|
||
var targetRow = trigger instanceof HTMLElement ? trigger : null;
|
||
if (!targetRow && sidebarFileTreeSelection.focusedRowId) {
|
||
targetRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(sidebarFileTreeSelection.focusedRowId) + '"]');
|
||
}
|
||
var targetDocumentId = fileTreeRowDocumentId(targetRow) || currentDocumentId();
|
||
if (!targetDocumentId) return false;
|
||
var action = runtimeState.sidebarFileTreeClipboard.action === 'cut' ? 'move' : 'copy';
|
||
var rows = fileTreeRowsByRowIds(runtimeState.sidebarFileTreeClipboard.rowIds);
|
||
if (rows.length === 0) return false;
|
||
var writable = await ensureFileTreeWritableTarget('paste', targetRow, runtimeState.sidebarFileTreeClipboard.rowIds, action === 'copy');
|
||
if (!writable) return false;
|
||
recordFileTreeAction('paste', {
|
||
rowId: targetRow ? targetRow.getAttribute('data-row-id') || '' : '',
|
||
documentId: targetDocumentId,
|
||
sourceRowIds: runtimeState.sidebarFileTreeClipboard.rowIds,
|
||
clipboardAction: runtimeState.sidebarFileTreeClipboard.action
|
||
});
|
||
var plan = buildSidebarFileTreeDeletePlan(rows);
|
||
var workspaceId = resolveWorkspaceId(targetRow || document.body);
|
||
var failures = [];
|
||
if (action === 'copy') {
|
||
dispatchSidebarEvent('tree.filetree.copy-requested', { rowIds: runtimeState.sidebarFileTreeClipboard.rowIds, targetDocumentId: targetDocumentId });
|
||
recordFileTreeActionStatus('copy-requested', { documentId: targetDocumentId });
|
||
return true;
|
||
}
|
||
for (var i = 0; i < plan.docRows.length; i += 1) {
|
||
var docRow = plan.docRows[i];
|
||
var documentId = fileTreeRowDocumentId(docRow);
|
||
if (!documentId || documentId === targetDocumentId) continue;
|
||
try {
|
||
await dispatchTreeCommand(targetRow || docRow, {
|
||
action: 'move',
|
||
workspaceId: workspaceId,
|
||
documentId: documentId,
|
||
parentId: targetDocumentId,
|
||
sortOrder: fileTreeChildCount(targetDocumentId) + i
|
||
});
|
||
} catch (error) {
|
||
failures.push(documentId + ': ' + (error && error.message ? error.message : '移动页面失败'));
|
||
}
|
||
}
|
||
var assetIds = plan.fileAssetRows.map(fileTreeRowAssetId).filter(Boolean);
|
||
if (assetIds.length > 0) {
|
||
try {
|
||
await postSidebarFileTreeJson('/api/media/batch', {
|
||
action: 'move',
|
||
assetIds: assetIds,
|
||
targetDocumentId: targetDocumentId
|
||
});
|
||
} catch (error) {
|
||
failures.push(assetIds.join(',') + ': ' + (error && error.message ? error.message : '移动附件失败'));
|
||
}
|
||
}
|
||
if (failures.length > 0) {
|
||
recordFileTreeActionStatus('failed', { documentId: targetDocumentId, fallback: 'alert' });
|
||
window.alert('部分对象移动失败:' + failures.join(';'));
|
||
return false;
|
||
}
|
||
runtimeState.sidebarFileTreeClipboard = null;
|
||
recordFileTreeActionStatus('applied', { documentId: targetDocumentId });
|
||
return true;
|
||
}
|
||
|
||
async function deleteSelectedSidebarFileTreeRows(trigger) {
|
||
var rows = selectedSidebarFileTreeRows();
|
||
var plan = buildSidebarFileTreeDeletePlan(rows);
|
||
var total = plan.docRows.length + plan.folderRows.length + plan.fileAssetRows.length + plan.mindmapRows.length + plan.tableRows.length;
|
||
if (total === 0) return false;
|
||
if (!window.confirm(sidebarFileTreeDeleteConfirmText(plan))) return false;
|
||
recordFileTreeAction('bulk-delete', { rowId: trigger instanceof HTMLElement ? trigger.getAttribute('data-row-id') || '' : '', count: total });
|
||
recordFileTreeActionStatus('pending', { count: total });
|
||
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',
|
||
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',
|
||
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',
|
||
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);
|
||
}
|
||
}
|
||
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',
|
||
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',
|
||
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) {
|
||
recordFileTreeActionStatus('failed', { count: total, failures: failures.slice(0, 20), fallback: 'alert' });
|
||
window.alert('部分对象删除失败:' + failures.slice(0, 5).join(', ') + (failures.length > 5 ? '…' : ''));
|
||
return false;
|
||
}
|
||
document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-applied', 'true');
|
||
recordFileTreeActionStatus('archived', { count: total, undo: 'trash-modal' });
|
||
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,
|
||
fileTreeRowKind,
|
||
isFileTreeDownloadableAssetRow,
|
||
isFileTreeDownloadableRow,
|
||
fileTreeAssetDownloadDetail,
|
||
selectedSidebarFileTreeRowsForDownload,
|
||
selectedSidebarFileTreeAssetRowsForDownload,
|
||
downloadSelectedFileTreeAssetRows,
|
||
hasSelectedDocumentAncestor,
|
||
classifySidebarFileTreeAsset,
|
||
buildSidebarFileTreeDeletePlan,
|
||
sidebarFileTreeDeleteConfirmText,
|
||
postSidebarFileTreeJson,
|
||
fileTreeRowsByRowIds,
|
||
fileTreeChildCount,
|
||
pasteSidebarFileTreeClipboard,
|
||
deleteSelectedSidebarFileTreeRows,
|
||
};
|
||
};
|