fix: relink rust web tree editor runtime
This commit is contained in:
@@ -20,6 +20,7 @@ pub struct WorkspaceShellItem {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub icon: Option<String>,
|
||||
pub parent_id: Option<String>,
|
||||
pub href: String,
|
||||
pub depth: u32,
|
||||
pub active: bool,
|
||||
@@ -71,9 +72,7 @@ pub fn build_workspace_shell_projection(
|
||||
})
|
||||
.or_else(|| my_page_items.first().map(|item| item.id.clone()));
|
||||
|
||||
for item in &mut my_page_items {
|
||||
item.active = active_page_id.as_deref().is_some_and(|active_id| active_id == item.id);
|
||||
}
|
||||
apply_active_page_to_items(&mut my_page_items, active_page_id.as_deref());
|
||||
|
||||
let mut starred_items = documents
|
||||
.iter()
|
||||
@@ -88,12 +87,7 @@ pub fn build_workspace_shell_projection(
|
||||
.collect::<Vec<_>>();
|
||||
starred_items.sort_by_key(|item| (item.depth, item.title.clone(), item.id.clone()));
|
||||
|
||||
let active_page_title = active_page_id.as_deref().and_then(|active_id| {
|
||||
my_page_items
|
||||
.iter()
|
||||
.find(|item| item.id == active_id)
|
||||
.map(|item| item.title.clone())
|
||||
});
|
||||
let active_page_title = active_title_from_items(&my_page_items, active_page_id.as_deref());
|
||||
|
||||
WorkspaceShellProjection {
|
||||
schema: "mnote.workspace_shell.v1".into(),
|
||||
@@ -176,16 +170,17 @@ fn document_to_item(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("无标题");
|
||||
let parent_id = document
|
||||
.get("parent_id")
|
||||
.or_else(|| document.get("parentId"))
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned);
|
||||
let depth = document
|
||||
.get("depth")
|
||||
.and_then(Value::as_u64)
|
||||
.or_else(|| {
|
||||
document
|
||||
.get("parent_id")
|
||||
.or_else(|| document.get("parentId"))
|
||||
.and_then(Value::as_str)
|
||||
.map(|_| 1)
|
||||
})
|
||||
.or_else(|| parent_id.as_ref().map(|_| 1))
|
||||
.unwrap_or(0) as u32;
|
||||
Some(WorkspaceShellItem {
|
||||
id: id.to_string(),
|
||||
@@ -196,12 +191,51 @@ fn document_to_item(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned),
|
||||
parent_id,
|
||||
href: format!("/documents/{id}?workspaceId={workspace_id}"),
|
||||
depth,
|
||||
active: active_page_id.is_some_and(|active_id| active_id == id),
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_active_page_to_items(items: &mut [WorkspaceShellItem], active_page_id: Option<&str>) {
|
||||
for item in items {
|
||||
item.active = active_page_id.is_some_and(|active_id| active_id == item.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn active_title_from_items(
|
||||
items: &[WorkspaceShellItem],
|
||||
active_page_id: Option<&str>,
|
||||
) -> Option<String> {
|
||||
active_page_id.and_then(|active_id| {
|
||||
items
|
||||
.iter()
|
||||
.find(|item| item.id == active_id)
|
||||
.map(|item| item.title.clone())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_active_page(projection: &mut WorkspaceShellProjection, active_page_id: Option<&str>) {
|
||||
let normalized = active_page_id
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned);
|
||||
projection.active_page_id = normalized;
|
||||
apply_active_page_to_items(
|
||||
&mut projection.my_page_items,
|
||||
projection.active_page_id.as_deref(),
|
||||
);
|
||||
apply_active_page_to_items(
|
||||
&mut projection.starred_items,
|
||||
projection.active_page_id.as_deref(),
|
||||
);
|
||||
projection.active_page_title = active_title_from_items(
|
||||
&projection.my_page_items,
|
||||
projection.active_page_id.as_deref(),
|
||||
);
|
||||
}
|
||||
|
||||
trait StringEmptyExt {
|
||||
fn if_empty_else(self, fallback: impl FnOnce() -> String) -> String;
|
||||
}
|
||||
@@ -248,15 +282,65 @@ mod tests {
|
||||
assert_eq!(projection.starred_items[0].title, "个人");
|
||||
assert_eq!(projection.my_page_items.len(), 2);
|
||||
assert!(projection.my_page_items[0].active);
|
||||
assert!(projection.bottom_entries.iter().any(|entry| entry.label == "垃圾箱"));
|
||||
assert!(projection.bottom_entries.iter().any(|entry| entry.label == "模板中心"));
|
||||
assert_eq!(
|
||||
projection.my_page_items[1].parent_id.as_deref(),
|
||||
Some("page_home")
|
||||
);
|
||||
assert!(projection
|
||||
.bottom_entries
|
||||
.iter()
|
||||
.any(|entry| entry.label == "垃圾箱"));
|
||||
assert!(projection
|
||||
.bottom_entries
|
||||
.iter()
|
||||
.any(|entry| entry.label == "模板中心"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_shell_sidebar_html_outputs_projection_rows_with_active_and_parent() {
|
||||
let dataset = json!({
|
||||
"workspaces": [{ "id": "ws_demo", "name": "开发用户 的工作区" }],
|
||||
"documents": [
|
||||
{ "id": "doc_root", "workspace_id": "ws_demo", "title": "Root", "parent_id": null, "sort_order": 0 },
|
||||
{ "id": "doc_child", "workspace_id": "ws_demo", "title": "Child", "parent_id": "doc_root", "sort_order": 1 }
|
||||
]
|
||||
});
|
||||
let projection = build_workspace_shell_projection(
|
||||
&dataset,
|
||||
"ws_demo",
|
||||
Some("doc_root"),
|
||||
"开发用户 的工作区",
|
||||
);
|
||||
let html = render_workspace_shell_sidebar_html(&projection, None, None);
|
||||
|
||||
assert!(html.contains("data-testid=\"wolai-sidebar-row\""));
|
||||
assert!(html.contains("data-node-id=\"doc_root\""));
|
||||
assert!(html.contains("data-node-id=\"doc_child\""));
|
||||
assert!(html.contains("data-parent-id=\"doc_root\""));
|
||||
assert!(html.contains("data-active=\"true\""));
|
||||
assert!(html.contains("/documents/doc_root?workspaceId=ws_demo"));
|
||||
assert!(html.contains("data-testid=\"wolai-sidebar-create-page\""));
|
||||
assert!(html.contains("data-mnote-action=\"create-page\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_shell_sidebar_html_outputs_empty_state() {
|
||||
let dataset = json!({
|
||||
"workspaces": [{ "id": "ws_demo", "name": "开发用户 的工作区" }],
|
||||
"documents": []
|
||||
});
|
||||
let projection =
|
||||
build_workspace_shell_projection(&dataset, "ws_demo", None, "开发用户 的工作区");
|
||||
let html = render_workspace_shell_sidebar_html(&projection, None, None);
|
||||
|
||||
assert!(html.contains("data-testid=\"wolai-sidebar-empty-state\""));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn render_workspace_shell_sidebar_html(
|
||||
projection: &WorkspaceShellProjection,
|
||||
sidebar_tree_html: Option<&str>,
|
||||
file_tree_html: Option<&str>,
|
||||
) -> String {
|
||||
let starred_rows = if projection.starred_items.is_empty() {
|
||||
String::new()
|
||||
@@ -279,14 +363,28 @@ pub fn render_workspace_shell_sidebar_html(
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|html| {
|
||||
format!(
|
||||
r#"<div class="sidebar-tree-section"><div class="sidebar-tree-divider"></div><div id="sidebar-tree-root" class="sidebar-tree">{html}</div></div>"#
|
||||
r#"<div class="sidebar-tree-section sidebar-page-tree-section" data-testid="wolai-sidebar-page-tree-section"><div class="sidebar-tree-divider"></div><div id="sidebar-tree-root" class="sidebar-tree" data-tree-shell-mode="page" data-workspace-id="{}">{html}</div></div>"#,
|
||||
escape_html(&projection.workspace_id),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let my_pages = if projected_my_pages.is_empty() {
|
||||
let file_tree_panel = 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>"#,
|
||||
escape_html(&projection.workspace_id),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
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
|
||||
} else if !projected_my_pages.is_empty() {
|
||||
projected_my_pages
|
||||
} else {
|
||||
format!("{projected_my_pages}{tree_html}")
|
||||
empty_state.to_string()
|
||||
};
|
||||
let bottom_entries = projection
|
||||
.bottom_entries
|
||||
@@ -303,7 +401,8 @@ 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="我的页面"><div class="wolai-section-title"><span>我的页面</span><span class="wolai-section-caret">⌄</span><span class="wolai-section-add">+</span></div>{my_pages}</section><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-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>"#,
|
||||
escape_html(&projection.workspace_id),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -314,10 +413,22 @@ fn render_item_row(item: &WorkspaceShellItem) -> String {
|
||||
} else {
|
||||
format!(r#" style="padding-left:{}px""#, 12 + item.depth.min(6) * 18)
|
||||
};
|
||||
let parent_attr = item
|
||||
.parent_id
|
||||
.as_deref()
|
||||
.map(|parent_id| format!(r#" data-parent-id="{}""#, escape_html(parent_id)))
|
||||
.unwrap_or_default();
|
||||
let aria_current = if item.active {
|
||||
r#" aria-current="page""#
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!(
|
||||
r#"<a class="wolai-page-row{active_class}" href="{}" data-node-id="{}"{depth_style}><span class="wolai-row-icon">{}</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-icon">{}</span><span class="wolai-row-title">{}</span></a>"#,
|
||||
escape_html(&item.href),
|
||||
escape_html(&item.id),
|
||||
item.depth,
|
||||
item.active,
|
||||
escape_html(item.icon.as_deref().unwrap_or("▣")),
|
||||
escape_html(&item.title),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user