diff --git a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md
index 6dc3852d..6e1f234e 100644
--- a/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md
+++ b/design/10-review/process/16-mnote-web-runtime-module-maintainability-checklist-v1.md
@@ -122,7 +122,7 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web --lib routes::tests::mno
- [x] B1. `document-pane-runtime.js`:secondary pane URL、pane resize、pane close、pane runtime registry。
- [x] B2. `document-tiptap-conversion-runtime.js`:legacy block / inline content / marks 转 Tiptap document。
- [ ] B3. `document-resource-tab-runtime.js`:resource tab registry、MRU、close guard、resource text/image/frame editor mount。
-- [ ] B4. `document-mindmap-host-runtime.js`:primary mindmap object shell、mindmap resource tab mount/unmount。
+- [x] B4. `document-mindmap-host-runtime.js`:primary mindmap object shell、mindmap resource tab mount/unmount。
- [x] B5. `document-slash-position-runtime.js`:slash menu active root、positioning、mutation observer。
- [ ] B6. entrypoint 只负责读取 bootstrap、加载 runtime、装配各子模块。
diff --git a/rust/crates/mnote-web/browser/document-editor-adapter-runtime.js b/rust/crates/mnote-web/browser/document-editor-adapter-runtime.js
index 0ce3d0af..c36f5e32 100644
--- a/rust/crates/mnote-web/browser/document-editor-adapter-runtime.js
+++ b/rust/crates/mnote-web/browser/document-editor-adapter-runtime.js
@@ -1,3 +1,4 @@
+import { createMindmapHostRuntime } from './document-mindmap-host-runtime.js';
import {
installGlobalSlashMenuPositioning,
markIntendedSlashRoot,
@@ -169,7 +170,6 @@ import {
const documentSessionRegistry = new Map();
const localFolderEventRegistry = new Map();
const paneViewRegistry = new Map();
- const mindmapPaneViewRegistry = new Map();
const resourceTabRegistry = new Map();
const resourceTabMru = { primary: [], secondary: [] };
const resourceTabMruMax = 20;
@@ -179,6 +179,14 @@ import {
const treeExternalConflictMessage = '当前页面已在其它窗口更新,请刷新或保存前先处理冲突';
const SESSION_RELEASE_DELAY_MS = 1200;
const LOCAL_FOLDER_SELF_CHANGE_SUPPRESSIONS_KEY = 'mnote.localFolder.selfChangeSuppressions.v1';
+ const mindmapHost = createMindmapHostRuntime({
+ loadRuntime,
+ paneViewRegistry,
+ unmountEditorViewBinding: (...args) => unmountEditorViewBinding(...args),
+ pushUrlState,
+ rootSelector: ROOT_SELECTOR,
+ currentDocumentId: () => currentWebShellDocumentId(),
+ });
const ensureLocalFolderSelfChangeSuppressions = () => {
const now = Date.now();
@@ -206,26 +214,6 @@ import {
return map;
};
- const unmountMindmapPane = (paneRole) => {
- const view = mindmapPaneViewRegistry.get(paneRole);
- if (!view) return;
- mindmapPaneViewRegistry.delete(paneRole);
- if (view.mountId != null && view.runtime && typeof view.runtime.unmount === 'function') {
- try {
- view.runtime.unmount(view.mountId);
- } catch (error) {
- console.warn('mnote mindmap pane unmount failed', error);
- }
- }
- if (view.root instanceof HTMLElement) {
- view.root.removeAttribute('data-runtime-mount-id');
- view.root.removeAttribute('data-mnote-object-editor');
- view.root.removeAttribute('data-mnote-object-identity');
- view.root.removeAttribute('data-mnote-mindmap-id');
- view.root.replaceChildren();
- }
- };
-
const parseLocalFolderEventPayload = (event) => {
try {
return JSON.parse(String(event?.data || '{}'));
@@ -1603,7 +1591,7 @@ import {
};
const replacePaneDocument = async (paneRole, descriptor, options = {}) => {
- unmountMindmapPane(paneRole);
+ mindmapHost.unmountMindmapPane(paneRole);
const runtime = await loadRuntime();
const root = document.querySelector(`${ROOT_SELECTOR}[data-pane-role="${paneRole}"]`);
const observability = document.querySelector(`[data-editor-host-observability][data-pane-role="${paneRole}"]`);
@@ -1733,123 +1721,6 @@ import {
replaceUrlState(url);
};
- const parseJsonScriptFromDocument = (doc, id) => {
- const node = doc?.getElementById?.(id);
- if (!node) return null;
- try {
- return JSON.parse(node.textContent || 'null');
- } catch (error) {
- console.warn(`mnote mindmap shell JSON 解析失败: ${id}`, error);
- return null;
- }
- };
-
- const fetchMindmapShellBootstrap = async (url) => {
- const response = await fetch(url.toString(), {
- cache: 'no-store',
- credentials: 'include',
- headers: { accept: 'text/html' },
- });
- if (!response.ok) throw new Error(`mindmap_shell_failed_${response.status}`);
- const html = await response.text();
- const parsed = new DOMParser().parseFromString(html, 'text/html');
- const bootstrap = parseJsonScriptFromDocument(parsed, '__MNOTE_MINDMAP_EDITOR_BOOTSTRAP__');
- if (!bootstrap || typeof bootstrap !== 'object') throw new Error('mindmap_shell_missing_bootstrap');
- const contract = parseJsonScriptFromDocument(parsed, '__MNOTE_MINDMAP_SHELL__') || {};
- const title = String(bootstrap.title || parsed.querySelector('title')?.textContent || '思维导图').trim() || '思维导图';
- return { bootstrap, contract, title };
- };
-
- const setPrimaryMindmapSelection = (documentId, mindmapId) => {
- const cssEscape = window.CSS && typeof window.CSS.escape === 'function' ? window.CSS.escape : (value) => String(value).replace(/["\\]/g, '\\$&');
- const escapedDocId = cssEscape(documentId);
- const escapedMindmapId = cssEscape(mindmapId);
- document.querySelectorAll('.tree-row[data-active="true"], .tree-row[data-selected="true"]').forEach((row) => {
- if (!(row instanceof HTMLElement)) return;
- row.setAttribute('data-active', 'false');
- row.setAttribute('data-selected', 'false');
- });
- document.querySelectorAll(`.tree-row[data-shell-mode="page"][data-node-id="${escapedDocId}"]`).forEach((row) => {
- if (row instanceof HTMLElement) row.setAttribute('data-active', 'true');
- });
- document.querySelectorAll(`.tree-row[data-shell-mode="filetree"][data-asset-id="${escapedMindmapId}"]`).forEach((row) => {
- if (row instanceof HTMLElement) row.setAttribute('data-selected', 'true');
- });
- };
-
- const updatePrimaryMindmapChrome = ({ documentId, mindmapId, workspaceId, title, root }) => {
- const pane = root.closest('[data-document-pane="true"]');
- if (pane instanceof HTMLElement) {
- pane.setAttribute('data-pane-document-id', `__mindmap_object__:${documentId}:${mindmapId}`);
- pane.setAttribute('data-pane-workspace-id', workspaceId || '');
- pane.setAttribute('data-pane-visible', 'true');
- pane.hidden = false;
- }
- const shell = root.closest('.document-shell');
- if (shell instanceof HTMLElement) {
- shell.setAttribute('data-editor-host', 'mindmap_object');
- shell.setAttribute('data-document-id', documentId);
- shell.setAttribute('data-workspace-id', workspaceId || '');
- shell.setAttribute('data-mindmap-id', mindmapId);
- }
- document.body.dataset.documentId = documentId;
- document.body.dataset.mindmapId = mindmapId;
- document.body.dataset.mnoteShell = 'mindmap';
- document.title = title;
- document.querySelectorAll('[data-page-title-input="true"][data-pane-role="primary"]').forEach((node) => {
- if (!(node instanceof HTMLTextAreaElement)) return;
- node.value = title;
- node.setAttribute('data-document-id', documentId);
- node.setAttribute('data-workspace-id', workspaceId || '');
- node.setAttribute('data-title-last-saved', title);
- node.setAttribute('data-title-save-status', 'saved');
- node.style.height = 'auto';
- node.style.height = `${Math.max(48, node.scrollHeight)}px`;
- });
- document.querySelectorAll('[data-page-title-current="true"]').forEach((node) => {
- if (node instanceof HTMLElement) node.textContent = title;
- });
- const topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
- if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
- root.setAttribute('data-mnote-object-editor', 'mindmap');
- root.setAttribute('data-mnote-object-identity', `resource:mindmap:${documentId}:${mindmapId}`);
- root.setAttribute('data-mnote-mindmap-id', mindmapId);
- setPrimaryMindmapSelection(documentId, mindmapId);
- };
-
- const replacePrimaryPaneMindmap = async ({ documentId, mindmapId, workspaceId, url }) => {
- const targetUrl = url instanceof URL
- ? url
- : new URL(`/mindmap/${encodeURIComponent(documentId)}/${encodeURIComponent(mindmapId)}`, window.location.origin);
- const root = document.querySelector(`${ROOT_SELECTOR}[data-pane-role="primary"]`);
- const observability = document.querySelector('[data-editor-host-observability][data-pane-role="primary"]');
- if (!(root instanceof HTMLElement)) throw new Error('primary_pane_root_missing');
- const runtime = await loadRuntime();
- const { bootstrap, title } = await fetchMindmapShellBootstrap(targetUrl);
- const previousView = paneViewRegistry.get('primary');
- if (previousView) {
- unmountEditorViewBinding(previousView);
- paneViewRegistry.delete('primary');
- }
- unmountMindmapPane('primary');
- if (observability instanceof HTMLElement) {
- observability.setAttribute('data-editor-host-active', 'mindmap_object');
- observability.setAttribute('data-editor-host-status', 'mounting');
- }
- root.replaceChildren();
- root.setAttribute('data-runtime-editor-status', 'booting');
- updatePrimaryMindmapChrome({ documentId, mindmapId, workspaceId, title, root });
- const mountId = runtime.mount(root, bootstrap);
- root.setAttribute('data-runtime-mount-id', String(mountId));
- root.setAttribute('data-editor-host-kind', 'mindmap_object');
- if (observability instanceof HTMLElement) {
- observability.setAttribute('data-editor-host-status', 'mounted');
- }
- mindmapPaneViewRegistry.set('primary', { paneRole: 'primary', root, runtime, mountId });
- pushUrlState(targetUrl);
- return true;
- };
-
const handleSessionChange = (session, view, event) => {
const payload = normalizeEnvelopePayload(event);
if (!payload) return;
@@ -2659,35 +2530,6 @@ import {
return true;
};
- const openMindmapResourceTab = async (entry, input) => {
- const documentId = String(input.documentId || currentWebShellDocumentId() || '').trim();
- const mindmapId = String(input.mindmapId || input.assetId || '').trim();
- if (!documentId || !mindmapId) throw new Error('mindmap_resource_identity_missing');
- const targetUrl = new URL(`/mindmap/${encodeURIComponent(documentId)}/${encodeURIComponent(mindmapId)}`, window.location.origin);
- const runtime = await loadRuntime();
- const { bootstrap, title } = await fetchMindmapShellBootstrap(targetUrl);
- entry.title = String(input.title || title || '思维导图').trim() || '思维导图';
- const titleNode = entry.tab?.querySelector?.('.mnote-main-tab-title');
- if (titleNode) titleNode.textContent = entry.title;
- entry.panel.innerHTML = '';
- entry.panel.setAttribute('data-mnote-object-editor', 'mindmap');
- entry.panel.setAttribute('data-mnote-object-identity', entry.objectIdentity);
- entry.panel.setAttribute('data-mnote-mindmap-id', mindmapId);
- const root = entry.panel.querySelector('[data-testid="mnote-mindmap-editor-root"]');
- if (!(root instanceof HTMLElement)) throw new Error('mindmap_resource_root_missing');
- root.setAttribute('data-mnote-object-editor', 'mindmap');
- root.setAttribute('data-mnote-object-identity', entry.objectIdentity);
- root.setAttribute('data-mnote-mindmap-id', mindmapId);
- root.setAttribute('data-document-id', documentId);
- const mountId = runtime.mount(root, bootstrap);
- root.setAttribute('data-runtime-mount-id', String(mountId));
- root.setAttribute('data-editor-host-kind', 'mindmap_resource_tab');
- root.setAttribute('data-runtime-editor-status', 'mounted');
- entry.view = null;
- entry.session = null;
- entry.mindmapRuntime = { runtime, mountId, root };
- };
-
const openUnsupportedSideTarget = (input = {}) => {
const url = currentUrl();
secondaryQueryParamNames.forEach((name) => url.searchParams.delete(name));
@@ -2696,7 +2538,7 @@ import {
unmountEditorViewBinding(previousView);
paneViewRegistry.delete('secondary');
}
- unmountMindmapPane('secondary');
+ mindmapHost.unmountMindmapPane('secondary');
const workspace = document.querySelector('.mnote-document-workspace');
if (workspace instanceof HTMLElement) {
workspace.setAttribute('data-has-secondary-pane', 'true');
@@ -2780,7 +2622,7 @@ import {
activateMainEditorTab(registryKey, paneRole);
try {
if (entry.kind === 'mindmap') {
- await openMindmapResourceTab(entry, input);
+ await mindmapHost.openMindmapResourceTab(entry, input);
} else if (entry.kind === 'markdown' || entry.kind === 'text' || entry.kind === 'code') {
await openTiptapResourceTab(entry, input);
} else {
@@ -2841,7 +2683,7 @@ import {
const docId = typeof documentId === 'string' ? documentId.trim() : '';
const mapId = typeof mindmapId === 'string' ? mindmapId.trim() : '';
if (!docId || !mapId) return false;
- await replacePrimaryPaneMindmap({
+ await mindmapHost.replacePrimaryPaneMindmap({
documentId: docId,
mindmapId: mapId,
workspaceId: typeof workspaceId === 'string' ? workspaceId.trim() : '',
diff --git a/rust/crates/mnote-web/browser/document-mindmap-host-runtime.js b/rust/crates/mnote-web/browser/document-mindmap-host-runtime.js
new file mode 100644
index 00000000..3e1a1fc7
--- /dev/null
+++ b/rust/crates/mnote-web/browser/document-mindmap-host-runtime.js
@@ -0,0 +1,184 @@
+export const createMindmapHostRuntime = ({
+ loadRuntime,
+ paneViewRegistry,
+ unmountEditorViewBinding,
+ pushUrlState,
+ rootSelector,
+ currentDocumentId,
+} = {}) => {
+ const mindmapPaneViewRegistry = new Map();
+ const selector = rootSelector || '[data-testid="mnote-leptos-tiptap-island-editor-root"]';
+
+ const unmountMindmapPane = (paneRole) => {
+ const view = mindmapPaneViewRegistry.get(paneRole);
+ if (!view) return;
+ mindmapPaneViewRegistry.delete(paneRole);
+ if (view.mountId != null && view.runtime && typeof view.runtime.unmount === 'function') {
+ try {
+ view.runtime.unmount(view.mountId);
+ } catch (error) {
+ console.warn('mnote mindmap pane unmount failed', error);
+ }
+ }
+ if (view.root instanceof HTMLElement) {
+ view.root.removeAttribute('data-runtime-mount-id');
+ view.root.removeAttribute('data-mnote-object-editor');
+ view.root.removeAttribute('data-mnote-object-identity');
+ view.root.removeAttribute('data-mnote-mindmap-id');
+ view.root.replaceChildren();
+ }
+ };
+
+ const parseJsonScriptFromDocument = (doc, id) => {
+ const node = doc?.getElementById?.(id);
+ if (!node) return null;
+ try {
+ return JSON.parse(node.textContent || 'null');
+ } catch (error) {
+ console.warn(`mnote mindmap shell JSON 解析失败: ${id}`, error);
+ return null;
+ }
+ };
+
+ const fetchMindmapShellBootstrap = async (url) => {
+ const response = await fetch(url.toString(), {
+ cache: 'no-store',
+ credentials: 'include',
+ headers: { accept: 'text/html' },
+ });
+ if (!response.ok) throw new Error(`mindmap_shell_failed_${response.status}`);
+ const html = await response.text();
+ const parsed = new DOMParser().parseFromString(html, 'text/html');
+ const bootstrap = parseJsonScriptFromDocument(parsed, '__MNOTE_MINDMAP_EDITOR_BOOTSTRAP__');
+ if (!bootstrap || typeof bootstrap !== 'object') throw new Error('mindmap_shell_missing_bootstrap');
+ const contract = parseJsonScriptFromDocument(parsed, '__MNOTE_MINDMAP_SHELL__') || {};
+ const title = String(bootstrap.title || parsed.querySelector('title')?.textContent || '思维导图').trim() || '思维导图';
+ return { bootstrap, contract, title };
+ };
+
+ const setPrimaryMindmapSelection = (documentId, mindmapId) => {
+ const cssEscape = window.CSS && typeof window.CSS.escape === 'function' ? window.CSS.escape : (value) => String(value).replace(/["\\]/g, '\\$&');
+ const escapedDocId = cssEscape(documentId);
+ const escapedMindmapId = cssEscape(mindmapId);
+ document.querySelectorAll('.tree-row[data-active="true"], .tree-row[data-selected="true"]').forEach((row) => {
+ if (!(row instanceof HTMLElement)) return;
+ row.setAttribute('data-active', 'false');
+ row.setAttribute('data-selected', 'false');
+ });
+ document.querySelectorAll(`.tree-row[data-shell-mode="page"][data-node-id="${escapedDocId}"]`).forEach((row) => {
+ if (row instanceof HTMLElement) row.setAttribute('data-active', 'true');
+ });
+ document.querySelectorAll(`.tree-row[data-shell-mode="filetree"][data-asset-id="${escapedMindmapId}"]`).forEach((row) => {
+ if (row instanceof HTMLElement) row.setAttribute('data-selected', 'true');
+ });
+ };
+
+ const updatePrimaryMindmapChrome = ({ documentId, mindmapId, workspaceId, title, root }) => {
+ const pane = root.closest('[data-document-pane="true"]');
+ if (pane instanceof HTMLElement) {
+ pane.setAttribute('data-pane-document-id', `__mindmap_object__:${documentId}:${mindmapId}`);
+ pane.setAttribute('data-pane-workspace-id', workspaceId || '');
+ pane.setAttribute('data-pane-visible', 'true');
+ pane.hidden = false;
+ }
+ const shell = root.closest('.document-shell');
+ if (shell instanceof HTMLElement) {
+ shell.setAttribute('data-editor-host', 'mindmap_object');
+ shell.setAttribute('data-document-id', documentId);
+ shell.setAttribute('data-workspace-id', workspaceId || '');
+ shell.setAttribute('data-mindmap-id', mindmapId);
+ }
+ document.body.dataset.documentId = documentId;
+ document.body.dataset.mindmapId = mindmapId;
+ document.body.dataset.mnoteShell = 'mindmap';
+ document.title = title;
+ document.querySelectorAll('[data-page-title-input="true"][data-pane-role="primary"]').forEach((node) => {
+ if (!(node instanceof HTMLTextAreaElement)) return;
+ node.value = title;
+ node.setAttribute('data-document-id', documentId);
+ node.setAttribute('data-workspace-id', workspaceId || '');
+ node.setAttribute('data-title-last-saved', title);
+ node.setAttribute('data-title-save-status', 'saved');
+ node.style.height = 'auto';
+ node.style.height = `${Math.max(48, node.scrollHeight)}px`;
+ });
+ document.querySelectorAll('[data-page-title-current="true"]').forEach((node) => {
+ if (node instanceof HTMLElement) node.textContent = title;
+ });
+ const topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
+ if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
+ root.setAttribute('data-mnote-object-editor', 'mindmap');
+ root.setAttribute('data-mnote-object-identity', `resource:mindmap:${documentId}:${mindmapId}`);
+ root.setAttribute('data-mnote-mindmap-id', mindmapId);
+ setPrimaryMindmapSelection(documentId, mindmapId);
+ };
+
+ const replacePrimaryPaneMindmap = async ({ documentId, mindmapId, workspaceId, url }) => {
+ const targetUrl = url instanceof URL
+ ? url
+ : new URL(`/mindmap/${encodeURIComponent(documentId)}/${encodeURIComponent(mindmapId)}`, window.location.origin);
+ const root = document.querySelector(`${selector}[data-pane-role="primary"]`);
+ const observability = document.querySelector('[data-editor-host-observability][data-pane-role="primary"]');
+ if (!(root instanceof HTMLElement)) throw new Error('primary_pane_root_missing');
+ const runtime = await loadRuntime();
+ const { bootstrap, title } = await fetchMindmapShellBootstrap(targetUrl);
+ const previousView = paneViewRegistry?.get?.('primary');
+ if (previousView) {
+ unmountEditorViewBinding(previousView);
+ paneViewRegistry.delete('primary');
+ }
+ unmountMindmapPane('primary');
+ if (observability instanceof HTMLElement) {
+ observability.setAttribute('data-editor-host-active', 'mindmap_object');
+ observability.setAttribute('data-editor-host-status', 'mounting');
+ }
+ root.replaceChildren();
+ root.setAttribute('data-runtime-editor-status', 'booting');
+ updatePrimaryMindmapChrome({ documentId, mindmapId, workspaceId, title, root });
+ const mountId = runtime.mount(root, bootstrap);
+ root.setAttribute('data-runtime-mount-id', String(mountId));
+ root.setAttribute('data-editor-host-kind', 'mindmap_object');
+ if (observability instanceof HTMLElement) {
+ observability.setAttribute('data-editor-host-status', 'mounted');
+ }
+ mindmapPaneViewRegistry.set('primary', { paneRole: 'primary', root, runtime, mountId });
+ pushUrlState(targetUrl);
+ return true;
+ };
+
+ const openMindmapResourceTab = async (entry, input) => {
+ const documentId = String(input.documentId || currentDocumentId?.() || '').trim();
+ const mindmapId = String(input.mindmapId || input.assetId || '').trim();
+ if (!documentId || !mindmapId) throw new Error('mindmap_resource_identity_missing');
+ const targetUrl = new URL(`/mindmap/${encodeURIComponent(documentId)}/${encodeURIComponent(mindmapId)}`, window.location.origin);
+ const runtime = await loadRuntime();
+ const { bootstrap, title } = await fetchMindmapShellBootstrap(targetUrl);
+ entry.title = String(input.title || title || '思维导图').trim() || '思维导图';
+ const titleNode = entry.tab?.querySelector?.('.mnote-main-tab-title');
+ if (titleNode) titleNode.textContent = entry.title;
+ entry.panel.innerHTML = '';
+ entry.panel.setAttribute('data-mnote-object-editor', 'mindmap');
+ entry.panel.setAttribute('data-mnote-object-identity', entry.objectIdentity);
+ entry.panel.setAttribute('data-mnote-mindmap-id', mindmapId);
+ const root = entry.panel.querySelector('[data-testid="mnote-mindmap-editor-root"]');
+ if (!(root instanceof HTMLElement)) throw new Error('mindmap_resource_root_missing');
+ root.setAttribute('data-mnote-object-editor', 'mindmap');
+ root.setAttribute('data-mnote-object-identity', entry.objectIdentity);
+ root.setAttribute('data-mnote-mindmap-id', mindmapId);
+ root.setAttribute('data-document-id', documentId);
+ const mountId = runtime.mount(root, bootstrap);
+ root.setAttribute('data-runtime-mount-id', String(mountId));
+ root.setAttribute('data-editor-host-kind', 'mindmap_resource_tab');
+ root.setAttribute('data-runtime-editor-status', 'mounted');
+ entry.view = null;
+ entry.session = null;
+ entry.mindmapRuntime = { runtime, mountId, root };
+ };
+
+ return {
+ fetchMindmapShellBootstrap,
+ openMindmapResourceTab,
+ replacePrimaryPaneMindmap,
+ unmountMindmapPane,
+ };
+};
diff --git a/rust/crates/mnote-web/src/routes/mod.rs b/rust/crates/mnote-web/src/routes/mod.rs
index 130f4d39..778985ce 100644
--- a/rust/crates/mnote-web/src/routes/mod.rs
+++ b/rust/crates/mnote-web/src/routes/mod.rs
@@ -146,6 +146,10 @@ pub fn build_router(state: AppState) -> Router {
"/api/mnote-browser-runtime/document-pane-runtime.js",
get(web_shell::document_pane_runtime_asset),
)
+ .route(
+ "/api/mnote-browser-runtime/document-mindmap-host-runtime.js",
+ get(web_shell::document_mindmap_host_runtime_asset),
+ )
.route(
"/api/mnote-browser-runtime/document-slash-position-runtime.js",
get(web_shell::document_slash_position_runtime_asset),
@@ -572,6 +576,7 @@ mod tests {
"/api/mnote-browser-runtime/tree-shell-runtime.js",
"/api/mnote-browser-runtime/document-conflict-panel-runtime.js",
"/api/mnote-browser-runtime/document-pane-runtime.js",
+ "/api/mnote-browser-runtime/document-mindmap-host-runtime.js",
"/api/mnote-browser-runtime/document-slash-position-runtime.js",
"/api/mnote-browser-runtime/document-tiptap-conversion-runtime.js",
"/api/mnote-browser-runtime/document-editor-adapter-runtime.js",
diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs
index d2abeb5c..9269899f 100644
--- a/rust/crates/mnote-web/src/routes/web_shell.rs
+++ b/rust/crates/mnote-web/src/routes/web_shell.rs
@@ -869,6 +869,20 @@ pub async fn document_pane_runtime_asset() -> Response {
.unwrap_or_else(|_| Response::new(Body::empty()))
}
+pub async fn document_mindmap_host_runtime_asset() -> Response {
+ const JS: &str = include_str!("../../browser/document-mindmap-host-runtime.js");
+ Response::builder()
+ .status(StatusCode::OK)
+ .header(
+ header::CONTENT_TYPE,
+ "application/javascript; charset=utf-8",
+ )
+ .header(header::CACHE_CONTROL, "no-store")
+ .header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
+ .body(Body::from(JS))
+ .unwrap_or_else(|_| Response::new(Body::empty()))
+}
+
pub async fn document_slash_position_runtime_asset() -> Response {
const JS: &str = include_str!("../../browser/document-slash-position-runtime.js");
Response::builder()
@@ -1320,6 +1334,8 @@ mod tests {
const DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS: &str =
include_str!("../../browser/document-editor-adapter-runtime.js");
+ const DOCUMENT_MINDMAP_HOST_RUNTIME_JS: &str =
+ include_str!("../../browser/document-mindmap-host-runtime.js");
const DOCUMENT_PANE_RUNTIME_JS: &str = include_str!("../../browser/document-pane-runtime.js");
const DOCUMENT_SLASH_POSITION_RUNTIME_JS: &str =
include_str!("../../browser/document-slash-position-runtime.js");
@@ -1607,9 +1623,10 @@ mod tests {
assert!(runtime.contains("openResourceInActiveTab"));
assert!(runtime.contains("/api/local-folder/resource/read"));
assert!(runtime.contains("/api/local-folder/resource/write"));
- assert!(runtime.contains("replacePrimaryPaneMindmap"));
- assert!(runtime.contains("__MNOTE_MINDMAP_EDITOR_BOOTSTRAP__"));
- assert!(runtime.contains("data-mnote-object-identity"));
+ let mindmap_runtime = DOCUMENT_MINDMAP_HOST_RUNTIME_JS;
+ assert!(mindmap_runtime.contains("replacePrimaryPaneMindmap"));
+ assert!(mindmap_runtime.contains("__MNOTE_MINDMAP_EDITOR_BOOTSTRAP__"));
+ assert!(mindmap_runtime.contains("data-mnote-object-identity"));
assert!(runtime.contains("__MNOTE_DOCUMENT_PANES_BOOTSTRAP__"));
assert!(html.contains("__MNOTE_TREE_LIVE_BOOTSTRAP__"));
assert!(html.contains("mnote.tree_live_bootstrap.v1"));
@@ -2075,6 +2092,9 @@ mod tests {
assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("PANES_BOOTSTRAP_ID"));
assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("ROOT_SELECTOR"));
assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("BRIDGE_PROTOCOL"));
+ assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("document-mindmap-host-runtime.js"));
+ assert!(DOCUMENT_MINDMAP_HOST_RUNTIME_JS.contains("replacePrimaryPaneMindmap"));
+ assert!(DOCUMENT_MINDMAP_HOST_RUNTIME_JS.contains("openMindmapResourceTab"));
assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("document-pane-runtime.js"));
assert!(DOCUMENT_PANE_RUNTIME_JS.contains("openDocumentInSecondaryPane"));
assert!(DOCUMENT_PANE_RUNTIME_JS.contains("buildPaneRuntime"));