export const paneRouteConfig = { primary: { sourceKindParam: 'sourceKind', rootUriParam: 'rootUri', secondary: false, }, secondary: { documentIdParam: 'secondaryDocumentId', sourceKindParam: 'secondarySourceKind', rootUriParam: 'secondaryRootUri', secondary: true, }, }; export const secondaryQueryParamNames = [ paneRouteConfig.secondary.documentIdParam, paneRouteConfig.secondary.sourceKindParam, paneRouteConfig.secondary.rootUriParam, ]; export const currentUrl = () => new URL(window.location.href); export const replaceUrlState = (url) => { window.history.replaceState({}, '', url.pathname + url.search + url.hash); }; export const pushUrlState = (url) => { window.history.pushState({}, '', url.pathname + url.search + url.hash); }; export const clearSecondaryParams = () => { const url = currentUrl(); secondaryQueryParamNames.forEach((name) => url.searchParams.delete(name)); replaceUrlState(url); }; export const secondaryUrlForDocument = (documentId, detail = {}) => { const url = currentUrl(); url.searchParams.set(paneRouteConfig.secondary.documentIdParam, documentId); const detailSourceKind = typeof detail.sourceKind === 'string' ? detail.sourceKind.trim() : ''; const detailRootUri = typeof detail.rootUri === 'string' ? detail.rootUri.trim() : ''; const primarySourceKind = (url.searchParams.get(paneRouteConfig.primary.sourceKindParam) || '').trim(); const primaryRootUri = (url.searchParams.get(paneRouteConfig.primary.rootUriParam) || '').trim(); const secondarySourceKind = detailSourceKind || primarySourceKind; const secondaryRootUri = detailRootUri || primaryRootUri; if (secondarySourceKind) url.searchParams.set(paneRouteConfig.secondary.sourceKindParam, secondarySourceKind); else url.searchParams.delete(paneRouteConfig.secondary.sourceKindParam); if (secondaryRootUri) url.searchParams.set(paneRouteConfig.secondary.rootUriParam, secondaryRootUri); else url.searchParams.delete(paneRouteConfig.secondary.rootUriParam); return { url, sourceKind: secondarySourceKind, rootUri: secondaryRootUri }; }; export const openDocumentInSecondaryPane = (documentId, detail = {}) => { const id = typeof documentId === 'string' ? documentId.trim() : ''; if (!id) return false; const target = secondaryUrlForDocument(id, detail); if (typeof window.__mnoteDocumentPaneRuntime?.openSecondaryDocument === 'function') { void window.__mnoteDocumentPaneRuntime.openSecondaryDocument({ documentId: id, workspaceId: typeof detail.workspaceId === 'string' ? detail.workspaceId.trim() : '', sourceKind: target.sourceKind || null, rootUri: target.rootUri || null, url: target.url, }); return true; } window.location.assign(target.url.pathname + target.url.search + target.url.hash); return true; }; export const installSecondaryPaneOpenListeners = () => { window.addEventListener('tree.page.open-right', (event) => { const detail = event?.detail && typeof event.detail === 'object' ? event.detail : {}; const documentId = typeof detail.documentId === 'string' ? detail.documentId.trim() : ''; openDocumentInSecondaryPane(documentId, detail); }); window.addEventListener('tree.page.open', (event) => { const detail = event?.detail && typeof event.detail === 'object' ? event.detail : {}; const openTarget = typeof detail.openTarget === 'string' ? detail.openTarget.trim().toLowerCase() : ''; if (openTarget !== 'side') return; const documentId = typeof detail.documentId === 'string' ? detail.documentId.trim() : ''; openDocumentInSecondaryPane(documentId, detail); }); }; export const installSecondaryPaneCloseButtons = () => { document.querySelectorAll('[data-mnote-pane-close="secondary"]').forEach((button) => { button.addEventListener('click', (event) => { event.preventDefault(); const url = currentUrl(); secondaryQueryParamNames.forEach((name) => url.searchParams.delete(name)); if (typeof window.__mnoteDocumentPaneRuntime?.closeSecondaryDocument === 'function') { window.__mnoteDocumentPaneRuntime.closeSecondaryDocument({ url }); return; } window.location.assign(url.pathname + url.search + url.hash); }); }); }; export const createSecondaryPaneWidthController = () => { const workspace = document.querySelector('[data-testid="mnote-document-workspace"]'); const resizer = document.querySelector('[data-document-pane-resizer="true"]'); const secondaryWidthKey = 'mnote.document.secondary.width'; const applyStoredSecondaryWidth = () => { if (!(workspace instanceof HTMLElement)) return; if (workspace.getAttribute('data-has-secondary-pane') !== 'true') { workspace.style.removeProperty('grid-template-columns'); return; } try { const raw = window.localStorage ? window.localStorage.getItem(secondaryWidthKey) : ''; const width = Number(raw || 0); if (Number.isFinite(width) && width >= 320) { workspace.style.gridTemplateColumns = `minmax(0, 1fr) 6px minmax(320px, ${Math.round(width)}px)`; } } catch (_) {} }; if (workspace instanceof HTMLElement && resizer instanceof HTMLElement) { resizer.addEventListener('pointerdown', (event) => { if (workspace.getAttribute('data-has-secondary-pane') !== 'true') return; event.preventDefault(); const move = (nextEvent) => { const rect = workspace.getBoundingClientRect(); const width = Math.min(Math.max(320, rect.right - nextEvent.clientX), Math.max(420, rect.width - 360)); workspace.style.gridTemplateColumns = `minmax(0, 1fr) 6px minmax(320px, ${Math.round(width)}px)`; try { if (window.localStorage) window.localStorage.setItem(secondaryWidthKey, String(Math.round(width))); } catch (_) {} }; const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); }; window.addEventListener('pointermove', move); window.addEventListener('pointerup', up); }); } return { applyStoredSecondaryWidth }; }; export const buildPaneRuntime = (paneDescriptor, rootSelector) => { const paneRole = typeof paneDescriptor?.role === 'string' ? paneDescriptor.role : 'primary'; const paneRoot = document.querySelector(`${rootSelector}[data-pane-role="${paneRole}"]`); const observability = document.querySelector(`[data-editor-host-observability][data-pane-role="${paneRole}"]`); if (!(paneRoot instanceof HTMLElement)) return null; if (!paneDescriptor?.aggregate || !paneDescriptor?.bootstrap) return null; return { paneRole, root: paneRoot, observability, aggregate: paneDescriptor.aggregate, bootstrap: paneDescriptor.bootstrap, }; };