@godcreator/word-report 0.4.1 → 0.5.0

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,31 @@ 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, `认不出组件 <${name ?? ""}>:行内只能用 WordRef、WordCite、WordPlainMath、sub、sup、strong、em、br`);
308
319
  }
309
320
  }
321
+ const PLAIN_MATH_MARKS = [
322
+ "em",
323
+ "sub",
324
+ "sup"
325
+ ];
326
+ /** <WordPlainMath> 里写给 Word 的文字:只收文字、斜体、下标、上标 */
327
+ function plainMathRunsOf(context, element) {
328
+ const usage = "<WordPlainMath tex=\"…\"> 里写 Word 用的文字,只能用文字、*斜体*、<sub>、<sup>";
329
+ const inlines = inlinesOf(context, element.children);
330
+ if (!inlines.length) failAt(context, element, `${usage},这里是空的`);
331
+ return inlines.map((inline) => {
332
+ if (inline.type !== "text") failAt(context, element, `${usage},这里出现了 ${inline.type}`);
333
+ const marks = inline.marks ?? [];
334
+ const unsupported = marks.find((mark) => !PLAIN_MATH_MARKS.includes(mark));
335
+ if (unsupported !== void 0) failAt(context, element, `${usage},这里出现了 ${unsupported}`);
336
+ if (marks.includes("sub") && marks.includes("sup")) failAt(context, element, "<WordPlainMath> 里同一段文字不能既是下标又是上标");
337
+ return marks.length ? {
338
+ text: inline.text,
339
+ marks
340
+ } : { text: inline.text };
341
+ });
342
+ }
310
343
  function linkTextOf(context, link) {
311
344
  const inlines = [];
312
345
  collectInlines(context, link.children, [], inlines);
@@ -343,7 +376,7 @@ function plainTextOf(inlines) {
343
376
  return inlines.map((inline) => {
344
377
  switch (inline.type) {
345
378
  case "text": return inline.text;
346
- case "math": return `$${inline.latex}$`;
379
+ case "math": return inline.plainText ? inline.plainText.map((run) => run.text).join("") : `$${inline.latex}$`;
347
380
  case "link": return inline.text;
348
381
  case "break": return " ";
349
382
  default: return "";
@@ -386,15 +419,15 @@ const BLOCK_NAMES = {
386
419
  table: "表",
387
420
  equation: "公式",
388
421
  code: "代码块",
389
- bibliography: "<Bibliography />",
390
- abstract: "<Abstract>"
422
+ bibliography: "<WordBibliography />",
423
+ abstract: "<WordAbstract>"
391
424
  };
392
425
  const PLACE_NAMES = {
393
426
  top: "正文",
394
427
  listItem: "列表项",
395
428
  cell: "表格单元格",
396
429
  note: "脚注",
397
- abstract: "<Abstract>"
430
+ abstract: "<WordAbstract>"
398
431
  };
399
432
  const explicitOrRequested = (context, node, rawId, source, kind) => {
400
433
  if (rawId !== void 0) node.id = explicitIdOf(rawId, context.file, positionOf(source));
@@ -542,24 +575,24 @@ function codeOf(context, node) {
542
575
  }
543
576
  function componentOf(context, element, place) {
544
577
  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":
578
+ case "WordFigure": return figureOf(context, element);
579
+ case "WordFigures": return multiFigureOf(context, element);
580
+ case "WordTable": return numberedTableOf(context, element);
581
+ case "WordAbstract": return abstractOf(context, element, place);
582
+ case "WordBibliography":
550
583
  attributesOf(context, element, {});
551
- if (element.children.some((child) => !isCommentOnlyExpression(child))) failAt(context, element, "<Bibliography /> 不收子内容");
584
+ if (element.children.some((child) => !isCommentOnlyExpression(child))) failAt(context, element, "<WordBibliography /> 不收子内容");
552
585
  return located(context, context.ids.request({
553
586
  type: "bibliography",
554
587
  id: ""
555
588
  }, "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> 里");
589
+ case "WordReference": return failAt(context, element, "<WordReference> 只能写在报告目录的 references.mdx 里");
590
+ case "WordCaption": return failAt(context, element, "<WordCaption> 只能写在 <WordTable> 或 <WordFigures> 里");
591
+ case "WordPanel": return failAt(context, element, "<WordPanel> 只能写在 <WordFigures> 里");
559
592
  case "table":
560
593
  case "tr":
561
594
  case "th":
562
- case "td": return failAt(context, element, `<${element.name}> 只能写在 <Table> 里:不编号的简单表格直接写 GFM 表格`);
595
+ case "td": return failAt(context, element, `<${element.name}> 只能写在 <WordTable> 里:不编号的简单表格直接写 GFM 表格`);
563
596
  default: return failAt(context, element, `认不出组件 <${element.name ?? ""}>:块级只能用 Figure、Figures、Table、Abstract、Bibliography`);
564
597
  }
565
598
  }
@@ -574,7 +607,7 @@ function figureOf(context, element) {
574
607
  src: "required",
575
608
  alt: "optional"
576
609
  });
577
- const caption = captionOf(context, element, "<Figure> 的图题");
610
+ const caption = captionOf(context, element, "<WordFigure> 的图题");
578
611
  return explicitOrRequested(context, {
579
612
  type: "figure",
580
613
  id: "",
@@ -590,12 +623,12 @@ function multiFigureOf(context, element) {
590
623
  columns: "required"
591
624
  });
592
625
  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}>`);
626
+ const captions = children.filter((child) => child.name === "WordCaption");
627
+ if (captions.length !== 1) failAt(context, element, "<WordFigures> 里要有且只有一个 <WordCaption> 写整个图的图题");
628
+ for (const child of children) if (child.name !== "WordCaption" && child.name !== "WordPanel") failAt(context, child, `<WordFigures> 里只能放 <WordCaption> 与 <WordPanel>,不能放 <${child.name}>`);
596
629
  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));
630
+ const caption = captionOf(context, captions[0], "<WordFigures> 的图题");
631
+ const panels = children.filter((child) => child.name === "WordPanel").map((panel) => panelOf(context, panel));
599
632
  const figure = {
600
633
  type: "figure",
601
634
  id: "",
@@ -617,7 +650,7 @@ function panelOf(context, element) {
617
650
  });
618
651
  const caption = inlinesOf(context, phrasingChildrenOf(context, element));
619
652
  const alt = attributes["alt"] ?? plainTextOf(caption);
620
- if (!alt) failAt(context, element, "<Panel> 没有小标题时要写 alt");
653
+ if (!alt) failAt(context, element, "<WordPanel> 没有小标题时要写 alt");
621
654
  const panel = {
622
655
  id: "",
623
656
  row: positiveIntegerOf(context, element, "row", attributes["row"]),
@@ -633,12 +666,12 @@ function numberedTableOf(context, element) {
633
666
  const attributes = attributesOf(context, element, { id: "optional" });
634
667
  const parts = element.children.filter((child) => !isCommentOnlyExpression(child) && !isWhitespaceText(child)).flatMap((child) => child.type === "paragraph" ? child.children.filter((inner) => !isWhitespaceText(inner)) : [child]);
635
668
  let caption;
636
- if (parts[0]?.name === "Caption") {
669
+ if (parts[0]?.name === "WordCaption") {
637
670
  attributesOf(context, parts[0], {});
638
- caption = captionOf(context, parts.shift(), "<Caption> 的表题");
671
+ caption = captionOf(context, parts.shift(), "<WordCaption> 的表题");
639
672
  }
640
673
  const body = parts[0];
641
- if (parts.length !== 1 || !body || !(body.type === "table" || body.name === "table")) failAt(context, element, "<Table> 里先写可省的 <Caption>,再写一张表(GFM 表格或 <table>),别的不放");
674
+ if (parts.length !== 1 || !body || !(body.type === "table" || body.name === "table")) failAt(context, element, "<WordTable> 里先写可省的 <WordCaption>,再写一张表(GFM 表格或 <table>),别的不放");
642
675
  return body.type === "table" ? tableFromGfm(context, body, true, caption, attributes["id"] ?? null, element) : tableFromJsx(context, body, caption, attributes["id"] ?? null, element);
643
676
  }
644
677
  function finishedTable(context, table, caption, rawId, source) {
@@ -719,13 +752,13 @@ function tableFromJsx(context, element, caption, rawId, source) {
719
752
  }, caption, rawId, source);
720
753
  }
721
754
  function abstractOf(context, element, place) {
722
- if (context.role !== "index" || place !== "top") failAt(context, element, "<Abstract> 只能写在 index.mdx 的正文最前面");
755
+ if (context.role !== "index" || place !== "top") failAt(context, element, "<WordAbstract> 只能写在 index.mdx 的正文最前面");
723
756
  const attributes = attributesOf(context, element, {
724
757
  keywords: "required",
725
758
  title: "optional"
726
759
  });
727
760
  const keywords = attributes["keywords"].split(";").map((keyword) => keyword.trim());
728
- if (keywords.some((keyword) => !keyword)) failAt(context, element, "<Abstract> 的 keywords 用全角分号「;」隔开,每个关键词都不能空");
761
+ if (keywords.some((keyword) => !keyword)) failAt(context, element, "<WordAbstract> 的 keywords 用全角分号「;」隔开,每个关键词都不能空");
729
762
  const abstract = {
730
763
  type: "abstract",
731
764
  id: "",
@@ -881,13 +914,13 @@ function referencesOf(context, root) {
881
914
  for (const node of root.children) {
882
915
  if (isCommentOnlyExpression(node)) continue;
883
916
  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>,一条一段");
917
+ if (element.type !== "mdxJsxFlowElement" && element.type !== "mdxJsxTextElement" || element.name !== "WordReference") failAt(context, node, "references.mdx 里只能写 <WordReference id=\"…\" url=\"…\">著录文字</WordReference>,一条一段");
885
918
  const attributes = attributesOf(context, element, {
886
919
  id: "required",
887
920
  url: "optional"
888
921
  });
889
922
  const text = inlinesOf(context, phrasingChildrenOf(context, element));
890
- if (text.length !== 1 || text[0].type !== "text" || text[0].marks) failAt(context, element, "<Reference> 的著录文字只能是一行纯文字");
923
+ if (text.length !== 1 || text[0].type !== "text" || text[0].marks) failAt(context, element, "<WordReference> 的著录文字只能是一行纯文字");
891
924
  const entry = {
892
925
  id: explicitIdOf(attributes["id"], context.file, positionOf(element)),
893
926
  text: text[0].text
@@ -995,295 +1028,4 @@ function assembleMdxReport(input, options = {}) {
995
1028
  };
996
1029
  }
997
1030
  //#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 };
1031
+ 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-B01-yaAq.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-Dfr71bRv.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-C1RVv6E-.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-C1RVv6E-.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-Dfr71bRv.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";
package/dist/site.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { r as AssemblyResult, v as ReportDocument } from "./types-Bcl48xQN.mjs";
2
- import { a as ReportTocItem, i as ReportSummary, n as ReportPageComments, o as reportAssetHrefOf, r as ReportPageData, s as reportHrefOf, t as ReportListData } from "./page-data-CJ6nCBnW.mjs";
1
+ import { r as AssemblyResult, y as ReportDocument } from "./types-Ea2OT6Ac.mjs";
2
+ import { a as ReportTocItem, i as ReportSummary, n as ReportPageComments, o as reportAssetHrefOf, r as ReportPageData, s as reportHrefOf, t as ReportListData } from "./page-data-C1veAE55.mjs";
3
3
  import { CommentAvatar, CommentBodyComponent } from "@godcreator/comments/site";
4
4
  //#region src/site/report-pages.d.ts
5
5
  declare function ReportListPage(): import("react").JSX.Element;