2026-06-01 09:29:12 +08:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
const assert = require("node:assert/strict");
|
|
|
|
|
const fs = require("node:fs");
|
|
|
|
|
const path = require("node:path");
|
|
|
|
|
const vm = require("node:vm");
|
|
|
|
|
|
|
|
|
|
const RUNTIME_PATH = path.join(
|
|
|
|
|
process.cwd(),
|
|
|
|
|
"rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function loadConversionRuntime() {
|
|
|
|
|
const source = fs
|
|
|
|
|
.readFileSync(RUNTIME_PATH, "utf8")
|
|
|
|
|
.replace(/\bexport const\s+([A-Za-z0-9_]+)\s*=/g, "const $1 =");
|
2026-07-15 23:12:15 +08:00
|
|
|
const sandbox = {
|
|
|
|
|
console,
|
|
|
|
|
URL,
|
|
|
|
|
window: {
|
|
|
|
|
location: {
|
|
|
|
|
origin: "http://127.0.0.1:3000",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
2026-06-01 09:29:12 +08:00
|
|
|
vm.runInNewContext(
|
|
|
|
|
`${source}\n;globalThis.__mnoteExports = { pageBodyTiptapDocumentSource, pageBodyTiptapDocument };`,
|
|
|
|
|
sandbox,
|
|
|
|
|
{ filename: RUNTIME_PATH },
|
|
|
|
|
);
|
|
|
|
|
return sandbox.__mnoteExports;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function paragraphText(doc) {
|
|
|
|
|
return doc?.content?.[0]?.content?.[0]?.text || "";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 17:17:49 +08:00
|
|
|
function firstTableCellText(doc) {
|
|
|
|
|
return doc?.content?.[0]?.content?.[0]?.content?.[0]?.content?.[0]?.content?.[0]?.text || "";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
function taskItemChecked(doc) {
|
|
|
|
|
return doc?.content?.[0]?.content?.[0]?.attrs?.checked;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-15 23:12:15 +08:00
|
|
|
function firstLinkMarkAttrs(doc) {
|
|
|
|
|
return doc?.content?.[0]?.content?.[0]?.marks?.find((mark) => mark.type === "link")?.attrs || {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
const {
|
|
|
|
|
pageBodyTiptapDocumentSource,
|
|
|
|
|
pageBodyTiptapDocument,
|
|
|
|
|
} = loadConversionRuntime();
|
|
|
|
|
|
|
|
|
|
const blockDocument = {
|
|
|
|
|
type: "doc",
|
|
|
|
|
content: [{
|
|
|
|
|
type: "paragraph",
|
|
|
|
|
attrs: { blockId: "block-truth" },
|
|
|
|
|
content: [{ type: "text", text: "Block truth" }],
|
|
|
|
|
}],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const legacyContent = [{
|
|
|
|
|
id: "legacy-1",
|
|
|
|
|
type: "paragraph",
|
|
|
|
|
content: [{ type: "text", text: "Legacy fallback" }],
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
const localWithBlockAndLegacy = {
|
|
|
|
|
projectionSource: "local_markdown.content",
|
|
|
|
|
blockDocument,
|
|
|
|
|
content: legacyContent,
|
|
|
|
|
};
|
|
|
|
|
assert.equal(
|
|
|
|
|
pageBodyTiptapDocumentSource(localWithBlockAndLegacy),
|
|
|
|
|
"page_aggregate.block_document",
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
paragraphText(pageBodyTiptapDocument(localWithBlockAndLegacy)),
|
|
|
|
|
"Block truth",
|
|
|
|
|
);
|
|
|
|
|
|
2026-06-02 17:17:49 +08:00
|
|
|
const localWithProjectedTable = {
|
|
|
|
|
projectionSource: "local_markdown.content",
|
|
|
|
|
blockDocument: {
|
|
|
|
|
documentId: "local-md:design~2F07-ai~2Fdone~2F7-45-chatonly-api-provider-runtime-v1.md",
|
|
|
|
|
rootBlockIds: ["table-1"],
|
|
|
|
|
blocks: [{
|
|
|
|
|
blockId: "table-1",
|
|
|
|
|
type: "table",
|
|
|
|
|
attrs: {
|
|
|
|
|
tiptapTable: {
|
|
|
|
|
type: "table",
|
|
|
|
|
content: [{
|
|
|
|
|
type: "tableRow",
|
|
|
|
|
content: [{
|
|
|
|
|
type: "tableCell",
|
|
|
|
|
attrs: { colspan: 1, rowspan: 1, colwidth: null },
|
|
|
|
|
content: [{
|
|
|
|
|
type: "paragraph",
|
|
|
|
|
content: [{ type: "text", text: "Provider 类别" }],
|
|
|
|
|
}],
|
|
|
|
|
}],
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
contentNodes: [],
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
assert.equal(
|
|
|
|
|
firstTableCellText(pageBodyTiptapDocument(localWithProjectedTable)),
|
|
|
|
|
"Provider 类别",
|
|
|
|
|
);
|
|
|
|
|
|
2026-06-04 18:51:16 +08:00
|
|
|
const localWithProjectedTodo = {
|
|
|
|
|
projectionSource: "local_markdown.content",
|
|
|
|
|
blockDocument: {
|
|
|
|
|
documentId: "local-md:design~2F07-ai~2Fprocess~2F7-46-document-evidence-retrieval-kernel-v1.md",
|
|
|
|
|
rootBlockIds: ["todo-1"],
|
|
|
|
|
blocks: [{
|
|
|
|
|
blockId: "todo-1",
|
|
|
|
|
type: "todo",
|
|
|
|
|
attrs: { checked: true },
|
|
|
|
|
contentNodes: [{ text: "graph traversal 结果必须带证据引用。", styles: {} }],
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
assert.equal(
|
|
|
|
|
taskItemChecked(pageBodyTiptapDocument(localWithProjectedTodo)),
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-15 23:12:15 +08:00
|
|
|
const localWithPdfAttachment = {
|
|
|
|
|
projectionSource: "local_markdown.content",
|
|
|
|
|
blockDocument: {
|
|
|
|
|
documentId: "local-md:实验资料~2F核磁~2F核磁.md",
|
|
|
|
|
rootBlockIds: ["pdf-1"],
|
|
|
|
|
blocks: [{
|
|
|
|
|
blockId: "pdf-1",
|
|
|
|
|
type: "media",
|
|
|
|
|
attrs: {
|
|
|
|
|
name: "核磁常见杂质化学位移表.pdf",
|
|
|
|
|
sourcePath: ".assets/file/核磁常见杂质化学位移表.pdf",
|
|
|
|
|
},
|
|
|
|
|
contentNodes: [{
|
|
|
|
|
payload: {
|
|
|
|
|
type: "text",
|
|
|
|
|
text: "核磁常见杂质化学位移表.pdf",
|
|
|
|
|
marks: [],
|
|
|
|
|
},
|
|
|
|
|
}],
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
content: [{
|
|
|
|
|
id: "pdf-1",
|
|
|
|
|
type: "media",
|
|
|
|
|
props: {
|
|
|
|
|
name: "核磁常见杂质化学位移表.pdf",
|
|
|
|
|
sourcePath: ".assets/file/核磁常见杂质化学位移表.pdf",
|
|
|
|
|
},
|
|
|
|
|
}],
|
|
|
|
|
attachmentRefs: [{
|
|
|
|
|
rawHref: ".assets/file/核磁常见杂质化学位移表.pdf",
|
|
|
|
|
normalizedHref: ".assets/file/核磁常见杂质化学位移表.pdf",
|
|
|
|
|
label: "核磁常见杂质化学位移表.pdf",
|
|
|
|
|
ext: "pdf",
|
|
|
|
|
fileSize: 49510,
|
|
|
|
|
exists: true,
|
|
|
|
|
}],
|
|
|
|
|
};
|
|
|
|
|
const pdfLinkAttrs = firstLinkMarkAttrs(pageBodyTiptapDocument(localWithPdfAttachment, "", {
|
|
|
|
|
sourceKind: "local_folder",
|
|
|
|
|
rootUri: "file:///tmp/mnote-task522",
|
|
|
|
|
documentId: "local-md:实验资料~2F核磁~2F核磁.md",
|
|
|
|
|
}));
|
|
|
|
|
const pdfOpenUrl = new URL(pdfLinkAttrs.href);
|
|
|
|
|
assert.equal(pdfOpenUrl.pathname, "/api/local-folder/files/open");
|
|
|
|
|
assert.equal(pdfOpenUrl.searchParams.get("rootUri"), "file:///tmp/mnote-task522");
|
|
|
|
|
assert.equal(
|
|
|
|
|
pdfOpenUrl.searchParams.get("path"),
|
|
|
|
|
"实验资料/核磁/.assets/file/核磁常见杂质化学位移表.pdf",
|
|
|
|
|
);
|
|
|
|
|
assert.ok(pdfLinkAttrs.class.includes("mnote-uploaded-attachment-pdf"));
|
|
|
|
|
assert.equal(pdfLinkAttrs["data-file-size"], "48.35 KB");
|
|
|
|
|
|
|
|
|
|
const localWithPageReference = {
|
|
|
|
|
projectionSource: "local_markdown.content",
|
|
|
|
|
blockDocument: {
|
|
|
|
|
documentId: "local-md:liaibo~2F项目~2F项目.md",
|
|
|
|
|
rootBlockIds: ["page-ref-1"],
|
|
|
|
|
blocks: [{
|
|
|
|
|
blockId: "page-ref-1",
|
|
|
|
|
type: "page_reference",
|
|
|
|
|
attrs: {
|
|
|
|
|
title: "完结项目",
|
|
|
|
|
sourcePath: "liaibo/项目/完结项目/完结项目.md",
|
|
|
|
|
},
|
|
|
|
|
contentNodes: [{
|
|
|
|
|
payload: {
|
|
|
|
|
type: "text",
|
|
|
|
|
text: "完结项目",
|
|
|
|
|
marks: [],
|
|
|
|
|
},
|
|
|
|
|
}],
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const pageRefMarkAttrs = firstLinkMarkAttrs(pageBodyTiptapDocument(localWithPageReference, "", {
|
|
|
|
|
sourceKind: "local_folder",
|
|
|
|
|
rootUri: "file:///tmp/mnote-task522",
|
|
|
|
|
documentId: "local-md:liaibo~2F项目~2F项目.md",
|
|
|
|
|
}));
|
|
|
|
|
assert.equal(pageRefMarkAttrs.class, "mnote-page-block-link");
|
|
|
|
|
assert.equal(pageRefMarkAttrs.target, "_self");
|
|
|
|
|
assert.ok(pageRefMarkAttrs.href.startsWith("/documents/local-md:liaibo"));
|
|
|
|
|
const pageRefUrl = new URL(pageRefMarkAttrs.href, "http://127.0.0.1:3000");
|
|
|
|
|
assert.equal(pageRefUrl.searchParams.get("sourceKind"), "local_folder");
|
|
|
|
|
assert.equal(pageRefUrl.searchParams.get("rootUri"), "file:///tmp/mnote-task522");
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
const localLegacyOnly = {
|
|
|
|
|
projectionSource: "local_markdown.content",
|
|
|
|
|
content: legacyContent,
|
|
|
|
|
};
|
|
|
|
|
assert.equal(
|
|
|
|
|
pageBodyTiptapDocumentSource(localLegacyOnly),
|
|
|
|
|
"local_markdown.content",
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
paragraphText(pageBodyTiptapDocument(localLegacyOnly)),
|
|
|
|
|
"Legacy fallback",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const compatLegacyOnly = {
|
|
|
|
|
content: legacyContent,
|
|
|
|
|
};
|
|
|
|
|
assert.equal(
|
|
|
|
|
pageBodyTiptapDocumentSource(compatLegacyOnly),
|
|
|
|
|
"compat.legacy_content",
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
paragraphText(pageBodyTiptapDocument(compatLegacyOnly)),
|
|
|
|
|
"Legacy fallback",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
pageBodyTiptapDocumentSource({}, "Fallback text"),
|
|
|
|
|
"degraded.fallback_text",
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
paragraphText(pageBodyTiptapDocument({}, "Fallback text")),
|
|
|
|
|
"Fallback text",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log("task522 page aggregate compat fallback contract passed");
|