import { createMindmapHostRuntime } from './document-mindmap-host-runtime.js'; import { createResourceTabRuntime } from './document-resource-tab-runtime.js'; import { createDocumentSessionRuntime } from './document-session-runtime.js'; import { installGlobalSlashMenuPositioning, markIntendedSlashRoot, observeSlashMenuPosition, scheduleSlashMenuPosition, } from './document-slash-position-runtime.js'; import { buildPaneRuntime, clearSecondaryParams, createSecondaryPaneWidthController, currentUrl, installSecondaryPaneCloseButtons, installSecondaryPaneOpenListeners, pushUrlState, replaceUrlState, secondaryQueryParamNames, } from './document-pane-runtime.js'; import { conflictDetectionKeyBelongsToSession, hydrateMindmapAttrsFromDom, legacyInlineContentToTiptap, toTiptapDocument, } from './document-tiptap-conversion-runtime.js'; (() => { const PANES_BOOTSTRAP_ID = '__MNOTE_DOCUMENT_PANES_BOOTSTRAP__'; const ROOT_SELECTOR = '[data-testid="mnote-leptos-tiptap-island-editor-root"]'; const EVENT_PREFIX = 'mnote:leptos-tiptap-spike'; const CHANGE_EVENT = `${EVENT_PREFIX}:change`; const SAVE_EVENT = `${EVENT_PREFIX}:save-request`; const READY_EVENT = `${EVENT_PREFIX}:ready`; const STATE_EVENT = `${EVENT_PREFIX}:state`; const ERROR_EVENT = `${EVENT_PREFIX}:error`; const COMMAND_EVENT = `${EVENT_PREFIX}:command`; const BRIDGE_PROTOCOL = 'mnote.leptos_tiptap.bridge.v1'; const DEV_HOT_BUSTER = (() => { try { return new URL(import.meta.url).searchParams.get('devHot') || ''; } catch (_) { return ''; } })(); const withDevHot = (path) => { const url = new URL(path, window.location.origin); if (DEV_HOT_BUSTER) url.searchParams.set('devHot', DEV_HOT_BUSTER); return url.toString(); }; const parseJsonScript = (id) => { const node = document.getElementById(id); if (!node) return null; try { return JSON.parse(node.textContent || 'null'); } catch (error) { console.warn(`mnote rust-web editor bootstrap JSON 解析失败: ${id}`, error); return null; } }; const panesBootstrap = parseJsonScript(PANES_BOOTSTRAP_ID); if (!panesBootstrap || !Array.isArray(panesBootstrap.panes)) return; if (panesBootstrap.secondaryInvalid === true) clearSecondaryParams(); installSecondaryPaneOpenListeners(); installSecondaryPaneCloseButtons(); const { workspace, applyStoredSecondaryWidth } = createSecondaryPaneWidthController(); applyStoredSecondaryWidth(); const paneRuntimes = panesBootstrap.panes .map((paneDescriptor) => buildPaneRuntime(paneDescriptor, ROOT_SELECTOR)) .filter(Boolean); const loadRuntime = async () => { if (window.__mnoteLeptosTiptapRuntimePromise) { return window.__mnoteLeptosTiptapRuntimePromise; } window.__mnoteLeptosTiptapRuntimePromise = (async () => { const manifestResponse = await fetch(withDevHot('/api/leptos-tiptap-runtime/manifest.json')); 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 = withDevHot(`/api/leptos-tiptap-runtime/${manifest.entryAssetPath}`); const wasmUrl = manifest.wasmAssetPath ? withDevHot(`/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({ module_or_path: 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; }; // Tiptap/legacy conversion helpers live in document-tiptap-conversion-runtime.js. const normalizeBridgeValue = (value) => { if (value instanceof Map) { const out = {}; for (const [key, item] of value.entries()) out[key] = normalizeBridgeValue(item); return out; } if (Array.isArray(value)) return value.map(normalizeBridgeValue); if (value && typeof value === 'object') { return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, normalizeBridgeValue(item)])); } return value; }; const normalizeEnvelopePayload = (event) => { const detail = normalizeBridgeValue(event?.detail); if (!detail || typeof detail !== 'object') return null; const payload = detail.payload && typeof detail.payload === 'object' ? detail.payload : detail; return payload && typeof payload === 'object' ? payload : null; }; const setStatus = (runtime, status, message) => { runtime.root.setAttribute('data-runtime-editor-status', status); if (message) { runtime.root.setAttribute('data-runtime-editor-error', message); } else { runtime.root.removeAttribute('data-runtime-editor-error'); } if (runtime.observability instanceof HTMLElement) { runtime.observability.setAttribute('data-editor-host-status', status); runtime.observability.setAttribute('data-editor-host-active', 'leptos_tiptap_island'); } }; const enhanceEditorAttachmentLinksSoon = () => { const run = () => { if (typeof window.__mnoteEnhanceEditorAttachmentLinks === 'function') { window.__mnoteEnhanceEditorAttachmentLinks(); } }; run(); [120, 500, 1200].forEach((delayMs) => window.setTimeout(run, delayMs)); }; const pageAggregateUrl = (bootstrap) => { const url = new URL(`/api/page-aggregate/${encodeURIComponent(bootstrap.documentId)}`, window.location.origin); url.searchParams.set('sourceKind', bootstrap.sourceKind || 'local_folder'); if (bootstrap.workspaceId) url.searchParams.set('workspaceId', bootstrap.workspaceId); if (bootstrap.rootUri) url.searchParams.set('rootUri', bootstrap.rootUri); return url; }; const pageAggregateUrlFromDescriptor = (descriptor) => { const url = new URL(`/api/page-aggregate/${encodeURIComponent(descriptor.documentId)}`, window.location.origin); url.searchParams.set('sourceKind', descriptor.sourceKind || 'local_folder'); if (descriptor.workspaceId) url.searchParams.set('workspaceId', descriptor.workspaceId); if (descriptor.rootUri) url.searchParams.set('rootUri', descriptor.rootUri); return url; }; const buildBootstrapFromAggregate = (aggregate, descriptor, paneRole) => ({ schema: 'mnote.editor_bootstrap.v1', documentId: aggregate?.identity?.documentId || aggregate?.identity?.document_id || descriptor.documentId, workspaceId: aggregate?.identity?.workspaceId || aggregate?.identity?.workspace_id || descriptor.workspaceId || '', paneRole, sourceKind: descriptor.sourceKind || 'local_folder', rootUri: descriptor.rootUri || '', pageAggregateScriptId: paneRole === 'secondary' ? '__MNOTE_SECONDARY_PAGE_AGGREGATE__' : '__MNOTE_PAGE_AGGREGATE__', saveEndpoint: (descriptor.sourceKind || 'local_folder') === 'local_folder' ? '/api/page-body/write' : '/api/documents/save', titleEndpoint: '/api/documents/title', editorHostKind: 'leptos_tiptap_island', }); const aggregateDocumentId = (aggregate) => { return String(aggregate?.identity?.documentId || aggregate?.identity?.document_id || aggregate?.pageId || aggregate?.page_id || '').trim(); }; const aggregatePageOptions = (aggregate) => { return aggregate?.layout?.pageOptions || aggregate?.layout?.page_options || aggregate?.layoutOptions || aggregate?.layout_options || null; }; const withPageOptions = (aggregate, pageOptions) => { if (!aggregate || !pageOptions) return aggregate; const next = { ...aggregate }; next.layout = next.layout && typeof next.layout === 'object' ? { ...next.layout } : {}; next.layout.pageOptions = { ...pageOptions }; next.layout_options = { ...pageOptions }; next.layoutOptions = { ...pageOptions }; return next; }; const syncPageAggregateScript = (session, aggregate) => { if (!session || !aggregate) return; const scriptId = session.pageAggregateScriptId || '__MNOTE_PAGE_AGGREGATE__'; let node = document.getElementById(scriptId); if (!node) { node = document.createElement('script'); node.id = scriptId; node.type = 'application/json'; document.body.appendChild(node); } try { let existingAggregate = null; try { existingAggregate = JSON.parse(node.textContent || 'null'); } catch (_) { existingAggregate = null; } const localWriteAt = Number(node.getAttribute('data-mnote-page-options-local-write-at') || 0); const preserveRecentLocalOptions = localWriteAt > 0 && Date.now() - localWriteAt < 15000 && aggregateDocumentId(existingAggregate) === aggregateDocumentId(aggregate); const nextAggregate = preserveRecentLocalOptions ? withPageOptions(aggregate, aggregatePageOptions(existingAggregate)) : aggregate; node.textContent = JSON.stringify(nextAggregate); node.setAttribute('data-mnote-page-aggregate-synced-at', String(Date.now())); window.dispatchEvent(new CustomEvent('mnote:page-aggregate-synced', { detail: { scriptId, documentId: aggregateDocumentId(nextAggregate) } })); enhanceEditorAttachmentLinksSoon(); } catch (error) { console.warn('mnote Page Aggregate script 同步失败', error); } }; const paneViewRegistry = new Map(); const currentWebShellWorkspaceId = () => { try { return currentUrl().searchParams.get('workspaceId') || ''; } catch (_) { return ''; } }; const currentWebShellDocumentId = () => { const fromBody = document.body?.dataset?.documentId || ''; if (fromBody) return String(fromBody).trim(); const pageTab = document.querySelector('[data-mnote-main-tab="page"]'); if (pageTab instanceof HTMLElement) return String(pageTab.getAttribute('data-document-id') || '').trim(); return ''; }; const currentWebShellSourceKind = () => { try { return currentUrl().searchParams.get('sourceKind') || ''; } catch (_) { return ''; } }; const currentWebShellRootUri = () => { try { return currentUrl().searchParams.get('rootUri') || ''; } catch (_) { return ''; } }; const cssSafe = (value) => { const text = String(value || ''); if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text); return text.replace(/["\\]/g, '\\$&'); }; const localFileOpenUrl = (rootUri, path) => { const normalizedRootUri = String(rootUri || '').trim(); const normalizedPath = String(path || '').trim(); if (!normalizedRootUri || !normalizedPath) return ''; const url = new URL('/api/local-folder/files/open', window.location.origin); url.searchParams.set('rootUri', normalizedRootUri); url.searchParams.set('path', normalizedPath); return url.toString(); }; const resourceHrefFromUrlState = (rootUri, path, title) => { const fileUrl = localFileOpenUrl(rootUri, path); if (!fileUrl) return ''; const name = String(title || path || ''); if (/\.pdf$/i.test(name)) { const url = new URL('/pdf-preview', window.location.origin); url.searchParams.set('fileUrl', fileUrl); url.searchParams.set('fileName', title || path); return url.toString(); } const officeMatch = name.match(/\.([a-z0-9]+)$/i); const officeType = officeMatch ? officeMatch[1].toLowerCase() : ''; if (['doc', 'docx', 'odt', 'rtf', 'xls', 'xlsx', 'ods', 'csv', 'ppt', 'pptx', 'odp'].indexOf(officeType) >= 0) { const url = new URL('/office-preview', window.location.origin); url.searchParams.set('fileUrl', fileUrl); url.searchParams.set('fileName', title || path); url.searchParams.set('fileType', officeType); const workspaceId = currentWebShellWorkspaceId(); const sourceKind = currentWebShellSourceKind() || 'local_folder'; if (workspaceId) url.searchParams.set('workspaceId', workspaceId); if (sourceKind) url.searchParams.set('sourceKind', sourceKind); if (rootUri) url.searchParams.set('rootUri', rootUri); return url.toString(); } return fileUrl; }; const applyDocumentEvidenceLocatorFromUrl = (root) => { if (!(root instanceof HTMLElement)) return; const url = currentUrl(); const blockId = String(url.searchParams.get('blockId') || url.searchParams.get('evidenceBlockId') || '').trim(); const lineRange = String(url.searchParams.get('lineRange') || '').trim(); if (!blockId && !lineRange) return; root.setAttribute('data-mnote-evidence-open', 'true'); if (blockId) root.setAttribute('data-mnote-evidence-block-id', blockId); if (lineRange) root.setAttribute('data-mnote-evidence-line-range', lineRange); let target = blockId ? root.querySelector(`[data-block-id="${cssSafe(blockId)}"]`) : null; if (!(target instanceof HTMLElement) && lineRange) { const start = Number(String(lineRange).split('-')[0] || 0); if (Number.isFinite(start) && start > 0) { const blocks = Array.from(root.querySelectorAll('.ProseMirror [data-block-id]')) .filter((node) => node instanceof HTMLElement); target = blocks[Math.max(0, Math.min(blocks.length - 1, start - 1))] || null; } } if (target instanceof HTMLElement) { root.querySelectorAll('[data-mnote-evidence-text-highlight="true"]').forEach((node) => { if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-text-highlight'); }); target.setAttribute('data-mnote-evidence-text-highlight', 'true'); target.scrollIntoView({ block: 'center', inline: 'nearest' }); } }; const restoreResourceTabInputFromUrl = () => { const url = currentUrl(); const raw = String(url.searchParams.get('resourceTab') || '').trim(); if (!raw) return null; const objectIdentity = raw.startsWith('resource:') ? raw : (raw.includes('::') ? raw.slice(raw.indexOf('::') + 2) : raw); if (!objectIdentity.startsWith('resource:file:')) return null; const rootUri = currentWebShellRootUri(); if (!rootUri) return null; const rest = objectIdentity.slice('resource:file:'.length); const prefix = `${rootUri}:`; if (!rest.startsWith(prefix)) return null; const path = rest.slice(prefix.length).replace(/^\/+/, ''); if (!path) return null; const title = path.split('/').filter(Boolean).pop() || path; const href = resourceHrefFromUrlState(rootUri, path, title); return { objectIdentity, documentId: currentWebShellDocumentId(), workspaceId: currentWebShellWorkspaceId(), sourceKind: currentWebShellSourceKind() || 'local_folder', rootUri, path, assetId: `local:asset:${path}`, title, fileName: title, href, officeUrl: href, openTarget: 'active-tab', page: url.searchParams.get('page') || undefined, bbox: url.searchParams.get('bbox') || undefined, sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(), blockId: String(url.searchParams.get('blockId') || '').trim(), paragraphOrdinal: String(url.searchParams.get('paragraphOrdinal') || '').trim(), paraIdStart: String(url.searchParams.get('paraIdStart') || '').trim(), paraIdEnd: String(url.searchParams.get('paraIdEnd') || '').trim(), textFingerprint: String(url.searchParams.get('textFingerprint') || '').trim(), evidenceText: String(url.searchParams.get('evidenceText') || '').trim(), lineRange: url.searchParams.get('lineRange') || null, charRange: url.searchParams.get('charRange') || null, }; }; let nextViewId = 1; const mindmapHost = createMindmapHostRuntime({ loadRuntime, paneViewRegistry, unmountEditorViewBinding: (...args) => unmountEditorViewBinding(...args), pushUrlState, rootSelector: ROOT_SELECTOR, currentDocumentId: () => currentWebShellDocumentId(), }); let resourceTabs = null; const syncResourceSessionTabGuards = (session) => { if (resourceTabs) resourceTabs.syncResourceSessionTabGuards(session); }; const documentSessions = createDocumentSessionRuntime({ bridgeProtocol: BRIDGE_PROTOCOL, commandEvent: COMMAND_EVENT, normalizeEnvelopePayload, pageAggregateUrl, setStatus, syncPageAggregateScript, syncResourceSessionTabGuards, }); const documentSessionRegistry = documentSessions.documentSessionRegistry; const externalConflictMessage = documentSessions.externalConflictMessage; const sessionViews = (...args) => documentSessions.sessionViews(...args); const releaseDocumentSession = (...args) => documentSessions.releaseDocumentSession(...args); const scheduleDocumentSessionRelease = (...args) => documentSessions.scheduleDocumentSessionRelease(...args); const cancelDocumentSessionRelease = (...args) => documentSessions.cancelDocumentSessionRelease(...args); const currentEditorText = (...args) => documentSessions.currentEditorText(...args); const clearEmbeddedLocalDraft = (...args) => documentSessions.clearEmbeddedLocalDraft(...args); const setSessionStatus = (...args) => documentSessions.setSessionStatus(...args); const dispatchSessionContentToView = (...args) => documentSessions.dispatchSessionContentToView(...args); const broadcastSessionContent = (...args) => documentSessions.broadcastSessionContent(...args); const sessionHasRecentExternalSignal = (...args) => documentSessions.sessionHasRecentExternalSignal(...args); const sessionHasRecentLocalInput = (...args) => documentSessions.sessionHasRecentLocalInput(...args); const clearSessionConflictSurface = (...args) => documentSessions.clearSessionConflictSurface(...args); const markSessionExternalConflict = (...args) => documentSessions.markSessionExternalConflict(...args); const queueSessionSave = (...args) => documentSessions.queueSessionSave(...args); const refreshSessionFromExternalChange = (...args) => documentSessions.refreshSessionFromExternalChange(...args); const ensureLocalFolderEventChannel = (...args) => documentSessions.ensureLocalFolderEventChannel(...args); const sessionMatchesDocumentWorkspace = (...args) => documentSessions.sessionMatchesDocumentWorkspace(...args); const getOrCreateDocumentSession = (...args) => documentSessions.getOrCreateDocumentSession(...args); const updatePaneChrome = (runtimeDescriptor) => { const pane = runtimeDescriptor.root.closest('[data-document-pane="true"]'); if (!(pane instanceof HTMLElement)) return; if (runtimeDescriptor.paneRole === 'primary') { document.querySelectorAll('[data-mnote-navigation-page-placeholder][data-pane-role="primary"]').forEach((node) => { if (node instanceof HTMLElement) node.hidden = true; }); } const aggregate = runtimeDescriptor.aggregate || {}; const bootstrap = runtimeDescriptor.bootstrap || {}; const title = aggregate?.head?.title || '无标题'; const documentId = bootstrap.documentId || aggregate?.identity?.documentId || ''; const workspaceId = bootstrap.workspaceId || aggregate?.identity?.workspaceId || ''; pane.setAttribute('data-pane-document-id', documentId); pane.setAttribute('data-pane-workspace-id', workspaceId); pane.setAttribute('data-pane-visible', 'true'); pane.removeAttribute('data-mnote-side-target'); pane.hidden = false; const shell = pane.querySelector('.document-shell'); if (shell instanceof HTMLElement) { shell.setAttribute('data-document-id', documentId); shell.setAttribute('data-workspace-id', workspaceId); const options = aggregate?.layout?.pageOptions || {}; shell.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout))); shell.setAttribute('data-page-small-text', String(Boolean(options.smallText))); shell.setAttribute('data-layout-density', String(options.layoutDensity || 'normal')); shell.setAttribute('data-page-font', String(options.pageFont || 'default')); shell.setAttribute('data-page-show-heading-numbers', String(Boolean(options.showHeadingNumbers))); const hideTitleHeader = Boolean(options.hideTitleHeader || options.hide_title_header); const header = shell.querySelector('.document-shell-header'); if (header instanceof HTMLElement) { header.hidden = hideTitleHeader; header.setAttribute('data-page-title-hidden', String(hideTitleHeader)); } } pane.querySelectorAll('[data-page-title-input="true"]').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`; }); pane.querySelectorAll('[data-page-title-current="true"]').forEach((node) => { if (node instanceof HTMLElement) node.textContent = title; }); const pageTab = document.querySelector(`[data-mnote-main-tab="page"][data-pane-role="${runtimeDescriptor.paneRole}"]`); if (pageTab instanceof HTMLElement) { pageTab.setAttribute('data-document-id', documentId); pageTab.setAttribute('data-workspace-id', workspaceId); const pageTabTitle = pageTab.querySelector('.mnote-main-tab-title'); if (pageTabTitle instanceof HTMLElement) pageTabTitle.textContent = title; } if (runtimeDescriptor.paneRole === 'primary') { document.body.dataset.documentId = documentId; document.body.dataset.mnoteShell = 'document'; delete document.body.dataset.mindmapId; document.title = title; const topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]'); if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title; window.dispatchEvent(new CustomEvent('mnote:primary-document-activated', { detail: { documentId, workspaceId, title, sourceKind: bootstrap.sourceKind || '', rootUri: bootstrap.rootUri || '', pageBlockNavigation: runtimeDescriptor.pageBlockNavigation === true, } })); document.documentElement.removeAttribute('data-mnote-side-target-unsupported'); document.documentElement.removeAttribute('data-mnote-side-target-asset-id'); const activeTreeTab = document.querySelector('[data-mnote-sidebar-tree-tab][aria-selected="true"]'); const activeTreeMode = activeTreeTab instanceof HTMLElement && activeTreeTab.getAttribute('data-mnote-sidebar-tree-tab') === 'filetree' ? 'filetree' : 'page'; const activeTreeRows = activeTreeMode === 'filetree' ? '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]' : '#sidebar-tree-root .tree-row[data-shell-mode="page"]'; document.querySelectorAll(`${activeTreeRows}[data-active="true"], ${activeTreeRows}[data-selected="true"]`).forEach((row) => { if (row instanceof HTMLElement) { row.setAttribute('data-active', 'false'); row.setAttribute('data-selected', 'false'); } }); const escapedId = window.CSS && typeof window.CSS.escape === 'function' ? window.CSS.escape(documentId) : String(documentId).replace(/["\\]/g, '\\$&'); document.querySelectorAll(`${activeTreeRows}[data-node-id="${escapedId}"], ${activeTreeRows}[data-document-id="${escapedId}"], ${activeTreeRows}[data-doc-id="${escapedId}"]`).forEach((row) => { if (!(row instanceof HTMLElement)) return; if (row.getAttribute('data-shell-mode') === 'filetree') { row.setAttribute('data-selected', String(row.getAttribute('data-row-id') === `doc:${documentId}`)); } else { row.setAttribute('data-active', 'true'); } }); } runtimeDescriptor.root.removeAttribute('data-mnote-object-editor'); runtimeDescriptor.root.removeAttribute('data-mnote-object-identity'); runtimeDescriptor.root.removeAttribute('data-mnote-mindmap-id'); runtimeDescriptor.root.removeAttribute('data-mnote-side-target-unsupported'); runtimeDescriptor.root.removeAttribute('data-mnote-side-target-asset-id'); }; const fetchPageAggregateForPane = async (descriptor) => { const response = await fetch(pageAggregateUrlFromDescriptor(descriptor).toString(), { cache: 'no-store', headers: { accept: 'application/json' }, }); if (!response.ok) throw new Error(`page_aggregate_failed_${response.status}`); const payload = await response.json(); if (!payload?.result) throw new Error('page_aggregate_missing_result'); return payload.result; }; const ensureLazyPrimaryPaneRoot = () => { let root = document.querySelector(`${ROOT_SELECTOR}[data-pane-role="primary"]`); if (root instanceof HTMLElement) return root; const pagePanel = document.querySelector('[data-mnote-page-tab-panel][data-pane-role="primary"]'); if (!(pagePanel instanceof HTMLElement)) return null; const pane = document.createElement('section'); pane.className = 'document-pane'; pane.setAttribute('data-document-pane', 'true'); pane.setAttribute('data-pane-role', 'primary'); pane.setAttribute('data-pane-document-id', ''); pane.setAttribute('data-pane-workspace-id', currentWebShellWorkspaceId()); pane.setAttribute('data-pane-visible', 'false'); pane.hidden = true; const shell = document.createElement('main'); shell.className = 'document-shell'; shell.setAttribute('data-editor-host', 'leptos_tiptap_island'); shell.setAttribute('data-document-id', ''); shell.setAttribute('data-workspace-id', currentWebShellWorkspaceId()); shell.setAttribute('data-pane-role', 'primary'); shell.setAttribute('data-page-wide-layout', 'false'); shell.setAttribute('data-page-small-text', 'false'); shell.setAttribute('data-layout-density', 'normal'); shell.setAttribute('data-page-font', 'default'); shell.setAttribute('data-page-show-heading-numbers', 'false'); const header = document.createElement('header'); header.className = 'document-shell-header'; header.setAttribute('data-page-title-hidden', 'true'); header.hidden = true; const titleHeading = document.createElement('h1'); titleHeading.className = 'document-title-heading'; const titleInput = document.createElement('textarea'); titleInput.id = 'mnote-page-title-input'; titleInput.className = 'document-title-input'; titleInput.setAttribute('aria-label', '页面标题'); titleInput.setAttribute('data-page-title-input', 'true'); titleInput.setAttribute('data-document-id', ''); titleInput.setAttribute('data-workspace-id', currentWebShellWorkspaceId()); titleInput.setAttribute('data-pane-role', 'primary'); titleInput.setAttribute('data-title-endpoint', '/api/documents/title'); titleInput.rows = 1; titleHeading.appendChild(titleInput); const meta = document.createElement('div'); meta.className = 'document-shell-meta'; meta.setAttribute('aria-label', '页面元信息'); const currentTitle = document.createElement('span'); currentTitle.setAttribute('data-page-title-current', 'true'); meta.appendChild(currentTitle); header.append(titleHeading, meta); const aggregateMarker = document.createElement('section'); aggregateMarker.setAttribute('data-page-aggregate-snapshot', 'mnote.page_aggregate.v1'); const subtree = document.createElement('section'); subtree.setAttribute('data-testid', 'mnote-page-subtree'); subtree.setAttribute('data-page-tree-source', 'page_aggregate.tree.pageSubtree'); subtree.setAttribute('data-page-subtree-present', 'false'); subtree.setAttribute('data-pane-role', 'primary'); const island = document.createElement('section'); island.id = 'mnote-editor-island'; island.setAttribute('data-editor-host', 'leptos_tiptap_island'); island.setAttribute('data-pane-role', 'primary'); root = document.createElement('div'); root.id = 'mnote-leptos-tiptap-island-editor-root'; root.setAttribute('data-testid', 'mnote-leptos-tiptap-island-editor-root'); root.setAttribute('data-editor-host-kind', 'leptos_tiptap_island'); root.setAttribute('data-runtime-editor-status', 'booting'); root.setAttribute('data-pane-role', 'primary'); const observability = document.createElement('div'); observability.className = 'sr-only'; observability.setAttribute('data-editor-host-observability', 'rust-web-inline-island'); observability.setAttribute('data-editor-host-active', 'leptos_tiptap_island'); observability.setAttribute('data-editor-host-requested', 'leptos_tiptap_island'); observability.setAttribute('data-editor-host-status', 'booting'); observability.setAttribute('data-pane-role', 'primary'); island.append(root, observability); shell.append(header, aggregateMarker, subtree, island); pane.appendChild(shell); pagePanel.appendChild(pane); return root; }; const replacePaneDocument = async (paneRole, descriptor, options = {}) => { mindmapHost.unmountMindmapPane(paneRole); const root = paneRole === 'primary' ? ensureLazyPrimaryPaneRoot() : document.querySelector(`${ROOT_SELECTOR}[data-pane-role="${paneRole}"]`); const observability = document.querySelector(`[data-editor-host-observability][data-pane-role="${paneRole}"]`); if (!(root instanceof HTMLElement)) throw new Error(`pane_root_missing_${paneRole}`); const runtimePromise = loadRuntime(); const aggregatePromise = options.aggregate ? Promise.resolve(options.aggregate) : fetchPageAggregateForPane(descriptor); const previousView = paneViewRegistry.get(paneRole); const [runtime, aggregate] = await Promise.all([runtimePromise, aggregatePromise]); if (previousView) { unmountEditorViewBinding(previousView); paneViewRegistry.delete(paneRole); } const bootstrap = buildBootstrapFromAggregate(aggregate, descriptor, paneRole); syncPageAggregateScript({ pageAggregateScriptId: bootstrap.pageAggregateScriptId }, aggregate); const runtimeDescriptor = { paneRole, root, observability, aggregate, bootstrap, pageBlockNavigation: descriptor.pageBlockNavigation === true }; updatePaneChrome(runtimeDescriptor); if (paneRole === 'secondary' && workspace instanceof HTMLElement) { workspace.setAttribute('data-has-secondary-pane', 'true'); setSecondaryEditorHostVisible(true); const resizerNode = document.querySelector('[data-document-pane-resizer="true"]'); if (resizerNode instanceof HTMLElement) resizerNode.hidden = false; applyStoredSecondaryWidth(); } const session = getOrCreateDocumentSession(runtimeDescriptor); const view = createEditorViewBinding(session, runtime, runtimeDescriptor); const mountOptions = { documentId: session.documentId, workspaceId: session.workspaceId, title: session.title, content: session.currentTiptapDocument, revision: session.revision, conflictDetectionKey: session.conflictDetectionKey, readOnly: session.readOnly, editable: !session.readOnly, pageOptions: runtimeDescriptor.aggregate.layout?.pageOptions || {}, }; setStatus(runtimeDescriptor, 'loading-assets'); clearEmbeddedLocalDraft(runtimeDescriptor); try { const mountId = runtime.mount(runtimeDescriptor.root, mountOptions); view.mountId = mountId; runtimeDescriptor.root.setAttribute('data-runtime-mount-id', String(mountId)); runtimeDescriptor.root.setAttribute('data-editor-host-kind', 'leptos_tiptap_island'); setStatus(runtimeDescriptor, 'mounting-editor'); enhanceEditorAttachmentLinksSoon(); paneViewRegistry.set(paneRole, view); return view; } catch (error) { unmountEditorViewBinding(view); throw error; } }; const descriptorFromCurrentUrl = (paneRole, documentId, explicit = {}) => { const url = currentUrl(); if (paneRole === 'secondary') { return { documentId, workspaceId: explicit.workspaceId || url.searchParams.get('workspaceId') || '', sourceKind: explicit.sourceKind || url.searchParams.get('secondarySourceKind') || url.searchParams.get('sourceKind') || 'local_folder', rootUri: explicit.rootUri || url.searchParams.get('secondaryRootUri') || url.searchParams.get('rootUri') || '', }; } return { documentId, workspaceId: explicit.workspaceId || url.searchParams.get('workspaceId') || '', sourceKind: explicit.sourceKind || url.searchParams.get('sourceKind') || 'local_folder', rootUri: explicit.rootUri || url.searchParams.get('rootUri') || '', }; }; const updatePrimaryUrl = (descriptor, urlFromCaller) => { const url = urlFromCaller instanceof URL ? urlFromCaller : new URL(`/documents/${encodeURIComponent(descriptor.documentId)}`, window.location.origin); if (!url.searchParams.get('workspaceId') && descriptor.workspaceId) url.searchParams.set('workspaceId', descriptor.workspaceId); if (descriptor.sourceKind && descriptor.sourceKind !== 'convex_workspace') url.searchParams.set('sourceKind', descriptor.sourceKind); if (descriptor.rootUri) url.searchParams.set('rootUri', descriptor.rootUri); pushUrlState(url); }; const openPrimaryDocumentFromPageBlockHref = (href) => { const raw = typeof href === 'string' ? href.trim() : ''; if (!raw) return false; let target = null; try { target = new URL(raw, window.location.href); } catch (_) { return false; } if (target.origin !== window.location.origin || !target.pathname.startsWith('/documents/')) return false; const encodedId = target.pathname.slice('/documents/'.length).split('/')[0] || ''; const documentId = decodeURIComponent(encodedId).trim(); if (!documentId || typeof window.__mnoteDocumentPaneRuntime?.openPrimaryDocument !== 'function') return false; const current = currentUrl(); const workspaceId = target.searchParams.get('workspaceId') || current.searchParams.get('workspaceId') || ''; const sourceKind = target.searchParams.get('sourceKind') || current.searchParams.get('sourceKind') || ''; const rootUri = target.searchParams.get('rootUri') || current.searchParams.get('rootUri') || ''; void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({ documentId, workspaceId, sourceKind, rootUri, url: target, pageBlockNavigation: true, }).catch((error) => { console.warn('mnote page block pane navigation failed, fallback to full navigation', error); window.location.assign(target.pathname + target.search + target.hash); }); return true; }; const installPageBlockOpenCapture = () => { if (window.__mnotePageBlockOpenCaptureInstalled === true) return; window.__mnotePageBlockOpenCaptureInstalled = true; document.addEventListener('mousedown', (event) => { if (event.button !== 0) return; const target = event.target; const anchor = target instanceof Element ? target.closest('a.mnote-page-block-link') : null; if (!(anchor instanceof HTMLAnchorElement)) return; const href = anchor.getAttribute('href') || ''; if (!href) return; // Tiptap's Link extension handles mouse down before the click bubble. Keep // the target aside so that it cannot trigger a native document navigation. anchor.setAttribute('data-mnote-page-block-href', href); anchor.removeAttribute('href'); }, true); document.addEventListener('click', (event) => { const target = event.target; const anchor = target instanceof Element ? target.closest('a.mnote-page-block-link') : null; if (!(anchor instanceof HTMLAnchorElement)) return; const href = anchor.getAttribute('data-mnote-page-block-href') || anchor.getAttribute('href') || ''; if (!href) return; anchor.removeAttribute('data-mnote-page-block-href'); anchor.removeAttribute('href'); if (!openPrimaryDocumentFromPageBlockHref(href)) { anchor.setAttribute('href', href); return; } event.preventDefault(); event.stopPropagation(); if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation(); }, true); }; const setSecondaryEditorHostVisible = (visible) => { const host = document.querySelector('[data-testid="mnote-secondary-editor-tab-host"]'); if (host instanceof HTMLElement) host.hidden = !visible; }; const closeSecondaryPane = (url) => { const view = paneViewRegistry.get('secondary'); if (view) { unmountEditorViewBinding(view); paneViewRegistry.delete('secondary'); } const pane = document.querySelector('[data-document-pane="true"][data-pane-role="secondary"]'); if (pane instanceof HTMLElement) { pane.hidden = true; pane.setAttribute('data-pane-visible', 'false'); pane.removeAttribute('data-mnote-side-target'); pane.removeAttribute('data-pane-document-id'); pane.removeAttribute('data-pane-workspace-id'); } const root = document.querySelector(`${ROOT_SELECTOR}[data-pane-role="secondary"]`); if (root instanceof HTMLElement) { root.replaceChildren(); root.removeAttribute('data-mnote-side-target-unsupported'); root.removeAttribute('data-mnote-side-target-asset-id'); } if (resourceTabs) resourceTabs.closeResourceTabsForPane('secondary'); if (workspace instanceof HTMLElement) { workspace.setAttribute('data-has-secondary-pane', 'false'); workspace.style.removeProperty('grid-template-columns'); } setSecondaryEditorHostVisible(false); const resizerNode = document.querySelector('[data-document-pane-resizer="true"]'); if (resizerNode instanceof HTMLElement) resizerNode.hidden = true; document.documentElement.removeAttribute('data-mnote-side-target-unsupported'); document.documentElement.removeAttribute('data-mnote-side-target-asset-id'); replaceUrlState(url); }; const handleSessionChange = (session, view, event) => { const payload = normalizeEnvelopePayload(event); if (!payload) return; const pendingExternalChange = session.sourceKind === 'local_folder' && session.externalChangePending; const recentExternalChange = sessionHasRecentExternalSignal(session); const recentLocalInput = sessionHasRecentLocalInput(session); const tiptapDocument = hydrateMindmapAttrsFromDom(toTiptapDocument( payload?.tiptapDocument || payload?.editorDocument || payload?.content, currentEditorText(view), ), view.runtimeDescriptor.root); const serialized = JSON.stringify(tiptapDocument); if (view.suppressedSerialized && view.suppressedSerialized === serialized) { view.suppressedSerialized = null; view.lastKnownSerialized = serialized; return; } const previousSerialized = session.currentSerialized; session.currentTiptapDocument = tiptapDocument; session.currentSerialized = serialized; view.lastKnownSerialized = serialized; if (typeof payload?.title === 'string' && payload.title.trim()) { session.title = payload.title.trim(); } if (payload?.meta && typeof payload.meta === 'object') { if (Number.isInteger(payload.meta.revision)) session.revision = payload.meta.revision; if (typeof payload.meta.conflictDetectionKey === 'string' && payload.meta.conflictDetectionKey.trim()) { const nextMetaConflictKey = payload.meta.conflictDetectionKey.trim(); if (conflictDetectionKeyBelongsToSession(session, nextMetaConflictKey)) { session.conflictDetectionKey = nextMetaConflictKey; } } if (typeof payload.meta.readOnly === 'boolean') { session.readOnly = payload.meta.readOnly; } } session.dirty = session.currentSerialized !== session.lastPersistedSerialized; syncResourceSessionTabGuards(session); if (serialized !== previousSerialized) { broadcastSessionContent(session, view); } if ((pendingExternalChange || recentExternalChange) && (session.dirty || recentLocalInput)) { if (session.sourceKind === 'local_folder' && session.sessionKind !== 'resource') { session.externalChangePending = true; if (session.dirty) queueSessionSave(session); return; } markSessionExternalConflict(session, externalConflictMessage); return; } if (session.hasExternalConflict) { setSessionStatus(session, 'external-change-conflict', externalConflictMessage); return; } if (session.dirty) { queueSessionSave(session); } else { setSessionStatus(session, 'saved'); } }; const unmountEditorViewBinding = (view, options = {}) => { if (!view || view.disposed) return; view.disposed = true; const scheduleRelease = options.scheduleRelease !== false; const root = view.runtimeDescriptor.root; if (typeof view.disconnectObserver === 'function') { view.disconnectObserver(); view.disconnectObserver = null; } if (view.onReady) root.removeEventListener(READY_EVENT, view.onReady); if (view.onError) root.removeEventListener(ERROR_EVENT, view.onError); if (view.onChange) root.removeEventListener(CHANGE_EVENT, view.onChange); if (view.onSave) root.removeEventListener(SAVE_EVENT, view.onSave); if (view.onState) root.removeEventListener(STATE_EVENT, view.onState); if (view.onKeydown) root.removeEventListener('keydown', view.onKeydown, true); if (view.onInput) root.removeEventListener('input', view.onInput); if (view.disconnectSlashObserver) { view.disconnectSlashObserver(); view.disconnectSlashObserver = null; } clearSessionConflictSurface(view.session); if (view.mountId != null && typeof view.runtime?.unmount === 'function') { try { view.runtime.unmount(view.mountId); } catch (error) { console.warn('mnote editor view unmount failed', error); } } view.mountId = null; root.removeAttribute('data-runtime-mount-id'); if (view.session.views.get(view.id) === view) { view.session.views.delete(view.id); } if (scheduleRelease) { scheduleDocumentSessionRelease(view.session); } }; const observeEditorViewBinding = (view) => { if (typeof MutationObserver !== 'function' || !(document.body instanceof HTMLElement)) { return; } const observer = new MutationObserver(() => { if (!view.disposed && !view.runtimeDescriptor.root.isConnected) { unmountEditorViewBinding(view); } }); observer.observe(document.body, { childList: true, subtree: true }); view.disconnectObserver = () => observer.disconnect(); }; installGlobalSlashMenuPositioning(); const createEditorViewBinding = (session, runtime, runtimeDescriptor) => { cancelDocumentSessionRelease(session); const view = { id: nextViewId++, mountId: null, ready: false, disposed: false, suppressedSerialized: null, lastKnownSerialized: session.currentSerialized, runtime, runtimeDescriptor, session, disconnectObserver: null, onReady: null, onError: null, onChange: null, onSave: null, onState: null, onKeydown: null, onInput: null, disconnectSlashObserver: null, }; view.onReady = () => { view.ready = true; if (view.lastKnownSerialized !== session.currentSerialized) { dispatchSessionContentToView(session, view, 'mnote-web-document-session-ready-sync'); } if (session.status === 'dirty' || session.status === 'saving' || session.status === 'saved' || session.status === 'error' || session.status === 'external-change-conflict' || session.status === 'synced-external-change') { setStatus(runtimeDescriptor, session.status, session.error); } else { setStatus(runtimeDescriptor, 'ready'); } if (session.pageBodySource) runtimeDescriptor.root.setAttribute('data-mnote-page-body-source', session.pageBodySource); runtimeDescriptor.root.setAttribute('data-mnote-page-body-local-compat-fallback', session.pageBodyLocalCompatFallback ? 'true' : 'false'); if (session.pageBodyHardGuard) runtimeDescriptor.root.setAttribute('data-mnote-page-body-hard-guard', session.pageBodyHardGuard); if (session.projectionSource) runtimeDescriptor.root.setAttribute('data-mnote-projection-source', session.projectionSource); if (session.blockProjectionVersion) runtimeDescriptor.root.setAttribute('data-mnote-block-projection-version', session.blockProjectionVersion); enhanceEditorAttachmentLinksSoon(); applyDocumentEvidenceLocatorFromUrl(runtimeDescriptor.root); }; view.onError = (event) => { const payload = normalizeEnvelopePayload(event); setStatus(runtimeDescriptor, 'error', payload?.message || 'runtime_error'); }; view.onChange = (event) => { scheduleSlashMenuPosition(runtimeDescriptor.root); handleSessionChange(session, view, event); enhanceEditorAttachmentLinksSoon(); }; view.onSave = (event) => { handleSessionChange(session, view, event); enhanceEditorAttachmentLinksSoon(); }; view.onState = () => { scheduleSlashMenuPosition(runtimeDescriptor.root); enhanceEditorAttachmentLinksSoon(); }; view.onKeydown = (event) => { if (event.key === '/') scheduleSlashMenuPosition(runtimeDescriptor.root); }; view.onInput = (event) => { const target = event.target; if (!(target instanceof Element) || !target.closest('.ProseMirror')) return; session.lastUserInputAt = Date.now(); scheduleSlashMenuPosition(runtimeDescriptor.root); }; runtimeDescriptor.root.addEventListener(READY_EVENT, view.onReady); runtimeDescriptor.root.addEventListener(ERROR_EVENT, view.onError); runtimeDescriptor.root.addEventListener(CHANGE_EVENT, view.onChange); runtimeDescriptor.root.addEventListener(SAVE_EVENT, view.onSave); runtimeDescriptor.root.addEventListener(STATE_EVENT, view.onState); runtimeDescriptor.root.addEventListener('keydown', view.onKeydown, true); runtimeDescriptor.root.addEventListener('input', view.onInput); session.views.set(view.id, view); observeSlashMenuPosition(view); observeEditorViewBinding(view); return view; }; resourceTabs = createResourceTabRuntime({ loadRuntime, createEditorViewBinding, ensureLocalFolderEventChannel, unmountEditorViewBinding, setStatus, documentSessionRegistry, sessionHasRecentLocalInput, currentUrl, replaceUrlState, applyStoredSecondaryWidth, workspace, setSecondaryEditorHostVisible, markIntendedSlashRoot, toTiptapDocument, openMindmapResourceTab: (...args) => mindmapHost.openMindmapResourceTab(...args), unmountMindmapPane: (...args) => mindmapHost.unmountMindmapPane(...args), paneViewRegistry, rootSelector: ROOT_SELECTOR, currentDocumentId: () => currentWebShellDocumentId(), currentWorkspaceId: () => currentWebShellWorkspaceId(), secondaryQueryParamNames, }); const normalizePaneRole = (...args) => resourceTabs.normalizePaneRole(...args); const resolveResourceOpen = (...args) => resourceTabs.resolveResourceOpen(...args); const openResourceInActiveTab = (...args) => resourceTabs.openResourceInActiveTab(...args); const activateMainEditorTab = (...args) => resourceTabs.activateMainEditorTab(...args); const bindMainEditorPageTab = (...args) => resourceTabs.bindMainEditorPageTab(...args); const buildOpenEditorsSnapshot = (...args) => resourceTabs.buildOpenEditorsSnapshot(...args); const mountPane = async (runtimeDescriptor) => { const runtime = await loadRuntime(); const session = getOrCreateDocumentSession(runtimeDescriptor); const view = createEditorViewBinding(session, runtime, runtimeDescriptor); const mountOptions = { documentId: session.documentId, workspaceId: session.workspaceId, title: session.title, content: session.currentTiptapDocument, revision: session.revision, conflictDetectionKey: session.conflictDetectionKey, readOnly: session.readOnly, editable: !session.readOnly, pageOptions: runtimeDescriptor.aggregate.layout?.pageOptions || {}, }; setStatus(runtimeDescriptor, 'loading-assets'); clearEmbeddedLocalDraft(runtimeDescriptor); try { const mountId = runtime.mount(runtimeDescriptor.root, mountOptions); view.mountId = mountId; runtimeDescriptor.root.setAttribute('data-runtime-mount-id', String(mountId)); runtimeDescriptor.root.setAttribute('data-editor-host-kind', 'leptos_tiptap_island'); setStatus(runtimeDescriptor, 'mounting-editor'); enhanceEditorAttachmentLinksSoon(); paneViewRegistry.set(runtimeDescriptor.paneRole, view); } catch (error) { unmountEditorViewBinding(view); throw error; } }; window.__mnoteDocumentPaneRuntime = { openPrimaryDocument: async ({ documentId, workspaceId, sourceKind, rootUri, url, pageBlockNavigation } = {}) => { const id = typeof documentId === 'string' ? documentId.trim() : ''; if (!id) return false; const descriptor = descriptorFromCurrentUrl('primary', id, { workspaceId, sourceKind, rootUri }); descriptor.pageBlockNavigation = pageBlockNavigation === true; await replacePaneDocument('primary', descriptor); activateMainEditorTab('', 'primary'); updatePrimaryUrl(descriptor, url instanceof URL ? url : null); return true; }, openPrimaryMindmap: async ({ documentId, mindmapId, workspaceId, url } = {}) => { const docId = typeof documentId === 'string' ? documentId.trim() : ''; const mapId = typeof mindmapId === 'string' ? mindmapId.trim() : ''; if (!docId || !mapId) return false; await mindmapHost.replacePrimaryPaneMindmap({ documentId: docId, mindmapId: mapId, workspaceId: typeof workspaceId === 'string' ? workspaceId.trim() : '', url, }); return true; }, openSecondaryDocument: async ({ documentId, workspaceId, sourceKind, rootUri, url } = {}) => { const id = typeof documentId === 'string' ? documentId.trim() : ''; if (!id) return false; const descriptor = descriptorFromCurrentUrl('secondary', id, { workspaceId, sourceKind, rootUri }); await replacePaneDocument('secondary', descriptor); activateMainEditorTab('', 'secondary'); if (url instanceof URL) replaceUrlState(url); return true; }, resolveResourceOpen: (input) => resolveResourceOpen(input), openResourceInActiveTab: openResourceInActiveTab, activatePageTab: ({ paneRole } = {}) => { const role = normalizePaneRole(paneRole || 'primary'); bindMainEditorPageTab(role); activateMainEditorTab('', role); return true; }, openResourceAsSideTarget: async (input = {}) => { return openResourceInActiveTab({ ...input, paneRole: 'secondary', openTarget: 'active-tab' }); }, closeSecondaryDocument: ({ url } = {}) => { closeSecondaryPane(url instanceof URL ? url : currentUrl()); return true; }, getOpenEditorsSnapshot: () => buildOpenEditorsSnapshot(), refreshDocument: async ({ documentId, workspaceId, source } = {}) => { const targets = Array.from(documentSessionRegistry.values()).filter((session) => ( sessionMatchesDocumentWorkspace(session, documentId, workspaceId) )); await Promise.all(targets.map((session) => refreshSessionFromExternalChange( session, source || 'mnote-web-programmatic-refresh', ))); return targets.length; }, refreshPrimaryDocument: async ({ documentId, workspaceId, source } = {}) => { const primary = paneViewRegistry.get('primary'); if (!primary?.session) return 0; if (!sessionMatchesDocumentWorkspace(primary.session, documentId, workspaceId)) return 0; await refreshSessionFromExternalChange(primary.session, source || 'mnote-web-programmatic-refresh'); return 1; }, }; installPageBlockOpenCapture(); bindMainEditorPageTab('primary'); bindMainEditorPageTab('secondary'); const shouldPrewarmPrimaryRuntime = paneRuntimes.length === 0 && document.querySelector('.mnote-workspace-empty-editor-host') && document.querySelector(`${ROOT_SELECTOR}[data-pane-role="primary"]`); if (shouldPrewarmPrimaryRuntime) { const prewarmRuntime = () => { loadRuntime().catch((error) => { console.warn('mnote editor runtime prewarm failed', error); }); }; if (typeof window.requestIdleCallback === 'function') { window.requestIdleCallback(prewarmRuntime, { timeout: 2000 }); } else { window.setTimeout(prewarmRuntime, 800); } } window.addEventListener('pagehide', () => { Array.from(documentSessionRegistry.values()).forEach((session) => { sessionViews(session).forEach((view) => { unmountEditorViewBinding(view, { scheduleRelease: false }); }); releaseDocumentSession(session); }); }, { once: true }); const restoreResourceTabFromUrl = () => { const restoreInput = restoreResourceTabInputFromUrl(); if (!restoreInput) return Promise.resolve(false); return openResourceInActiveTab(restoreInput).catch((error) => { console.warn('mnote resource tab URL 恢复失败', error); return false; }); }; if (paneRuntimes.length) { Promise.all(paneRuntimes.map((paneRuntime) => mountPane(paneRuntime))).then(() => { return restoreResourceTabFromUrl(); }).catch((error) => { console.error('mnote multi-pane editor mount failed', error); }); } else { void restoreResourceTabFromUrl(); } })();