export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => { const { activeSidebarTreeMode, cssEscape, currentDocumentId, currentFileTreeActiveRowId, currentRootUri, currentSourceKind, currentWorkspaceId, escapeHtml, fileTreeRuntimeFunction, localFilePathFromAssetId, navigateToDocument, refreshEditorLocalAttachmentExistence, restoreSidebarTreeTab, rowTitle, schedulePendingLocalFolderRestoreFocus, shortMindmapFileName, syncSidebarFileTreeSelection, } = dependencies; var fileTreeViewState = { rootUri: '', scope: '', expandedParents: new Set(), selectedRowIds: new Set(), focusedRowId: '', activeRowId: '', scrollTop: 0, rowsByParent: new Map(), loadedParents: new Set(), loadingParents: new Map(), dirtyParents: new Set(), staleParents: new Set(), revisionByParent: new Map(), requestGeneration: 0, latestGenerationByParent: new Map() }; var fileTreeState = fileTreeViewState; var fileTreeExpandedRelativePaths = fileTreeViewState.expandedParents; var pageTreeState = { pageTreeRequestGeneration: 0 }; var fileTreeLazyCacheRootUri = ''; var fileTreeExpansionStorageRootUri = ''; var fileTreeExpansionRestoreTimer = 0; var fileTreeVisibleHydrateTimer = 0; var fileTreeVisibleHydrateQueued = false; var fileTreeCommandBatchRefreshParents = new Map(); var FILETREE_VISIBLE_EXPANDED_HYDRATE_MAX = 20; var FILETREE_VISIBLE_EXPANDED_HYDRATE_BATCH = 2; var FILETREE_EXPANSION_STORAGE_KEY = 'mnote.localFileTree.expandedRelativePaths.v1'; var SIDEBAR_TREE_VIEW_STATE_KEY = 'mnote.sidebarTreeViewState.v1:{userId}:{workspaceId}:{sourceKind}:{treeKind}:{rootUriHash}:{scopeHash}'; var SIDEBAR_TREE_VIEW_STATE_API_KEY = 'sidebarTreeViewState.v1'; var SIDEBAR_TREE_VIEW_STATE_SAVE_DELAY_MS = 500; var sidebarTreeViewStates = new Map(); var sidebarTreeViewStateRequests = new Map(); var sidebarTreeViewStateSaveTimers = new Map(); function updateTitleEverywhere(documentId, title) { if (!documentId) return; var escaped = cssEscape(documentId); var escapedDocRowId = cssEscape('doc:' + documentId); var isCurrentDocument = currentDocumentId() === documentId; var pageSelectors = [ '.tree-row[data-shell-mode="page"][data-node-id="' + escaped + '"] > .tree-link > .tree-link-title', '.wolai-page-row[data-node-id="' + escaped + '"] > .wolai-row-title', 'a[href="/documents/' + escaped + '"] > .wolai-row-title', 'a[href^="/documents/' + escaped + '?"] > .wolai-row-title' ]; pageSelectors.forEach(function(selector) { document.querySelectorAll(selector).forEach(function(node) { if (node instanceof HTMLElement) node.textContent = title; }); }); document.querySelectorAll('.tree-row[data-shell-mode="filetree"][data-row-id="' + escapedDocRowId + '"] > .tree-link > .tree-link-title').forEach(function(node) { if (node instanceof HTMLElement) node.textContent = fileTreePageTitle(title); }); document.querySelectorAll('[data-page-title-input="true"][data-document-id="' + escaped + '"]').forEach(function(node) { if (!(node instanceof HTMLTextAreaElement)) return; node.value = title; node.setAttribute('data-title-last-saved', title); node.setAttribute('data-title-save-status', 'saved'); node.style.height = 'auto'; node.style.height = Math.max(48, node.scrollHeight) + 'px'; }); document.querySelectorAll('[data-document-pane="true"][data-pane-document-id="' + escaped + '"] [data-page-title-current="true"]').forEach(function(node) { if (node instanceof HTMLElement) node.textContent = title; }); if (isCurrentDocument) { document.title = title; var topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]'); if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title; } } function fileTreePageTitle(title) { var normalized = String(title || '无标题').trim() || '无标题'; return normalized.endsWith('.md') ? normalized : normalized + '.md'; } function isFileTreePageRow(row) { if (!(row instanceof HTMLElement)) return false; if (row.getAttribute('data-asset-id')) return false; var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || ''; if (documentId.indexOf('local-md:') === 0) return true; var rowKind = row.getAttribute('data-row-kind') || ''; return rowKind === 'document' || rowKind === 'doc' || rowKind === 'index' || rowKind === 'markdown'; } function normalizeFileTreePageRenameTitle(value) { var normalized = String(value || '').trim(); if (/\.md$/i.test(normalized)) normalized = normalized.slice(0, -3).trim(); return normalized; } function validateFileTreeRename(row, draft) { var raw = String(draft || '').trim(); if (!raw) return '名称不能为空'; if (/[\\/:*?"<>|]/.test(raw)) return '文件名不能包含 / \\ : * ? " < > |'; if (!isFileTreePageRow(row)) return ''; var pageTitle = normalizeFileTreePageRenameTitle(raw); if (!pageTitle) return '名称不能为空'; var expectedFileName = fileTreePageTitle(pageTitle).toLocaleLowerCase(); var rowId = row.getAttribute('data-row-id') || ''; var parentId = row.getAttribute('data-parent-id') || ''; var siblings = Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-kind="document"], #sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-kind="doc"], #sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-kind="markdown"]')); var duplicate = siblings.some(function(sibling) { if (!(sibling instanceof HTMLElement)) return false; if ((sibling.getAttribute('data-row-id') || '') === rowId) return false; if ((sibling.getAttribute('data-parent-id') || '') !== parentId) return false; return fileTreePageTitle(normalizeFileTreePageRenameTitle(rowTitle(sibling))).toLocaleLowerCase() === expectedFileName; }); return duplicate ? '同级已存在同名页面' : ''; } function documentIdFromDelta(data) { return String(data && (data.documentId || data.id || (data.document && data.document.id) || (data.node && data.node.id) || (data.args && data.args.documentId)) || '').trim(); } function parentIdFromDelta(data) { if (!data || typeof data !== 'object') return null; if (Object.prototype.hasOwnProperty.call(data, 'parentId')) return data.parentId ? String(data.parentId).trim() : null; if (Object.prototype.hasOwnProperty.call(data, 'parent_id')) return data.parent_id ? String(data.parent_id).trim() : null; if (data.args && Object.prototype.hasOwnProperty.call(data.args, 'parentId')) return data.args.parentId ? String(data.args.parentId).trim() : null; return null; } function treeRootForMode(mode) { var id = mode === 'filetree' ? 'sidebar-file-tree-root' : 'sidebar-tree-root'; return document.querySelector('#' + id + ' .tree-root'); } function rowSelectorForDocument(mode, documentId) { var escaped = cssEscape(documentId); if (mode === 'filetree') { return '.tree-row[data-shell-mode="filetree"][data-doc-id="' + escaped + '"], .tree-row[data-shell-mode="filetree"][data-document-id="' + escaped + '"]'; } return '.tree-row[data-shell-mode="page"][data-node-id="' + escaped + '"]'; } function ensureTreeChildren(parentRow) { var parentNode = parentRow ? parentRow.closest('.tree-node') : null; if (!parentNode) return null; var children = parentNode.querySelector(':scope > .tree-children'); if (!children) { children = document.createElement('ul'); children.className = 'tree-children'; parentNode.appendChild(children); } children.classList.remove('tree-children--collapsed'); parentRow.setAttribute('aria-expanded', 'true'); var toggle = parentRow.querySelector('[data-rust-action="toggle"]'); if (toggle) toggle.setAttribute('aria-expanded', 'true'); return children; } function removeDocumentRowForMode(mode, documentId) { var row = document.querySelector(rowSelectorForDocument(mode, documentId)); var node = row ? row.closest('.tree-node') : null; if (!node || !node.parentElement) return false; node.parentElement.removeChild(node); return true; } function commandDocumentId(result, fallback) { var value = result && ( (result.execution && (result.execution.documentId || result.execution.id || result.execution.nodeId)) || result.documentId || result.id || result.nodeId || (result.document && (result.document.id || result.document.documentId)) || (result.node && (result.node.id || result.node.documentId)) || (result.payload && result.payload.documentId) ); return String(value || fallback || '').trim(); } function commandDocumentTitle(result, fallback) { var value = result && ( (result.execution && result.execution.title) || result.title || (result.document && result.document.title) || (result.node && result.node.title) || (result.payload && result.payload.title) ); return String(value || fallback || '新页面').trim() || '新页面'; } function normalizeDocumentRowId(value) { return String(value || '').trim().replace(/^doc:/, '').replace(/^index:/, ''); } function appendRenderedTreeNode(container, html) { if (!container || !html) return false; var empty = container.querySelector(':scope > .tree-empty'); if (empty && empty.parentElement) empty.parentElement.removeChild(empty); var template = document.createElement('template'); template.innerHTML = html; var node = template.content.firstElementChild; if (!node) return false; container.appendChild(node); return true; } function localPageInsertDepth(parentId) { if (!parentId) return 0; var parentRow = document.querySelector(rowSelectorForDocument('page', parentId)); var depth = parentRow ? Number(parentRow.getAttribute('data-depth') || 0) : 0; return Number.isFinite(depth) ? depth + 1 : 1; } function upsertPageDocumentRow(documentId, parentId, title) { if (!documentId) return false; var existing = document.querySelector(rowSelectorForDocument('page', documentId)); if (existing instanceof HTMLElement) { updateTitleEverywhere(documentId, title); moveDocumentRowForMode('page', documentId, parentId || null); return true; } var root = treeRootForMode('page'); if (!root) return false; var targetContainer = root; if (parentId) { var parentRow = document.querySelector(rowSelectorForDocument('page', parentId)); targetContainer = ensureTreeChildren(parentRow); if (!targetContainer) targetContainer = root; } var grouped = new Map(); grouped.set(parentId || '', [{ id: documentId, nodeId: documentId, documentId: documentId, parentNodeId: parentId || '', rowKind: 'document', title: title, expandable: false, childCount: 0 }]); return appendRenderedTreeNode(targetContainer, renderPageRows(parentId || '', grouped, currentDocumentId(), localPageInsertDepth(parentId))); } function fileTreeParentContainer(parentId) { var root = treeRootForMode('filetree'); if (!root) return null; if (!parentId) return root; var parentRow = document.querySelector('.tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + parentId) + '"]'); var children = ensureTreeChildren(parentRow); return children || root; } function localFileInsertDepth(parentId) { if (!parentId) return 0; var parentRow = document.querySelector('.tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + parentId) + '"]'); var level = parentRow ? Number(parentRow.getAttribute('aria-level') || 1) : 1; return Number.isFinite(level) ? level : 1; } function upsertFileDocumentRows(documentId, parentId, title) { if (!documentId) return false; var existing = document.querySelector('.tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + documentId) + '"]'); if (existing instanceof HTMLElement) { updateTitleEverywhere(documentId, title); moveDocumentRowForMode('filetree', documentId, parentId || null); return true; } var targetContainer = fileTreeParentContainer(parentId); if (!targetContainer) return false; var parentNodeId = parentId ? 'doc:' + parentId : ''; var docNodeId = 'doc:' + documentId; var depth = localFileInsertDepth(parentId); var grouped = new Map(); grouped.set(parentNodeId, [{ id: docNodeId, nodeId: docNodeId, rowId: docNodeId, rowKind: 'document', title: fileTreePageTitle(title), documentId: documentId, parentNodeId: parentNodeId, depth: depth, expandable: false, childCount: 0 }]); return appendRenderedTreeNode(targetContainer, renderFileRows(parentNodeId, grouped, currentDocumentId())); } function applyCreatedDocumentLocally(result, parentId, fallbackTitle) { var documentId = commandDocumentId(result, null); if (!documentId) return false; var normalizedParentId = normalizeDocumentRowId(parentId); var title = commandDocumentTitle(result, fallbackTitle); var pageChanged = upsertPageDocumentRow(documentId, normalizedParentId, title); var fileChanged = upsertFileDocumentRows(documentId, normalizedParentId, title); if (pageChanged || fileChanged) { document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'create'); } return pageChanged || fileChanged; } function localCommandNeedsProjectionRefresh(action, result) { if (currentSourceKind() !== 'local_folder') return false; if (action === 'create' || action === 'rename' || action === 'move' || action === 'delete' || action === 'archive' || action === 'trash' || action === 'purge') { return true; } if (result && (result.previousDocumentId || result.previousRelativePath || result.relativePath || result.trashPath)) { return true; } return false; } function parentRelativePathForPath(relativePath) { var normalized = String(relativePath || '').trim().replace(/^\/+|\/+$/g, ''); if (!normalized || normalized === '.') return ''; var index = normalized.lastIndexOf('/'); return index > 0 ? normalized.slice(0, index) : ''; } function addCommandRefreshParent(parents, value) { var normalized = String(value || '').trim().replace(/^\/+|\/+$/g, ''); if (normalized === '.') normalized = ''; parents.add(normalized); } function watchBatchPathIsMarkdown(item) { var relativePath = String(item && (item.relativePath || item.relative_path) || '').trim().toLowerCase(); return relativePath.endsWith('.md') || relativePath.endsWith('.markdown'); } function watchBatchEventKind(item) { return String(item && (item.kind || item.eventKind || item.event_kind) || '').trim(); } function watchBatchNeedsPageTreeRefresh(item) { if (!watchBatchPathIsMarkdown(item)) return false; var kind = watchBatchEventKind(item); return kind.indexOf('Create') >= 0 || kind.indexOf('Remove') >= 0 || kind.indexOf('Modify(Name') >= 0 || kind.indexOf('Rename') >= 0; } function addAffectedParentsFromCommandResult(parents, result) { var affectedParents = Array.isArray(result && result.affectedParents) ? result.affectedParents : Array.isArray(result && result.execution && result.execution.affectedParents) ? result.execution.affectedParents : null; if (!affectedParents) return false; var before = parents.size; affectedParents.forEach(function(parent) { addCommandRefreshParent(parents, parent && (parent.relativePath || parent.relative_path)); }); return parents.size > before; } function collectFileTreeRefreshParentsForCommand(result, body) { var parents = new Set(); if (!addAffectedParentsFromCommandResult(parents, result)) { document.documentElement.setAttribute('data-mnote-filetree-command-refresh-fallback', 'legacy-path-fields'); var values = [ result && (result.parentRelativePath || result.parent_relative_path), result && result.execution && (result.execution.parentRelativePath || result.execution.parent_relative_path), result && (result.relativePath || result.relative_path), result && result.execution && (result.execution.relativePath || result.execution.relative_path), result && (result.previousRelativePath || result.previous_relative_path), result && result.execution && (result.execution.previousRelativePath || result.execution.previous_relative_path), body && (body.parentRelativePath || body.parent_relative_path) ]; values.forEach(function(value) { var normalized = String(value || '').trim(); if (!normalized) return; if (normalized.indexOf('/') >= 0 || /\.[^/]+$/.test(normalized)) addCommandRefreshParent(parents, parentRelativePathForPath(normalized)); else addCommandRefreshParent(parents, normalized); }); } if (!parents.size) addCommandRefreshParent(parents, currentFileTreeScope()); return parents; } function fileTreeRefreshParentsForCommand(result, body) { var parents = collectFileTreeRefreshParentsForCommand(result, body); return Promise.all(Array.from(parents).map(function(parentRelativePath) { return refreshFileTreeParent(parentRelativePath).catch(function(error) { setTreeLiveApplyError(error && error.message ? error.message : '文件树局部刷新失败'); return false; }); })); } function queueFileTreeBatchRefresh(batchId, result, body) { var normalizedBatchId = String(batchId || '').trim(); if (!normalizedBatchId) return false; var batchParents = fileTreeCommandBatchRefreshParents.get(normalizedBatchId); if (!batchParents) { batchParents = new Set(); fileTreeCommandBatchRefreshParents.set(normalizedBatchId, batchParents); } collectFileTreeRefreshParentsForCommand(result, body).forEach(function(parent) { batchParents.add(parent); }); document.documentElement.setAttribute('data-mnote-filetree-batch-refresh-pending', normalizedBatchId); return true; } function flushFileTreeBatchRefresh(batchId) { var normalizedBatchId = String(batchId || '').trim(); if (!normalizedBatchId) return false; var parents = fileTreeCommandBatchRefreshParents.get(normalizedBatchId); fileTreeCommandBatchRefreshParents.delete(normalizedBatchId); if (!parents || !parents.size) return false; document.documentElement.setAttribute('data-mnote-filetree-batch-refresh-applied', normalizedBatchId); void Promise.all(Array.from(parents).map(function(parentRelativePath) { return refreshFileTreeParent(parentRelativePath).catch(function(error) { setTreeLiveApplyError(error && error.message ? error.message : '文件树批量刷新失败'); return false; }); })); return true; } function applyLocalFolderWatchBatch(payload) { var batch = payload && (payload.payload || payload); if (batch && (batch.fallbackResync === true || batch.requiresResync === true)) { document.documentElement.setAttribute('data-mnote-local-folder-watch-batch-fallback', String(batch.revision || 'resync')); void refreshLocalFolderSidebarSnapshot(); return true; } var changedPaths = Array.isArray(batch && batch.changedPaths) ? batch.changedPaths : Array.isArray(batch && batch.changed_paths) ? batch.changed_paths : []; var affectedParents = Array.isArray(batch && batch.affectedParents) ? batch.affectedParents : Array.isArray(batch && batch.affected_parents) ? batch.affected_parents : []; if (!affectedParents.length) { affectedParents = changedPaths.map(function(item) { var relativePath = String(item && (item.relativePath || item.relative_path) || '').trim(); return { relativePath: parentRelativePathForPath(relativePath), reason: 'derived-from-changed-path' }; }).filter(function(parent, index, list) { return index === list.findIndex(function(candidate) { return candidate.relativePath === parent.relativePath; }); }); if (affectedParents.length) { document.documentElement.setAttribute('data-mnote-local-folder-watch-batch-derived-parents', String(affectedParents.length)); } } if (!affectedParents.length) { setTreeLiveApplyError('local_folder_watch_batch_missing_affected_parents'); return false; } var parents = new Set(); affectedParents.forEach(function(parent) { addCommandRefreshParent(parents, parent && (parent.relativePath || parent.relative_path)); }); var needsSidebarRefresh = changedPaths.some(watchBatchNeedsPageTreeRefresh); document.documentElement.setAttribute('data-mnote-local-folder-watch-batch-applied', String(batch.revision || 'true')); void Promise.all(Array.from(parents).map(function(parentRelativePath) { return refreshFileTreeParent(parentRelativePath).catch(function(error) { setTreeLiveApplyError(error && error.message ? error.message : '文件树 watch batch 刷新失败'); return false; }); })).then(function() { if (needsSidebarRefresh) { void refreshLocalFolderSidebarSnapshot(); } else { document.documentElement.setAttribute('data-mnote-local-folder-watch-sidebar-refresh-skipped', 'content-only'); } markLocalFolderWatchApplied('watch_batch'); }); return true; } function refreshLocalFolderAfterCommand(action, result, body) { if (!localCommandNeedsProjectionRefresh(action, result)) return Promise.resolve(false); if (body && body.batchId) { queueFileTreeBatchRefresh(body.batchId, result, body); return Promise.resolve(true); } return fileTreeRefreshParentsForCommand(result, body); } function sortOrderFromDelta(data) { var raw = data && (data.sortOrder ?? data.sort_order); var value = Number(raw); return Number.isFinite(value) && value >= 0 ? Math.floor(value) : null; } function insertTreeNodeAtSortOrder(targetContainer, node, sortOrder) { if (!targetContainer || !node) return false; if (sortOrder === null || sortOrder === undefined) { targetContainer.appendChild(node); return true; } var siblings = Array.from(targetContainer.querySelectorAll(':scope > .tree-node')).filter(function(candidate) { return candidate !== node; }); var targetIndex = Math.max(0, Math.min(Number(sortOrder), siblings.length)); var referenceNode = siblings[targetIndex] || null; if (referenceNode) targetContainer.insertBefore(node, referenceNode); else targetContainer.appendChild(node); return true; } function moveDocumentRowForMode(mode, documentId, parentId, sortOrder) { var row = document.querySelector(rowSelectorForDocument(mode, documentId)); var node = row ? row.closest('.tree-node') : null; var root = treeRootForMode(mode); if (!row || !node || !root) return false; var targetContainer = root; if (parentId) { var parentRow = document.querySelector(rowSelectorForDocument(mode, parentId)); targetContainer = ensureTreeChildren(parentRow); if (!targetContainer) return false; row.setAttribute('data-parent-id', parentId); } else { row.removeAttribute('data-parent-id'); } return insertTreeNodeAtSortOrder(targetContainer, node, sortOrder); } function applyMoveDocumentDelta(data) { var documentId = documentIdFromDelta(data); if (!documentId) return false; var parentId = parentIdFromDelta(data); var sortOrder = sortOrderFromDelta(data); var movedPage = moveDocumentRowForMode('page', documentId, parentId, sortOrder); var movedFile = moveDocumentRowForMode('filetree', documentId, parentId, sortOrder); return movedPage || movedFile; } function applyRemoveDocumentDelta(data) { var documentId = documentIdFromDelta(data); if (!documentId) return false; var removedPage = removeDocumentRowForMode('page', documentId); var removedFile = removeDocumentRowForMode('filetree', documentId); return removedPage || removedFile; } function readProjection(value) { var runtimeFn = fileTreeRuntimeFunction('readProjection'); if (runtimeFn) return runtimeFn(value); if (!value || typeof value !== 'object') return null; if (value.result && typeof value.result === 'object') return value.result; if (value.snapshot && value.snapshot.tree) return value.snapshot.tree; if (value.data && value.data.tree) return value.data.tree; if (value.tree && typeof value.tree === 'object') return value.tree; return value; } function readSidebarDataset(value) { var runtimeFn = fileTreeRuntimeFunction('readSidebarDataset'); if (runtimeFn) return runtimeFn(value); if (!value || typeof value !== 'object') return null; if (value.snapshot && value.snapshot.dataset && typeof value.snapshot.dataset === 'object') return value.snapshot.dataset; if (value.data && value.data.dataset && typeof value.data.dataset === 'object') return value.data.dataset; if (value.dataset && typeof value.dataset === 'object') return value.dataset; if (value.sidebar && typeof value.sidebar === 'object') return value.sidebar; return null; } function readDatasetProjection(value, snakeCaseKey, camelCaseKey) { var runtimeFn = fileTreeRuntimeFunction('readDatasetProjection'); if (runtimeFn) return runtimeFn(value, snakeCaseKey, camelCaseKey); var dataset = readSidebarDataset(value); if (!dataset || typeof dataset !== 'object') return null; if (dataset[snakeCaseKey] && typeof dataset[snakeCaseKey] === 'object') return dataset[snakeCaseKey]; if (dataset[camelCaseKey] && typeof dataset[camelCaseKey] === 'object') return dataset[camelCaseKey]; return null; } function setTreeLiveApplyError(reason) { document.documentElement.setAttribute('data-mnote-tree-live-apply-error', String(reason || 'tree_live_apply_failed')); } function projectionItems(projection) { var runtimeFn = fileTreeRuntimeFunction('projectionItems'); if (runtimeFn) return runtimeFn(projection); var resolved = readProjection(projection); return resolved && Array.isArray(resolved.items) ? resolved.items : []; } function hasProjectionItems(projection) { var runtimeFn = fileTreeRuntimeFunction('hasProjectionItems'); if (runtimeFn) return runtimeFn(projection); var resolved = readProjection(projection); return Boolean(resolved && Array.isArray(resolved.items)); } function hasProjectionItemArray(projection) { var resolved = readProjection(projection); return Boolean(resolved && Array.isArray(resolved.items)); } function nodeIdOf(item) { var runtimeFn = fileTreeRuntimeFunction('nodeIdOf'); if (runtimeFn) return runtimeFn(item); return String(item && (item.nodeId || item.id || item.documentId) || '').trim(); } function rowIdOf(item) { var runtimeFn = fileTreeRuntimeFunction('rowIdOf'); if (runtimeFn) return runtimeFn(item); return String(item && (item.rowId || item.nodeId || item.id) || '').trim(); } function parentIdOf(item) { var runtimeFn = fileTreeRuntimeFunction('parentIdOf'); if (runtimeFn) return runtimeFn(item); return String(item && (item.parentNodeId || item.parentId || '') || '').trim(); } function titleOf(item) { var runtimeFn = fileTreeRuntimeFunction('titleOf'); if (runtimeFn) return runtimeFn(item); return String(item && item.title || '无标题').trim() || '无标题'; } function fileWorkspaceRelativePath(item) { var runtimeFn = fileTreeRuntimeFunction('fileWorkspaceRelativePath'); if (runtimeFn) return runtimeFn(item); var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {}; var workspacePath = meta.workspacePath && typeof meta.workspacePath === 'object' ? meta.workspacePath : {}; var fromWorkspacePath = String(workspacePath.relativePath || '').trim(); if (fromWorkspacePath) return fromWorkspacePath; var extra = meta.extra && typeof meta.extra === 'object' ? meta.extra : {}; var source = extra.source && typeof extra.source === 'object' ? extra.source : {}; var fromSource = String(source.relativePath || '').trim(); if (fromSource) return fromSource; return String(item && (item.relativePath || item.rootRelativePath) || '').trim(); } function normalizeFileTreeRelativePath(value) { var normalized = String(value || '').trim().replace(/^\/+|\/+$/g, ''); return normalized === '.' ? '' : normalized; } function fileTreeRowByRelativePath(relativePath) { var normalized = normalizeFileTreeRelativePath(relativePath); if (!normalized) return null; return document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="' + cssEscape(normalized) + '"]'); } function groupRowsByParent(rows) { var runtimeFn = fileTreeRuntimeFunction('groupRowsByParent'); if (runtimeFn) return runtimeFn(rows); var ids = new Set(rows.map(nodeIdOf).filter(Boolean)); var grouped = new Map(); rows.forEach(function(item) { var parentId = parentIdOf(item); if (!ids.has(parentId)) parentId = ''; if (!grouped.has(parentId)) grouped.set(parentId, []); grouped.get(parentId).push(item); }); return grouped; } function fileTreeParentKey(rootUri, parentRelativePath) { return String(rootUri || '').trim() + '\n' + String(parentRelativePath || '').trim(); } function currentFileTreeParentKey(parentRelativePath) { return fileTreeParentKey(currentRootUri(), parentRelativePath); } function stableTreeViewStateHash(value) { var hash = 0xcbf29ce484222325n; var prime = 0x100000001b3n; var text = String(value || '').trim(); for (var index = 0; index < text.length; index += 1) { hash ^= BigInt(text.charCodeAt(index) & 0xff); hash = (hash * prime) & 0xffffffffffffffffn; } return hash.toString(16).padStart(16, '0'); } function currentActorStorageId() { var body = document.body instanceof HTMLElement ? document.body : null; var fromBody = body ? String(body.getAttribute('data-mnote-actor-id') || '').trim() : ''; if (fromBody) return fromBody; var match = document.cookie.match(/(?:^|;\s*)mnote_actor_id=([^;]+)/); if (match) { try { return decodeURIComponent(match[1]); } catch (_) { return match[1] || ''; } } return 'anonymous'; } function sidebarTreeViewScope(treeKind) { var normalizedTreeKind = String(treeKind || '').trim() === 'pagetree' ? 'pagetree' : 'filetree'; var rootUri = String(currentRootUri() || '').trim(); var scope = currentFileTreeScope() || 'root'; return { userId: currentActorStorageId(), workspaceId: String(currentWorkspaceId() || '').trim() || (rootUri ? 'local:' + rootUri.replace(/^file:\/\//, '').replace(/[^A-Za-z0-9]+/g, '_') : 'default'), sourceKind: String(currentSourceKind() || 'local_folder').trim() || 'local_folder', treeKind: normalizedTreeKind, rootUri: rootUri, scope: scope }; } function sidebarTreeViewStateMapKey(scope) { return [ scope.userId, scope.workspaceId, scope.sourceKind, scope.treeKind, scope.rootUri, scope.scope ].join('\n'); } function sidebarTreeViewLocalStorageKey(scope) { return SIDEBAR_TREE_VIEW_STATE_KEY .replace('{userId}', encodeURIComponent(scope.userId || 'anonymous')) .replace('{workspaceId}', encodeURIComponent(scope.workspaceId || 'default')) .replace('{sourceKind}', encodeURIComponent(scope.sourceKind || 'local_folder')) .replace('{treeKind}', encodeURIComponent(scope.treeKind || 'filetree')) .replace('{rootUriHash}', stableTreeViewStateHash(scope.rootUri || '')) .replace('{scopeHash}', stableTreeViewStateHash(scope.scope || 'root')); } function normalizeSidebarTreeViewState(treeKind, value, source) { var scope = sidebarTreeViewScope(treeKind); var state = value && typeof value === 'object' ? value : {}; return { schemaVersion: 1, treeKind: scope.treeKind, rootUri: scope.rootUri, scope: scope.scope, expandedIds: new Set(Array.isArray(state.expandedIds) ? state.expandedIds.map(String).filter(Boolean) : []), expandedRelativePaths: new Set(Array.isArray(state.expandedRelativePaths) ? state.expandedRelativePaths.map(normalizeFileTreeRelativePath).filter(Boolean) : []), selectedId: String(state.selectedId || '').trim(), focusedId: String(state.focusedId || '').trim(), activeId: String(state.activeId || '').trim(), scrollTop: Math.max(0, Number(state.scrollTop || 0) || 0), updatedAtMs: Number(state.updatedAtMs || 0) || 0, hasUserState: source === 'sqlite' || source === 'local' || source === 'legacy', source: source || 'default' }; } function serializeSidebarTreeViewState(state) { return { schemaVersion: 1, treeKind: state.treeKind, rootUri: state.rootUri, scope: state.scope, expandedIds: Array.from(state.expandedIds || []).slice(0, 512), expandedRelativePaths: Array.from(state.expandedRelativePaths || []).slice(0, 512), selectedId: String(state.selectedId || ''), focusedId: String(state.focusedId || ''), activeId: String(state.activeId || ''), scrollTop: Math.max(0, Number(state.scrollTop || 0) || 0), updatedAtMs: Number(state.updatedAtMs || 0) || Date.now() }; } function sidebarTreeViewStateFor(treeKind) { var scope = sidebarTreeViewScope(treeKind); var key = sidebarTreeViewStateMapKey(scope); var state = sidebarTreeViewStates.get(key); if (!state) { var localValue = null; try { if (window.localStorage) { localValue = JSON.parse(window.localStorage.getItem(sidebarTreeViewLocalStorageKey(scope)) || 'null'); } } catch (_) { localValue = null; } state = normalizeSidebarTreeViewState(scope.treeKind, localValue, localValue ? 'local' : 'default'); sidebarTreeViewStates.set(key, state); void loadSidebarTreeViewState(scope.treeKind); } if (scope.treeKind === 'filetree') { fileTreeViewState.expandedParents = state.expandedRelativePaths; fileTreeExpandedRelativePaths = state.expandedRelativePaths; } return state; } function loadSidebarTreeViewState(treeKind) { var scope = sidebarTreeViewScope(treeKind); var key = sidebarTreeViewStateMapKey(scope); if (sidebarTreeViewStateRequests.has(key)) return sidebarTreeViewStateRequests.get(key); var url = new URL('/api/tree/view-state', window.location.origin); url.searchParams.set('workspaceId', scope.workspaceId); url.searchParams.set('sourceKind', scope.sourceKind); url.searchParams.set('treeKind', scope.treeKind); if (scope.rootUri) url.searchParams.set('rootUri', scope.rootUri); url.searchParams.set('scope', scope.scope); var promise = fetch(url.toString(), { credentials: 'include', cache: 'no-store', headers: { accept: 'application/json' } }).then(function(response) { return response.json().catch(function() { return null; }).then(function(payload) { if (!response.ok) return null; var result = payload && payload.result ? payload.result : null; if (!result || !result.state) return null; var latestScope = sidebarTreeViewScope(scope.treeKind); if (sidebarTreeViewStateMapKey(latestScope) !== key) return null; var loaded = normalizeSidebarTreeViewState(scope.treeKind, result.state, result.source === 'sqlite' ? 'sqlite' : 'default'); sidebarTreeViewStates.set(key, loaded); applySidebarTreeViewState(scope.treeKind, loaded); return loaded; }); }).catch(function(error) { document.documentElement.setAttribute('data-mnote-sidebar-tree-view-state-load-error', String(error && error.message || error || 'load_failed')); return null; }).finally(function() { sidebarTreeViewStateRequests.delete(key); }); sidebarTreeViewStateRequests.set(key, promise); return promise; } function persistSidebarTreeViewState(treeKind, options) { options = options || {}; var scope = sidebarTreeViewScope(treeKind); var key = sidebarTreeViewStateMapKey(scope); var state = sidebarTreeViewStateFor(scope.treeKind); state.hasUserState = true; state.source = state.source === 'legacy' ? 'legacy' : 'local'; state.updatedAtMs = Date.now(); var serialized = serializeSidebarTreeViewState(state); try { if (window.localStorage) { window.localStorage.setItem(sidebarTreeViewLocalStorageKey(scope), JSON.stringify(serialized)); } } catch (_) {} var flush = function() { sidebarTreeViewStateSaveTimers.delete(key); var latestScope = sidebarTreeViewScope(scope.treeKind); if (sidebarTreeViewStateMapKey(latestScope) !== key) return; fetch('/api/tree/view-state', { method: 'PUT', credentials: 'include', cache: 'no-store', headers: { accept: 'application/json', 'content-type': 'application/json' }, body: JSON.stringify({ workspaceId: scope.workspaceId, sourceKind: scope.sourceKind, treeKind: scope.treeKind, rootUri: scope.rootUri, scope: scope.scope, state: serialized }) }).then(function(response) { return response.json().catch(function() { return null; }).then(function(payload) { if (!response.ok) throw new Error(payload && (payload.error || payload.message) || 'tree_view_state_save_failed_' + response.status); document.documentElement.setAttribute('data-mnote-sidebar-tree-view-state-saved', scope.treeKind + ':' + scope.scope); return payload; }); }).catch(function(error) { document.documentElement.setAttribute('data-mnote-sidebar-tree-view-state-save-error', String(error && error.message || error || 'save_failed')); }); }; if (options.flush) { if (sidebarTreeViewStateSaveTimers.has(key)) { window.clearTimeout(sidebarTreeViewStateSaveTimers.get(key)); sidebarTreeViewStateSaveTimers.delete(key); } flush(); return; } if (sidebarTreeViewStateSaveTimers.has(key)) window.clearTimeout(sidebarTreeViewStateSaveTimers.get(key)); sidebarTreeViewStateSaveTimers.set(key, window.setTimeout(flush, SIDEBAR_TREE_VIEW_STATE_SAVE_DELAY_MS)); } function flushPendingSidebarTreeViewState(treeKind) { var scope = sidebarTreeViewScope(treeKind); var key = sidebarTreeViewStateMapKey(scope); if (!sidebarTreeViewStateSaveTimers.has(key)) return; persistSidebarTreeViewState(scope.treeKind, { flush: true }); } function applySidebarTreeViewState(treeKind, state) { if (String(treeKind || '').trim() === 'pagetree') { applyPageTreeExpansionState(state); return; } fileTreeExpandedRelativePaths = state.expandedRelativePaths; fileTreeViewState.expandedParents = state.expandedRelativePaths; restorePersistedFileTreeExpansionState(); } function applyPageTreeExpansionState(state) { if (!state || !state.hasUserState) return false; var changed = false; document.querySelectorAll('#sidebar-tree-root .tree-row[data-shell-mode="page"]').forEach(function(row) { if (!(row instanceof HTMLElement)) return; var nodeId = String(row.getAttribute('data-node-id') || '').trim(); var button = row.querySelector('[data-rust-action="toggle"]'); var node = row.closest('.tree-node'); var children = node ? node.querySelector(':scope > .tree-children') : null; if (!nodeId || !button || !(children instanceof HTMLElement)) return; var expanded = state.expandedIds.has(nodeId); row.setAttribute('aria-expanded', String(expanded)); button.setAttribute('aria-expanded', String(expanded)); children.classList.toggle('tree-children--collapsed', !expanded); changed = true; }); if (changed) document.documentElement.setAttribute('data-mnote-page-tree-view-state-applied', state.scope || 'root'); return changed; } function pageTreeActiveParentIds(grouped, activeId) { var parentsById = new Map(); grouped.forEach(function(items) { items.forEach(function(item) { parentsById.set(nodeIdOf(item), parentIdOf(item)); }); }); var parents = new Set(); var cursor = String(activeId || '').trim(); var guard = 0; while (cursor && parentsById.has(cursor) && guard < 512) { cursor = String(parentsById.get(cursor) || '').trim(); if (cursor) parents.add(cursor); guard += 1; } return parents; } function projectionParentRelativePath(projection) { var resolved = readProjection(projection); return String(resolved && (resolved.parentRelativePath || resolved.parent_relative_path) || '').trim(); } function isFileTreeRootProjectionParent(parentRelativePath) { return normalizeFileTreeRelativePath(parentRelativePath) === normalizeFileTreeRelativePath(currentFileTreeScope()); } function rememberFileTreeProjection(parentRelativePath, rows, projection) { var key = currentFileTreeParentKey(parentRelativePath); fileTreeState.rowsByParent.set(key, rows); fileTreeState.loadedParents.add(key); fileTreeState.dirtyParents.delete(key); fileTreeState.staleParents.delete(key); var resolved = readProjection(projection); var watchRevision = resolved && (resolved.watchRevision || resolved.watch_revision); if (watchRevision) fileTreeState.revisionByParent.set(key, watchRevision); } function cachedFileTreeRows(parentRelativePath) { return fileTreeState.rowsByParent.get(currentFileTreeParentKey(parentRelativePath)) || []; } function beginFileTreeRequest(key) { fileTreeState.requestGeneration += 1; fileTreeState.latestGenerationByParent.set(key, fileTreeState.requestGeneration); return fileTreeState.requestGeneration; } function isLatestFileTreeRequest(key, generation) { return fileTreeState.latestGenerationByParent.get(key) === generation; } function markFileTreeParentStale(key) { fileTreeState.staleParents.add(key); fileTreeState.dirtyParents.add(key); } function beginPageTreeRequest() { pageTreeState.pageTreeRequestGeneration += 1; return pageTreeState.pageTreeRequestGeneration; } function isLatestPageTreeRequest(generation) { return generation === pageTreeState.pageTreeRequestGeneration; } function yieldForPageTreeRender() { return new Promise(function(resolve) { if (typeof window !== 'undefined' && typeof window.requestIdleCallback === 'function') { window.requestIdleCallback(function() { resolve(); }, { timeout: 120 }); return; } if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') { window.requestAnimationFrame(function() { resolve(); }); return; } setTimeout(resolve, 0); }); } function pageTreeChevronSvg() { return ''; } function renderPageRows(parentId, grouped, activeId, inheritedDepth) { var computedDepth = Number(inheritedDepth || 0); var pageTreeStateView = sidebarTreeViewStateFor('pagetree'); var pageTreeExpandedIds = pageTreeStateView.expandedIds; var activeParentIds = pageTreeActiveParentIds(grouped, activeId); return (grouped.get(parentId) || []).map(function(item) { var nodeId = nodeIdOf(item); var title = titleOf(item); var depth = computedDepth; var parent = parentIdOf(item); var children = grouped.get(nodeId) || []; var expandable = Boolean(item.expandable || item.childCount > 0 || children.length); var expanded = expandable && ( pageTreeExpandedIds.has(nodeId) || (!pageTreeStateView.hasUserState && (activeParentIds.has(nodeId) || item.expandedByDefault !== false)) ); var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {}; var openable = Boolean(meta.documentId || item.documentId || !String(nodeId).startsWith('local-dir:')); var toggle = expandable ? '' : ''; var childHtml = expandable ? '' : ''; var currentAttr = nodeId === activeId ? ' aria-current="page" aria-selected="true"' : ' aria-selected="false"'; return '
  • ' + toggle + '
    ' + childHtml + '
  • '; }).join(''); } async function renderPageProjection(projection) { var generation = beginPageTreeRequest(); var tree = document.getElementById('sidebar-tree-root'); if (!tree) return false; var rows = projectionItems(projection).filter(function(item) { return String(item.rowKind || 'document') === 'document'; }); await yieldForPageTreeRender(); if (!isLatestPageTreeRequest(generation)) { document.documentElement.setAttribute('data-mnote-page-tree-stale-projection-ignored', String(generation)); return false; } var activeId = currentDocumentId(); var template = document.createElement('template'); template.innerHTML = ''; if (!isLatestPageTreeRequest(generation)) { document.documentElement.setAttribute('data-mnote-page-tree-stale-projection-ignored', String(generation)); return false; } tree.replaceChildren(template.content.cloneNode(true)); return true; } function fileDocumentId(item) { var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {}; if (meta.documentId) return String(meta.documentId).trim(); if (item && item.documentId) return String(item.documentId).trim(); if (item && item.rowKind === 'document') return nodeIdOf(item); if (item && item.rowKind === 'index') return nodeIdOf(item).replace(/^index:/, ''); return ''; } function fileAssetId(item) { var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {}; if (meta.assetId) return String(meta.assetId).trim(); if (item && item.assetId) return String(item.assetId).trim(); if (item && item.rowKind === 'asset') return nodeIdOf(item).replace(/^asset:/, ''); return ''; } function fileObjectIdentity(item) { var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {}; // Phase A1: workspacePath 优先于旧猜测逻辑 var wp = meta.workspacePath; if (wp && typeof wp === 'object' && wp.objectIdentity && typeof wp.objectIdentity === 'object') { return wp.objectIdentity; } if (meta.objectIdentity && typeof meta.objectIdentity === 'object') return meta.objectIdentity; var rowKind = String(item && item.rowKind || ''); var documentId = fileDocumentId(item) || null; var assetId = fileAssetId(item) || null; var iconKind = iconKindOf(item); var objectKind = rowKind === 'document' ? 'page' : rowKind === 'index' ? 'index' : iconKind === 'mindmap' ? 'mindmap' : 'attachment'; return { objectKind: objectKind, documentId: documentId, blockId: null, assetId: assetId }; } function fileOwnerDocumentId(item, fallbackDocumentId, objectIdentity) { var identity = objectIdentity && typeof objectIdentity === 'object' ? objectIdentity : fileObjectIdentity(item); var owner = String(identity && identity.documentId || '').trim(); if (owner) return owner; var assetId = fileAssetId(item); var localPath = localFilePathFromAssetId(assetId); if (localPath && localPath.indexOf('/') > 0) { var parts = localPath.split('/'); var bundleName = parts[0] || ''; if (bundleName) return 'local-md:' + bundleName + '~2F' + bundleName + '.md'; } return String(fallbackDocumentId || '').trim(); } function objectIdentityAttr(identity) { try { return JSON.stringify(identity || {}); } catch (_error) { return ''; } } function iconKindOf(item) { return String(item && (item.iconHint || item.rowKind || 'file') || 'file').trim() || 'file'; } function fileCapabilitiesAttr(item) { try { return JSON.stringify(Array.isArray(item && item.capabilities) ? item.capabilities : []); } catch (_error) { return '[]'; } } function isFileTreeProjectionPageRow(rowKind, assetId) { if (assetId) return false; return rowKind === 'document' || rowKind === 'doc' || rowKind === 'markdown'; } function normalizeMindmapFileTreeTitle(rawTitle, assetId, iconKind, objectIdentity) { var title = String(rawTitle || '').trim(); var isMindmap = iconKind === 'mindmap' || String(objectIdentity && objectIdentity.objectKind || '') === 'mindmap'; if (!isMindmap) return title || '无标题'; return title || shortMindmapFileName(assetId); } function renderFileRows(parentId, grouped, activeId, activeRowId) { var fileTreeStateView = sidebarTreeViewStateFor('filetree'); var expandedRelativePaths = fileTreeStateView.expandedRelativePaths; return (grouped.get(parentId) || []).map(function(item) { var nodeId = nodeIdOf(item); var rowId = rowIdOf(item); var rowKind = String(item.rowKind || 'document'); var rawTitle = titleOf(item); var depth = Number(item.depth || 0); var parent = parentIdOf(item); var documentId = fileDocumentId(item); var assetId = fileAssetId(item); var relativePath = fileWorkspaceRelativePath(item); var objectIdentity = fileObjectIdentity(item); var ownerDocumentId = fileOwnerDocumentId(item, documentId, objectIdentity); var iconKind = iconKindOf(item); var indexStatus = String((item && item.indexStatus) || (item && item.resourceMeta && item.resourceMeta.indexStatus) || '').trim(); if (indexStatus !== 'indexed' && indexStatus !== 'failed') indexStatus = ''; var cachedChildren = relativePath ? cachedFileTreeRows(relativePath) : []; var title = isFileTreeProjectionPageRow(rowKind, assetId) ? fileTreePageTitle(rawTitle) : normalizeMindmapFileTreeTitle(rawTitle, assetId, iconKind, objectIdentity); var children = grouped.get(nodeId) || []; var expandable = Boolean(item.expandable || item.childCount > 0 || children.length || cachedChildren.length); var expanded = expandable && (expandedRelativePaths.has(relativePath) || (!fileTreeStateView.hasUserState && item.expandedByDefault !== false)); var selected = activeRowId ? rowId === activeRowId : rowId === 'doc:' + activeId; var testId = rowKind === 'document' ? 'filetree-doc-row' : rowKind === 'index' ? 'filetree-index-row' : 'filetree-asset-row'; var toggle = expandable ? '' : ''; var createAction = rowKind === 'document' ? '' : ''; var childRowsHtml = children.length ? renderFileRows(nodeId, grouped, activeId, activeRowId) : cachedChildren.length ? renderFileRows('', groupRowsByParent(cachedChildren), activeId, activeRowId) : ''; var childHtml = expandable && expanded && childRowsHtml ? '' : ''; return '
  • ' + toggle + '
    ' + createAction + '
    ' + childHtml + '
  • '; }).join(''); } function ensureFileTreeLazyCacheScope() { var rootUri = currentRootUri(); var scope = currentFileTreeScope(); if (rootUri === fileTreeLazyCacheRootUri && scope === fileTreeState.scope) return; fileTreeLazyCacheRootUri = rootUri; fileTreeState.rootUri = rootUri; fileTreeState.scope = scope; fileTreeState.rowsByParent.clear(); fileTreeState.loadedParents.clear(); fileTreeState.loadingParents.clear(); fileTreeState.dirtyParents.clear(); fileTreeState.staleParents.clear(); fileTreeState.revisionByParent.clear(); fileTreeState.latestGenerationByParent.clear(); fileTreeState.requestGeneration += 1; var nextState = sidebarTreeViewStateFor('filetree'); fileTreeExpandedRelativePaths = nextState.expandedRelativePaths; fileTreeViewState.expandedParents = nextState.expandedRelativePaths; loadStoredFileTreeExpansionState(rootUri); } function storedFileTreeExpansionBuckets() { try { if (!window.sessionStorage) return {}; var parsed = JSON.parse(window.sessionStorage.getItem(FILETREE_EXPANSION_STORAGE_KEY) || '{}'); return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}; } catch (_) { return {}; } } function loadStoredFileTreeExpansionState(rootUri) { var normalizedRootUri = String(rootUri || '').trim(); if (!normalizedRootUri || fileTreeExpansionStorageRootUri === normalizedRootUri) return; fileTreeExpansionStorageRootUri = normalizedRootUri; var state = sidebarTreeViewStateFor('filetree'); if (state.hasUserState && state.expandedRelativePaths.size) return; var buckets = storedFileTreeExpansionBuckets(); var paths = Array.isArray(buckets[normalizedRootUri]) ? buckets[normalizedRootUri] : []; paths.forEach(function(path) { var normalized = String(path || '').trim(); if (normalized) state.expandedRelativePaths.add(normalized); }); if (state.expandedRelativePaths.size) { state.hasUserState = true; state.source = 'legacy'; fileTreeExpandedRelativePaths = state.expandedRelativePaths; fileTreeViewState.expandedParents = state.expandedRelativePaths; persistSidebarTreeViewState('filetree'); } } function persistFileTreeExpansionState() { var rootUri = String(currentRootUri() || fileTreeLazyCacheRootUri || '').trim(); if (!rootUri) return; var state = sidebarTreeViewStateFor('filetree'); try { if (!window.sessionStorage) return; var buckets = storedFileTreeExpansionBuckets(); buckets[rootUri] = Array.from(state.expandedRelativePaths) .map(function(path) { return String(path || '').trim(); }) .filter(Boolean) .slice(0, 256); window.sessionStorage.setItem(FILETREE_EXPANSION_STORAGE_KEY, JSON.stringify(buckets)); } catch (_) {} } function scheduleRestorePersistedFileTreeExpansionState() { if (fileTreeExpansionRestoreTimer) window.clearTimeout(fileTreeExpansionRestoreTimer); fileTreeExpansionRestoreTimer = window.setTimeout(function() { fileTreeExpansionRestoreTimer = 0; restorePersistedFileTreeExpansionState(); }, 0); } function scheduleFileTreeIdleTask(callback, timeout) { if (typeof window.requestIdleCallback === 'function') { return window.requestIdleCallback(callback, { timeout: timeout || 800 }); } if (typeof window.requestAnimationFrame === 'function') { return window.requestAnimationFrame(function() { window.setTimeout(callback, 0); }); } return window.setTimeout(callback, 0); } function isFileTreeRowVisibleForHydrate(row) { if (!(row instanceof HTMLElement)) return false; if (typeof row.getBoundingClientRect !== 'function') return true; var rect = row.getBoundingClientRect(); var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0; if (!viewportHeight) return true; return rect.bottom >= -64 && rect.top <= viewportHeight + 256; } function fileTreeRowNeedsVisibleHydrate(row) { if (!(row instanceof HTMLElement)) return false; if (currentSourceKind() !== 'local_folder') return false; if (!currentRootUri()) return false; if (row.getAttribute('data-shell-mode') !== 'filetree') return false; if (String(row.getAttribute('data-row-kind') || '').trim() !== 'folder') return false; if (row.getAttribute('aria-expanded') === 'true') { var relativePath = localFileTreeRelativePathFromRow(row); if (!relativePath || !fileTreeExpandedRelativePaths.has(relativePath)) return false; if (row.getAttribute('data-filetree-children-loaded') !== 'true' && row.getAttribute('data-filetree-children-loading') !== 'true') { return isFileTreeRowVisibleForHydrate(row); } } return false; } function collectVisibleExpandedFileTreeRowsForHydrate() { var rows = []; document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) { if (rows.length >= FILETREE_VISIBLE_EXPANDED_HYDRATE_MAX) return; if (fileTreeRowNeedsVisibleHydrate(row)) rows.push(row); }); return rows; } function scheduleHydrateVisibleExpandedFileTreeRows(reason) { if (fileTreeVisibleHydrateQueued) return; if (currentSourceKind() !== 'local_folder') return; fileTreeVisibleHydrateQueued = true; fileTreeVisibleHydrateTimer = scheduleFileTreeIdleTask(function() { fileTreeVisibleHydrateTimer = 0; fileTreeVisibleHydrateQueued = false; hydrateVisibleExpandedFileTreeRows(reason || 'idle'); }, 800); } function hydrateVisibleExpandedFileTreeRows(reason) { if (currentSourceKind() !== 'local_folder') return false; var rows = collectVisibleExpandedFileTreeRowsForHydrate(); if (!rows.length) return false; var batch = rows.slice(0, FILETREE_VISIBLE_EXPANDED_HYDRATE_BATCH); batch.forEach(function(row) { var button = row.querySelector('[data-rust-action="toggle"]'); void loadFileTreeChildren(row, button, { persist: false, idleHydrate: true }).then(function(loaded) { if (loaded) { document.documentElement.setAttribute('data-mnote-filetree-idle-hydrate-applied', String(reason || 'idle')); scheduleHydrateVisibleExpandedFileTreeRows('cascade'); } }); }); if (rows.length > batch.length) scheduleHydrateVisibleExpandedFileTreeRows('batch'); return true; } function rememberFileTreeExpansionState() { var changed = false; document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) { if (!(row instanceof HTMLElement)) return; var relativePath = localFileTreeRelativePathFromRow(row); if (!relativePath) return; if (row.getAttribute('aria-expanded') === 'true') { if (!fileTreeExpandedRelativePaths.has(relativePath)) changed = true; fileTreeExpandedRelativePaths.add(relativePath); } else { if (fileTreeExpandedRelativePaths.has(relativePath)) changed = true; fileTreeExpandedRelativePaths.delete(relativePath); } }); if (changed) persistFileTreeExpansionState(); } function rememberFileTreeSelectionState() { fileTreeViewState.selectedRowIds.clear(); fileTreeViewState.focusedRowId = ''; fileTreeViewState.activeRowId = ''; document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) { if (!(row instanceof HTMLElement)) return; var rowId = String(row.getAttribute('data-row-id') || '').trim(); if (!rowId) return; if (row.getAttribute('data-selected') === 'true') fileTreeViewState.selectedRowIds.add(rowId); if (row.getAttribute('data-focused') === 'true') fileTreeViewState.focusedRowId = rowId; if (row.getAttribute('data-active') === 'true') fileTreeViewState.activeRowId = rowId; }); } function reprojectFileTreeSelectionState() { document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) { if (!(row instanceof HTMLElement)) return; var rowId = String(row.getAttribute('data-row-id') || '').trim(); row.setAttribute('data-selected', String(fileTreeViewState.selectedRowIds.has(rowId))); row.setAttribute('data-focused', String(rowId === fileTreeViewState.focusedRowId)); if (fileTreeViewState.activeRowId) { row.setAttribute('data-active', String(rowId === fileTreeViewState.activeRowId)); } }); } function renderFileProjection(projection) { var tree = document.getElementById('sidebar-file-tree-root'); if (!tree) return false; ensureFileTreeLazyCacheScope(); rememberFileTreeExpansionState(); rememberFileTreeSelectionState(); var rows = projectionItems(projection); var parentRelativePath = projectionParentRelativePath(projection) || currentFileTreeScope(); rememberFileTreeProjection(parentRelativePath, rows, projection); if (!isFileTreeRootProjectionParent(parentRelativePath)) { return patchFileTreeParentChildren(parentRelativePath, rows); } var activeId = currentDocumentId(); var activeRowId = currentFileTreeActiveRowId(); var template = document.createElement('template'); template.innerHTML = ''; tree.replaceChildren(template.content.cloneNode(true)); reprojectFileTreeSelectionState(); scheduleRestorePersistedFileTreeExpansionState(); scheduleHydrateVisibleExpandedFileTreeRows('render'); return true; } function patchFileTreeParentChildren(parentRelativePath, rows) { var row = fileTreeRowByRelativePath(parentRelativePath); if (!(row instanceof HTMLElement)) return true; var node = row.closest('.tree-node'); if (!node) return true; rememberFileTreeSelectionState(); var button = row.querySelector('[data-rust-action="toggle"]'); var wasExpanded = row.getAttribute('aria-expanded') === 'true'; var children = node.querySelector(':scope > .tree-children'); if (!children && !wasExpanded && row.getAttribute('data-filetree-children-loaded') !== 'true') { return true; } if (!children) { children = document.createElement('ul'); children.className = 'tree-children'; node.appendChild(children); } var template = document.createElement('template'); template.innerHTML = renderFileRows('', groupRowsByParent(rows), currentDocumentId(), currentFileTreeActiveRowId()); children.replaceChildren(template.content.cloneNode(true)); children.classList.toggle('tree-children--collapsed', !wasExpanded); row.setAttribute('data-filetree-children-loaded', 'true'); row.removeAttribute('data-filetree-children-loading'); setTreeRowExpanded(row, button, wasExpanded); syncSidebarFileTreeSelection(); reprojectFileTreeSelectionState(); scheduleHydrateVisibleExpandedFileTreeRows('patch'); return true; } function renderSidebarSnapshot(payload) { var renderedPage = false; if (hasProjectionItemArray(payload)) { renderedPage = true; void renderPageProjection(payload); } var fileProjection = readDatasetProjection(payload, 'kernel_file_tree_projection', 'kernelFileTreeProjection'); var renderedFile = fileProjection && hasProjectionItemArray(fileProjection) ? renderFileProjection(fileProjection) : false; return renderedPage || renderedFile; } function renderLiveSidebarSnapshot(payload) { var renderedPage = false; if (hasProjectionItemArray(payload)) { renderedPage = true; void renderPageProjection(payload); } var fileProjection = readDatasetProjection(payload, 'kernel_file_tree_projection', 'kernelFileTreeProjection'); var hasFileProjection = fileProjection && hasProjectionItemArray(fileProjection); if (currentFileTreeScope()) { if (!hasFileProjection) return renderedPage; var projectionParent = projectionParentRelativePath(fileProjection); if (isFileTreeRootProjectionParent(projectionParent)) { return renderFileProjection(fileProjection) || renderedPage; } document.documentElement.setAttribute('data-mnote-filetree-scoped-live-snapshot-ignored', projectionParent || 'root'); return true; } var renderedFile = hasFileProjection ? renderFileProjection(fileProjection) : false; return renderedPage || renderedFile; } function replaceSidebarTreeFromDocument(nextDocument, rootId) { var runtimeFn = fileTreeRuntimeFunction('replaceSidebarTreeFromDocument'); if (runtimeFn) return runtimeFn(nextDocument, rootId); var current = document.getElementById(rootId); var next = nextDocument ? nextDocument.getElementById(rootId) : null; if (!(current instanceof HTMLElement) || !(next instanceof HTMLElement)) return false; current.innerHTML = next.innerHTML; Array.from(next.attributes || []).forEach(function(attr) { current.setAttribute(attr.name, attr.value); }); return true; } function applyLocalFolderSidebarSnapshot(nextDocument, options) { var runtimeFn = fileTreeRuntimeFunction('applyLocalFolderSidebarSnapshot'); if (runtimeFn) return runtimeFn(nextDocument, options); options = options || {}; var pageRootId = String(options.pageRootId || 'sidebar-tree-root'); var fileRootId = String(options.fileRootId || 'sidebar-file-tree-root'); var appliedPage = replaceSidebarTreeFromDocument(nextDocument, pageRootId); var appliedFile = replaceSidebarTreeFromDocument(nextDocument, fileRootId); var applied = appliedPage || appliedFile; if (applied && typeof options.onApplied === 'function') options.onApplied({ pageRootApplied: appliedPage, fileRootApplied: appliedFile }); return applied; } function markLocalFolderWatchApplied(value) { var runtimeFn = fileTreeRuntimeFunction('markLocalFolderWatchApplied'); if (runtimeFn) return runtimeFn(value); var appliedValue = String(value || 'projection'); document.documentElement.setAttribute('data-mnote-local-folder-watch-applied', appliedValue); document.documentElement.removeAttribute('data-mnote-local-folder-watch-disabled'); return true; } function currentFileTreeScope() { var params = new URLSearchParams(window.location.search); var fromUrl = String(params.get('fileTreeScope') || '').trim(); if (fromUrl) return fromUrl; var root = document.getElementById('sidebar-file-tree-root'); return root instanceof HTMLElement ? String(root.getAttribute('data-mnote-filetree-scope') || '').trim() : ''; } async function fetchFileTreeProjection(parentRelativePath, options) { options = options || {}; var rootUri = currentRootUri(); if (!rootUri) return null; var url = new URL(options.childrenOnly ? '/api/tree/projections/file/children' : '/api/tree/projections/file', window.location.origin); var workspaceId = currentWorkspaceId(); if (workspaceId) url.searchParams.set('workspaceId', workspaceId); url.searchParams.set('sourceKind', 'local_folder'); url.searchParams.set('rootUri', rootUri); if (parentRelativePath) url.searchParams.set('parentRelativePath', parentRelativePath); var currentId = currentDocumentId(); if (!options.childrenOnly && currentId) url.searchParams.set('rootNodeId', currentId); var response = await fetch(url.toString(), { headers: { accept: 'application/json' } }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && (payload.error || payload.message) || 'filetree_projection_failed_' + response.status); return readProjection(payload && (payload.result || payload)); } async function refreshFileTreeParent(parentRelativePath) { ensureFileTreeLazyCacheScope(); var key = currentFileTreeParentKey(parentRelativePath); fileTreeState.dirtyParents.add(key); if (!isFileTreeRootProjectionParent(parentRelativePath)) { var row = fileTreeRowByRelativePath(parentRelativePath); if (row instanceof HTMLElement && row.getAttribute('aria-expanded') !== 'true') { markFileTreeParentStale(key); return true; } } if (fileTreeState.loadingParents.has(key)) { // 变更事件可能正好撞上同一 parent 的懒加载请求。 // 旧请求结果不包含刚落盘的文件,不能直接拿它满足本次刷新。 await fileTreeState.loadingParents.get(key).catch(function() { return []; }); } var projection = await fetchFileTreeProjection(parentRelativePath, { childrenOnly: false }); if (!projection) return false; return renderFileProjection(projection); } async function refreshLocalFolderSidebarSnapshot() { var workspaceId = currentWorkspaceId(); var rootUri = currentRootUri(); var currentId = currentDocumentId(); var fileTreeScope = currentFileTreeScope(); if (!workspaceId || !rootUri) return false; var sidebarUrl = new URL('/api/tree/projections/sidebar', window.location.origin); sidebarUrl.searchParams.set('workspaceId', workspaceId); sidebarUrl.searchParams.set('sourceKind', 'local_folder'); sidebarUrl.searchParams.set('rootUri', rootUri); if (currentId) sidebarUrl.searchParams.set('rootNodeId', currentId); if (fileTreeScope) sidebarUrl.searchParams.set('parentRelativePath', fileTreeScope); var responses = await Promise.allSettled([ fetch(sidebarUrl.toString(), { headers: { accept: 'application/json' } }), refreshFileTreeParent(fileTreeScope) ]); var sidebarResponse = responses[0].status === 'fulfilled' ? responses[0].value : null; var renderedFile = responses[1].status === 'fulfilled' ? responses[1].value : false; if ((!sidebarResponse || !sidebarResponse.ok) && !renderedFile) return false; var sidebarPayload = sidebarResponse && sidebarResponse.ok ? await sidebarResponse.json().catch(function() { return null; }) : null; var resolvedSidebarPayload = sidebarPayload ? (sidebarPayload.result || sidebarPayload) : null; var renderedPage = false; if (resolvedSidebarPayload && hasProjectionItemArray(resolvedSidebarPayload)) { renderedPage = await renderPageProjection(resolvedSidebarPayload); } if (renderedFile && fileTreeScope) { var fileRoot = document.getElementById('sidebar-file-tree-root'); if (fileRoot instanceof HTMLElement) fileRoot.setAttribute('data-mnote-filetree-scope', fileTreeScope); document.documentElement.setAttribute('data-mnote-filetree-scope', fileTreeScope); } if (!renderedPage && !renderedFile) return false; syncSidebarFileTreeSelection(); schedulePendingLocalFolderRestoreFocus(); restoreSidebarTreeTab(); refreshEditorLocalAttachmentExistence(); markLocalFolderWatchApplied('projection'); return true; } function startLocalFolderSidebarWatch() { if (currentSourceKind() !== 'local_folder') return; var rootUri = currentRootUri(); if (!rootUri) return; ensureFileTreeLazyCacheScope(); scheduleRestorePersistedFileTreeExpansionState(); var treeTransport = document.documentElement.getAttribute('data-mnote-tree-live-transport') || ''; if (treeTransport !== 'local-folder-events') { document.documentElement.setAttribute('data-mnote-local-folder-watch-applied', 'static'); document.documentElement.setAttribute('data-mnote-local-folder-watch-disabled', 'events-required'); } } function isTitleOnlyDocumentPatch(candidate) { if (!candidate || typeof candidate !== 'object') return false; var allowedKeys = { id: true, documentId: true, title: true, updatedAt: true, updated_at: true }; return Object.keys(candidate).every(function(key) { return allowedKeys[key] === true; }); } function deltaNeedsProjectionRefresh(payload) { var data = payload && (payload.data || payload.delta || payload); var op = data && typeof data.op === 'string' ? String(data.op).trim() : ''; if (!op || op === 'noop') return false; if (op === 'upsert_document') { return !isTitleOnlyDocumentPatch(data.node || data.document || null); } if (op === 'upsert_documents') { var documents = Array.isArray(data.upsertDocuments) ? data.upsertDocuments : Array.isArray(data.upsert_documents) ? data.upsert_documents : []; if (documents.length > 0 && documents.every(isTitleOnlyDocumentPatch)) { return false; } } return true; } function localFileTreeRelativePathFromRow(row) { if (!(row instanceof HTMLElement)) return ''; var direct = String(row.getAttribute('data-local-relative-path') || '').trim(); if (direct) return direct; var runtimeFn = fileTreeRuntimeFunction('fileTreeRowLocalRelativePath'); if (runtimeFn) { try { return String(runtimeFn(row, { localFilePathFromAssetId: localFilePathFromAssetId }) || '').trim(); } catch (_) {} } var rowId = String(row.getAttribute('data-row-id') || '').trim(); var nodeId = String(row.getAttribute('data-node-id') || '').trim(); if (rowId.indexOf('local:folder:') === 0) return rowId.slice('local:folder:'.length); if (rowId.indexOf('local:markdown:') === 0) return rowId.slice('local:markdown:'.length); if (rowId.indexOf('local:asset:') === 0) return rowId.slice('local:asset:'.length); if (nodeId.indexOf('local:node:') === 0) return nodeId.slice('local:node:'.length); return ''; } function setTreeRowExpanded(row, button, expanded, options) { row.setAttribute('aria-expanded', expanded ? 'true' : 'false'); var shellMode = row.getAttribute('data-shell-mode') || ''; if (button) { button.setAttribute('aria-expanded', expanded ? 'true' : 'false'); if (shellMode === 'filetree') button.textContent = expanded ? '▾' : '▸'; } var shouldPersist = !(options && options.persist === false); if (shellMode === 'page') { var nodeId = String(row.getAttribute('data-node-id') || '').trim(); if (!nodeId) return; var pageState = sidebarTreeViewStateFor('pagetree'); if (expanded) pageState.expandedIds.add(nodeId); else pageState.expandedIds.delete(nodeId); pageState.hasUserState = true; if (shouldPersist) persistSidebarTreeViewState('pagetree'); return; } var relativePath = localFileTreeRelativePathFromRow(row); if (!relativePath) return; if (expanded) fileTreeExpandedRelativePaths.add(relativePath); else fileTreeExpandedRelativePaths.delete(relativePath); if (shouldPersist) { persistFileTreeExpansionState(); persistSidebarTreeViewState('filetree'); } } function markExistingFileTreeChildrenLoaded(row, button, options) { if (!(row instanceof HTMLElement)) return false; var node = row.closest('.tree-node'); if (!node) return false; var children = node.querySelector(':scope > .tree-children'); if (!(children instanceof HTMLElement)) return false; row.setAttribute('data-filetree-children-loaded', 'true'); children.classList.remove('tree-children--collapsed'); setTreeRowExpanded(row, button, true, options); syncSidebarFileTreeSelection(); return true; } function renderCachedFileTreeChildren(row, button, relativePath, options) { var key = currentFileTreeParentKey(relativePath); if (fileTreeState.staleParents.has(key) || fileTreeState.dirtyParents.has(key)) return false; var cachedRows = cachedFileTreeRows(relativePath); if (!cachedRows.length) return false; var node = row.closest('.tree-node'); if (!node) return false; var children = node.querySelector(':scope > .tree-children'); if (!children) { children = document.createElement('ul'); children.className = 'tree-children'; node.appendChild(children); } var template = document.createElement('template'); template.innerHTML = renderFileRows('', groupRowsByParent(cachedRows), currentDocumentId(), currentFileTreeActiveRowId()); children.replaceChildren(template.content.cloneNode(true)); children.classList.remove('tree-children--collapsed'); row.setAttribute('data-filetree-children-loaded', 'true'); setTreeRowExpanded(row, button, true, options); syncSidebarFileTreeSelection(); return true; } async function getFileTreeChildren(parentRelativePath) { ensureFileTreeLazyCacheScope(); var key = currentFileTreeParentKey(parentRelativePath); if (fileTreeState.loadedParents.has(key) && !fileTreeState.dirtyParents.has(key) && !fileTreeState.staleParents.has(key)) { return fileTreeState.rowsByParent.get(key) || []; } if (fileTreeState.loadingParents.has(key)) { return fileTreeState.loadingParents.get(key); } var generation = beginFileTreeRequest(key); var promise = fetchFileTreeProjection(parentRelativePath, { childrenOnly: true }).then(function(projection) { if (!isLatestFileTreeRequest(key, generation)) { return fileTreeState.rowsByParent.get(key) || []; } var rows = projectionItems(projection); rememberFileTreeProjection(parentRelativePath, rows, projection); return rows; }).finally(function() { fileTreeState.loadingParents.delete(key); }); fileTreeState.loadingParents.set(key, promise); return promise; } async function loadFileTreeChildren(row, button, options) { if (!(row instanceof HTMLElement)) return false; if (row.getAttribute('data-shell-mode') !== 'filetree') return false; if (currentSourceKind() !== 'local_folder') return false; ensureFileTreeLazyCacheScope(); var relativePath = localFileTreeRelativePathFromRow(row); var key = currentFileTreeParentKey(relativePath); var stale = fileTreeState.staleParents.has(key) || fileTreeState.dirtyParents.has(key); if (!stale && markExistingFileTreeChildrenLoaded(row, button, options)) return true; if (!stale && relativePath && renderCachedFileTreeChildren(row, button, relativePath, options)) return true; if (row.getAttribute('data-filetree-children-loaded') === 'true') return false; var rootUri = currentRootUri(); if (!rootUri || !relativePath) return false; setTreeRowExpanded(row, button, true, options); row.setAttribute('data-filetree-children-loading', 'true'); try { var rows = await getFileTreeChildren(relativePath); if (!rows.length) { row.setAttribute('data-filetree-children-loaded', 'true'); setTreeRowExpanded(row, button, true, options); return true; } return renderCachedFileTreeChildren(row, button, relativePath, options); } catch (error) { setTreeLiveApplyError(error && error.message ? error.message : '文件树子目录加载失败'); return false; } finally { row.removeAttribute('data-filetree-children-loading'); } } function fileTreeParentChainForRelativePath(relativePath) { var normalized = normalizeFileTreeRelativePath(relativePath); if (!normalized) return []; var parts = normalized.split('/').filter(Boolean); parts.pop(); var chain = []; for (var index = 0; index < parts.length; index += 1) { chain.push(parts.slice(0, index + 1).join('/')); } return chain; } async function revealFileTreeResource(input) { input = input || {}; var rootUri = String(input.rootUri || currentRootUri() || '').trim(); if (!rootUri || rootUri !== currentRootUri()) return false; ensureFileTreeLazyCacheScope(); var relativePath = normalizeFileTreeRelativePath(input.relativePath || input.relative_path || ''); var rowId = String(input.rowId || input.row_id || '').trim(); var parentChain = fileTreeParentChainForRelativePath(relativePath); for (var index = 0; index < parentChain.length; index += 1) { var parentRelativePath = parentChain[index]; var parentRow = fileTreeRowByRelativePath(parentRelativePath); if (!(parentRow instanceof HTMLElement)) continue; var button = parentRow.querySelector('[data-rust-action="toggle"]'); await getFileTreeChildren(parentRelativePath); renderCachedFileTreeChildren(parentRow, button, parentRelativePath); setTreeRowExpanded(parentRow, button, true); } var targetRow = rowId ? document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]') : null; if (!(targetRow instanceof HTMLElement) && relativePath) { targetRow = fileTreeRowByRelativePath(relativePath); } if (!(targetRow instanceof HTMLElement)) return false; var targetRowId = String(targetRow.getAttribute('data-row-id') || rowId || '').trim(); if (targetRowId) { fileTreeViewState.selectedRowIds = new Set([targetRowId]); fileTreeViewState.focusedRowId = targetRowId; fileTreeViewState.activeRowId = targetRowId; } reprojectFileTreeSelectionState(); try { targetRow.scrollIntoView({ block: 'nearest' }); } catch (_) {} return true; } function toggleChildren(row, button) { var li = row && row.parentElement; var children = li ? li.querySelector(':scope > .tree-children') : null; if (!children) { void loadFileTreeChildren(row, button); return; } children.classList.toggle('tree-children--collapsed'); var collapsed = children.classList.contains('tree-children--collapsed'); setTreeRowExpanded(row, button, !collapsed); } function shouldAutoRestoreFileTreeExpansion(relativePath) { var normalized = normalizeFileTreeRelativePath(relativePath); if (!normalized) return false; var scope = normalizeFileTreeRelativePath(currentFileTreeScope()); if (!scope) return normalized.indexOf('/') < 0; if (normalized.indexOf(scope + '/') !== 0) return false; return normalized.slice(scope.length + 1).indexOf('/') < 0; } function restorePersistedFileTreeExpansionState() { if (currentSourceKind() !== 'local_folder') return false; ensureFileTreeLazyCacheScope(); if (!fileTreeExpandedRelativePaths.size) return false; var restored = false; var needsVisibleHydrate = false; document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) { if (!(row instanceof HTMLElement)) return; if (String(row.getAttribute('data-row-kind') || '').trim() !== 'folder') return; var relativePath = localFileTreeRelativePathFromRow(row); if (!relativePath || !fileTreeExpandedRelativePaths.has(relativePath)) return; // 首屏只自动恢复当前 scope 的直接子目录。历史 view-state 可能记录了 // 多层 design 目录展开;一次性递归恢复会长时间占满浏览器主线程。 if (!shouldAutoRestoreFileTreeExpansion(relativePath)) return; var button = row.querySelector('[data-rust-action="toggle"]'); if (markExistingFileTreeChildrenLoaded(row, button, { persist: false })) { restored = true; return; } if (row.getAttribute('data-filetree-children-loaded') === 'true') { setTreeRowExpanded(row, button, true, { persist: false }); restored = true; return; } setTreeRowExpanded(row, button, true, { persist: false }); needsVisibleHydrate = true; restored = true; }); if (restored) document.documentElement.setAttribute('data-mnote-filetree-expansion-restored', 'true'); if (needsVisibleHydrate) scheduleHydrateVisibleExpandedFileTreeRows('restore'); return restored; } function installTreeLiveApplyEventListeners() { sidebarTreeViewStateFor('pagetree'); document.addEventListener('visibilitychange', function() { if (!document.hidden) return; flushPendingSidebarTreeViewState('filetree'); flushPendingSidebarTreeViewState('pagetree'); }); window.addEventListener('pagehide', function() { flushPendingSidebarTreeViewState('filetree'); flushPendingSidebarTreeViewState('pagetree'); }); document.addEventListener('scroll', function() { scheduleHydrateVisibleExpandedFileTreeRows('scroll'); }, true); window.addEventListener('tree:title-updated', function(event) { var detail = event.detail || {}; var previousDocumentId = detail.previousDocumentId || (detail.payload && detail.payload.result && detail.payload.result.previousDocumentId) || ''; if (previousDocumentId && previousDocumentId !== detail.documentId) { removeDocumentRowForMode('page', previousDocumentId); removeDocumentRowForMode('filetree', previousDocumentId); if (currentDocumentId() === previousDocumentId) { navigateToDocument(detail.documentId, detail.workspaceId || currentWorkspaceId(), { treeView: activeSidebarTreeMode() }); } } updateTitleEverywhere(detail.documentId, detail.title); if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot(); document.documentElement.setAttribute('data-mnote-title-local-applied', 'true'); }); window.addEventListener('tree:local-command', function(event) { var detail = event.detail || {}; var body = detail.body || {}; var action = String(body.action || '').trim(); var result = detail.result || {}; if (action === 'create') { applyCreatedDocumentLocally(result, body.parentId || null, body.title || '新页面'); refreshLocalFolderAfterCommand(action, result, body); return; } if (action === 'rename' && body.documentId && body.title) { var newDocumentId = commandDocumentId(result, body.documentId); if (newDocumentId && newDocumentId !== body.documentId) { removeDocumentRowForMode('page', body.documentId); removeDocumentRowForMode('filetree', body.documentId); if (currentDocumentId() === body.documentId) { navigateToDocument(newDocumentId, body.workspaceId || currentWorkspaceId(), { treeView: activeSidebarTreeMode() }); } } else { updateTitleEverywhere(body.documentId, body.title); } refreshLocalFolderAfterCommand(action, result, body); document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'rename'); return; } if (action === 'move' && body.documentId) { if (applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null, sortOrder: body.sortOrder })) { document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'move'); } refreshLocalFolderAfterCommand(action, result, body); return; } if ((action === 'purge' || action === 'delete' || action === 'archive') && body.documentId) { if (applyRemoveDocumentDelta({ documentId: body.documentId })) { document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'remove'); } refreshLocalFolderAfterCommand(action, result, body); } }); window.addEventListener('tree:local-command-batch-complete', function(event) { var detail = event.detail || {}; flushFileTreeBatchRefresh(detail.batchId || detail.batch_id || ''); }); window.addEventListener('tree:snapshot', function(event) { var payload = event.detail && event.detail.payload ? event.detail.payload : event.detail; if (renderLiveSidebarSnapshot(payload)) { document.documentElement.setAttribute('data-mnote-tree-live-applied', 'snapshot'); return; } setTreeLiveApplyError('tree_snapshot_missing_projection_payload'); }); window.addEventListener('tree:delta', function(event) { var payload = event.detail && event.detail.payload ? event.detail.payload : event.detail; var data = payload && (payload.data || payload.delta || payload); var documentPatch = data && (data.document || data.node); if (data && data.op === 'upsert_document' && documentPatch) { updateTitleEverywhere(documentPatch.id || documentPatch.documentId, documentPatch.title || '无标题'); } if (data && data.op === 'move_document' && applyMoveDocumentDelta(data)) { document.documentElement.setAttribute('data-mnote-tree-live-applied', 'delta'); return; } if (data && data.op === 'remove_document' && applyRemoveDocumentDelta(data)) { document.documentElement.setAttribute('data-mnote-tree-live-applied', 'delta'); return; } var documents = data && (data.upsertDocuments || data.upsert_documents); if (Array.isArray(documents)) { documents.forEach(function(doc) { updateTitleEverywhere(doc.id || doc.documentId, doc.title || '无标题'); }); } if (renderLiveSidebarSnapshot(payload)) { document.documentElement.setAttribute('data-mnote-tree-live-applied', 'delta'); return; } document.documentElement.setAttribute('data-mnote-tree-live-applied', 'delta'); if (deltaNeedsProjectionRefresh(payload)) { setTreeLiveApplyError('tree_delta_missing_projection_payload'); } }); window.addEventListener('tree:resync', function(event) { var payload = event.detail && event.detail.payload ? event.detail.payload : event.detail; if (renderLiveSidebarSnapshot(payload)) { refreshEditorLocalAttachmentExistence(); document.documentElement.setAttribute('data-mnote-tree-live-applied', 'resync'); return; } document.documentElement.setAttribute('data-mnote-tree-live-applied', 'resync'); setTreeLiveApplyError('tree_resync_missing_projection_payload'); }); window.addEventListener('tree:local-folder-watch-batch', function(event) { var payload = event.detail && event.detail.payload ? event.detail.payload : event.detail; applyLocalFolderWatchBatch(payload); }); window.addEventListener('tree:error', function(event) { var payload = event.detail && event.detail.payload ? event.detail.payload : event.detail; var code = payload && (payload.code || payload.error || payload.message); document.documentElement.setAttribute('data-mnote-tree-live-error-schema', String(payload && payload.schema || '')); setTreeLiveApplyError(code || 'tree_live_error'); }); } return { applyCreatedDocumentLocally, applyMoveDocumentDelta, applyRemoveDocumentDelta, commandDocumentId, commandDocumentTitle, deltaNeedsProjectionRefresh, fileTreePageTitle, flushFileTreeBatchRefresh, installTreeLiveApplyEventListeners, isFileTreePageRow, applyLocalFolderWatchBatch, localCommandNeedsProjectionRefresh, normalizeFileTreePageRenameTitle, objectIdentityAttr, refreshLocalFolderSidebarSnapshot, refreshLocalFolderAfterCommand, removeDocumentRowForMode, renderPageProjection, renderSidebarSnapshot, revealFileTreeResource, restorePersistedFileTreeExpansionState, setTreeLiveApplyError, startLocalFolderSidebarWatch, toggleChildren, updateTitleEverywhere, validateFileTreeRename, }; };