fix: relink rust web tree editor runtime

This commit is contained in:
lix-2026
2026-04-29 14:36:24 +08:00
parent 6c3d20ca55
commit 33ddda9dfd
28 changed files with 1839 additions and 303 deletions
+93 -22
View File
@@ -3,27 +3,65 @@
use leptos::prelude::*;
const SIDEBAR_TREE_JS: &str = r##"
<script>
(function(){
var tree = document.getElementById('sidebar-tree-root');
if (!tree) return;
function closestAction(target, selector) {
return target && typeof target.closest === 'function' ? target.closest(selector) : null;
}
// 高亮当前页
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');
function resolveWorkspaceId(trigger) {
var direct = (trigger.getAttribute('data-workspace-id') || '').trim();
if (direct) return direct;
var root = trigger.closest('[data-workspace-id]');
return root ? (root.getAttribute('data-workspace-id') || '').trim() : '';
}
async function dispatchTreeCommand(trigger, body) {
trigger.setAttribute('data-pending', 'true');
trigger.setAttribute('disabled', 'disabled');
try {
var response = await fetch('/api/tree/commands', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || !payload.result) {
throw new Error((payload && payload.message) || 'tree_command_failed_' + response.status);
}
return payload.result;
} catch (error) {
trigger.removeAttribute('disabled');
trigger.setAttribute('data-pending', 'false');
trigger.setAttribute('data-error', error instanceof Error ? error.message : String(error));
throw error;
}
}
// 展开/折叠
tree.addEventListener('click', function(e) {
var btn = e.target.closest('[data-rust-action]');
async function createPage(trigger, parentId) {
var workspaceId = resolveWorkspaceId(trigger);
var effectiveParentId = (parentId || trigger.getAttribute('data-parent-id') || '').trim();
if (!workspaceId) return;
var result = await dispatchTreeCommand(trigger, {
action: 'create',
workspaceId: workspaceId,
parentId: effectiveParentId || null,
title: '新页面'
});
var nextWorkspaceId = result.workspaceId || workspaceId;
window.location.href = '/documents/' + encodeURIComponent(result.documentId) + '?workspaceId=' + encodeURIComponent(nextWorkspaceId);
}
document.addEventListener('click', function(e) {
var createTrigger = closestAction(e.target, '[data-mnote-action="create-page"]');
if (createTrigger) {
e.preventDefault();
void createPage(createTrigger, null);
return;
}
var tree = document.getElementById('sidebar-tree-root');
if (!tree || !tree.contains(e.target)) return;
var btn = closestAction(e.target, '[data-rust-action]');
if (!btn) return;
var nodeId = btn.getAttribute('data-node-id');
var action = btn.getAttribute('data-rust-action');
@@ -41,12 +79,40 @@ const SIDEBAR_TREE_JS: &str = r##"
}
e.preventDefault();
} else if (action === 'open') {
window.location.href = '/documents/' + encodeURIComponent(nodeId);
var workspaceId = resolveWorkspaceId(btn);
window.location.href = '/documents/' + encodeURIComponent(nodeId) + (workspaceId ? '?workspaceId=' + encodeURIComponent(workspaceId) : '');
e.preventDefault();
} else if (action === 'create') {
e.preventDefault();
void createPage(btn, nodeId);
} else if (action === 'rename') {
e.preventDefault();
var title = window.prompt('重命名页面');
if (title && title.trim()) {
void dispatchTreeCommand(btn, {
action: 'rename',
workspaceId: resolveWorkspaceId(btn),
documentId: nodeId,
title: title.trim()
}).then(function(){ window.location.reload(); });
}
}
});
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');
}
}
}
})();
</script>
"##;
/// MNOTE Wolai 风格页面布局
@@ -76,6 +142,9 @@ pub fn PageLayout(
/// workspace shell 侧栏 sections HTML(可选),由 projection 渲染
#[prop(optional)]
workspace_sidebar_html: Option<String>,
/// 顶栏当前页面标题(可选)
#[prop(optional)]
topbar_title: Option<String>,
) -> impl IntoView {
let sidebar_tree_html = sidebar_tree_html.unwrap_or_default();
@@ -83,6 +152,10 @@ pub fn PageLayout(
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "开发用户 的空间".to_string());
let topbar_title = topbar_title
.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())
@@ -92,14 +165,12 @@ pub fn PageLayout(
"documents": []
});
let projection = crate::workspace_shell::build_workspace_shell_projection(
&dataset,
"default",
None,
&ws_name,
&dataset, "default", None, &ws_name,
);
crate::workspace_shell::render_workspace_shell_sidebar_html(
&projection,
Some(sidebar_tree_html.as_str()),
None,
)
});
@@ -124,7 +195,7 @@ 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>{"个人"}</span></div>
<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-actions" aria-label="页面操作">
<span title="收藏">""</span>
<span title="演示">""</span>