2026-05-13 22:43:16 +08:00
|
|
|
use crate::app::AppState;
|
2026-04-29 12:24:44 +08:00
|
|
|
use crate::context::RequestContext;
|
|
|
|
|
use crate::error::WebError;
|
2026-05-13 22:43:16 +08:00
|
|
|
use crate::routes::web_shell::{
|
|
|
|
|
build_page_aggregate_snapshot, load_file_tree_html, load_sidebar_tree_html,
|
|
|
|
|
load_workspace_shell_projection,
|
|
|
|
|
};
|
2026-04-29 12:24:44 +08:00
|
|
|
use crate::ssr::pages::mindmap::MindmapPage;
|
2026-05-13 22:43:16 +08:00
|
|
|
use crate::workspace_shell::render_workspace_shell_sidebar_html;
|
|
|
|
|
use axum::extract::{Extension, Path, State};
|
2026-04-29 12:24:44 +08:00
|
|
|
use axum::http::{HeaderMap, HeaderName, HeaderValue};
|
|
|
|
|
use axum::response::{Html, IntoResponse, Response};
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
|
|
|
|
|
const HEADER_MNOTE_WEB_OWNER: &str = "x-mnote-web-owner";
|
|
|
|
|
const HEADER_MNOTE_WEB_SHELL: &str = "x-mnote-web-shell";
|
|
|
|
|
|
|
|
|
|
pub async fn mindmap_object_shell(
|
2026-05-13 22:43:16 +08:00
|
|
|
State(state): State<AppState>,
|
2026-04-29 12:24:44 +08:00
|
|
|
Extension(context): Extension<RequestContext>,
|
|
|
|
|
Path((doc_id, mindmap_id)): Path<(String, String)>,
|
|
|
|
|
) -> Result<Response, WebError> {
|
2026-05-13 22:43:16 +08:00
|
|
|
let default_workspace_name = format!("{} 的空间", state.config().dev_user_name);
|
|
|
|
|
let aggregate = build_page_aggregate_snapshot(&state, &context, &doc_id, None, None, None)
|
|
|
|
|
.await
|
|
|
|
|
.ok();
|
|
|
|
|
let workspace_id = aggregate
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|value| value.identity.workspace_id.clone())
|
|
|
|
|
.filter(|value| !value.trim().is_empty());
|
|
|
|
|
let title = aggregate
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|value| value.head.title.trim().to_string())
|
|
|
|
|
.filter(|value| !value.is_empty())
|
|
|
|
|
.unwrap_or_else(|| "思维导图".to_string());
|
|
|
|
|
let (workspace_name, sidebar_tree_html, workspace_sidebar_html) =
|
|
|
|
|
if let Some(workspace_id) = workspace_id.as_deref() {
|
|
|
|
|
let workspace_projection = load_workspace_shell_projection(
|
|
|
|
|
state.config(),
|
|
|
|
|
&context,
|
|
|
|
|
workspace_id,
|
|
|
|
|
Some(&doc_id),
|
|
|
|
|
&default_workspace_name,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
let sidebar_tree_html =
|
|
|
|
|
load_sidebar_tree_html(state.config(), &context, workspace_id, Some(&doc_id))
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let file_tree_html =
|
|
|
|
|
load_file_tree_html(state.config(), &context, workspace_id, Some(&doc_id))
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let workspace_name = workspace_projection.workspace_name.clone();
|
|
|
|
|
let workspace_sidebar_html = render_workspace_shell_sidebar_html(
|
|
|
|
|
&workspace_projection,
|
|
|
|
|
Some(sidebar_tree_html.as_str()),
|
|
|
|
|
Some(file_tree_html.as_str()),
|
|
|
|
|
);
|
|
|
|
|
(
|
|
|
|
|
Some(workspace_name),
|
|
|
|
|
Some(sidebar_tree_html),
|
|
|
|
|
Some(workspace_sidebar_html),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
(None, None, None)
|
|
|
|
|
};
|
|
|
|
|
let editor_bootstrap = json!({
|
|
|
|
|
"documentId": format!("__mindmap_object__:{doc_id}:{mindmap_id}"),
|
|
|
|
|
"workspaceId": workspace_id
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|value| format!("__mindmap_object__:{value}")),
|
|
|
|
|
"title": title.clone(),
|
|
|
|
|
"content": {
|
|
|
|
|
"type": "doc",
|
|
|
|
|
"content": [
|
|
|
|
|
{
|
|
|
|
|
"type": "paragraph",
|
|
|
|
|
"attrs": {
|
|
|
|
|
"mindmapId": mindmap_id,
|
|
|
|
|
"mnoteBlockType": "mindmap",
|
|
|
|
|
"projectionVersion": 1,
|
|
|
|
|
"rootNodeId": "root",
|
|
|
|
|
"mnoteMindmapData": serde_json::Value::Null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"readOnly": false,
|
|
|
|
|
"editable": true,
|
|
|
|
|
"standaloneObject": {
|
|
|
|
|
"kind": "mindmap",
|
|
|
|
|
"documentId": doc_id,
|
|
|
|
|
"mindmapId": mindmap_id
|
|
|
|
|
},
|
|
|
|
|
"revision": serde_json::Value::Null,
|
|
|
|
|
"conflictDetectionKey": serde_json::Value::Null,
|
|
|
|
|
"pageOptions": {
|
|
|
|
|
"pageWidth": "full",
|
|
|
|
|
"smallText": false,
|
|
|
|
|
"showHeadingNumbers": false,
|
|
|
|
|
"fontFamily": "sans"
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-04-29 12:24:44 +08:00
|
|
|
let contract = json!({
|
|
|
|
|
"schema": "mnote.mindmap_shell.v1",
|
|
|
|
|
"owner": "mnote-web",
|
|
|
|
|
"shell": "mindmap",
|
|
|
|
|
"documentId": doc_id,
|
|
|
|
|
"mindmapId": mindmap_id,
|
|
|
|
|
"projection": {
|
2026-05-11 12:27:40 +08:00
|
|
|
"schema": "mnote.mindmap.simple_mind_map_scene.v1",
|
|
|
|
|
"runtime": "simple-mind-map",
|
|
|
|
|
"source": "compat-blob",
|
2026-04-30 05:46:36 +08:00
|
|
|
"owner": "rust-kernel",
|
2026-05-11 12:27:40 +08:00
|
|
|
"queryName": "mindmap.simple_mind_map_scene.get"
|
2026-04-29 12:24:44 +08:00
|
|
|
},
|
|
|
|
|
"island": {
|
2026-05-11 12:27:40 +08:00
|
|
|
"kind": "leptos_mindmap_adapter",
|
2026-04-29 12:24:44 +08:00
|
|
|
"mountId": "mnote-mindmap-island",
|
2026-05-11 12:27:40 +08:00
|
|
|
"runtimeRole": "simple_mind_map_adapter",
|
2026-04-30 05:46:36 +08:00
|
|
|
"commandName": "mindmap.command.apply"
|
2026-04-29 12:24:44 +08:00
|
|
|
},
|
|
|
|
|
"requestId": context.trace.request_id,
|
|
|
|
|
"traceId": context.trace.trace_id
|
|
|
|
|
});
|
2026-05-13 22:43:16 +08:00
|
|
|
let editor_bootstrap_json =
|
|
|
|
|
serde_json::to_string(&editor_bootstrap).unwrap_or_else(|_| "null".to_string());
|
2026-04-29 12:24:44 +08:00
|
|
|
let contract_json = serde_json::to_string(&contract).unwrap_or_else(|_| "null".to_string());
|
|
|
|
|
let body_content = crate::ssr::render_view(leptos::view! {
|
|
|
|
|
<MindmapPage
|
|
|
|
|
document_id={doc_id.clone()}
|
|
|
|
|
mindmap_id={mindmap_id.clone()}
|
2026-05-13 22:43:16 +08:00
|
|
|
title={title.clone()}
|
|
|
|
|
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()}
|
2026-04-29 12:24:44 +08:00
|
|
|
/>
|
|
|
|
|
});
|
|
|
|
|
let html = format!(
|
|
|
|
|
r#"<!doctype html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
2026-05-13 22:43:16 +08:00
|
|
|
<title>{}</title>
|
2026-04-29 12:24:44 +08:00
|
|
|
<style>{}</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body data-mnote-web-owner="mnote-web" data-mnote-shell="mindmap" data-document-id="{}" data-mindmap-id="{}">
|
|
|
|
|
{}
|
2026-05-13 22:43:16 +08:00
|
|
|
<script id="__MNOTE_MINDMAP_EDITOR_BOOTSTRAP__" type="application/json">{}</script>
|
2026-04-29 12:24:44 +08:00
|
|
|
<script id="__MNOTE_MINDMAP_SHELL__" type="application/json">{}</script>
|
2026-05-13 22:43:16 +08:00
|
|
|
{}
|
2026-04-29 12:24:44 +08:00
|
|
|
</body>
|
|
|
|
|
</html>"#,
|
2026-05-13 22:43:16 +08:00
|
|
|
escape_html(&title),
|
2026-04-29 12:24:44 +08:00
|
|
|
crate::ssr::MNOTE_CSS,
|
|
|
|
|
escape_html(&doc_id),
|
|
|
|
|
escape_html(&mindmap_id),
|
|
|
|
|
body_content,
|
2026-05-13 22:43:16 +08:00
|
|
|
escape_script_json(&editor_bootstrap_json),
|
2026-04-29 12:24:44 +08:00
|
|
|
escape_script_json(&contract_json),
|
2026-05-13 22:43:16 +08:00
|
|
|
render_mindmap_standalone_bootstrap_script(),
|
2026-04-29 12:24:44 +08:00
|
|
|
);
|
|
|
|
|
let mut response = Html(html).into_response();
|
|
|
|
|
stamp_shell_headers(response.headers_mut(), "mindmap");
|
|
|
|
|
Ok(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn stamp_shell_headers(headers: &mut HeaderMap, shell: &'static str) {
|
|
|
|
|
if let Ok(name) = HeaderName::from_lowercase(HEADER_MNOTE_WEB_OWNER.as_bytes()) {
|
|
|
|
|
headers.insert(name, HeaderValue::from_static("mnote-web"));
|
|
|
|
|
}
|
|
|
|
|
if let Ok(name) = HeaderName::from_lowercase(HEADER_MNOTE_WEB_SHELL.as_bytes()) {
|
|
|
|
|
headers.insert(name, HeaderValue::from_static(shell));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn escape_html(value: &str) -> String {
|
|
|
|
|
value
|
|
|
|
|
.replace('&', "&")
|
|
|
|
|
.replace('<', "<")
|
|
|
|
|
.replace('>', ">")
|
|
|
|
|
.replace('"', """)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn escape_script_json(value: &str) -> String {
|
|
|
|
|
value.replace("</script", "<\\/script")
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 22:43:16 +08:00
|
|
|
fn render_mindmap_standalone_bootstrap_script() -> &'static str {
|
|
|
|
|
r#"<script type="module">
|
|
|
|
|
(() => {
|
|
|
|
|
const BOOTSTRAP_ID = '__MNOTE_MINDMAP_EDITOR_BOOTSTRAP__';
|
|
|
|
|
const MOUNT_ID = 'mnote-mindmap-island';
|
|
|
|
|
|
|
|
|
|
const parseJsonScript = (id) => {
|
|
|
|
|
const node = document.getElementById(id);
|
|
|
|
|
if (!node) return null;
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(node.textContent || 'null');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn(`mnote mindmap bootstrap JSON 解析失败: ${id}`, error);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadRuntime = async () => {
|
|
|
|
|
if (window.__mnoteLeptosTiptapRuntimePromise) {
|
|
|
|
|
return window.__mnoteLeptosTiptapRuntimePromise;
|
|
|
|
|
}
|
|
|
|
|
window.__mnoteLeptosTiptapRuntimePromise = (async () => {
|
|
|
|
|
const manifestResponse = await fetch('/api/leptos-tiptap-runtime/manifest.json', { cache: 'no-store' });
|
|
|
|
|
if (!manifestResponse.ok) throw new Error(`manifest_failed_${manifestResponse.status}`);
|
|
|
|
|
const manifest = await manifestResponse.json();
|
|
|
|
|
if (!manifest.entryAssetPath) throw new Error('island manifest 缺少 entryAssetPath');
|
|
|
|
|
const entryUrl = `/api/leptos-tiptap-runtime/${manifest.entryAssetPath}`;
|
|
|
|
|
const wasmUrl = manifest.wasmAssetPath ? `/api/leptos-tiptap-runtime/${manifest.wasmAssetPath}` : undefined;
|
|
|
|
|
const runtime = await import(entryUrl);
|
|
|
|
|
if (typeof runtime.default !== 'function' || typeof runtime.mount !== 'function' || typeof runtime.unmount !== 'function') {
|
|
|
|
|
throw new Error('island runtime 导出不完整');
|
|
|
|
|
}
|
|
|
|
|
await runtime.default(wasmUrl);
|
|
|
|
|
if (typeof runtime.mount_mindmap_shell === 'function' && typeof runtime.unmount_mindmap_shell === 'function') {
|
|
|
|
|
window.__MNOTE_MINDMAP_RUST_SHELL__ = {
|
|
|
|
|
mount: runtime.mount_mindmap_shell,
|
|
|
|
|
unmount: runtime.unmount_mindmap_shell,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return runtime;
|
|
|
|
|
})();
|
|
|
|
|
return window.__mnoteLeptosTiptapRuntimePromise;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const bootstrap = parseJsonScript(BOOTSTRAP_ID);
|
|
|
|
|
const mountTarget = document.getElementById(MOUNT_ID);
|
|
|
|
|
if (!bootstrap || !(mountTarget instanceof HTMLElement)) return;
|
|
|
|
|
|
|
|
|
|
let mountId = null;
|
|
|
|
|
let runtimeModule = null;
|
|
|
|
|
|
|
|
|
|
const mountStandaloneMindmap = async () => {
|
|
|
|
|
runtimeModule = await loadRuntime();
|
|
|
|
|
mountId = runtimeModule.mount(mountTarget, bootstrap);
|
|
|
|
|
mountTarget.setAttribute('data-runtime-mount-id', String(mountId));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void mountStandaloneMindmap().catch((error) => {
|
|
|
|
|
console.error('mnote standalone mindmap mount failed', error);
|
|
|
|
|
mountTarget.setAttribute('data-runtime-editor-status', 'error');
|
|
|
|
|
mountTarget.setAttribute('data-runtime-editor-error', error instanceof Error ? error.message : 'unknown');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.addEventListener('beforeunload', () => {
|
|
|
|
|
if (mountId != null && runtimeModule && typeof runtimeModule.unmount === 'function') {
|
|
|
|
|
try {
|
|
|
|
|
runtimeModule.unmount(mountId);
|
|
|
|
|
} catch (_error) {}
|
|
|
|
|
}
|
|
|
|
|
}, { once: true });
|
|
|
|
|
})();
|
|
|
|
|
</script>"#
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 12:24:44 +08:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use crate::app::{build_app, AppConfig, AppState};
|
|
|
|
|
use axum::body::{to_bytes, Body};
|
|
|
|
|
use axum::http::{Request, StatusCode};
|
|
|
|
|
use tower::util::ServiceExt;
|
|
|
|
|
|
|
|
|
|
fn app() -> axum::Router {
|
|
|
|
|
build_app(AppState::new(AppConfig {
|
|
|
|
|
service_name: "mnote-web".into(),
|
|
|
|
|
service_version: "0.1.0".into(),
|
|
|
|
|
bind_addr: "127.0.0.1:0".into(),
|
|
|
|
|
public_bind_addr: "127.0.0.1:3000".into(),
|
|
|
|
|
legacy_next_base_url: Some("http://127.0.0.1:3100".into()),
|
|
|
|
|
enable_legacy_next_compat: true,
|
|
|
|
|
enable_debug_shell_routes: false,
|
|
|
|
|
hermes_base_path: "/api/hermes".into(),
|
|
|
|
|
compat_next_base_path: "/api/compat/next".into(),
|
|
|
|
|
convex_url: None,
|
|
|
|
|
convex_admin_key: None,
|
|
|
|
|
allow_dev_fixtures: true,
|
|
|
|
|
query_fixtures_json: None,
|
|
|
|
|
mutation_fixtures_json: None,
|
|
|
|
|
dev_user_id: "dev-user".into(),
|
|
|
|
|
dev_user_name: "开发用户".into(),
|
|
|
|
|
dev_user_email: "dev@mnote.local".into(),
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 12:27:40 +08:00
|
|
|
fn app_with_mindmap_fixture() -> axum::Router {
|
|
|
|
|
build_app(AppState::new(AppConfig {
|
|
|
|
|
service_name: "mnote-web".into(),
|
|
|
|
|
service_version: "0.1.0".into(),
|
|
|
|
|
bind_addr: "127.0.0.1:0".into(),
|
|
|
|
|
public_bind_addr: "127.0.0.1:3000".into(),
|
|
|
|
|
legacy_next_base_url: Some("http://127.0.0.1:3100".into()),
|
|
|
|
|
enable_legacy_next_compat: true,
|
|
|
|
|
enable_debug_shell_routes: false,
|
|
|
|
|
hermes_base_path: "/api/hermes".into(),
|
|
|
|
|
compat_next_base_path: "/api/compat/next".into(),
|
|
|
|
|
convex_url: None,
|
|
|
|
|
convex_admin_key: None,
|
|
|
|
|
allow_dev_fixtures: true,
|
|
|
|
|
query_fixtures_json: Some(
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"mindmaps:get": {
|
|
|
|
|
"ok": true,
|
|
|
|
|
"source": "compat-blob",
|
|
|
|
|
"revision": 7,
|
|
|
|
|
"data": {
|
|
|
|
|
"data": {"uid": "root", "text": "KMIND"},
|
|
|
|
|
"children": [
|
|
|
|
|
{"data": {"uid": "topic", "text": "二级节点"}, "children": []}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"meta": {
|
|
|
|
|
"document_id": "doc_1",
|
|
|
|
|
"mindmap_id": "mind_1"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.to_string(),
|
|
|
|
|
),
|
|
|
|
|
mutation_fixtures_json: None,
|
|
|
|
|
dev_user_id: "dev-user".into(),
|
|
|
|
|
dev_user_name: "开发用户".into(),
|
|
|
|
|
dev_user_email: "dev@mnote.local".into(),
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 12:24:44 +08:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn mindmap_shell_returns_rust_object_shell_contract() {
|
|
|
|
|
let response = app()
|
|
|
|
|
.oneshot(
|
|
|
|
|
Request::builder()
|
|
|
|
|
.uri("/mindmap/doc_1/mind_1")
|
|
|
|
|
.body(Body::empty())
|
|
|
|
|
.expect("request"),
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.expect("response");
|
|
|
|
|
|
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("x-mnote-web-owner")
|
|
|
|
|
.and_then(|value| value.to_str().ok()),
|
|
|
|
|
Some("mnote-web")
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
response
|
|
|
|
|
.headers()
|
|
|
|
|
.get("x-mnote-web-shell")
|
|
|
|
|
.and_then(|value| value.to_str().ok()),
|
|
|
|
|
Some("mindmap")
|
|
|
|
|
);
|
|
|
|
|
let body = to_bytes(response.into_body(), usize::MAX)
|
|
|
|
|
.await
|
|
|
|
|
.expect("body");
|
|
|
|
|
let html = String::from_utf8(body.to_vec()).expect("utf8");
|
|
|
|
|
assert!(html.contains("mnote.mindmap_shell.v1"));
|
2026-05-13 22:43:16 +08:00
|
|
|
assert!(html.contains("data-mnote-object-editor=\"mindmap\""));
|
2026-05-14 15:10:33 +08:00
|
|
|
assert!(html.contains("data-mnote-object-identity=\"resource:mindmap:doc_1:mind_1\""));
|
2026-05-11 12:27:40 +08:00
|
|
|
assert!(html.contains("data-leptos-mindmap-island=\"standalone\""));
|
|
|
|
|
assert!(html.contains("mindmap.simple_mind_map_scene.get"));
|
2026-04-30 05:46:36 +08:00
|
|
|
assert!(html.contains("mindmap.command.apply"));
|
2026-05-13 22:43:16 +08:00
|
|
|
assert!(html.contains("__MNOTE_MINDMAP_EDITOR_BOOTSTRAP__"));
|
|
|
|
|
assert!(html.contains("mnoteBlockType"));
|
|
|
|
|
assert!(html.contains("__mindmap_object__:doc_1:mind_1"));
|
|
|
|
|
assert!(html.contains("\"standaloneObject\""));
|
|
|
|
|
assert!(html.contains("\"documentId\":\"doc_1\""));
|
|
|
|
|
assert!(html.contains("\"mindmapId\":\"mind_1\""));
|
|
|
|
|
assert!(html.contains("runtimeModule.mount(mountTarget, bootstrap)"));
|
|
|
|
|
assert!(html.contains("/api/leptos-tiptap-runtime/manifest.json"));
|
2026-05-11 12:27:40 +08:00
|
|
|
assert!(!html.contains("react_mindmap_runtime"));
|
2026-04-30 05:46:36 +08:00
|
|
|
assert!(!html.contains("next-app-router"));
|
2026-04-29 12:24:44 +08:00
|
|
|
}
|
2026-05-11 12:27:40 +08:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn mindmap_api_returns_same_adapter_contract_for_standalone_and_block() {
|
|
|
|
|
let response = app_with_mindmap_fixture()
|
|
|
|
|
.oneshot(
|
|
|
|
|
Request::builder()
|
|
|
|
|
.uri("/api/mindmap/doc_1/mind_1?view=simple_mind_map_scene&queryName=mindmap.simple_mind_map_scene.get")
|
|
|
|
|
.body(Body::empty())
|
|
|
|
|
.expect("request"),
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.expect("response");
|
|
|
|
|
|
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
|
let body = to_bytes(response.into_body(), usize::MAX)
|
|
|
|
|
.await
|
|
|
|
|
.expect("body");
|
|
|
|
|
let payload: serde_json::Value =
|
|
|
|
|
serde_json::from_slice(&body).expect("adapter projection json");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
payload["schema"],
|
|
|
|
|
serde_json::json!("mnote.mindmap.simple_mind_map_scene.v1")
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(payload["runtime"], serde_json::json!("simple-mind-map"));
|
|
|
|
|
assert_eq!(payload["root"]["data"]["uid"], serde_json::json!("root"));
|
|
|
|
|
assert_eq!(payload["root"]["data"]["text"], serde_json::json!("KMIND"));
|
|
|
|
|
assert_eq!(payload["kernelRevision"], serde_json::json!(7));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
payload["compatPayload"]["source"],
|
|
|
|
|
serde_json::json!("compat-blob")
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-29 12:24:44 +08:00
|
|
|
}
|