diff --git a/rust/crates/mnote-web/browser/sidebar-tree-live-apply-runtime.js b/rust/crates/mnote-web/browser/sidebar-tree-live-apply-runtime.js index e3378868..393837a7 100644 --- a/rust/crates/mnote-web/browser/sidebar-tree-live-apply-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-tree-live-apply-runtime.js @@ -21,6 +21,9 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => { var fileTreeLazyChildrenCache = new Map(); var fileTreeExpandedRelativePaths = new Set(); var fileTreeLazyCacheRootUri = ''; + var fileTreeExpansionStorageRootUri = ''; + var fileTreeExpansionRestoreTimer = 0; + var FILETREE_EXPANSION_STORAGE_KEY = 'mnote.localFileTree.expandedRelativePaths.v1'; function updateTitleEverywhere(documentId, title) { if (!documentId) return; @@ -616,19 +619,68 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => { fileTreeLazyCacheRootUri = rootUri; fileTreeLazyChildrenCache.clear(); fileTreeExpandedRelativePaths.clear(); + 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 buckets = storedFileTreeExpansionBuckets(); + var paths = Array.isArray(buckets[normalizedRootUri]) ? buckets[normalizedRootUri] : []; + paths.forEach(function(path) { + var normalized = String(path || '').trim(); + if (normalized) fileTreeExpandedRelativePaths.add(normalized); + }); + } + + function persistFileTreeExpansionState() { + var rootUri = String(currentRootUri() || fileTreeLazyCacheRootUri || '').trim(); + if (!rootUri) return; + try { + if (!window.sessionStorage) return; + var buckets = storedFileTreeExpansionBuckets(); + buckets[rootUri] = Array.from(fileTreeExpandedRelativePaths) + .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 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 renderFileProjection(projection) { @@ -727,6 +779,8 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => { if (currentSourceKind() !== 'local_folder') return; var rootUri = currentRootUri(); if (!rootUri) return; + ensureFileTreeLazyCacheScope(); + scheduleRestorePersistedFileTreeExpansionState(); var revision = ''; var refreshTimer = 0; var scheduleRefresh = function() { @@ -824,6 +878,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => { if (!relativePath) return; if (expanded) fileTreeExpandedRelativePaths.add(relativePath); else fileTreeExpandedRelativePaths.delete(relativePath); + persistFileTreeExpansionState(); } function renderCachedFileTreeChildren(row, button, relativePath) { @@ -896,6 +951,29 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => { setTreeRowExpanded(row, button, !collapsed); } + function restorePersistedFileTreeExpansionState() { + if (currentSourceKind() !== 'local_folder') return false; + ensureFileTreeLazyCacheScope(); + if (!fileTreeExpandedRelativePaths.size) return false; + var restored = 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; + var button = row.querySelector('[data-rust-action="toggle"]'); + if (row.getAttribute('data-filetree-children-loaded') === 'true') { + setTreeRowExpanded(row, button, true); + restored = true; + return; + } + void loadFileTreeChildren(row, button); + restored = true; + }); + if (restored) document.documentElement.setAttribute('data-mnote-filetree-expansion-restored', 'true'); + return restored; + } + function installTreeLiveApplyEventListeners() { window.addEventListener('tree:title-updated', function(event) { @@ -1021,6 +1099,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => { refreshLocalFolderSidebarSnapshot, removeDocumentRowForMode, renderSidebarSnapshot, + restorePersistedFileTreeExpansionState, setTreeLiveApplyError, startLocalFolderSidebarWatch, toggleChildren, diff --git a/rust/crates/mnote-web/src/routes/onlyoffice.rs b/rust/crates/mnote-web/src/routes/onlyoffice.rs index 7e6faa0d..cb059e58 100644 --- a/rust/crates/mnote-web/src/routes/onlyoffice.rs +++ b/rust/crates/mnote-web/src/routes/onlyoffice.rs @@ -474,7 +474,16 @@ pub async fn page(Query(query): Query) -> Result) -> AppState {