fix: align rust web stitch sidebar tabs

This commit is contained in:
lix-2026
2026-04-29 16:23:49 +08:00
parent 33ddda9dfd
commit 6ddc95412c
9 changed files with 874 additions and 24 deletions
@@ -37,11 +37,21 @@ pub fn DocumentPage(
.map(str::trim)
.filter(|value| !value.is_empty() && *value != "null")
.is_some();
let workspace_label = workspace_name
.clone()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "个人空间".to_string());
view! {
<PageLayout current_nav="documents" sidebar_tree_html={sidebar_tree_html.unwrap_or_default()} workspace_name={workspace_name.unwrap_or_default()} workspace_sidebar_html={workspace_sidebar_html.unwrap_or_default()} topbar_title={title.clone()}>
<main class="document-shell" data-editor-host="leptos_tiptap_island" data-document-id={document_id.clone()}>
<header class="document-shell-header">
<div class="document-page-icon" aria-hidden="true">""</div>
<h1>{title}</h1>
<div class="document-shell-meta" aria-label="页面元信息">
<span><span aria-hidden="true">""</span>{workspace_label}</span>
<span><span aria-hidden="true">""</span>"已同步"</span>
</div>
</header>
<section data-page-aggregate-snapshot="mnote.page_aggregate.v1"></section>
<section
+6 -1
View File
@@ -43,9 +43,14 @@ pub fn HomePage(
<PageLayout current_nav="home" sidebar_tree_html={sidebar_tree_html.unwrap_or_default()} workspace_name={workspace_name.unwrap_or_default()} workspace_sidebar_html={workspace_sidebar_html.unwrap_or_default()} topbar_title={active_page_title.clone()}>
{move || if has_active_page {
view! {
<main class="document-shell" data-root-active-page-id={active_page_id.clone()} data-editor-host="leptos_tiptap_island">
<main class="document-shell document-shell--workspace-entry" data-root-active-page-id={active_page_id.clone()} data-editor-host="leptos_tiptap_island">
<header class="document-shell-header">
<div class="document-page-icon" aria-hidden="true">""</div>
<h1>{active_page_title.clone()}</h1>
<div class="document-shell-meta" aria-label="页面元信息">
<span><span aria-hidden="true">""</span>"个人空间"</span>
<span><span aria-hidden="true">""</span>"工作区首页"</span>
</div>
</header>
<section class="mnote-workspace-active-state" data-testid="mnote-workspace-active-page-state">
<a class="mnote-workspace-active-link" href={active_href.clone()}>"打开当前页面"</a>
+44 -13
View File
@@ -51,7 +51,32 @@ const SIDEBAR_TREE_JS: &str = r##"
window.location.href = '/documents/' + encodeURIComponent(result.documentId) + '?workspaceId=' + encodeURIComponent(nextWorkspaceId);
}
function switchSidebarTreeTab(trigger) {
var mode = (trigger.getAttribute('data-mnote-sidebar-tree-tab') || 'page').trim();
var shell = trigger.closest('[data-testid="wolai-sidebar-page-tree-shell"]');
if (!shell) return;
var tabs = shell.querySelectorAll('[data-mnote-sidebar-tree-tab]');
for (var i = 0; i < tabs.length; i++) {
var isActive = tabs[i].getAttribute('data-mnote-sidebar-tree-tab') === mode;
tabs[i].setAttribute('aria-selected', isActive ? 'true' : 'false');
tabs[i].classList.toggle('wolai-sidebar-tab-active', isActive);
tabs[i].classList.toggle('wolai-sidebar-tab-muted', !isActive);
}
var panels = shell.querySelectorAll('[data-mnote-sidebar-tree-panel]');
for (var j = 0; j < panels.length; j++) {
var isCurrentPanel = panels[j].getAttribute('data-mnote-sidebar-tree-panel') === mode;
panels[j].hidden = !isCurrentPanel;
}
}
document.addEventListener('click', function(e) {
var tabTrigger = closestAction(e.target, '[data-mnote-sidebar-tree-tab]');
if (tabTrigger) {
e.preventDefault();
switchSidebarTreeTab(tabTrigger);
return;
}
var createTrigger = closestAction(e.target, '[data-mnote-action="create-page"]');
if (createTrigger) {
e.preventDefault();
@@ -184,10 +209,10 @@ pub fn PageLayout(
</div>
<nav class="mnote-sidebar-nav wolai-quick-actions" aria-label="快捷操作">
<a href="/search" class:active={current_nav == "search"} title="搜索"><span class="nav-icon">""</span></a>
<a href="/graph" title="关系图"><span class="nav-icon">""</span></a>
<a href="/graph" title="关系图"><span class="nav-icon">""</span></a>
<a href="/actions" title="快捷动作"><span class="nav-icon">""</span></a>
<a href="/inbox" title="收纳"><span class="nav-icon">""</span></a>
<a href="/files" title="文件"><span class="nav-icon">""</span></a>
<a href="/help" title="帮助"><span class="nav-icon">"?"</span></a>
<a href="/files" title="文件"><span class="nav-icon">""</span></a>
<a href="/more" title="更多"><span class="nav-icon">""</span></a>
</nav>
<div class="wolai-sidebar-body" inner_html={sidebar_sections_html}></div>
@@ -195,23 +220,29 @@ pub fn PageLayout(
</aside>
<div class="mnote-main">
<header class="wolai-topbar" data-testid="wolai-topbar">
<div class="wolai-topbar-left"><span class="wolai-menu-icon">""</span><span class="wolai-home-icon">""</span><span>{topbar_title}</span></div>
<div class="wolai-topbar-left">
<button type="button" class="wolai-icon-button wolai-menu-button" aria-label="切换侧栏">""</button>
<nav class="wolai-breadcrumb" aria-label="页面路径">
<span class="wolai-breadcrumb-root">"The Digital Atelier"</span>
<span class="wolai-breadcrumb-separator" aria-hidden="true">"/"</span>
<span class="wolai-breadcrumb-current"><span class="wolai-home-icon" aria-hidden="true">""</span>{topbar_title}</span>
</nav>
</div>
<div class="wolai-topbar-actions" aria-label="页面操作">
<span title="收藏">""</span>
<span title="演示">""</span>
<span title="评论">""</span>
<span title="关系">""</span>
<span title="成员">"♙+"</span>
<span title="历史">""</span>
<span title="更多">""</span>
<span class="wolai-public-pill"><span aria-hidden="true">""</span>"Public"</span>
<button type="button" class="wolai-icon-button" title="收藏" aria-label="收藏">""</button>
<button type="button" class="wolai-icon-button" title="历史" aria-label="历史">""</button>
<button type="button" class="wolai-icon-button wolai-ai-inline" title="AI" aria-label="AI">""</button>
<button type="button" class="wolai-icon-button" title="搜索" aria-label="搜索">""</button>
<button type="button" class="wolai-icon-button" title="更多" aria-label="更多">""</button>
</div>
</header>
<article class="mnote-content">
{children()}
</article>
<div class="wolai-floating-actions" aria-label="浮动操作">
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button">"AI"</button>
<button type="button" data-testid="wolai-floating-help" class="wolai-floating-button">"?"</button>
<button type="button" data-testid="wolai-floating-help" class="wolai-floating-button wolai-floating-button--help" aria-label="帮助">"?"</button>
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button wolai-floating-button--ai" aria-label="AI 助手">""</button>
</div>
</div>
</div>
+700 -1
View File
@@ -865,6 +865,705 @@ a:hover {
padding-bottom: 16px;
}
}
/* ===== Stitch 260429 UI parity overrides ===== */
:root {
--atelier-surface: #FAF9F9;
--atelier-sidebar: #F4F3F3;
--atelier-sidebar-hover: #ECEBE9;
--atelier-sidebar-active: #E3E2E2;
--atelier-document: #FFFFFF;
--atelier-text: #1B1C1C;
--atelier-text-muted: #8A8782;
--atelier-text-soft: #B7B4AF;
--atelier-outline: #EAEAEA;
--atelier-primary: #006E28;
--atelier-primary-container: #31AA4D;
--atelier-secondary: #2367F6;
--atelier-shadow: 0 16px 32px rgba(27, 28, 28, 0.04);
}
html,
body {
background: var(--atelier-document);
}
body {
color: var(--atelier-text);
font-family: Inter, var(--wolai-font-sans);
letter-spacing: 0;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.mnote-shell,
.wolai-workspace-shell {
background: var(--atelier-document);
}
.mnote-sidebar,
.wolai-sidebar {
width: 240px;
background: var(--atelier-sidebar);
border-right: 0;
box-shadow: inset -1px 0 0 rgba(27, 28, 28, 0.06);
overflow-x: hidden;
}
.mnote-sidebar-header,
.wolai-sidebar-header {
height: 68px;
padding: 16px 12px 8px;
gap: 10px;
}
.wolai-avatar {
width: 32px;
height: 32px;
border-radius: 5px;
background: #D6534D;
font-size: 17px;
font-weight: 650;
}
.wolai-sidebar-header .sidebar-workspace-name,
.sidebar-workspace-name {
max-width: 160px;
color: var(--atelier-text);
font-size: 17px;
font-weight: 700;
line-height: 1.1;
}
.wolai-sidebar-chevron {
color: #55514C;
font-size: 17px;
}
.wolai-quick-actions {
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 10px;
padding: 8px 16px 26px;
}
.wolai-quick-actions a,
.mnote-sidebar-nav a {
border-radius: 4px;
}
.wolai-quick-actions a {
height: 28px;
color: #3F3D39;
font-size: 19px;
}
.wolai-quick-actions a:hover {
background: rgba(27, 28, 28, 0.07);
}
.wolai-sidebar-body {
padding-bottom: 0;
}
.wolai-sidebar-section {
padding: 0 8px 18px;
}
.wolai-section-title,
.wolai-sidebar-tabs {
height: 30px;
display: flex;
align-items: center;
gap: 6px;
padding: 0 8px;
color: var(--atelier-text-muted);
font-size: 14px;
font-weight: 500;
}
.wolai-section-icon {
color: #FF9F1A;
}
.wolai-section-caret {
color: var(--atelier-text-soft);
font-size: 13px;
}
.wolai-sidebar-tabs {
gap: 16px;
margin-bottom: 6px;
}
.wolai-sidebar-tab {
display: inline-flex;
align-items: center;
gap: 5px;
min-height: 26px;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
color: var(--atelier-text-muted);
font: inherit;
line-height: 1;
white-space: nowrap;
cursor: pointer;
}
.wolai-sidebar-tab:hover {
color: var(--atelier-text);
}
.wolai-sidebar-tab-active {
color: var(--atelier-text);
border-bottom: 2px solid var(--atelier-primary-container);
}
.wolai-sidebar-tab-muted {
color: #9B9892;
}
.wolai-sidebar-tab-panels {
min-height: 0;
}
.wolai-sidebar-tab-panel[hidden] {
display: none !important;
}
.wolai-folder-icon {
color: #A8A49E;
font-size: 15px;
}
.wolai-section-add {
margin-left: auto;
width: 22px;
height: 22px;
border-radius: 4px;
color: var(--atelier-text-soft);
font-size: 20px;
}
.wolai-section-add:hover {
background: var(--atelier-sidebar-hover);
color: var(--atelier-text);
}
.wolai-page-row {
min-height: 30px;
gap: 7px;
margin: 1px 4px;
padding: 0 8px 0 12px;
border-radius: 4px;
color: #2D2E2E;
font-size: 14px;
line-height: 1.2;
}
.wolai-page-row:hover {
background: var(--atelier-sidebar-hover);
}
.wolai-page-row[data-active="true"],
.wolai-active-row {
background: var(--atelier-sidebar-active);
color: var(--atelier-text);
}
.wolai-row-caret {
width: 12px;
flex: 0 0 12px;
color: #B8B5AF;
font-size: 16px;
}
.wolai-row-icon {
width: 20px;
color: #111111;
font-size: 16px;
}
.wolai-row-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sidebar-tree-section {
border-top: 0;
margin-top: 0;
padding-top: 0;
}
.sidebar-tree {
padding: 0;
}
.sidebar-tree .tree-empty,
.wolai-sidebar-empty {
padding: 6px 12px;
color: var(--atelier-text-soft);
font-size: 13px;
}
.sidebar-tree .tree-row {
height: 29px;
gap: 5px;
margin: 1px 4px;
padding: 0 8px;
border-radius: 4px;
color: #2D2E2E;
font-size: 14px;
}
.sidebar-tree .tree-row:hover {
background: var(--atelier-sidebar-hover);
}
.sidebar-tree .tree-row[data-active="true"],
.sidebar-tree .tree-row[data-selected="true"] {
background: var(--atelier-sidebar-active);
color: var(--atelier-text);
}
.sidebar-tree .tree-toggle,
.sidebar-tree .tree-spacer {
width: 16px;
height: 22px;
color: #B8B5AF;
font-size: 13px;
}
.sidebar-tree .tree-toggle:hover {
background: rgba(27, 28, 28, 0.06);
}
.sidebar-tree .tree-kind-badge {
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 20px;
color: #111111;
font-size: 15px;
}
.sidebar-tree .tree-kind-badge::before {
content: "□";
}
.sidebar-tree .tree-kind-badge[data-kind="page"]::before {
content: "⌂";
}
.sidebar-tree[data-tree-shell-mode="filetree"] .tree-kind-badge[data-kind="page"]::before,
.sidebar-tree .tree-kind-badge[data-kind="document"]::before {
content: "◇";
}
.sidebar-tree .tree-kind-badge[data-kind="index"]::before {
content: "▤";
color: var(--atelier-secondary);
}
.sidebar-tree .tree-kind-badge[data-kind="folder"]::before {
content: "▱";
color: #D08A1F;
}
.sidebar-tree .tree-link {
padding: 0 2px;
color: inherit;
font-size: 14px;
}
.sidebar-tree .tree-link-title {
font-weight: 400;
}
.sidebar-tree .tree-row[data-active="true"] .tree-link-title {
font-weight: 500;
}
.sidebar-tree .tree-actions {
gap: 1px;
}
.sidebar-tree .tree-action {
width: 20px;
height: 20px;
color: #9A968F;
border-radius: 4px;
}
.sidebar-tree .tree-action:hover {
background: rgba(27, 28, 28, 0.07);
color: var(--atelier-text);
}
.sidebar-tree .tree-node .tree-children .tree-row {
padding-left: 20px;
}
.sidebar-tree .tree-node .tree-children .tree-children .tree-row {
padding-left: 32px;
}
.sidebar-tree .tree-node .tree-children .tree-children .tree-children .tree-row {
padding-left: 44px;
}
.sidebar-tree .tree-node .tree-children .tree-children .tree-children .tree-children .tree-row {
padding-left: 56px;
}
.wolai-file-tree-section {
padding-top: 2px;
}
.wolai-explorer-title {
margin-bottom: 4px;
}
.wolai-sidebar-footer {
border-top: 1px solid rgba(27, 28, 28, 0.08);
background: var(--atelier-sidebar);
}
.wolai-footer-entry {
height: 44px;
gap: 8px;
color: #55514C;
font-size: 14px;
}
.wolai-footer-entry + .wolai-footer-entry {
border-left: 1px solid rgba(27, 28, 28, 0.08);
}
.mnote-main,
.mnote-content {
background: var(--atelier-document);
}
.wolai-topbar {
height: 44px;
padding: 0 20px 0 22px;
background: rgba(255, 255, 255, 0.92);
color: #5A5A5A;
font-size: 14px;
line-height: 1;
backdrop-filter: blur(16px);
}
.wolai-topbar-left,
.wolai-topbar-actions {
gap: 10px;
}
.wolai-breadcrumb {
display: flex;
align-items: center;
gap: 9px;
min-width: 0;
color: #6D6A65;
}
.wolai-breadcrumb-root {
white-space: nowrap;
}
.wolai-breadcrumb-separator {
color: #D0CDC8;
}
.wolai-breadcrumb-current {
display: inline-flex;
align-items: center;
gap: 5px;
min-width: 0;
color: var(--atelier-text);
font-weight: 500;
}
.wolai-home-icon {
color: #111111;
font-size: 16px;
}
.wolai-icon-button {
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
border-radius: 4px;
background: transparent;
color: #5A5A5A;
font: inherit;
font-size: 18px;
line-height: 1;
cursor: pointer;
}
.wolai-icon-button:hover {
background: #F4F3F3;
color: var(--atelier-text);
}
.wolai-menu-button {
color: #3D3B37;
font-size: 21px;
}
.wolai-public-pill {
height: 26px;
display: inline-flex;
align-items: center;
gap: 5px;
padding: 0 11px;
border: 1px solid #D6F2DC;
border-radius: 999px;
background: #ECFCEF;
color: #087B31;
font-size: 12px;
font-weight: 600;
}
.wolai-public-pill span {
color: var(--atelier-primary-container);
font-size: 12px;
}
.wolai-ai-inline {
color: var(--atelier-primary-container);
}
.document-shell {
max-width: 880px;
min-height: calc(100vh - 44px);
padding: 78px 48px 160px;
margin: 0 auto;
}
.document-shell-header {
max-width: none;
padding: 0;
margin: 0 0 28px;
}
.document-page-icon {
height: 82px;
display: flex;
align-items: center;
color: #111111;
font-size: 72px;
line-height: 1;
margin-bottom: 12px;
}
.document-shell-header h1 {
padding: 0;
margin: 0 0 14px;
color: var(--atelier-text);
font-size: 42px;
font-weight: 700;
line-height: 1.12;
letter-spacing: 0;
text-align: left;
}
.document-shell-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 18px;
padding-bottom: 28px;
border-bottom: 1px solid #F0EFED;
color: #A19D97;
font-size: 14px;
line-height: 1.4;
}
.document-shell-meta span {
display: inline-flex;
align-items: center;
gap: 6px;
}
#mnote-editor-island {
width: 100%;
max-width: none;
min-height: 360px;
margin: 0;
padding: 0 0 48px;
}
#mnote-leptos-tiptap-island-editor-root {
min-height: 320px;
}
#mnote-leptos-tiptap-island-editor-root .editor-surface {
border: 0 !important;
box-shadow: none !important;
background: transparent !important;
padding: 0 !important;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror,
.ProseMirror {
color: var(--atelier-text);
font-size: 16px;
line-height: 1.65;
outline: none;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror {
width: 100% !important;
min-height: 280px;
margin: 0 !important;
padding: 0 !important;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror p {
margin: 0 0 24px;
}
#mnote-leptos-tiptap-island-editor-root .ProseMirror h1,
#mnote-leptos-tiptap-island-editor-root .ProseMirror h2,
#mnote-leptos-tiptap-island-editor-root .ProseMirror h3 {
line-height: 1.2;
letter-spacing: 0;
}
.mnote-workspace-active-state {
padding-top: 10px;
}
.mnote-workspace-active-link {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 30px;
color: var(--atelier-text);
font-size: 15px;
text-decoration: underline;
text-decoration-color: #D7D4CE;
text-underline-offset: 5px;
}
.mnote-workspace-active-link::before {
content: "▤";
color: #A19D97;
text-decoration: none;
}
.wolai-floating-actions {
right: 24px;
bottom: 24px;
gap: 10px;
z-index: 50;
}
.wolai-floating-button {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
cursor: pointer;
}
.wolai-floating-button--help {
width: 40px;
height: 40px;
border: 1px solid rgba(27, 28, 28, 0.1);
background: rgba(255, 255, 255, 0.86);
color: #5A5A5A;
box-shadow: 0 10px 24px rgba(27, 28, 28, 0.08);
backdrop-filter: blur(16px);
font-size: 18px;
}
.wolai-floating-button--ai {
width: 52px;
height: 52px;
border: 0;
background: linear-gradient(135deg, var(--atelier-primary), var(--atelier-primary-container));
color: #FFFFFF;
box-shadow: 0 16px 32px rgba(0, 110, 40, 0.22);
font-size: 23px;
}
@media (max-width: 768px) {
.mnote-sidebar,
.wolai-sidebar {
width: 220px;
}
.wolai-topbar {
padding: 0 12px;
}
.wolai-public-pill,
.wolai-breadcrumb-root,
.wolai-breadcrumb-separator {
display: none;
}
.document-shell {
padding: 48px 28px 120px;
}
.document-page-icon {
height: 60px;
font-size: 54px;
}
.document-shell-header h1 {
font-size: 32px;
}
}
@media (max-width: 480px) {
.mnote-shell {
flex-direction: column;
}
.mnote-sidebar,
.wolai-sidebar {
width: 100%;
max-height: 42vh;
border-bottom: 1px solid rgba(27, 28, 28, 0.08);
box-shadow: none;
}
.document-shell {
padding: 36px 20px 112px;
}
.document-shell-header h1 {
font-size: 28px;
}
.wolai-topbar-actions {
gap: 2px;
}
}
"##;
#[cfg(test)]
@@ -919,6 +1618,6 @@ mod tests {
// 至少 2000 字符才能包含完整样式
assert!(MNOTE_CSS.len() > 2000);
// 最多 20000 字符避免过于臃肿
assert!(MNOTE_CSS.len() < 20000);
assert!(MNOTE_CSS.len() < 32000);
}
}
+39 -8
View File
@@ -102,13 +102,13 @@ pub fn build_workspace_shell_projection(
id: "trash".into(),
label: "垃圾箱".into(),
href: "/trash".into(),
icon: "🗑".into(),
icon: "".into(),
},
WorkspaceShellEntry {
id: "templates".into(),
label: "模板中心".into(),
href: "/templates".into(),
icon: "📦".into(),
icon: "".into(),
},
],
}
@@ -323,6 +323,35 @@ mod tests {
assert!(html.contains("data-mnote-action=\"create-page\""));
}
#[test]
fn workspace_shell_sidebar_html_uses_single_tabbed_tree_host() {
let dataset = json!({
"workspaces": [{ "id": "ws_demo", "name": "开发用户 的工作区" }],
"documents": [
{ "id": "doc_root", "workspace_id": "ws_demo", "title": "Root", "parent_id": null, "sort_order": 0 }
]
});
let projection = build_workspace_shell_projection(
&dataset,
"ws_demo",
Some("doc_root"),
"开发用户 的工作区",
);
let html = render_workspace_shell_sidebar_html(
&projection,
Some(r#"<ul data-rust-page-renderer="initial_v1"></ul>"#),
Some(r#"<ul data-rust-filetree-renderer="initial_v1"></ul>"#),
);
assert!(html.contains(r#"data-mnote-sidebar-tree-tab="page""#));
assert!(html.contains(r#"data-mnote-sidebar-tree-tab="filetree""#));
assert!(html.contains(r#"data-mnote-sidebar-tree-panel="page""#));
assert!(html.contains(r#"data-mnote-sidebar-tree-panel="filetree" hidden"#));
assert!(html.contains(r#"id="sidebar-tree-root""#));
assert!(html.contains(r#"id="sidebar-file-tree-root""#));
assert!(!html.contains("wolai-file-tree-section"));
}
#[test]
fn workspace_shell_sidebar_html_outputs_empty_state() {
let dataset = json!({
@@ -368,16 +397,18 @@ pub fn render_workspace_shell_sidebar_html(
)
})
.unwrap_or_default();
let file_tree_panel = file_tree_html
let file_tree_content = file_tree_html
.map(str::trim)
.filter(|value| !value.is_empty())
.map(|html| {
format!(
r#"<section class="wolai-sidebar-section wolai-file-tree-section" aria-label="文件树" data-testid="wolai-sidebar-file-tree-section"><div class="wolai-section-title"><span>文件树</span><span class="wolai-section-caret">⌄</span></div><div class="sidebar-tree-section sidebar-file-tree-section"><div id="sidebar-file-tree-root" class="sidebar-tree" data-tree-shell-mode="filetree" data-workspace-id="{}">{html}</div></div></section>"#,
r#"<div class="sidebar-tree-section sidebar-file-tree-section"><div id="sidebar-file-tree-root" class="sidebar-tree" data-tree-shell-mode="filetree" data-workspace-id="{}">{html}</div></div>"#,
escape_html(&projection.workspace_id),
)
})
.unwrap_or_default();
.unwrap_or_else(|| {
r#"<div class="wolai-sidebar-empty" data-testid="wolai-sidebar-filetree-empty-state">暂无文件或页面</div>"#.to_string()
});
let empty_state = r#"<div class="wolai-sidebar-empty" data-testid="wolai-sidebar-empty-state">暂无页面</div>"#;
let my_pages = if !tree_html.is_empty() {
tree_html
@@ -401,7 +432,7 @@ pub fn render_workspace_shell_sidebar_html(
.join("");
format!(
r#"<section class="wolai-sidebar-section wolai-starred-section" aria-label="星标置顶"><div class="wolai-section-title"><span class="wolai-section-icon">★</span>星标置顶<span class="wolai-section-caret">⌄</span></div>{starred_rows}</section><section class="wolai-sidebar-section wolai-my-pages-section" aria-label="页面树" data-testid="wolai-sidebar-page-tree-shell"><div class="wolai-section-title"><span>我的页面</span><span class="wolai-section-caret">⌄</span><button type="button" class="wolai-section-add" data-testid="wolai-sidebar-create-page" data-mnote-action="create-page" data-workspace-id="{}" title="新建页面" aria-label="新建页面">+</button></div>{my_pages}</section>{file_tree_panel}<div class="wolai-sidebar-footer">{bottom_entries}</div>"#,
r#"<section class="wolai-sidebar-section wolai-starred-section" aria-label="星标置顶"><div class="wolai-section-title"><span class="wolai-section-icon">★</span>星标置顶<span class="wolai-section-caret">⌄</span></div>{starred_rows}</section><section class="wolai-sidebar-section wolai-my-pages-section" aria-label="页面树" data-testid="wolai-sidebar-page-tree-shell"><div class="wolai-sidebar-tabs" data-testid="wolai-sidebar-tree-tabs" role="tablist" aria-label="页面树和文件树"><button type="button" class="wolai-sidebar-tab wolai-sidebar-tab-active" data-mnote-sidebar-tree-tab="page" aria-selected="true" aria-controls="wolai-sidebar-page-tree-panel">我的页面</button><button type="button" class="wolai-sidebar-tab wolai-sidebar-tab-muted" data-mnote-sidebar-tree-tab="filetree" aria-selected="false" aria-controls="wolai-sidebar-file-tree-panel"><span class="wolai-folder-icon">▱</span>Explorer</button><span class="wolai-section-caret">⌄</span><button type="button" class="wolai-section-add" data-testid="wolai-sidebar-create-page" data-mnote-action="create-page" data-workspace-id="{}" title="新建页面" aria-label="新建页面">+</button></div><div class="wolai-sidebar-tab-panels"><div id="wolai-sidebar-page-tree-panel" class="wolai-sidebar-tab-panel" data-mnote-sidebar-tree-panel="page">{my_pages}</div><div id="wolai-sidebar-file-tree-panel" class="wolai-sidebar-tab-panel" data-mnote-sidebar-tree-panel="filetree" hidden>{file_tree_content}</div></div></section><div class="wolai-sidebar-footer">{bottom_entries}</div>"#,
escape_html(&projection.workspace_id),
)
}
@@ -411,7 +442,7 @@ fn render_item_row(item: &WorkspaceShellItem) -> String {
let depth_style = if item.depth == 0 {
String::new()
} else {
format!(r#" style="padding-left:{}px""#, 12 + item.depth.min(6) * 18)
format!(r#" style="padding-left:{}px""#, 12 + item.depth.min(6) * 12)
};
let parent_attr = item
.parent_id
@@ -424,7 +455,7 @@ fn render_item_row(item: &WorkspaceShellItem) -> String {
""
};
format!(
r#"<a class="wolai-page-row{active_class}" href="{}" data-testid="wolai-sidebar-row" data-node-id="{}"{parent_attr} data-depth="{}" data-active="{}"{aria_current}{depth_style}><span class="wolai-row-icon">{}</span><span class="wolai-row-title">{}</span></a>"#,
r#"<a class="wolai-page-row{active_class}" href="{}" data-testid="wolai-sidebar-row" data-node-id="{}"{parent_attr} data-depth="{}" data-active="{}"{aria_current}{depth_style}><span class="wolai-row-caret" aria-hidden="true"></span><span class="wolai-row-icon">{}</span><span class="wolai-row-title">{}</span></a>"#,
escape_html(&item.href),
escape_html(&item.id),
item.depth,