refactor: split sidebar page tree runtime
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
export const createSidebarPageTreeRuntime = (dependencies = {}) => {
|
||||
const {
|
||||
activeSidebarTreeMode,
|
||||
commandDocumentId,
|
||||
copyWorkspaceSourceParams,
|
||||
cssEscape,
|
||||
currentDocumentId,
|
||||
currentSourceKind,
|
||||
dispatchSidebarEvent,
|
||||
dispatchTreeCommand,
|
||||
normalizeSidebarTreeMode,
|
||||
openPageTreeContextMenu,
|
||||
persistLocalFolderSelfChangeSuppression,
|
||||
persistSidebarTreeMode,
|
||||
refreshLocalFolderSidebarSnapshot,
|
||||
resolveWorkspaceId,
|
||||
rowCenter,
|
||||
selectSidebarFileTreeDocument,
|
||||
toggleChildren,
|
||||
updateTitleEverywhere,
|
||||
} = dependencies;
|
||||
|
||||
const PAGE_DRAG_MIME = 'application/x-mnote-page-tree-node';
|
||||
var mnoteNavigationInFlight = '';
|
||||
var draggingPageNodeId = '';
|
||||
var activePageDropRow = null;
|
||||
|
||||
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();
|
||||
}
|
||||
document.querySelectorAll('.tree-row[data-active="true"], .tree-row[data-selected="true"]').forEach(function(row) {
|
||||
if (row instanceof HTMLElement) {
|
||||
row.setAttribute('data-active', 'false');
|
||||
row.setAttribute('data-selected', 'false');
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('.tree-row[data-node-id="' + cssEscape(nodeId) + '"], .tree-row[data-document-id="' + cssEscape(nodeId) + '"], .tree-row[data-doc-id="' + cssEscape(nodeId) + '"]').forEach(function(row) {
|
||||
if (row instanceof HTMLElement) {
|
||||
if (row.getAttribute('data-shell-mode') === 'filetree') {
|
||||
row.setAttribute('data-selected', String(row.getAttribute('data-row-id') === 'doc:' + nodeId));
|
||||
} else {
|
||||
row.setAttribute('data-active', 'true');
|
||||
}
|
||||
}
|
||||
});
|
||||
selectSidebarFileTreeDocument(nodeId, { 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');
|
||||
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') || 'convex_workspace',
|
||||
rootUri: targetUrl.searchParams.get('rootUri') || '',
|
||||
url: targetUrl,
|
||||
}).then(function(){
|
||||
if (mnoteNavigationInFlight === url) mnoteNavigationInFlight = '';
|
||||
document.documentElement.removeAttribute('data-mnote-navigation-pending');
|
||||
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();
|
||||
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') {
|
||||
await refreshLocalFolderSidebarSnapshot();
|
||||
selectSidebarFileTreeDocument(nextDocumentId, { scrollIntoView: true });
|
||||
document.documentElement.setAttribute('data-mnote-create-page-selected-document-id', nextDocumentId);
|
||||
}
|
||||
navigateToDocument(nextDocumentId, nextWorkspaceId, { treeView: activeSidebarTreeMode() });
|
||||
}
|
||||
|
||||
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 = null;
|
||||
}
|
||||
|
||||
function setActivePageDropRow(row) {
|
||||
activePageDropRow = row instanceof HTMLElement ? row : null;
|
||||
}
|
||||
|
||||
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,
|
||||
startPageDrag,
|
||||
readPageDragNodeId,
|
||||
clearPageDropFeedback,
|
||||
setActivePageDropRow,
|
||||
clearPageDragState,
|
||||
handlePageTreeClick,
|
||||
handlePageTreeContextMenu,
|
||||
canDropPage,
|
||||
pageDropPosition,
|
||||
resolvePageMoveTarget,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user