对齐 Wolai 侧栏体验并收拢设计入库

This commit is contained in:
lix-2026
2026-04-30 16:18:54 +08:00
parent 8c895b3dc0
commit afb2a5b8a0
89 changed files with 23188 additions and 84 deletions
@@ -45,6 +45,8 @@ pub fn build_page_tree_dom_rows(rows: &[PageTreeRenderRow]) -> Vec<PageTreeDomRo
.collect()
}
const PAGE_TREE_CHEVRON_SVG: &str = r#"<svg class="tree-toggle-icon" viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" focusable="false"><path d="M7.84 14.955c.206 0 .37-.07.505-.21l4.277-4.179a.79.79 0 0 0 .264-.574.78.78 0 0 0-.258-.574L8.35 5.24a.7.7 0 0 0-.51-.21.721.721 0 0 0-.498 1.247l3.814 3.721-3.814 3.709a.721.721 0 0 0 .498 1.248"></path></svg>"#;
fn escape_html(input: &str) -> String {
input
.replace('&', "&amp;")
@@ -54,6 +56,26 @@ fn escape_html(input: &str) -> String {
.replace('\'', "&#39;")
}
fn render_depth_for_node(rows: &[PageTreeRenderRow], node_id: &str, fallback_depth: u32) -> u32 {
if fallback_depth > 0 {
return fallback_depth;
}
let parent_by_id = rows
.iter()
.map(|row| (row.node_id.as_str(), row.parent_node_id.as_deref()))
.collect::<BTreeMap<_, _>>();
let mut depth = 0_u32;
let mut cursor = parent_by_id.get(node_id).and_then(|parent| *parent);
while let Some(parent_id) = cursor {
depth += 1;
cursor = parent_by_id.get(parent_id).and_then(|parent| *parent);
if depth > 32 {
break;
}
}
depth
}
fn render_page_row(
html: &mut String,
row: &PageTreeRenderRow,
@@ -80,11 +102,12 @@ fn render_page_row(
.unwrap_or(false);
let toggle_html = if row.expandable {
format!(
r#"<button type="button" class="tree-toggle" data-testid="tree-node-toggle" data-rust-action="toggle" data-node-id="{node_id}" aria-label="{label} {title}">{marker}</button>"#,
r#"<button type="button" class="tree-toggle" data-testid="tree-node-toggle" data-rust-action="toggle" data-node-id="{node_id}" aria-label="{label} {title}" aria-expanded="{expanded_state}">{marker}</button>"#,
node_id = escape_html(&row.node_id),
label = if expanded { "折叠" } else { "展开" },
title = escape_html(&row.title),
marker = if expanded { "" } else { "" },
expanded_state = if expanded { "true" } else { "false" },
marker = PAGE_TREE_CHEVRON_SVG,
)
} else {
r#"<span class="tree-spacer" aria-hidden="true"></span>"#.to_string()
@@ -96,15 +119,18 @@ fn render_page_row(
.and_then(|source| source.parent_node_id.as_deref())
.map(|parent_id| format!(r#" data-parent-id="{}""#, escape_html(parent_id)))
.unwrap_or_default();
let render_depth = render_depth_for_node(&input.rows, &row.node_id, row.depth);
html.push_str(&format!(
r#"<li class="tree-node" data-node-id="{node_id}"><div class="tree-row" role="treeitem" aria-level="{aria_level}" aria-expanded="{expanded_attr}" data-rust-rendered-row="page" data-testid="wolai-sidebar-row" data-tree-testid="{test_id}" data-node-id="{node_id}"{parent_attr} data-depth="{depth}" data-shell-mode="page" data-active="{active}" data-focused="{focused}" data-draggable="true" draggable="true" tabindex="{tab_index}">{toggle_html}<span class="tree-kind-badge" data-kind="page" aria-hidden="true"></span><button type="button" class="tree-link" data-testid="tree-node-open" data-rust-action="open" data-node-id="{node_id}"><span class="tree-link-title">{title}</span></button><div class="tree-actions"><button type="button" class="tree-action" data-testid="tree-action-create" data-rust-action="create" data-node-id="{node_id}" aria-label="新建子页面">+</button><button type="button" class="tree-action" data-testid="tree-action-rename" data-rust-action="rename" data-node-id="{node_id}" aria-label="重命名">✎</button><button type="button" class="tree-action" data-testid="tree-action-menu" data-rust-action="menu" data-node-id="{node_id}" aria-label="更多操作"></button></div></div>"#,
r#"<li class="tree-node" data-node-id="{node_id}"><div class="tree-row" role="treeitem" aria-level="{aria_level}" aria-expanded="{expanded_attr}" data-rust-rendered-row="page" data-testid="wolai-sidebar-row" data-tree-testid="{test_id}" data-node-id="{node_id}"{parent_attr} data-depth="{depth}" data-shell-mode="page" data-active="{active}" aria-selected="{selected}"{current_attr} data-focused="{focused}" data-draggable="true" draggable="true" tabindex="{tab_index}">{toggle_html}<button type="button" class="tree-link" data-testid="tree-node-open" data-rust-action="open" data-node-id="{node_id}"><span class="tree-link-title">{title}</span></button><div class="tree-actions"><button type="button" class="tree-action" data-testid="tree-action-menu" data-rust-action="menu" data-node-id="{node_id}" aria-label="更多操作"></button><button type="button" class="tree-action" data-testid="tree-action-create" data-rust-action="create" data-node-id="{node_id}" aria-label="新建子页面">+</button></div></div>"#,
node_id = escape_html(&row.node_id),
aria_level = row.depth + 1,
aria_level = render_depth + 1,
expanded_attr = if row.expandable && expanded { "true" } else { "false" },
test_id = row.test_id,
parent_attr = parent_attr,
depth = row.depth,
depth = render_depth,
active = active,
selected = active,
current_attr = if active { r#" aria-current="page""# } else { "" },
focused = focused,
tab_index = if focused { "0" } else { "-1" },
toggle_html = toggle_html,