Land password-vault dedicated workbench and mnote-vault-core/CLI, agent token read path design, vault transport split, and retire obsolete filetree smokes. Ignore local vault reimport scripts that trip secret scanners.
327 lines
13 KiB
JavaScript
327 lines
13 KiB
JavaScript
export const createSidebarPageTreeRuntime = (dependencies = {}) => {
|
|
const {
|
|
activeSidebarTreeMode,
|
|
commandDocumentId,
|
|
copyWorkspaceSourceParams,
|
|
cssEscape,
|
|
currentDocumentId,
|
|
currentSourceKind,
|
|
dispatchSidebarEvent,
|
|
dispatchTreeCommand,
|
|
normalizeSidebarTreeMode,
|
|
openPageTreeContextMenu,
|
|
persistLocalFolderSelfChangeSuppression,
|
|
persistSidebarTreeMode,
|
|
refreshLocalFolderSidebarSnapshot,
|
|
refreshLocalFolderAfterCommand,
|
|
resolveWorkspaceId,
|
|
rowCenter,
|
|
selectSidebarFileTreeDocument,
|
|
toggleChildren,
|
|
updateTitleEverywhere,
|
|
} = dependencies;
|
|
|
|
const PAGE_DRAG_MIME = 'application/x-mnote-page-tree-node';
|
|
var mnoteNavigationInFlight = '';
|
|
var draggingPageNodeId = '';
|
|
var activePageDropRow = null;
|
|
|
|
function localCreateSelectionOptions(result) {
|
|
var selectTarget = result && result.selectTarget && typeof result.selectTarget === 'object'
|
|
? result.selectTarget
|
|
: result && result.revealTarget && typeof result.revealTarget === 'object'
|
|
? result.revealTarget
|
|
: null;
|
|
if (!selectTarget) return { scrollIntoView: true };
|
|
return {
|
|
scrollIntoView: true,
|
|
rootUri: result.rootUri || '',
|
|
relativePath: selectTarget.relativePath || selectTarget.relative_path || '',
|
|
rowId: selectTarget.rowId || selectTarget.row_id || ''
|
|
};
|
|
}
|
|
|
|
function selectSidebarPageTreeDocument(nodeId, options) {
|
|
var normalizedNodeId = String(nodeId || '').trim();
|
|
if (!normalizedNodeId) return false;
|
|
var targetRow = document.querySelector(
|
|
'#sidebar-tree-root .tree-row[data-shell-mode="page"][data-node-id="' + cssEscape(normalizedNodeId) + '"]'
|
|
);
|
|
if (!(targetRow instanceof HTMLElement)) return false;
|
|
|
|
var parentId = String(targetRow.getAttribute('data-parent-id') || '').trim();
|
|
var guard = 0;
|
|
while (parentId && guard < 512) {
|
|
var parentRow = document.querySelector(
|
|
'#sidebar-tree-root .tree-row[data-shell-mode="page"][data-node-id="' + cssEscape(parentId) + '"]'
|
|
);
|
|
if (!(parentRow instanceof HTMLElement)) break;
|
|
if (parentRow.getAttribute('aria-expanded') !== 'true') {
|
|
toggleChildren(parentRow, parentRow.querySelector('[data-rust-action="toggle"]'));
|
|
}
|
|
parentId = String(parentRow.getAttribute('data-parent-id') || '').trim();
|
|
guard += 1;
|
|
}
|
|
|
|
document.querySelectorAll('#sidebar-tree-root .tree-row[data-shell-mode="page"][data-active="true"]').forEach(function(row) {
|
|
if (!(row instanceof HTMLElement)) return;
|
|
row.setAttribute('data-active', 'false');
|
|
row.setAttribute('aria-selected', 'false');
|
|
row.removeAttribute('aria-current');
|
|
});
|
|
targetRow.setAttribute('data-active', 'true');
|
|
targetRow.setAttribute('aria-selected', 'true');
|
|
targetRow.setAttribute('aria-current', 'page');
|
|
if (!options || options.scrollIntoView !== false) {
|
|
try { targetRow.scrollIntoView({ block: 'nearest' }); } catch (_) {}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function selectActiveTreeDocument(nodeId, treeView, options) {
|
|
if (treeView === 'filetree') {
|
|
selectSidebarFileTreeDocument(nodeId, options);
|
|
return;
|
|
}
|
|
selectSidebarPageTreeDocument(nodeId, options);
|
|
}
|
|
|
|
function navigateToDocument(nodeId, workspaceId, options) {
|
|
if (!nodeId) return;
|
|
var treeView = normalizeSidebarTreeMode(options && options.treeView ? options.treeView : activeSidebarTreeMode());
|
|
persistSidebarTreeMode(treeView);
|
|
if (currentDocumentId() === nodeId && window.location.pathname.indexOf('/documents/') === 0) {
|
|
if (typeof window.__mnoteDocumentPaneRuntime?.activatePageTab === 'function') {
|
|
window.__mnoteDocumentPaneRuntime.activatePageTab();
|
|
}
|
|
selectActiveTreeDocument(nodeId, treeView, { scrollIntoView: true });
|
|
return;
|
|
}
|
|
var targetUrl = new URL('/documents/' + encodeURIComponent(nodeId), window.location.origin);
|
|
if (workspaceId) targetUrl.searchParams.set('workspaceId', workspaceId);
|
|
if (treeView === 'filetree') targetUrl.searchParams.set('treeView', 'filetree');
|
|
var fileTreeScope = String(options && options.fileTreeScope || '').trim();
|
|
if (fileTreeScope) targetUrl.searchParams.set('fileTreeScope', fileTreeScope);
|
|
copyWorkspaceSourceParams(targetUrl);
|
|
var url = targetUrl.pathname + targetUrl.search;
|
|
if (mnoteNavigationInFlight === url) return;
|
|
if (typeof window.__mnoteDocumentPaneRuntime?.openPrimaryDocument === 'function') {
|
|
mnoteNavigationInFlight = url;
|
|
document.documentElement.setAttribute('data-mnote-navigation-pending', 'true');
|
|
document.documentElement.setAttribute('data-mnote-navigation-target', nodeId);
|
|
window.__mnoteDocumentPaneRuntime.openPrimaryDocument({
|
|
documentId: nodeId,
|
|
workspaceId: workspaceId || '',
|
|
sourceKind: targetUrl.searchParams.get('sourceKind') || 'local_folder',
|
|
rootUri: targetUrl.searchParams.get('rootUri') || '',
|
|
url: targetUrl,
|
|
}).then(function(){
|
|
if (mnoteNavigationInFlight === url) mnoteNavigationInFlight = '';
|
|
document.documentElement.removeAttribute('data-mnote-navigation-pending');
|
|
if (treeView === 'filetree') {
|
|
selectSidebarFileTreeDocument(nodeId, { scrollIntoView: true });
|
|
}
|
|
}).catch(function(error){
|
|
console.warn('mnote pane 内导航失败,将回退整页导航', error);
|
|
if (window.__mnoteTreeLiveEventSource && typeof window.__mnoteTreeLiveEventSource.close === 'function') {
|
|
window.__mnoteTreeLiveEventSource.close();
|
|
}
|
|
window.location.assign(url);
|
|
});
|
|
return;
|
|
}
|
|
mnoteNavigationInFlight = url;
|
|
document.documentElement.setAttribute('data-mnote-navigation-pending', 'true');
|
|
document.documentElement.setAttribute('data-mnote-navigation-target', nodeId);
|
|
if (window.__mnoteTreeLiveEventSource && typeof window.__mnoteTreeLiveEventSource.close === 'function') {
|
|
window.__mnoteTreeLiveEventSource.close();
|
|
}
|
|
window.location.assign(url);
|
|
}
|
|
|
|
async function createPage(trigger, parentId) {
|
|
var workspaceId = resolveWorkspaceId(trigger);
|
|
var effectiveParentId = (parentId || trigger.getAttribute('data-parent-id') || '').trim();
|
|
var treeView = activeSidebarTreeMode();
|
|
if (!workspaceId) return;
|
|
var result = await dispatchTreeCommand(trigger, {
|
|
action: 'create',
|
|
workspaceId: workspaceId,
|
|
parentId: effectiveParentId || null,
|
|
title: '新页面'
|
|
});
|
|
var nextWorkspaceId = result.workspaceId || workspaceId;
|
|
var nextDocumentId = commandDocumentId(result, '');
|
|
if (nextDocumentId) {
|
|
persistLocalFolderSelfChangeSuppression(nextDocumentId, Date.now() + 5000);
|
|
}
|
|
if (nextDocumentId && currentSourceKind() === 'local_folder') {
|
|
if (typeof refreshLocalFolderAfterCommand === 'function') {
|
|
await refreshLocalFolderAfterCommand('create', result, { parentId: effectiveParentId || null });
|
|
}
|
|
if (treeView === 'filetree') {
|
|
selectSidebarFileTreeDocument(nextDocumentId, localCreateSelectionOptions(result));
|
|
}
|
|
document.documentElement.setAttribute('data-mnote-create-page-selected-document-id', nextDocumentId);
|
|
}
|
|
navigateToDocument(nextDocumentId, nextWorkspaceId, { treeView: treeView });
|
|
}
|
|
|
|
function startPageDrag(pageRow, event) {
|
|
if (!(pageRow instanceof HTMLElement)) return false;
|
|
draggingPageNodeId = pageRow.getAttribute('data-node-id') || '';
|
|
if (event && event.dataTransfer) {
|
|
event.dataTransfer.effectAllowed = 'move';
|
|
event.dataTransfer.setData(PAGE_DRAG_MIME, draggingPageNodeId);
|
|
event.dataTransfer.setData('text/plain', draggingPageNodeId);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function readPageDragNodeId(event) {
|
|
var fromTransfer = event.dataTransfer ? event.dataTransfer.getData(PAGE_DRAG_MIME) || event.dataTransfer.getData('text/plain') : '';
|
|
return (fromTransfer || draggingPageNodeId || '').trim();
|
|
}
|
|
|
|
function clearPageDropFeedback() {
|
|
if (activePageDropRow instanceof HTMLElement) {
|
|
activePageDropRow.setAttribute('data-drop-feedback', 'false');
|
|
activePageDropRow.setAttribute('data-drop-target', 'false');
|
|
activePageDropRow.removeAttribute('data-drop-position');
|
|
}
|
|
activePageDropRow = null;
|
|
}
|
|
|
|
function setActivePageDropRow(row, position) {
|
|
if (!(row instanceof HTMLElement)) {
|
|
clearPageDropFeedback();
|
|
return;
|
|
}
|
|
if (activePageDropRow && activePageDropRow !== row) {
|
|
clearPageDropFeedback();
|
|
}
|
|
activePageDropRow = row;
|
|
activePageDropRow.setAttribute('data-drop-feedback', 'true');
|
|
activePageDropRow.setAttribute('data-drop-target', 'true');
|
|
// Always refresh position so before↔after on the same row updates the edge line.
|
|
if (position) {
|
|
activePageDropRow.setAttribute('data-drop-position', position);
|
|
} else {
|
|
activePageDropRow.removeAttribute('data-drop-position');
|
|
}
|
|
}
|
|
|
|
function clearPageDragState() {
|
|
draggingPageNodeId = '';
|
|
clearPageDropFeedback();
|
|
}
|
|
|
|
function handlePageTreeClick(event, helpers = {}) {
|
|
var closestAction = helpers.closestAction;
|
|
if (typeof closestAction !== 'function') return false;
|
|
var tree = document.getElementById('sidebar-tree-root');
|
|
if (!tree || !tree.contains(event.target)) return false;
|
|
var pageRow = closestAction(event.target, '.tree-row[data-shell-mode="page"]');
|
|
var btn = closestAction(event.target, '[data-rust-action]');
|
|
if (!btn && !pageRow) return false;
|
|
var nodeId = (btn && btn.getAttribute('data-node-id')) || (pageRow && pageRow.getAttribute('data-node-id')) || '';
|
|
var action = btn ? btn.getAttribute('data-rust-action') : 'open';
|
|
|
|
if (action === 'toggle') {
|
|
var row = btn.closest('.tree-row');
|
|
if (!row) return true;
|
|
toggleChildren(row, btn);
|
|
event.preventDefault();
|
|
} else if (action === 'open') {
|
|
var openTrigger = btn || pageRow;
|
|
if (openTrigger && openTrigger.getAttribute('data-page-openable') === 'false') {
|
|
event.preventDefault();
|
|
return true;
|
|
}
|
|
var workspaceId = resolveWorkspaceId(openTrigger);
|
|
navigateToDocument(nodeId, workspaceId, { treeView: 'page' });
|
|
event.preventDefault();
|
|
} else if (action === 'create') {
|
|
event.preventDefault();
|
|
void createPage(btn, nodeId);
|
|
} else if (action === 'rename') {
|
|
event.preventDefault();
|
|
var title = window.prompt('重命名页面');
|
|
if (title && title.trim()) {
|
|
void dispatchTreeCommand(btn, {
|
|
action: 'rename',
|
|
workspaceId: resolveWorkspaceId(btn),
|
|
documentId: nodeId,
|
|
title: title.trim()
|
|
}).then(function(){
|
|
updateTitleEverywhere(nodeId, title.trim());
|
|
});
|
|
}
|
|
} else if (action === 'menu') {
|
|
event.preventDefault();
|
|
var menuPoint = rowCenter(btn);
|
|
dispatchSidebarEvent('tree.page.context-menu', { documentId: nodeId, target: { documentId: nodeId } });
|
|
openPageTreeContextMenu(btn.closest('.tree-row'), menuPoint.x, menuPoint.y, btn);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function handlePageTreeContextMenu(event, helpers = {}) {
|
|
var closestAction = helpers.closestAction;
|
|
if (typeof closestAction !== 'function') return false;
|
|
var pageRow = closestAction(event.target, '.tree-row[data-shell-mode="page"]');
|
|
if (!pageRow) return false;
|
|
event.preventDefault();
|
|
openPageTreeContextMenu(pageRow, event.clientX, event.clientY, pageRow);
|
|
return true;
|
|
}
|
|
|
|
function canDropPage(sourceNodeId, targetRow) {
|
|
if (!sourceNodeId || !(targetRow instanceof HTMLElement)) return false;
|
|
var targetNodeId = targetRow.getAttribute('data-node-id') || '';
|
|
if (!targetNodeId || targetNodeId === sourceNodeId) return false;
|
|
var sourceNode = document.querySelector('#sidebar-tree-root .tree-node[data-node-id="' + cssEscape(sourceNodeId) + '"]');
|
|
return !(sourceNode instanceof HTMLElement && sourceNode.contains(targetRow));
|
|
}
|
|
|
|
function pageDropPosition(event, row) {
|
|
var rect = row.getBoundingClientRect();
|
|
var ratio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;
|
|
if (ratio < 0.25) return 'before';
|
|
if (ratio > 0.75) return 'after';
|
|
return 'inside';
|
|
}
|
|
|
|
function resolvePageMoveTarget(targetRow, position) {
|
|
var targetNodeId = targetRow.getAttribute('data-node-id') || '';
|
|
var parentId = targetRow.getAttribute('data-parent-id') || null;
|
|
if (position === 'inside') {
|
|
var children = targetRow.parentElement ? targetRow.parentElement.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="page"]') : [];
|
|
return { parentId: targetNodeId, sortOrder: children.length };
|
|
}
|
|
var siblings = Array.from(document.querySelectorAll('#sidebar-tree-root .tree-row[data-shell-mode="page"]')).filter(function(row) {
|
|
return (row.getAttribute('data-parent-id') || '') === (parentId || '');
|
|
});
|
|
var index = siblings.indexOf(targetRow);
|
|
return { parentId: parentId, sortOrder: Math.max(0, index + (position === 'after' ? 1 : 0)) };
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
navigateToDocument,
|
|
createPage,
|
|
selectSidebarPageTreeDocument,
|
|
startPageDrag,
|
|
readPageDragNodeId,
|
|
clearPageDropFeedback,
|
|
setActivePageDropRow,
|
|
clearPageDragState,
|
|
handlePageTreeClick,
|
|
handlePageTreeContextMenu,
|
|
canDropPage,
|
|
pageDropPosition,
|
|
resolvePageMoveTarget,
|
|
};
|
|
};
|