From ee5aeb45be266454358ccc6d189fe98a20d76421 Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Tue, 26 May 2026 01:03:04 +0800 Subject: [PATCH] refactor: split document mindmap host runtime --- ...ime-module-maintainability-checklist-v1.md | 2 +- .../document-editor-adapter-runtime.js | 184 ++---------------- .../browser/document-mindmap-host-runtime.js | 184 ++++++++++++++++++ rust/crates/mnote-web/src/routes/mod.rs | 5 + rust/crates/mnote-web/src/routes/web_shell.rs | 26 ++- 5 files changed, 226 insertions(+), 175 deletions(-) create mode 100644 rust/crates/mnote-web/browser/document-mindmap-host-runtime.js 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"));