收口 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
+34 -34
View File
@@ -50,6 +50,7 @@ async function uploadLocalAsset(page, root, documentId, fileName, mimeType, byte
const form = new FormData();
form.append("rootUri", rootUri);
form.append("documentId", documentId);
form.append("uploadIntent", "editor.markdown.attach");
form.append("kind", kind);
form.append("file", new File([new Uint8Array(bytes)], fileName, { type: mimeType }));
const response = await fetch("/api/local-folder/assets/upload", { method: "POST", body: form });
@@ -137,8 +138,8 @@ async function main() {
timeout: UI_TIMEOUT_MS,
});
// ======== Test 1: docx opens in main editor tab with /onlyoffice iframe ========
console.log("Test 1: docx opens in main editor tab with /onlyoffice iframe");
// ======== Test 1: docx opens in main editor tab with Office reader iframe ========
console.log("Test 1: docx opens in main editor tab with Office reader iframe");
await page.evaluate(({ asset, documentId }) => {
window.dispatchEvent(new CustomEvent("tree.asset.open", {
detail: {
@@ -171,16 +172,20 @@ async function main() {
// docx should have badge kind "word"
assert.equal(officeTabInfo.badgeKind, "word", `docx should have badgeKind 'word', got '${officeTabInfo.badgeKind}'`);
assert.ok(officeTabInfo.iframeExists, "Office tab should have an iframe");
assert.ok(officeTabInfo.iframeSrc.includes("/onlyoffice"), `iframe src should include /onlyoffice: ${officeTabInfo.iframeSrc}`);
// Parse iframe URL and verify required params
const iframeUrl = new URL(officeTabInfo.iframeSrc, BASE_URL);
assert.equal(iframeUrl.pathname, "/onlyoffice", `iframe pathname should be /onlyoffice: ${iframeUrl.pathname}`);
assert.ok(
iframeUrl.pathname === "/office-preview" || iframeUrl.pathname === "/onlyoffice",
`iframe pathname should be /office-preview or /onlyoffice: ${iframeUrl.pathname}`,
);
// Check that assetId is in the URL (either in query params or encoded)
const iframeAssetId = iframeUrl.searchParams.get("assetId") || "";
assert.ok(iframeAssetId, "iframe URL should carry assetId param");
assert.ok(iframeUrl.searchParams.has("mode"), "iframe URL should carry mode param");
if (iframeUrl.pathname === "/onlyoffice") {
assert.equal(iframeUrl.searchParams.get("mode"), "view", "default /onlyoffice open should use view mode");
}
assert.ok(iframeUrl.searchParams.has("fileUrl"), "iframe URL should carry fileUrl param");
// The fileUrl for local-folder should point to /api/local-folder/files/open
@@ -304,7 +309,7 @@ async function main() {
await page.waitForTimeout(300);
// ======== Test 4: new-window defaults to view mode ========
console.log("Test 4: new-window opens /onlyoffice with view mode");
console.log("Test 4: new-window opens Office reader in view mode");
const [officePopup] = await Promise.all([
page.waitForEvent("popup", { timeout: UI_TIMEOUT_MS }).catch(() => null),
page.evaluate(({ asset, documentId }) => {
@@ -324,11 +329,16 @@ async function main() {
await officePopup.waitForLoadState("domcontentloaded", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
const popupUrl = new URL(officePopup.url());
console.log(` New-window URL: ${popupUrl.toString()}`);
assert.equal(popupUrl.pathname, "/onlyoffice", `new-window pathname should be /onlyoffice: ${popupUrl.pathname}`);
assert.ok(
popupUrl.pathname === "/office-preview" || popupUrl.pathname === "/onlyoffice",
`new-window pathname should be /office-preview or /onlyoffice: ${popupUrl.pathname}`,
);
// new-window 是浏览器目标,不代表编辑权限;默认仍应只读
const popupMode = popupUrl.searchParams.get("mode") || "";
assert.equal(popupMode, "view", `new-window mode should be 'view', got '${popupMode}'`);
if (popupUrl.pathname === "/onlyoffice") {
assert.equal(popupMode, "view", `new-window mode should be 'view', got '${popupMode}'`);
}
// Check assetId is present
const popupAssetId = popupUrl.searchParams.get("assetId") || "";
@@ -387,21 +397,17 @@ async function main() {
onlyofficeUrl.searchParams.set("assetId", "test-asset-id");
onlyofficeUrl.searchParams.set("mode", "edit");
const response = await page.goto(onlyofficeUrl.toString(), {
waitUntil: "domcontentloaded",
timeout: UI_TIMEOUT_MS,
});
// Verify the page rendered
const pageContent = await page.evaluate(() => {
return {
title: document.title,
hasFrameContainer: !!document.getElementById("onlyoffice-frame"),
hasErrorContainer: !!document.getElementById("onlyoffice-error"),
hasScript: typeof window.__MNOTE_ONLYOFFICE_READY__ !== "undefined",
initialConfig: typeof window.initial !== "undefined",
};
});
const response = await context.request.get(onlyofficeUrl.toString(), { timeout: UI_TIMEOUT_MS });
assert.equal(response.status(), 200, `/onlyoffice should respond 200, got ${response.status()}`);
const onlyofficeHtml = await response.text();
// Verify the page rendered without executing DocEditor boot against a fake file path.
const pageContent = {
hasFrameContainer: onlyofficeHtml.includes('id="onlyoffice-frame"'),
hasErrorContainer: onlyofficeHtml.includes('id="onlyoffice-error"'),
hasScript: onlyofficeHtml.includes("window.__MNOTE_ONLYOFFICE_READY__"),
initialConfig: onlyofficeHtml.includes("const initial ="),
};
console.log(` /onlyoffice page: ${JSON.stringify(pageContent)}`);
assert.ok(pageContent.hasFrameContainer, "/onlyoffice should have #onlyoffice-frame container");
@@ -409,17 +415,11 @@ async function main() {
assert.ok(pageContent.hasScript, "/onlyoffice should have runtime script");
// Verify the config has correct document URL and callback URL source
const scriptContent = await page.evaluate(() => {
const scripts = document.querySelectorAll("script");
const relevant = Array.from(scripts).filter(s => s.textContent && s.textContent.includes("buildCallbackUrl"));
const text = relevant.length > 0 ? relevant[0].textContent : "";
return {
hasCallbackUrlBuilder: text.includes("buildCallbackUrl"),
hasResolveDocumentUrl: text.includes("resolveDocumentUrl"),
hasProxyLogic: text.includes("/api/onlyoffice/proxy"),
snippet: text.substring(0, 200),
};
});
const scriptContent = {
hasCallbackUrlBuilder: onlyofficeHtml.includes("buildCallbackUrl"),
hasResolveDocumentUrl: onlyofficeHtml.includes("resolveDocumentUrl"),
hasProxyLogic: onlyofficeHtml.includes("/api/onlyoffice/proxy"),
};
console.log(` Script: hasCallbackUrl=${scriptContent.hasCallbackUrlBuilder}, hasResolveDocumentUrl=${scriptContent.hasResolveDocumentUrl}`);
assert.ok(scriptContent.hasCallbackUrlBuilder, "OnlyOffice page should define buildCallbackUrl");