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 16ea7395..e6d88f92 100644 --- a/rust/crates/mnote-web/browser/document-editor-adapter-runtime.js +++ b/rust/crates/mnote-web/browser/document-editor-adapter-runtime.js @@ -160,6 +160,21 @@ import { 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__'; @@ -171,8 +186,24 @@ import { document.body.appendChild(node); } try { - node.textContent = JSON.stringify(aggregate); + 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) } + })); } catch (error) { console.warn('mnote Page Aggregate script 同步失败', error); } diff --git a/rust/crates/mnote-web/browser/document-session-runtime.js b/rust/crates/mnote-web/browser/document-session-runtime.js index d9e6bfa2..0671c8f7 100644 --- a/rust/crates/mnote-web/browser/document-session-runtime.js +++ b/rust/crates/mnote-web/browser/document-session-runtime.js @@ -988,6 +988,8 @@ export const createDocumentSessionRuntime = (dependencies = {}) => { return; } } + session.latestAggregate = nextAggregate; + syncPageAggregateScript(session, nextAggregate); const nextBody = nextAggregate?.body || {}; const nextPermissions = nextAggregate?.head?.permissions || {}; const nextConflictKey = conflictDetectionKeyFromBody(nextBody); diff --git a/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js index 8a2edf3f..98491d20 100644 --- a/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js @@ -648,6 +648,7 @@ export function createSidebarPageSettingsRuntime(context) { aggregate.layout_options = Object.assign({}, nextOptions); script.textContent = JSON.stringify(aggregate); script.setAttribute('data-mnote-page-options-synced-at', String(Date.now())); + script.setAttribute('data-mnote-page-options-local-write-at', String(Date.now())); } } catch (_) {} } diff --git a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js index 6b65974c..6aee85a8 100644 --- a/rust/crates/mnote-web/browser/sidebar-tree-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-tree-runtime.js @@ -2769,6 +2769,15 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim if (isPageSettingsOpen()) renderPageSettingsPopover(); selectSidebarFileTreeDocument(detail.documentId, { scrollIntoView: false }); }); + window.addEventListener('mnote:page-aggregate-synced', function(event) { + var detail = event && event.detail ? event.detail : {}; + if (detail.scriptId && detail.scriptId !== '__MNOTE_PAGE_AGGREGATE__') return; + var documentId = searchText(detail.documentId || ''); + if (documentId && currentDocumentId() && documentId !== currentDocumentId()) return; + pageUiState.pageOptions = null; + applyPageOptionsToShell(); + if (isPageSettingsOpen()) renderPageSettingsPopover(); + }); restoreSidebarTreeTab(); startLocalFolderSidebarWatch(); })(); diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs index ba50b150..9600024d 100644 --- a/rust/crates/mnote-web/src/routes/web_shell.rs +++ b/rust/crates/mnote-web/src/routes/web_shell.rs @@ -2412,6 +2412,13 @@ mod tests { assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("document.createElement('script')")); assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS .contains("syncPageAggregateScript({ pageAggregateScriptId")); + assert!( + DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("data-mnote-page-options-local-write-at") + ); + assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("mnote:page-aggregate-synced")); + assert!( + DOCUMENT_SESSION_RUNTIME_JS.contains("syncPageAggregateScript(session, nextAggregate)") + ); assert!(DOCUMENT_EDITOR_ADAPTER_RUNTIME_JS.contains("document-slash-position-runtime.js")); assert!(DOCUMENT_SLASH_POSITION_RUNTIME_JS.contains("observeSlashMenuPosition")); assert!(DOCUMENT_SLASH_POSITION_RUNTIME_JS.contains("scheduleSlashMenuPosition")); diff --git a/rust/crates/mnote-web/src/ssr/pages/layout.rs b/rust/crates/mnote-web/src/ssr/pages/layout.rs index 9f1ea9ed..bc8f6598 100644 --- a/rust/crates/mnote-web/src/ssr/pages/layout.rs +++ b/rust/crates/mnote-web/src/ssr/pages/layout.rs @@ -908,6 +908,7 @@ mod tests { assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-mnote-dev-hot-reload")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("window.clearInterval(timer)")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote:primary-document-activated")); + assert!(SIDEBAR_TREE_RUNTIME_JS.contains("mnote:page-aggregate-synced")); assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageUiState.pageOptions = null")); }