Files
mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs
T

149 lines
6.1 KiB
Rust

//! MNOTE 通用页面布局组件(Wolai / Notion 风格)
use leptos::prelude::*;
const SIDEBAR_TREE_JS: &str = r##"
<script>
(function(){
var tree = document.getElementById('sidebar-tree-root');
if (!tree) return;
// 高亮当前页
var currentPath = window.location.pathname;
var match = currentPath.match(/^\/documents\/([^\/]+)/);
if (match) {
var activeId = match[1];
var links = tree.querySelectorAll('[data-rust-action="open"]');
for (var i = 0; i < links.length; i++) {
if (links[i].getAttribute('data-node-id') === activeId) {
links[i].closest('.tree-row').setAttribute('data-active', 'true');
}
}
}
// 展开/折叠
tree.addEventListener('click', function(e) {
var btn = e.target.closest('[data-rust-action]');
if (!btn) return;
var nodeId = btn.getAttribute('data-node-id');
var action = btn.getAttribute('data-rust-action');
if (action === 'toggle') {
var row = btn.closest('.tree-row');
if (!row) return;
var li = row.parentElement;
var children = li.querySelector(':scope > .tree-children');
if (children) {
children.classList.toggle('tree-children--collapsed');
var isCollapsed = children.classList.contains('tree-children--collapsed');
row.setAttribute('aria-expanded', isCollapsed ? 'false' : 'true');
btn.textContent = isCollapsed ? '▸' : '▾';
}
e.preventDefault();
} else if (action === 'open') {
window.location.href = '/documents/' + encodeURIComponent(nodeId);
e.preventDefault();
}
});
})();
</script>
"##;
/// MNOTE Wolai 风格页面布局
///
/// 包含左侧栏 + 内容区的双栏布局。
/// 侧栏显示品牌、导航链接和可选的页面树。
///
/// # 用法
///
/// ```ignore
/// view! {
/// <PageLayout current_nav="home" sidebar_tree_html={None}>
/// <section>...</section>
/// </PageLayout>
/// }
/// ```
#[component]
pub fn PageLayout(
children: Children,
current_nav: &'static str,
/// 侧栏页面树 HTML(可选),由路由 handler 渲染
#[prop(optional)]
sidebar_tree_html: Option<String>,
/// 工作区名称(可选),显示在侧栏顶部
#[prop(optional)]
workspace_name: Option<String>,
/// workspace shell 侧栏 sections HTML(可选),由 projection 渲染
#[prop(optional)]
workspace_sidebar_html: Option<String>,
) -> impl IntoView {
let sidebar_tree_html = sidebar_tree_html.unwrap_or_default();
let ws_name = workspace_name
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "开发用户 的空间".to_string());
let sidebar_sections_html = workspace_sidebar_html
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| {
let dataset = serde_json::json!({
"workspaces": [{ "id": "default", "name": ws_name.clone() }],
"documents": []
});
let projection = crate::workspace_shell::build_workspace_shell_projection(
&dataset,
"default",
None,
&ws_name,
);
crate::workspace_shell::render_workspace_shell_sidebar_html(
&projection,
Some(sidebar_tree_html.as_str()),
)
});
view! {
<div class="mnote-shell wolai-workspace-shell" data-mnote-web-owner="mnote-web" data-mnote-shell="workspace">
<aside class="mnote-sidebar wolai-sidebar" data-testid="wolai-sidebar">
<div class="mnote-sidebar-header wolai-sidebar-header" data-testid="wolai-workspace-identity">
<a href="/" class="mnote-sidebar-brand wolai-avatar" aria-label="工作区首页">"L"</a>
<span class="sidebar-workspace-name">{ws_name}</span>
<span class="wolai-sidebar-chevron" aria-hidden="true">"⌄"</span>
</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="/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="/more" title="更多"><span class="nav-icon">"…"</span></a>
</nav>
<div class="wolai-sidebar-body" inner_html={sidebar_sections_html}></div>
<script inner_html={SIDEBAR_TREE_JS.to_string()}></script>
</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>{"个人"}</span></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>
</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>
</div>
</div>
</div>
}
}