Implement sidebar tree view state persistence

This commit is contained in:
lix-2026
2026-05-27 16:32:05 +08:00
parent bd7470d03e
commit e43efacbce
15 changed files with 1875 additions and 17 deletions
@@ -346,6 +346,29 @@ mod tests {
assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("fetch('/api/user/access-policy'"));
}
#[test]
fn sidebar_live_apply_runtime_uses_persisted_tree_view_state() {
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("SIDEBAR_TREE_VIEW_STATE_KEY"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("function loadSidebarTreeViewState"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("function persistSidebarTreeViewState"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("function applySidebarTreeViewState"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("/api/tree/view-state"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS
.contains("mnote.sidebarTreeViewState.v1:{userId}:{workspaceId}:{sourceKind}:{treeKind}:{rootUriHash}:{scopeHash}"));
let render_page_rows = js_function_body(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS, "renderPageRows");
assert!(render_page_rows.contains("sidebarTreeViewStateFor('pagetree'"));
assert!(render_page_rows.contains("expandedIds"));
assert!(
!render_page_rows.contains("var expanded = expandable && item.expandedByDefault !== false"),
"PageTree 不能只依赖 projection expandedByDefault 决定展开"
);
let render_file_rows = js_function_body(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS, "renderFileRows");
assert!(render_file_rows.contains("sidebarTreeViewStateFor('filetree'"));
assert!(render_file_rows.contains("expandedRelativePaths"));
}
#[test]
fn page_layout_hides_public_state_and_exposes_sidebar_shortcut_star() {
let html = crate::ssr::render_view(leptos::view! {
@@ -1163,6 +1186,35 @@ mod tests {
.contains("if (!isLatestPageTreeRequest(generation))"));
}
#[test]
fn sidebar_filetree_runtime_refreshes_scoped_page_tree_projection() {
assert!(
SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS
.contains("sidebarUrl.searchParams.set('parentRelativePath', fileTreeScope)"),
"scoped filetree 入口刷新 PageTree 时必须请求同 scope projection"
);
assert!(
SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("sidebarTreeViewStateFor('pagetree')"),
"SSR 初始 PageTree 也必须触发 pagetree 用户保存态加载"
);
}
#[test]
fn sidebar_page_create_does_not_full_refresh_local_folder_sidebar() {
assert!(
!SIDEBAR_PAGE_TREE_RUNTIME_JS.contains("await refreshLocalFolderSidebarSnapshot();"),
"本地新建页面不能全量刷新 sidebar,否则会折叠/重建其它已展开节点"
);
assert!(
SIDEBAR_PAGE_TREE_RUNTIME_JS.contains("await refreshLocalFolderAfterCommand('create'"),
"本地新建页面应等待局部 parent refresh,而不是全量刷新 sidebar"
);
assert!(
SIDEBAR_PAGE_TREE_RUNTIME_JS.contains("selectSidebarFileTreeDocument(nextDocumentId"),
"本地新建页面仍应只 reveal/select 新节点"
);
}
#[test]
fn sidebar_filetree_runtime_prefers_command_affected_parents() {
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS
+22 -1
View File
@@ -622,6 +622,7 @@ html[data-mnote-sidebar-resizing="true"] .mnote-sidebar-resizer::before {
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.mnote-sidebar-nav a,
@@ -1374,7 +1375,7 @@ body {
background: var(--atelier-sidebar);
border-right: 0;
box-shadow: none;
overflow-x: hidden;
overflow: hidden;
}
.mnote-sidebar-header,
@@ -1439,6 +1440,13 @@ body {
padding: 0 8px 18px;
}
.wolai-my-pages-section {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
}
.wolai-section-title,
.wolai-sidebar-tabs {
height: 32px;
@@ -1496,7 +1504,10 @@ body {
}
.wolai-sidebar-tab-panels {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
}
.wolai-sidebar-tab-panel[hidden] {
@@ -1996,6 +2007,7 @@ body {
}
.wolai-sidebar-footer {
flex: 0 0 auto;
border-top: 0;
background: var(--atelier-sidebar);
}
@@ -4470,6 +4482,15 @@ mod tests {
assert!(MNOTE_CSS.contains("::-webkit-scrollbar-thumb"));
}
#[test]
fn sidebar_scroll_is_contained_to_tree_panels() {
assert!(MNOTE_CSS.contains(".mnote-sidebar,\n.wolai-sidebar {\n width: var(--mnote-sidebar-width, 248px);\n background: var(--atelier-sidebar);\n border-right: 0;\n box-shadow: none;\n overflow: hidden;"));
assert!(MNOTE_CSS.contains(".wolai-sidebar-body {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;"));
assert!(MNOTE_CSS.contains(".wolai-my-pages-section {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;"));
assert!(MNOTE_CSS.contains(".wolai-sidebar-tab-panels {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;"));
assert!(MNOTE_CSS.contains(".wolai-sidebar-footer {\n flex: 0 0 auto;"));
}
#[test]
fn admin_policy_rows_wrap_long_grant_values() {
assert!(MNOTE_CSS.contains(