Fix local filetree navigation state and office proxy

This commit is contained in:
lix-2026
2026-05-26 15:52:30 +08:00
parent f961c1e2a1
commit 14fec9ef6a
2 changed files with 93 additions and 0 deletions
@@ -21,6 +21,9 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
var fileTreeLazyChildrenCache = new Map(); var fileTreeLazyChildrenCache = new Map();
var fileTreeExpandedRelativePaths = new Set(); var fileTreeExpandedRelativePaths = new Set();
var fileTreeLazyCacheRootUri = ''; var fileTreeLazyCacheRootUri = '';
var fileTreeExpansionStorageRootUri = '';
var fileTreeExpansionRestoreTimer = 0;
var FILETREE_EXPANSION_STORAGE_KEY = 'mnote.localFileTree.expandedRelativePaths.v1';
function updateTitleEverywhere(documentId, title) { function updateTitleEverywhere(documentId, title) {
if (!documentId) return; if (!documentId) return;
@@ -616,19 +619,68 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
fileTreeLazyCacheRootUri = rootUri; fileTreeLazyCacheRootUri = rootUri;
fileTreeLazyChildrenCache.clear(); fileTreeLazyChildrenCache.clear();
fileTreeExpandedRelativePaths.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() { function rememberFileTreeExpansionState() {
var changed = false;
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) { document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) {
if (!(row instanceof HTMLElement)) return; if (!(row instanceof HTMLElement)) return;
var relativePath = localFileTreeRelativePathFromRow(row); var relativePath = localFileTreeRelativePathFromRow(row);
if (!relativePath) return; if (!relativePath) return;
if (row.getAttribute('aria-expanded') === 'true') { if (row.getAttribute('aria-expanded') === 'true') {
if (!fileTreeExpandedRelativePaths.has(relativePath)) changed = true;
fileTreeExpandedRelativePaths.add(relativePath); fileTreeExpandedRelativePaths.add(relativePath);
} else { } else {
if (fileTreeExpandedRelativePaths.has(relativePath)) changed = true;
fileTreeExpandedRelativePaths.delete(relativePath); fileTreeExpandedRelativePaths.delete(relativePath);
} }
}); });
if (changed) persistFileTreeExpansionState();
} }
function renderFileProjection(projection) { function renderFileProjection(projection) {
@@ -727,6 +779,8 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
if (currentSourceKind() !== 'local_folder') return; if (currentSourceKind() !== 'local_folder') return;
var rootUri = currentRootUri(); var rootUri = currentRootUri();
if (!rootUri) return; if (!rootUri) return;
ensureFileTreeLazyCacheScope();
scheduleRestorePersistedFileTreeExpansionState();
var revision = ''; var revision = '';
var refreshTimer = 0; var refreshTimer = 0;
var scheduleRefresh = function() { var scheduleRefresh = function() {
@@ -824,6 +878,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
if (!relativePath) return; if (!relativePath) return;
if (expanded) fileTreeExpandedRelativePaths.add(relativePath); if (expanded) fileTreeExpandedRelativePaths.add(relativePath);
else fileTreeExpandedRelativePaths.delete(relativePath); else fileTreeExpandedRelativePaths.delete(relativePath);
persistFileTreeExpansionState();
} }
function renderCachedFileTreeChildren(row, button, relativePath) { function renderCachedFileTreeChildren(row, button, relativePath) {
@@ -896,6 +951,29 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
setTreeRowExpanded(row, button, !collapsed); 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() { function installTreeLiveApplyEventListeners() {
window.addEventListener('tree:title-updated', function(event) { window.addEventListener('tree:title-updated', function(event) {
@@ -1021,6 +1099,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
refreshLocalFolderSidebarSnapshot, refreshLocalFolderSidebarSnapshot,
removeDocumentRowForMode, removeDocumentRowForMode,
renderSidebarSnapshot, renderSidebarSnapshot,
restorePersistedFileTreeExpansionState,
setTreeLiveApplyError, setTreeLiveApplyError,
startLocalFolderSidebarWatch, startLocalFolderSidebarWatch,
toggleChildren, toggleChildren,
@@ -474,7 +474,16 @@ pub async fn page(Query(query): Query<OnlyOfficePageQuery>) -> Result<Response,
url.host = origin.host; url.host = origin.host;
return url.toString(); return url.toString();
}} }}
const isLocalFolderFileOpen = url.pathname === "/api/local-folder/files/open";
const isLocal = ["127.0.0.1", "localhost", "host.docker.internal"].includes(url.hostname); const isLocal = ["127.0.0.1", "localhost", "host.docker.internal"].includes(url.hostname);
if (proxyOrigin && isLocalFolderFileOpen) {{
const origin = new URL(proxyOrigin);
url.protocol = origin.protocol;
url.host = origin.host;
const proxy = new URL("/api/onlyoffice/proxy", proxyOrigin);
proxy.searchParams.set("u", base64UrlEncodeUtf8(url.toString()));
return proxy.toString();
}}
if (proxyOrigin && (isLocal || url.searchParams.has("token"))) {{ if (proxyOrigin && (isLocal || url.searchParams.has("token"))) {{
const proxy = new URL("/api/onlyoffice/proxy", proxyOrigin); const proxy = new URL("/api/onlyoffice/proxy", proxyOrigin);
proxy.searchParams.set("u", base64UrlEncodeUtf8(url.toString())); proxy.searchParams.set("u", base64UrlEncodeUtf8(url.toString()));
@@ -1827,6 +1836,11 @@ mod tests {
); );
assert!(html.contains("documentUrlBase,")); assert!(html.contains("documentUrlBase,"));
assert!(html.contains("resolvedFileUrl: fileState.resolvedFileUrl")); assert!(html.contains("resolvedFileUrl: fileState.resolvedFileUrl"));
assert!(html.contains(
r#"const isLocalFolderFileOpen = url.pathname === "/api/local-folder/files/open";"#
));
assert!(html.contains("if (proxyOrigin && isLocalFolderFileOpen)"));
assert!(html.contains("if (proxyOrigin && (isLocal || url.searchParams.has(\"token\")))"));
} }
fn test_state(legacy_next_base_url: Option<String>) -> AppState { fn test_state(legacy_next_base_url: Option<String>) -> AppState {