对齐 Wolai 侧栏体验并收拢设计入库
This commit is contained in:
@@ -2,7 +2,9 @@ use crate::app::AppState;
|
||||
use crate::context::RequestContext;
|
||||
use crate::error::WebError;
|
||||
use crate::routes::web_shell::{
|
||||
build_editor_bootstrap_json, build_page_aggregate_snapshot, escape_html, escape_script_json,
|
||||
load_file_tree_html, load_sidebar_tree_html, load_workspace_shell_projection,
|
||||
render_document_title_controller_script, render_editor_island_adapter_script,
|
||||
};
|
||||
use crate::transport::convex::execute_convex_mutation_by_name;
|
||||
use crate::workspace_shell::render_workspace_shell_sidebar_html;
|
||||
@@ -140,23 +142,79 @@ pub async fn root_entry(
|
||||
.active_page_title
|
||||
.clone()
|
||||
.unwrap_or_default();
|
||||
let content = crate::ssr::render_view(leptos::view! {
|
||||
<crate::ssr::pages::home::HomePage sidebar_tree_html={sidebar_tree_html} workspace_name={workspace_name} workspace_id={workspace_id.clone()} workspace_sidebar_html={workspace_sidebar_html} active_page_id={active_page_id} active_page_title={active_page_title} />
|
||||
});
|
||||
let render_workspace_entry = || {
|
||||
crate::ssr::render_view(leptos::view! {
|
||||
<crate::ssr::pages::home::HomePage
|
||||
sidebar_tree_html={sidebar_tree_html.clone()}
|
||||
workspace_name={workspace_name.clone()}
|
||||
workspace_id={workspace_id.clone()}
|
||||
workspace_sidebar_html={workspace_sidebar_html.clone()}
|
||||
active_page_id={active_page_id.clone()}
|
||||
active_page_title={active_page_title.clone()}
|
||||
/>
|
||||
})
|
||||
};
|
||||
let (html_title, content, body_extra) = if active_page_id.trim().is_empty() {
|
||||
("MNOTE".to_string(), render_workspace_entry(), String::new())
|
||||
} else {
|
||||
match build_page_aggregate_snapshot(
|
||||
&state,
|
||||
&context,
|
||||
&active_page_id,
|
||||
Some(workspace_id.as_str()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(aggregate) => {
|
||||
let title = aggregate.head.title.as_str();
|
||||
let page_subtree_json = serde_json::to_string(&aggregate.tree.page_subtree)
|
||||
.unwrap_or_else(|_| "null".to_string());
|
||||
let snapshot_json =
|
||||
serde_json::to_string(&aggregate).unwrap_or_else(|_| "null".to_string());
|
||||
let bootstrap_json = build_editor_bootstrap_json(&aggregate, &context);
|
||||
let content = crate::ssr::render_view(leptos::view! {
|
||||
<crate::ssr::pages::document::DocumentPage
|
||||
title={title.to_string()}
|
||||
document_id={active_page_id.clone()}
|
||||
workspace_id={workspace_id.clone()}
|
||||
sidebar_tree_html={sidebar_tree_html.clone()}
|
||||
workspace_name={workspace_name.clone()}
|
||||
workspace_sidebar_html={workspace_sidebar_html.clone()}
|
||||
page_subtree_json={page_subtree_json}
|
||||
/>
|
||||
});
|
||||
let body_extra = format!(
|
||||
r#"<script id="__MNOTE_PAGE_AGGREGATE__" type="application/json">{}</script>
|
||||
<script id="__MNOTE_EDITOR_BOOTSTRAP__" type="application/json">{}</script>
|
||||
{}
|
||||
{}"#,
|
||||
escape_script_json(&snapshot_json),
|
||||
escape_script_json(&bootstrap_json),
|
||||
render_document_title_controller_script(),
|
||||
render_editor_island_adapter_script(),
|
||||
);
|
||||
(title.to_string(), content, body_extra)
|
||||
}
|
||||
Err(_) => ("MNOTE".to_string(), render_workspace_entry(), String::new()),
|
||||
}
|
||||
};
|
||||
let mut response = Html(format!(
|
||||
r#"<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MNOTE</title>
|
||||
<title>{}</title>
|
||||
<style>{}</style>
|
||||
</head>
|
||||
<body data-mnote-web-owner="mnote-web" data-mnote-shell="workspace">
|
||||
{}
|
||||
{}
|
||||
</body>
|
||||
</html>"#,
|
||||
escape_html(&html_title),
|
||||
crate::ssr::MNOTE_CSS,
|
||||
content
|
||||
content,
|
||||
body_extra
|
||||
))
|
||||
.into_response();
|
||||
stamp_gateway_headers(response.headers_mut(), false);
|
||||
|
||||
@@ -36,7 +36,7 @@ use bridge_runtime::RuntimeCommandEnvelopeWire;
|
||||
use core_protocol::KernelProjectionKind;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
@@ -209,7 +209,7 @@ fn collect_expanded_ids(projection: &Value) -> BTreeSet<String> {
|
||||
}
|
||||
|
||||
pub(crate) fn collect_page_tree_render_rows(projection: &Value) -> Vec<PageTreeRenderRow> {
|
||||
projection
|
||||
let mut rows: Vec<PageTreeRenderRow> = projection
|
||||
.get("items")
|
||||
.and_then(Value::as_array)
|
||||
.map(|items| {
|
||||
@@ -232,6 +232,7 @@ pub(crate) fn collect_page_tree_render_rows(projection: &Value) -> Vec<PageTreeR
|
||||
node_id: node_id.to_string(),
|
||||
parent_node_id: item
|
||||
.get("parentNodeId")
|
||||
.or_else(|| item.get("parentId"))
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
@@ -261,7 +262,30 @@ pub(crate) fn collect_page_tree_render_rows(projection: &Value) -> Vec<PageTreeR
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
|
||||
let parent_by_id = rows
|
||||
.iter()
|
||||
.map(|row| (row.node_id.clone(), row.parent_node_id.clone()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for row in &mut rows {
|
||||
if row.depth == 0 && row.parent_node_id.is_some() {
|
||||
let mut depth = 0_u32;
|
||||
let mut cursor = row.parent_node_id.as_deref();
|
||||
while let Some(parent_id) = cursor {
|
||||
depth += 1;
|
||||
cursor = parent_by_id
|
||||
.get(parent_id)
|
||||
.and_then(|parent| parent.as_deref());
|
||||
if depth > 32 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
row.depth = depth;
|
||||
}
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
pub(crate) fn collect_filetree_render_rows(
|
||||
|
||||
@@ -126,7 +126,10 @@ pub async fn document_page_shell(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn build_editor_bootstrap_json(aggregate: &PageAggregate, context: &RequestContext) -> String {
|
||||
pub(crate) fn build_editor_bootstrap_json(
|
||||
aggregate: &PageAggregate,
|
||||
context: &RequestContext,
|
||||
) -> String {
|
||||
serde_json::to_string(&json!({
|
||||
"schema": "mnote.editor_bootstrap.v1",
|
||||
"documentId": aggregate.identity.document_id,
|
||||
@@ -142,7 +145,7 @@ fn build_editor_bootstrap_json(aggregate: &PageAggregate, context: &RequestConte
|
||||
.unwrap_or_else(|_| "{}".to_string())
|
||||
}
|
||||
|
||||
fn render_document_title_controller_script() -> &'static str {
|
||||
pub(crate) fn render_document_title_controller_script() -> &'static str {
|
||||
r#"<script>
|
||||
(() => {
|
||||
const CONTRACT = 'mnote.document_title_controller.v1';
|
||||
@@ -259,7 +262,7 @@ fn render_document_title_controller_script() -> &'static str {
|
||||
</script>"#
|
||||
}
|
||||
|
||||
fn render_editor_island_adapter_script() -> &'static str {
|
||||
pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
r#"<script type="module">
|
||||
(() => {
|
||||
const ROOT_SELECTOR = '[data-testid="mnote-leptos-tiptap-island-editor-root"]';
|
||||
@@ -586,7 +589,7 @@ pub async fn page_aggregate(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn build_page_aggregate_snapshot(
|
||||
pub(crate) async fn build_page_aggregate_snapshot(
|
||||
state: &AppState,
|
||||
context: &RequestContext,
|
||||
document_id: &str,
|
||||
@@ -661,7 +664,7 @@ fn stamp_shell_headers(headers: &mut HeaderMap, shell: &'static str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_html(value: &str) -> String {
|
||||
pub(crate) fn escape_html(value: &str) -> String {
|
||||
value
|
||||
.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
@@ -669,7 +672,7 @@ fn escape_html(value: &str) -> String {
|
||||
.replace('"', """)
|
||||
}
|
||||
|
||||
fn escape_script_json(value: &str) -> String {
|
||||
pub(crate) fn escape_script_json(value: &str) -> String {
|
||||
value.replace("</script", "<\\/script")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user