@godcreator/word-report 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
- import { a as walk, r as stableValue, s as ReportContentError, t as assembleDocument } from "./assemble-BpllQNVY.mjs";
1
+ import { a as walk, r as stableValue, s as ReportContentError, t as assembleDocument } from "./assemble-BEt9_xsB.mjs";
2
2
  import remarkFrontmatter from "remark-frontmatter";
3
3
  import remarkGfm from "remark-gfm";
4
4
  import remarkMath from "remark-math";
5
5
  import remarkMdx from "remark-mdx";
6
6
  import remarkParse from "remark-parse";
7
7
  import { unified } from "unified";
8
- import { parse, stringify } from "yaml";
8
+ import { parse } from "yaml";
9
9
  //#region src/core/mdx/parse.ts
10
10
  const mdxParser = unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkMdx).use(remarkGfm).use(remarkMath);
11
11
  /** 源位置写成「行:列」,放在 ReportContentError 的 pointer 里 */
@@ -169,14 +169,14 @@ const MARK_ELEMENTS = {
169
169
  };
170
170
  /** 只能单独成段的组件:写进一段文字中间时报错 */
171
171
  const BLOCK_COMPONENTS = /* @__PURE__ */ new Set([
172
- "Figure",
173
- "Figures",
174
- "Table",
175
- "Abstract",
176
- "Bibliography",
177
- "Reference",
178
- "Caption",
179
- "Panel",
172
+ "WordFigure",
173
+ "WordFigures",
174
+ "WordTable",
175
+ "WordAbstract",
176
+ "WordBibliography",
177
+ "WordReference",
178
+ "WordCaption",
179
+ "WordPanel",
180
180
  "table",
181
181
  "tr",
182
182
  "th",
@@ -253,7 +253,7 @@ function collectInlines(context, nodes, marks, out) {
253
253
  if (!isCommentOnlyExpression(node)) failAt(context, node, "正文里不能写 {…} 表达式;只想写花括号就写成 \\{ \\},注释写成 {/* … */}");
254
254
  break;
255
255
  case "image":
256
- case "imageReference": failAt(context, node, "Markdown 图片 ![…](…) 不收:图写成 <Figure src=\"assets/…\">图题</Figure>");
256
+ case "imageReference": failAt(context, node, "Markdown 图片 ![…](…) 不收:图写成 <WordFigure src=\"assets/…\">图题</WordFigure>");
257
257
  case "linkReference": failAt(context, node, "不支持引用式链接 [文字][名字]:链接写成 [文字](https://…)");
258
258
  case "delete": failAt(context, node, "不支持删除线 ~~…~~");
259
259
  case "html": failAt(context, node, "不支持原始 HTML");
@@ -268,16 +268,16 @@ function collectElement(context, element, marks, out) {
268
268
  return;
269
269
  }
270
270
  switch (name) {
271
- case "Ref": {
271
+ case "WordRef": {
272
272
  const attributes = attributesOf(context, element, {
273
273
  to: "required",
274
274
  show: "optional"
275
275
  });
276
276
  rejectChildren(context, element);
277
- if (/\s/.test(attributes["to"].trim())) failAt(context, element, "<Ref> 的 to 只能写一个 id");
277
+ if (/\s/.test(attributes["to"].trim())) failAt(context, element, "<WordRef> 的 to 只能写一个 id");
278
278
  const [target] = idTargetsOf(context, element, attributes["to"]);
279
279
  const show = attributes["show"];
280
- if (show !== void 0 && show !== "number" && show !== "title") failAt(context, element, `<Ref> 的 show 只能是 number 或 title(不写就显示「图 3-2」这种全称),这里写的是「${show}」`);
280
+ if (show !== void 0 && show !== "number" && show !== "title") failAt(context, element, `<WordRef> 的 show 只能是 number 或 title(不写就显示「图 3-2」这种全称),这里写的是「${show}」`);
281
281
  const display = show ?? "label";
282
282
  out.push(located(context, context.ids.request({
283
283
  type: "ref",
@@ -287,7 +287,7 @@ function collectElement(context, element, marks, out) {
287
287
  }, "ref"), element));
288
288
  return;
289
289
  }
290
- case "Cite": {
290
+ case "WordCite": {
291
291
  const attributes = attributesOf(context, element, { to: "required" });
292
292
  rejectChildren(context, element);
293
293
  out.push(located(context, context.ids.request({
@@ -297,6 +297,17 @@ function collectElement(context, element, marks, out) {
297
297
  }, "cite"), element));
298
298
  return;
299
299
  }
300
+ case "WordPlainMath": {
301
+ const attributes = attributesOf(context, element, { tex: "required" });
302
+ const plainText = plainMathRunsOf(context, element);
303
+ out.push(located(context, context.ids.request({
304
+ type: "math",
305
+ id: "",
306
+ latex: attributes["tex"],
307
+ plainText
308
+ }, "m"), element));
309
+ return;
310
+ }
300
311
  case "br":
301
312
  attributesOf(context, element, {});
302
313
  rejectChildren(context, element);
@@ -304,9 +315,36 @@ function collectElement(context, element, marks, out) {
304
315
  return;
305
316
  default:
306
317
  if (name !== null && BLOCK_COMPONENTS.has(name)) failAt(context, element, `<${name}> 要单独成段,前后空一行,不写在一段文字中间`);
307
- failAt(context, element, `认不出组件 <${name ?? ""}>:行内只能用 Ref、Cite、sub、sup、strong、em、br`);
318
+ failAt(context, element, unknownComponentMessage(name ?? "", "行内只能用 WordRef、WordCite、WordPlainMath 与 HTML 标签 sub、sup、strong、em、br"));
308
319
  }
309
320
  }
321
+ /** 认不出的元素:大写开头、不以 Word 开头的单独说明——报告的组件名都以 Word 开头,小写的只认几个 HTML 标签 */
322
+ function unknownComponentMessage(name, allowed) {
323
+ if (/^[A-Z]/.test(name) && !name.startsWith("Word")) return `报告里的组件都以 Word 开头,<${name}> 不认:${allowed}`;
324
+ return `认不出组件 <${name}>:${allowed}`;
325
+ }
326
+ const PLAIN_MATH_MARKS = [
327
+ "em",
328
+ "sub",
329
+ "sup"
330
+ ];
331
+ /** <WordPlainMath> 里写给 Word 的文字:只收文字、斜体、下标、上标 */
332
+ function plainMathRunsOf(context, element) {
333
+ const usage = "<WordPlainMath tex=\"…\"> 里写 Word 用的文字,只能用文字、*斜体*、<sub>、<sup>";
334
+ const inlines = inlinesOf(context, element.children);
335
+ if (!inlines.length) failAt(context, element, `${usage},这里是空的`);
336
+ return inlines.map((inline) => {
337
+ if (inline.type !== "text") failAt(context, element, `${usage},这里出现了 ${inline.type}`);
338
+ const marks = inline.marks ?? [];
339
+ const unsupported = marks.find((mark) => !PLAIN_MATH_MARKS.includes(mark));
340
+ if (unsupported !== void 0) failAt(context, element, `${usage},这里出现了 ${unsupported}`);
341
+ if (marks.includes("sub") && marks.includes("sup")) failAt(context, element, "<WordPlainMath> 里同一段文字不能既是下标又是上标");
342
+ return marks.length ? {
343
+ text: inline.text,
344
+ marks
345
+ } : { text: inline.text };
346
+ });
347
+ }
310
348
  function linkTextOf(context, link) {
311
349
  const inlines = [];
312
350
  collectInlines(context, link.children, [], inlines);
@@ -343,7 +381,7 @@ function plainTextOf(inlines) {
343
381
  return inlines.map((inline) => {
344
382
  switch (inline.type) {
345
383
  case "text": return inline.text;
346
- case "math": return `$${inline.latex}$`;
384
+ case "math": return inline.plainText ? inline.plainText.map((run) => run.text).join("") : `$${inline.latex}$`;
347
385
  case "link": return inline.text;
348
386
  case "break": return " ";
349
387
  default: return "";
@@ -386,15 +424,15 @@ const BLOCK_NAMES = {
386
424
  table: "表",
387
425
  equation: "公式",
388
426
  code: "代码块",
389
- bibliography: "<Bibliography />",
390
- abstract: "<Abstract>"
427
+ bibliography: "<WordBibliography />",
428
+ abstract: "<WordAbstract>"
391
429
  };
392
430
  const PLACE_NAMES = {
393
431
  top: "正文",
394
432
  listItem: "列表项",
395
433
  cell: "表格单元格",
396
434
  note: "脚注",
397
- abstract: "<Abstract>"
435
+ abstract: "<WordAbstract>"
398
436
  };
399
437
  const explicitOrRequested = (context, node, rawId, source, kind) => {
400
438
  if (rawId !== void 0) node.id = explicitIdOf(rawId, context.file, positionOf(source));
@@ -542,25 +580,25 @@ function codeOf(context, node) {
542
580
  }
543
581
  function componentOf(context, element, place) {
544
582
  switch (element.name) {
545
- case "Figure": return figureOf(context, element);
546
- case "Figures": return multiFigureOf(context, element);
547
- case "Table": return numberedTableOf(context, element);
548
- case "Abstract": return abstractOf(context, element, place);
549
- case "Bibliography":
583
+ case "WordFigure": return figureOf(context, element);
584
+ case "WordFigures": return multiFigureOf(context, element);
585
+ case "WordTable": return numberedTableOf(context, element);
586
+ case "WordAbstract": return abstractOf(context, element, place);
587
+ case "WordBibliography":
550
588
  attributesOf(context, element, {});
551
- if (element.children.some((child) => !isCommentOnlyExpression(child))) failAt(context, element, "<Bibliography /> 不收子内容");
589
+ if (element.children.some((child) => !isCommentOnlyExpression(child))) failAt(context, element, "<WordBibliography /> 不收子内容");
552
590
  return located(context, context.ids.request({
553
591
  type: "bibliography",
554
592
  id: ""
555
593
  }, "bib"), element);
556
- case "Reference": return failAt(context, element, "<Reference> 只能写在报告目录的 references.mdx 里");
557
- case "Caption": return failAt(context, element, "<Caption> 只能写在 <Table> 或 <Figures> 里");
558
- case "Panel": return failAt(context, element, "<Panel> 只能写在 <Figures> 里");
594
+ case "WordReference": return failAt(context, element, "<WordReference> 只能写在报告目录的 references.mdx 里");
595
+ case "WordCaption": return failAt(context, element, "<WordCaption> 只能写在 <WordTable> 或 <WordFigures> 里");
596
+ case "WordPanel": return failAt(context, element, "<WordPanel> 只能写在 <WordFigures> 里");
559
597
  case "table":
560
598
  case "tr":
561
599
  case "th":
562
- case "td": return failAt(context, element, `<${element.name}> 只能写在 <Table> 里:不编号的简单表格直接写 GFM 表格`);
563
- default: return failAt(context, element, `认不出组件 <${element.name ?? ""}>:块级只能用 Figure、Figures、Table、Abstract、Bibliography`);
600
+ case "td": return failAt(context, element, `<${element.name}> 只能写在 <WordTable> 里:不编号的简单表格直接写 GFM 表格`);
601
+ default: return failAt(context, element, unknownComponentMessage(element.name ?? "", "块级只能用 WordFigure、WordFigures、WordTable、WordAbstract、WordBibliography"));
564
602
  }
565
603
  }
566
604
  function captionOf(context, element, what) {
@@ -574,7 +612,7 @@ function figureOf(context, element) {
574
612
  src: "required",
575
613
  alt: "optional"
576
614
  });
577
- const caption = captionOf(context, element, "<Figure> 的图题");
615
+ const caption = captionOf(context, element, "<WordFigure> 的图题");
578
616
  return explicitOrRequested(context, {
579
617
  type: "figure",
580
618
  id: "",
@@ -590,12 +628,12 @@ function multiFigureOf(context, element) {
590
628
  columns: "required"
591
629
  });
592
630
  const children = elementChildrenOf(context, element);
593
- const captions = children.filter((child) => child.name === "Caption");
594
- if (captions.length !== 1) failAt(context, element, "<Figures> 里要有且只有一个 <Caption> 写整个图的图题");
595
- for (const child of children) if (child.name !== "Caption" && child.name !== "Panel") failAt(context, child, `<Figures> 里只能放 <Caption> 与 <Panel>,不能放 <${child.name}>`);
631
+ const captions = children.filter((child) => child.name === "WordCaption");
632
+ if (captions.length !== 1) failAt(context, element, "<WordFigures> 里要有且只有一个 <WordCaption> 写整个图的图题");
633
+ for (const child of children) if (child.name !== "WordCaption" && child.name !== "WordPanel") failAt(context, child, `<WordFigures> 里只能放 <WordCaption> 与 <WordPanel>,不能放 <${child.name}>`);
596
634
  attributesOf(context, captions[0], {});
597
- const caption = captionOf(context, captions[0], "<Figures> 的图题");
598
- const panels = children.filter((child) => child.name === "Panel").map((panel) => panelOf(context, panel));
635
+ const caption = captionOf(context, captions[0], "<WordFigures> 的图题");
636
+ const panels = children.filter((child) => child.name === "WordPanel").map((panel) => panelOf(context, panel));
599
637
  const figure = {
600
638
  type: "figure",
601
639
  id: "",
@@ -617,7 +655,7 @@ function panelOf(context, element) {
617
655
  });
618
656
  const caption = inlinesOf(context, phrasingChildrenOf(context, element));
619
657
  const alt = attributes["alt"] ?? plainTextOf(caption);
620
- if (!alt) failAt(context, element, "<Panel> 没有小标题时要写 alt");
658
+ if (!alt) failAt(context, element, "<WordPanel> 没有小标题时要写 alt");
621
659
  const panel = {
622
660
  id: "",
623
661
  row: positiveIntegerOf(context, element, "row", attributes["row"]),
@@ -633,12 +671,12 @@ function numberedTableOf(context, element) {
633
671
  const attributes = attributesOf(context, element, { id: "optional" });
634
672
  const parts = element.children.filter((child) => !isCommentOnlyExpression(child) && !isWhitespaceText(child)).flatMap((child) => child.type === "paragraph" ? child.children.filter((inner) => !isWhitespaceText(inner)) : [child]);
635
673
  let caption;
636
- if (parts[0]?.name === "Caption") {
674
+ if (parts[0]?.name === "WordCaption") {
637
675
  attributesOf(context, parts[0], {});
638
- caption = captionOf(context, parts.shift(), "<Caption> 的表题");
676
+ caption = captionOf(context, parts.shift(), "<WordCaption> 的表题");
639
677
  }
640
678
  const body = parts[0];
641
- if (parts.length !== 1 || !body || !(body.type === "table" || body.name === "table")) failAt(context, element, "<Table> 里先写可省的 <Caption>,再写一张表(GFM 表格或 <table>),别的不放");
679
+ if (parts.length !== 1 || !body || !(body.type === "table" || body.name === "table")) failAt(context, element, "<WordTable> 里先写可省的 <WordCaption>,再写一张表(GFM 表格或 <table>),别的不放");
642
680
  return body.type === "table" ? tableFromGfm(context, body, true, caption, attributes["id"] ?? null, element) : tableFromJsx(context, body, caption, attributes["id"] ?? null, element);
643
681
  }
644
682
  function finishedTable(context, table, caption, rawId, source) {
@@ -719,13 +757,13 @@ function tableFromJsx(context, element, caption, rawId, source) {
719
757
  }, caption, rawId, source);
720
758
  }
721
759
  function abstractOf(context, element, place) {
722
- if (context.role !== "index" || place !== "top") failAt(context, element, "<Abstract> 只能写在 index.mdx 的正文最前面");
760
+ if (context.role !== "index" || place !== "top") failAt(context, element, "<WordAbstract> 只能写在 index.mdx 的正文最前面");
723
761
  const attributes = attributesOf(context, element, {
724
762
  keywords: "required",
725
763
  title: "optional"
726
764
  });
727
765
  const keywords = attributes["keywords"].split(";").map((keyword) => keyword.trim());
728
- if (keywords.some((keyword) => !keyword)) failAt(context, element, "<Abstract> 的 keywords 用全角分号「;」隔开,每个关键词都不能空");
766
+ if (keywords.some((keyword) => !keyword)) failAt(context, element, "<WordAbstract> 的 keywords 用全角分号「;」隔开,每个关键词都不能空");
729
767
  const abstract = {
730
768
  type: "abstract",
731
769
  id: "",
@@ -881,13 +919,13 @@ function referencesOf(context, root) {
881
919
  for (const node of root.children) {
882
920
  if (isCommentOnlyExpression(node)) continue;
883
921
  const element = node.type === "paragraph" && node.children.length === 1 ? node.children[0] : node;
884
- if (element.type !== "mdxJsxFlowElement" && element.type !== "mdxJsxTextElement" || element.name !== "Reference") failAt(context, node, "references.mdx 里只能写 <Reference id=\"…\" url=\"…\">著录文字</Reference>,一条一段");
922
+ if (element.type !== "mdxJsxFlowElement" && element.type !== "mdxJsxTextElement" || element.name !== "WordReference") failAt(context, node, "references.mdx 里只能写 <WordReference id=\"…\" url=\"…\">著录文字</WordReference>,一条一段");
885
923
  const attributes = attributesOf(context, element, {
886
924
  id: "required",
887
925
  url: "optional"
888
926
  });
889
927
  const text = inlinesOf(context, phrasingChildrenOf(context, element));
890
- if (text.length !== 1 || text[0].type !== "text" || text[0].marks) failAt(context, element, "<Reference> 的著录文字只能是一行纯文字");
928
+ if (text.length !== 1 || text[0].type !== "text" || text[0].marks) failAt(context, element, "<WordReference> 的著录文字只能是一行纯文字");
891
929
  const entry = {
892
930
  id: explicitIdOf(attributes["id"], context.file, positionOf(element)),
893
931
  text: text[0].text
@@ -995,295 +1033,4 @@ function assembleMdxReport(input, options = {}) {
995
1033
  };
996
1034
  }
997
1035
  //#endregion
998
- //#region src/core/mdx/emit.ts
999
- const UNICODE_PUNCTUATION = /[\p{P}\p{S}]/u;
1000
- const MARK_WRAP_ORDER = [
1001
- "code",
1002
- "sub",
1003
- "sup",
1004
- "em",
1005
- "strong"
1006
- ];
1007
- /** Markdown 与 MDX 的特殊字符一律加反斜杠;GFM 会把网址与邮箱自动变成链接,冒号、点与 @ 也拆开 */
1008
- function escapedText(text) {
1009
- return text.replace(/[\\`*_{}[\]<>$|~&#@]/g, (character) => `\\${character}`).replace(/:\/\//g, "\\://").replace(/www\./gi, (match) => `${match.slice(0, 3)}\\.`);
1010
- }
1011
- /** 一段的开头像列表、标题、引用的,再加一层转义 */
1012
- function escapedLineStart(line) {
1013
- return line.replace(/^(\d+)([.)])/, "$1\\$2").replace(/^([-+])(\s|$)/, "\\$1$2");
1014
- }
1015
- function codeSpan(text) {
1016
- const longest = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
1017
- const fence = "`".repeat(longest + 1);
1018
- const padding = text.startsWith("`") || text.endsWith("`") ? " " : "";
1019
- return `${fence}${padding}${text}${padding}${fence}`;
1020
- }
1021
- /** 加粗、斜体在内容首尾不是空白与标点时用 ** 与 *(中文旁边才认得出),否则用 <strong> 与 <em> */
1022
- function wrappedWithMark(inner, plain, mark) {
1023
- if (mark === "sub" || mark === "sup") return `<${mark}>${inner}</${mark}>`;
1024
- const first = plain[0], last = plain.at(-1);
1025
- if (first !== void 0 && last !== void 0 && !/\s/.test(first) && !/\s/.test(last) && !UNICODE_PUNCTUATION.test(first) && !UNICODE_PUNCTUATION.test(last)) return mark === "strong" ? `**${inner}**` : `*${inner}*`;
1026
- return `<${mark}>${inner}</${mark}>`;
1027
- }
1028
- function attributeOf(name, value, problems, at) {
1029
- if (!value.includes("\"")) return ` ${name}="${value}"`;
1030
- if (!value.includes("'")) return ` ${name}='${value}'`;
1031
- problems.push(`${at}:${name} 里同时有单引号和双引号,写不成组件属性`);
1032
- return "";
1033
- }
1034
- var MdxWriter = class {
1035
- problems = [];
1036
- images = Object.create(null);
1037
- keptIds;
1038
- assets;
1039
- constructor(keptIds, assets) {
1040
- this.keptIds = keptIds;
1041
- this.assets = assets;
1042
- }
1043
- idAttribute(id, at) {
1044
- return id !== void 0 && this.keptIds.has(id) ? attributeOf("id", id, this.problems, at) : "";
1045
- }
1046
- /** 图片按文件名放进新报告的 assets/:同名不同图报错 */
1047
- imagePath(src, at) {
1048
- const original = this.assets[src] ?? src;
1049
- const name = original.slice(original.lastIndexOf("/") + 1);
1050
- const previous = this.images[name];
1051
- if (previous !== void 0 && previous !== src) this.problems.push(`${at}:图片 ${name} 和 ${previous} 同名,放进同一个 assets/ 会互相覆盖`);
1052
- this.images[name] = src;
1053
- return `assets/${name}`;
1054
- }
1055
- inlines(inlines, at) {
1056
- let previousWasMath = false;
1057
- return inlines.map((inline, index) => {
1058
- const here = `${at}/${index}`;
1059
- if (inline.type === "math" && previousWasMath) this.problems.push(`${here}:两个行内公式紧挨着,写成 $…$$…$ 会被读成一个`);
1060
- previousWasMath = inline.type === "math";
1061
- switch (inline.type) {
1062
- case "text": {
1063
- let written = (inline.marks ?? []).includes("code") ? codeSpan(inline.text) : escapedText(inline.text);
1064
- for (const mark of MARK_WRAP_ORDER) if (mark !== "code" && inline.marks?.includes(mark)) written = wrappedWithMark(written, inline.text, mark);
1065
- return written;
1066
- }
1067
- case "math":
1068
- if (inline.latex.includes("$") || /[\r\n]/.test(inline.latex)) this.problems.push(`${here}:行内公式里有 $ 或换行,写不成 $…$`);
1069
- return `$${inline.latex}$`;
1070
- case "ref": return `<Ref to="${inline.target}"${inline.display === "label" ? "" : ` show="${inline.display}"`} />`;
1071
- case "cite": return `<Cite to="${inline.targets.join(" ")}" />`;
1072
- case "link": return `[${escapedText(inline.text)}](${/[\s()<>]/.test(inline.href) ? `<${inline.href}>` : inline.href})`;
1073
- case "note_ref": return `[^${inline.target}]`;
1074
- case "break": return "<br />";
1075
- }
1076
- }).join("");
1077
- }
1078
- /** 段落各占一行写出;表格单元格里的段落不在行首,不用防它被读成列表,空段落就是空格子 */
1079
- paragraph(content, at, insideCell = false) {
1080
- if (!content.length && !insideCell) this.problems.push(`${at}:没有内容的段落在 MDX 里写不出来`);
1081
- const written = this.inlines(content, `${at}/content`);
1082
- return insideCell ? written : escapedLineStart(written);
1083
- }
1084
- /** 一个单元格:只有一段(含空段,即空格子)写成行内内容,否则 null(要写成块);没有块的格子编译回来是一个空段 */
1085
- cellInline(blocks, at) {
1086
- if (blocks.length === 0) return "";
1087
- const only = blocks.length === 1 ? blocks[0] : void 0;
1088
- return only?.type === "paragraph" ? this.paragraph(only.content, `${at}/blocks/0`, true) : null;
1089
- }
1090
- /** 块之间空一行;相邻两个同类列表中间放一个注释,否则会并成一个列表 */
1091
- blocks(blocks, at, depth, parentNumbering) {
1092
- const lines = [];
1093
- blocks.forEach((block, index) => {
1094
- const previous = blocks[index - 1];
1095
- if (block.type === "list" && previous?.type === "list" && previous.ordered === block.ordered) lines.push("{/* 两个列表的分界 */}");
1096
- lines.push(this.block(block, `${at}/${index}`, depth, parentNumbering));
1097
- });
1098
- return lines;
1099
- }
1100
- block(block, at, depth, parentNumbering) {
1101
- switch (block.type) {
1102
- case "section": return this.section(block, at, depth, parentNumbering);
1103
- case "paragraph": return this.paragraph(block.content, at);
1104
- case "list": return this.list(block, at, "");
1105
- case "figure": return this.figure(block, at);
1106
- case "table": return this.table(block, at);
1107
- case "equation": {
1108
- if (!block.numbered) {
1109
- if (block.latex.includes("$$")) this.problems.push(`${at}:公式里有 $$,写不成 $$ 块`);
1110
- if (this.keptIds.has(block.id)) this.problems.push(`${at}:不编号的公式被引用或写在 layout.json 里,MDX 里不编号的公式没有 id`);
1111
- return `$$\n${block.latex}\n$$`;
1112
- }
1113
- const fence = "`".repeat(Math.max(3, ...(block.latex.match(/`{3,}/g) ?? []).map((run) => run.length + 1)));
1114
- return `${fence}math${this.keptIds.has(block.id) ? ` ${block.id}` : ""}\n${block.latex}\n${fence}`;
1115
- }
1116
- case "code": {
1117
- if (block.language === "math") this.problems.push(`${at}:语言是 math 的代码块在 MDX 里会被当成编号公式`);
1118
- const fence = "`".repeat(Math.max(3, ...(block.text.match(/`{3,}/g) ?? []).map((run) => run.length + 1)));
1119
- return `${fence}${block.language}\n${block.text}\n${fence}`;
1120
- }
1121
- case "bibliography":
1122
- this.problems.push(`${at}:<Bibliography /> 只能放在文末(不写时组装会补上)`);
1123
- return "";
1124
- case "abstract": return this.abstract(block, at);
1125
- }
1126
- }
1127
- abstract(block, at) {
1128
- if (block.keywords.some((keyword) => keyword.includes(";"))) this.problems.push(`${at}:关键词里有全角分号,<Abstract keywords> 用它分隔`);
1129
- const title = block.title !== void 0 ? attributeOf("title", block.title, this.problems, at) : "";
1130
- const paragraphs = block.paragraphs.map((paragraph, index) => this.paragraph(paragraph.content, `${at}/paragraphs/${index}`));
1131
- return `<Abstract keywords="${block.keywords.join(";")}"${title}>\n\n${paragraphs.join("\n\n")}\n\n</Abstract>`;
1132
- }
1133
- section(section, at, depth, parentNumbering) {
1134
- if (depth > 6) this.problems.push(`${at}:标题超过六级`);
1135
- const expected = parentNumbering === "none" ? "none" : "decimal";
1136
- if (depth > 1 && section.numbering !== expected) this.problems.push(`${at}:${parentNumbering} 节下面的 ${section.numbering} 子节在 MDX 里写不出来(子节跟着上级编号)`);
1137
- if (/\[#[^\]]*\]\s*$/.test(section.title)) this.problems.push(`${at}:标题末尾的 [#…] 会被当成节的 id`);
1138
- return [`${"#".repeat(depth)} ${escapedText(section.title)}${this.keptIds.has(section.id) ? ` [#${section.id}]` : ""}`, ...this.blocks(section.children, `${at}/children`, depth + 1, section.numbering)].join("\n\n");
1139
- }
1140
- list(list, at, indent) {
1141
- return list.items.map((item, index) => {
1142
- const marker = list.ordered ? `${(list.start ?? 1) + index}. ` : "- ";
1143
- const continuation = indent + " ".repeat(marker.length);
1144
- return item.blocks.map((block, blockIndex) => {
1145
- const here = `${at}/items/${index}/blocks/${blockIndex}`;
1146
- return block.type === "list" ? this.list(block, here, continuation) : this.paragraph(block.content, here);
1147
- }).map((part, partIndex) => partIndex === 0 ? `${indent}${marker}${part.trimStart()}` : part.startsWith(continuation) ? part : `${continuation}${part}`).join("\n\n");
1148
- }).join("\n");
1149
- }
1150
- figure(figure, at) {
1151
- const caption = this.inlines(figure.caption, `${at}/caption`);
1152
- if (!figure.panels) {
1153
- const alt = figure.alt === plainTextOf(figure.caption) ? "" : attributeOf("alt", figure.alt, this.problems, at);
1154
- return `<Figure${this.idAttribute(figure.id, at)} src="${this.imagePath(figure.src, at)}"${alt}>${caption}</Figure>`;
1155
- }
1156
- const panels = figure.panels.map((panel, index) => {
1157
- const here = `${at}/panels/${index}`;
1158
- const panelCaption = panel.caption ? this.inlines(panel.caption, `${here}/caption`) : "";
1159
- const alt = panel.caption && panel.alt === plainTextOf(panel.caption) ? "" : attributeOf("alt", panel.alt, this.problems, here);
1160
- const open = ` <Panel${this.idAttribute(panel.id, here)} row="${panel.row}" column="${panel.column}"${attributeOf("label", panel.label, this.problems, here)} src="${this.imagePath(panel.src, here)}"${alt}`;
1161
- return panelCaption ? `${open}>${panelCaption}</Panel>` : `${open} />`;
1162
- });
1163
- return [
1164
- `<Figures${this.idAttribute(figure.id, at)} rows="${figure.rows}" columns="${figure.columns}">`,
1165
- ` <Caption>${caption}</Caption>`,
1166
- ...panels,
1167
- "</Figures>"
1168
- ].join("\n");
1169
- }
1170
- table(table, at) {
1171
- const caption = table.caption ? this.inlines(table.caption, `${at}/caption`) : void 0;
1172
- if (!table.numbered) {
1173
- if (caption !== void 0) this.problems.push(`${at}:带表题的不编号表格在 MDX 里写不出来`);
1174
- if (!this.fitsGfm(table)) this.problems.push(`${at}:不编号的表格只能写成 GFM 表格,这张有合并单元格、多行表头或多段内容`);
1175
- return this.gfmTable(table, at);
1176
- }
1177
- const body = this.fitsGfm(table) ? this.gfmTable(table, at) : this.jsxTable(table, at);
1178
- return [
1179
- `<Table${this.idAttribute(table.id, at)}>`,
1180
- ...caption !== void 0 ? [` <Caption>${caption}</Caption>`] : [],
1181
- "",
1182
- body,
1183
- "",
1184
- "</Table>"
1185
- ].join("\n");
1186
- }
1187
- fitsGfm(table) {
1188
- const fitsOneGfmCell = (blocks) => blocks.length === 0 || blocks.length === 1 && blocks[0].type === "paragraph" && blocks[0].content.every((inline) => inline.type !== "break" && (inline.type !== "math" || !inline.latex.includes("|")));
1189
- return table.headerRows === 1 && table.rows.every((row) => row.cells.length === table.columns.length && row.cells.every((cell) => !cell.rowSpan && !cell.colSpan && fitsOneGfmCell(cell.blocks)));
1190
- }
1191
- gfmTable(table, at) {
1192
- const lines = table.rows.map((row, rowIndex) => `| ${row.cells.map((cell, cellIndex) => this.cellInline(cell.blocks, `${at}/rows/${rowIndex}/cells/${cellIndex}`) ?? "").join(" | ")} |`);
1193
- lines.splice(1, 0, `| ${table.columns.map(() => "---").join(" | ")} |`);
1194
- return lines.join("\n");
1195
- }
1196
- jsxTable(table, at) {
1197
- return [
1198
- "<table>",
1199
- ...table.rows.map((row, rowIndex) => {
1200
- const tag = rowIndex < table.headerRows ? "th" : "td";
1201
- return ` <tr>${row.cells.map((cell, cellIndex) => {
1202
- const here = `${at}/rows/${rowIndex}/cells/${cellIndex}`;
1203
- const spans = `${cell.rowSpan ? ` rowSpan="${cell.rowSpan}"` : ""}${cell.colSpan ? ` colSpan="${cell.colSpan}"` : ""}`;
1204
- const inline = this.cellInline(cell.blocks, here);
1205
- if (inline !== null) return `<${tag}${spans}>${inline}</${tag}>`;
1206
- return `<${tag}${spans}>\n\n${this.blocks(cell.blocks, `${here}/blocks`, 7, "decimal").join("\n\n")}\n\n</${tag}>`;
1207
- }).join("")}</tr>`;
1208
- }),
1209
- "</table>"
1210
- ].join("\n");
1211
- }
1212
- };
1213
- const isGeneratedBibliography = (block, document) => block.type === "bibliography" && block.id === `${document.id.slice(0, 48)}_bibliography`;
1214
- /** 哪些 id 要写出来:被 ref 指到的、layout.json 的键、文献与脚注(它们本来就靠 id 对应) */
1215
- function keptIdsOf(document, layoutKeys) {
1216
- const kept = new Set(layoutKeys);
1217
- for (const { node } of walk(document)) if (node.type === "ref") kept.add(node.target);
1218
- for (const reference of document.references ?? []) kept.add(reference.id);
1219
- for (const note of document.notes ?? []) kept.add(note.id);
1220
- return kept;
1221
- }
1222
- function chapterFileNames(sections) {
1223
- const taken = /* @__PURE__ */ new Set([MDX_ENTRY_NAME, MDX_REFERENCES_NAME]);
1224
- return sections.map((section, index) => {
1225
- const preferred = /^[a-z0-9][a-z0-9-]{0,40}$/.test(section.id) ? `${section.id}.mdx` : `chapter-${index + 1}.mdx`;
1226
- const name = taken.has(preferred) ? `chapter-${index + 1}.mdx` : preferred;
1227
- taken.add(name);
1228
- return name;
1229
- });
1230
- }
1231
- /** GFM 只认同一个文件里的脚注定义:每条脚注的定义写进引用它的那个文件(入口正文或某一章)的末尾 */
1232
- function notesByFile(document, frontBlocks, sections, names, writer) {
1233
- const fileOfNote = /* @__PURE__ */ new Map();
1234
- const collect = (value, file) => {
1235
- for (const { node } of walk(value)) if (node.type === "note_ref") fileOfNote.set(node.target, file);
1236
- };
1237
- collect(frontBlocks, MDX_ENTRY_NAME);
1238
- sections.forEach((section, index) => collect(section, names[index]));
1239
- const byFile = /* @__PURE__ */ new Map();
1240
- (document.notes ?? []).forEach((note, index) => {
1241
- const at = `/notes/${index}`;
1242
- const [first, ...rest] = note.blocks;
1243
- if (first?.type !== "paragraph" || rest.length) writer.problems.push(`${at}:脚注只转一段文字的,这条有 ${note.blocks.length} 块`);
1244
- const file = fileOfNote.get(note.id) ?? "index.mdx";
1245
- byFile.set(file, [...byFile.get(file) ?? [], `[^${note.id}]: ${first?.type === "paragraph" ? writer.paragraph(first.content, `${at}/blocks/0`) : ""}`]);
1246
- });
1247
- return byFile;
1248
- }
1249
- /** 组装好的文档 → MDX 报告。assets 是组装结果的 assets(图在组装结果里的路径 → 原图路径) */
1250
- function mdxReportPlanOf(document, assets, layoutKeys) {
1251
- const keptIds = keptIdsOf(document, layoutKeys);
1252
- const writer = new MdxWriter(keptIds, assets);
1253
- const firstSection = document.blocks.findIndex((block) => block.type === "section");
1254
- const frontBlocks = (firstSection < 0 ? document.blocks : document.blocks.slice(0, firstSection)).filter((block, index, all) => !(firstSection < 0 && index === all.length - 1 && isGeneratedBibliography(block, document)));
1255
- const sections = [];
1256
- document.blocks.forEach((block, index) => {
1257
- if (index < firstSection || firstSection < 0) return;
1258
- if (block.type === "section") sections.push(block);
1259
- else if (!(index === document.blocks.length - 1 && isGeneratedBibliography(block, document))) writer.problems.push(`/blocks/${index}:第一章之后的顶层 ${block.type} 在 MDX 里写不出来(顶层内容只能写在 index.mdx 的正文、第一章之前)`);
1260
- });
1261
- const names = chapterFileNames(sections);
1262
- const notesOf = notesByFile(document, frontBlocks, sections, names, writer);
1263
- const files = Object.create(null);
1264
- for (const [index, section] of sections.entries()) {
1265
- const at = `/blocks/${firstSection + index}`;
1266
- const frontmatter = section.numbering === "decimal" ? "" : `---\nnumbering: ${section.numbering}\n---\n\n`;
1267
- files[names[index]] = `${frontmatter}${[writer.section(section, at, 1, "decimal"), ...notesOf.get(names[index]) ?? []].join("\n\n")}\n`;
1268
- }
1269
- const { metadata } = document;
1270
- const header = { title: metadata.title };
1271
- if (metadata.shortTitle !== void 0) header["shortTitle"] = metadata.shortTitle;
1272
- header["language"] = metadata.language;
1273
- if (metadata.authors !== void 0) header["authors"] = metadata.authors;
1274
- if (metadata.date !== void 0) header["date"] = metadata.date;
1275
- if (metadata.revision !== void 0) header["revision"] = metadata.revision;
1276
- if (metadata.fields !== void 0) header["fields"] = metadata.fields;
1277
- if (names.length) header["chapters"] = names;
1278
- const body = [...writer.blocks(frontBlocks, "/blocks", 1, "decimal"), ...notesOf.get("index.mdx") ?? []].join("\n\n");
1279
- files[MDX_ENTRY_NAME] = `---\n${stringify(header, { lineWidth: 0 })}---\n${body ? `\n${body}\n` : ""}`;
1280
- if (document.references?.length) files[MDX_REFERENCES_NAME] = `${document.references.map((reference, index) => `<Reference${attributeOf("id", reference.id, writer.problems, `/references/${index}`)}${reference.url !== void 0 ? attributeOf("url", reference.url, writer.problems, `/references/${index}`) : ""}>${escapedText(reference.text)}</Reference>`).join("\n\n")}\n`;
1281
- return {
1282
- files,
1283
- images: writer.images,
1284
- keptIds,
1285
- problems: writer.problems
1286
- };
1287
- }
1288
- //#endregion
1289
- export { assembleMdxReport as a, GENERATED_ID_PREFIX as c, MDX_REFERENCES_NAME as i, mdxReportPlanOf as n, compileMdxReport as o, MDX_ENTRY_NAME as r, mdxReportInputsOf as s, keptIdsOf as t };
1036
+ export { mdxReportInputsOf as a, compileMdxReport as i, MDX_REFERENCES_NAME as n, GENERATED_ID_PREFIX as o, assembleMdxReport as r, MDX_ENTRY_NAME as t };
package/dist/node.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { n as AssemblyOptions, r as AssemblyResult } from "./types-Bcl48xQN.mjs";
2
- import { a as ConvertReportResult, c as ReportCheckResult, i as reportExport, l as checkReports, n as ReportExportResult, o as convertReportToMdx, r as reportEntry, s as ReportCheckDiagnostic, t as ReportExportOptions } from "./export-Da4yhrZa.mjs";
1
+ import { n as AssemblyOptions, r as AssemblyResult } from "./types-Ea2OT6Ac.mjs";
2
+ import { a as ReportCheckDiagnostic, i as reportExport, n as ReportExportResult, o as ReportCheckResult, r as reportEntry, s as checkReports, t as ReportExportOptions } from "./export-DMKj4gl2.mjs";
3
3
  //#region src/node/read-report.d.ts
4
4
  interface SnapshotEntry {
5
5
  path: string;
@@ -38,4 +38,4 @@ declare function readReportFiles(siteDir: string, entry: string): Promise<Record
38
38
  /** 读一份 MDX 报告的全部原文:入口、chapters 列的各章、存在时的 references.mdx,键是相对站点目录的路径,交给 assembleMdxReport */
39
39
  declare function readMdxReportFiles(siteDir: string, entry: string): Promise<Record<string, string>>;
40
40
  //#endregion
41
- export { type ConvertReportResult, JSON_ENTRY_NAME, REPORT_ID_PATTERN, type ReadReportResult, type ReportCheckDiagnostic, type ReportCheckResult, type ReportExportOptions, type ReportExportResult, type SnapshotEntry, checkReports, convertReportToMdx, isMdxEntry, readMdxReportFiles, readReport, readReportFiles, reportEntry, reportEntryIn, reportEntryOf, reportExport, reportIdsOf, reportsDirName };
41
+ export { JSON_ENTRY_NAME, REPORT_ID_PATTERN, type ReadReportResult, type ReportCheckDiagnostic, type ReportCheckResult, type ReportExportOptions, type ReportExportResult, type SnapshotEntry, checkReports, isMdxEntry, readMdxReportFiles, readReport, readReportFiles, reportEntry, reportEntryIn, reportEntryOf, reportExport, reportIdsOf, reportsDirName };
package/dist/node.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as readReport, i as checkReports, n as reportEntry, r as reportExport, t as convertReportToMdx } from "./convert-o7bPWP9A.mjs";
2
- import { a as readReportFiles, c as reportIdsOf, i as readMdxReportFiles, l as reportsDirName, n as REPORT_ID_PATTERN, o as reportEntryIn, r as isMdxEntry, s as reportEntryOf, t as JSON_ENTRY_NAME } from "./report-files-BsIvifZe.mjs";
3
- export { JSON_ENTRY_NAME, REPORT_ID_PATTERN, checkReports, convertReportToMdx, isMdxEntry, readMdxReportFiles, readReport, readReportFiles, reportEntry, reportEntryIn, reportEntryOf, reportExport, reportIdsOf, reportsDirName };
1
+ import { i as readReport, n as reportExport, r as checkReports, t as reportEntry } from "./export-CGvF3Wz-.mjs";
2
+ import { a as readReportFiles, c as reportIdsOf, i as readMdxReportFiles, l as reportsDirName, n as REPORT_ID_PATTERN, o as reportEntryIn, r as isMdxEntry, s as reportEntryOf, t as JSON_ENTRY_NAME } from "./report-files-DyPBOCgh.mjs";
3
+ export { JSON_ENTRY_NAME, REPORT_ID_PATTERN, checkReports, isMdxEntry, readMdxReportFiles, readReport, readReportFiles, reportEntry, reportEntryIn, reportEntryOf, reportExport, reportIdsOf, reportsDirName };
@@ -1,4 +1,4 @@
1
- import { r as AssemblyResult } from "./types-Bcl48xQN.mjs";
1
+ import { r as AssemblyResult } from "./types-Ea2OT6Ac.mjs";
2
2
  import { CommentsAccess, PageThreadSummary } from "@godcreator/comments/site";
3
3
  //#region src/site/page-data.d.ts
4
4
  /** 报告列表里的一行:报告读不对时 problem 是原话,标题退回报告 id */
@@ -1,5 +1,5 @@
1
- import { i as validateDocument, n as safeRelativePath, o as parseReportJson, s as ReportContentError } from "./assemble-BpllQNVY.mjs";
2
- import { r as MDX_ENTRY_NAME, s as mdxReportInputsOf } from "./mdx-4Rs1N1nv.mjs";
1
+ import { i as validateDocument, n as safeRelativePath, o as parseReportJson, s as ReportContentError } from "./assemble-BEt9_xsB.mjs";
2
+ import { a as mdxReportInputsOf, t as MDX_ENTRY_NAME } from "./mdx-B1V34o8x.mjs";
3
3
  import { readFile, readdir, realpath, stat } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  //#region src/node/report-files.ts
@@ -1,4 +1,4 @@
1
- import { a as ReportTocItem, i as ReportSummary, n as ReportPageComments, r as ReportPageData, t as ReportListData } from "./page-data-CJ6nCBnW.mjs";
1
+ import { a as ReportTocItem, i as ReportSummary, n as ReportPageComments, r as ReportPageData, t as ReportListData } from "./page-data-C1veAE55.mjs";
2
2
  //#region src/site/loaders.server.d.ts
3
3
  /**
4
4
  * 报告功能的站点信息:站点目录(报告在它的 reports/ 下)、站名、报告页的地址前缀,
@@ -1,6 +1,6 @@
1
- import { t as assembleDocument } from "./assemble-BpllQNVY.mjs";
2
- import { a as assembleMdxReport, r as MDX_ENTRY_NAME, s as mdxReportInputsOf } from "./mdx-4Rs1N1nv.mjs";
3
- import { a as readReportFiles, c as reportIdsOf, i as readMdxReportFiles, n as REPORT_ID_PATTERN, o as reportEntryIn, r as isMdxEntry, t as JSON_ENTRY_NAME } from "./report-files-BsIvifZe.mjs";
1
+ import { t as assembleDocument } from "./assemble-BEt9_xsB.mjs";
2
+ import { a as mdxReportInputsOf, r as assembleMdxReport, t as MDX_ENTRY_NAME } from "./mdx-B1V34o8x.mjs";
3
+ import { a as readReportFiles, c as reportIdsOf, i as readMdxReportFiles, n as REPORT_ID_PATTERN, o as reportEntryIn, r as isMdxEntry, t as JSON_ENTRY_NAME } from "./report-files-DyPBOCgh.mjs";
4
4
  import { n as reportHrefOf } from "./page-data-DLPoVgXy.mjs";
5
5
  import { readFile, realpath, stat } from "node:fs/promises";
6
6
  import path from "node:path";