收口 MNote P0 P1 P2 审查尾项

- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
This commit is contained in:
lix-2026
2026-06-01 09:29:12 +08:00
parent 49a0545148
commit 1882db7681
143 changed files with 29810 additions and 3228 deletions
@@ -64,6 +64,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const resourceTabMru = { primary: [], secondary: [] };
const resourceTabMruMax = 20;
const resourceTabCloseGuardAttribute = 'data-resource-tab-close-guarded';
let onlyofficeBridgeReadyListenerBound = false;
const normalizePaneRole = (paneRole) => String(paneRole || '').trim() === 'secondary' ? 'secondary' : 'primary';
@@ -127,6 +128,30 @@ export const createResourceTabRuntime = (dependencies = {}) => {
}
};
const currentFileTreeWorkspacePath = () => {
try {
const row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path]');
const reader = window.__mnoteFileTreeRuntime?.readWorkspacePathFromRow;
if (row instanceof HTMLElement && typeof reader === 'function') {
const workspacePath = reader(row);
if (workspacePath && typeof workspacePath === 'object') {
return {
...workspacePath,
sourceKind: String(workspacePath.sourceKind || row.getAttribute('data-source-kind') || '').trim(),
rootUri: String(workspacePath.rootUri || row.getAttribute('data-root-uri') || '').trim(),
};
}
}
if (row instanceof HTMLElement) {
return {
sourceKind: String(row.getAttribute('data-source-kind') || '').trim(),
rootUri: String(row.getAttribute('data-root-uri') || '').trim(),
};
}
} catch (_) {}
return null;
};
const currentWebShellDocumentId = () => {
const explicit = typeof currentDocumentId === 'function' ? String(currentDocumentId() || '').trim() : '';
if (explicit) return explicit;
@@ -157,17 +182,23 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const currentWebShellSourceKind = () => {
try {
return currentUrl().searchParams.get('sourceKind') || '';
return currentUrl().searchParams.get('sourceKind')
|| document.body?.dataset?.mnoteSourceKind
|| currentFileTreeWorkspacePath()?.sourceKind
|| '';
} catch (_) {
return '';
return document.body?.dataset?.mnoteSourceKind || currentFileTreeWorkspacePath()?.sourceKind || '';
}
};
const currentWebShellRootUri = () => {
try {
return currentUrl().searchParams.get('rootUri') || '';
return currentUrl().searchParams.get('rootUri')
|| document.body?.dataset?.mnoteRootUri
|| currentFileTreeWorkspacePath()?.rootUri
|| '';
} catch (_) {
return '';
return document.body?.dataset?.mnoteRootUri || currentFileTreeWorkspacePath()?.rootUri || '';
}
};
@@ -196,7 +227,9 @@ export const createResourceTabRuntime = (dependencies = {}) => {
resourceKind: String(fromInput.resourceKind || fromInput.objectKind || resourceKind || '').trim(),
};
}
const id = String(objectIdentity || documentId || assetId || relativePath || '').trim();
const structuredObjectIdentity = objectIdentity && typeof objectIdentity === 'object' ? objectIdentity : null;
const objectIdentityText = structuredObjectIdentity ? '' : String(objectIdentity || '').trim();
const id = String(objectIdentityText || documentId || assetId || relativePath || '').trim();
if (!id) return null;
return {
schema: 'mnote.workspace_path.v1',
@@ -205,7 +238,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
rootUri: String(rootUri || '').trim(),
relativePath: String(relativePath || '').trim(),
documentId: String(documentId || '').trim(),
objectIdentity: String(objectIdentity || '').trim(),
objectIdentity: structuredObjectIdentity || objectIdentityText,
assetId: String(assetId || '').trim(),
resourceKind: String(resourceKind || '').trim(),
};
@@ -371,6 +404,18 @@ export const createResourceTabRuntime = (dependencies = {}) => {
});
};
const officeBridgeDebugForEntry = (entry) => {
if (!entry?.panel || !(entry.panel instanceof HTMLElement)) return null;
const frame = entry.panel.querySelector('iframe.mnote-resource-tab-frame');
if (!(frame instanceof HTMLIFrameElement)) return null;
try {
const debug = frame.contentWindow?.__MNOTE_ONLYOFFICE_DEBUG__;
return debug && typeof debug === 'object' ? debug : null;
} catch (_) {
return null;
}
};
const openEditorsSnapshotEntry = (entry, key, generatedAt = Date.now()) => {
const active = entry?.tab instanceof HTMLElement
? entry.tab.getAttribute('aria-selected') === 'true'
@@ -384,6 +429,13 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const assetId = String(entry?.assetId || entry?.session?.assetId || '').trim();
const kind = normalizeResourceTabKind(entry);
const dirtyState = resourceTabCloseGuardReason(entry?.session);
const officeBridgeDebug = kind === 'office' ? officeBridgeDebugForEntry(entry) : null;
const onlyofficeSessionId = String(
officeBridgeDebug?.bridgeSessionId
|| entry?.onlyofficeSessionId
|| entry?.bridgeSessionId
|| '',
).trim();
return {
objectIdentity,
workspacePath: buildWorkspacePath({
@@ -411,6 +463,11 @@ export const createResourceTabRuntime = (dependencies = {}) => {
dirtyGuard: dirtyState,
assetId,
path: relativePath,
onlyofficeSessionId,
bridgeSessionId: onlyofficeSessionId,
bridgeSessionReady: Boolean(onlyofficeSessionId),
bridgeDocumentId: String(officeBridgeDebug?.documentId || '').trim(),
bridgeAssetId: String(officeBridgeDebug?.assetId || '').trim(),
lastActiveAt: Number(entry?.session?.lastActiveAt || entry?.lastActiveAt || 0) || (active ? generatedAt : 0),
preview: false,
pinned: false,
@@ -427,6 +484,12 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const rootUri = currentWebShellRootUri();
const relativePath = sourceKind === 'local_folder' ? localMarkdownRelativePathFromDocumentId(documentId) : '';
const objectIdentity = `page:${paneRole}`;
const workspaceObjectIdentity = {
objectKind: 'page',
documentId,
blockId: null,
assetId: null,
};
const active = nodes.pageTab instanceof HTMLElement
? nodes.pageTab.getAttribute('aria-selected') === 'true'
: false;
@@ -443,7 +506,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
rootUri,
relativePath,
documentId,
objectIdentity,
objectIdentity: workspaceObjectIdentity,
assetId: '',
resourceKind: 'page',
}),
@@ -1035,6 +1098,17 @@ export const createResourceTabRuntime = (dependencies = {}) => {
markIntendedSlashRoot(entry);
};
const ensureOnlyofficeBridgeReadyListener = () => {
if (onlyofficeBridgeReadyListenerBound) return;
onlyofficeBridgeReadyListenerBound = true;
window.addEventListener('message', (event) => {
if (event.origin !== window.location.origin) return;
const detail = event.data && typeof event.data === 'object' ? event.data : null;
if (!detail || detail.type !== 'mnote:onlyoffice-bridge-ready') return;
syncOpenEditorsSnapshot();
});
};
const openPassiveResourceTab = (entry, input) => {
const href = String(input.officeUrl || input.href || '').trim();
if (entry.kind === 'image') {
@@ -1051,6 +1125,12 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const frame = entry.panel.querySelector('iframe');
if (frame instanceof HTMLIFrameElement) {
frame.title = entry.title;
if (entry.kind === 'office') {
ensureOnlyofficeBridgeReadyListener();
frame.addEventListener('load', () => {
syncOpenEditorsSnapshot();
}, { once: true });
}
frame.src = href;
}
installPassiveResourceWatch(entry);