@godcreator/word-report 0.1.0 → 0.3.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.
- package/README.md +3 -4
- package/dist/{assemble-Czu-J7tB.mjs → assemble-3cx8L2vo.mjs} +129 -18
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/core.d.mts +2 -2
- package/dist/core.mjs +1 -1
- package/dist/{export-Bi5yS3GW.d.mts → export-DC764Umz.d.mts} +1 -1
- package/dist/{export-BdwmhUIp.mjs → export-TbclsIsE.mjs} +7 -2
- package/dist/node.d.mts +2 -2
- package/dist/node.mjs +2 -2
- package/dist/{page-data-UaiHPCZA.d.mts → page-data-CJ6nCBnW.d.mts} +9 -2
- package/dist/page-data-DLPoVgXy.mjs +7 -0
- package/dist/{report-files-DwLdh0QQ.mjs → report-files-DM9Ea6yK.mjs} +1 -1
- package/dist/site-server.d.mts +14 -6
- package/dist/site-server.mjs +67 -9
- package/dist/site.d.mts +13 -4
- package/dist/site.mjs +69 -25
- package/dist/{types-BYcLlSjZ.d.mts → types-Bcl48xQN.d.mts} +27 -2
- package/engine/README.md +3 -3
- package/package.json +2 -1
- package/styles/report.css +6 -0
- package/template/report.tsx +3 -1
- package/template/routes.ts +1 -1
- package/template/site.server.ts +12 -2
package/README.md
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
文档站平台、按功能划分的功能包,以及两个 gwb 工作台插件。任何站点都是「fumadocs 官方 React Router 模板 + 挑选的功能包」:
|
|
6
6
|
平台包 `@godcreator/god-docsite` 带唯一的站点模板与建站器,功能包各自经 fumadocs 与 React Router 原生的扩展点接入站点。
|
|
7
|
-
仓正在重构,设计未定,以代码和 `AGENTS.md` 为准。
|
|
8
7
|
|
|
9
8
|
| 包(`packages/`) | 做什么 |
|
|
10
9
|
| --- | --- |
|
|
@@ -46,7 +45,7 @@ pnpm doc:render # 只重新生成生成物
|
|
|
46
45
|
|
|
47
46
|
## 文档在哪
|
|
48
47
|
|
|
49
|
-
`docs-site/`
|
|
48
|
+
`docs-site/` 是全仓唯一的文档站:总览、建站与写页面、包(一个包一页)、插件、Gitea 与 NAS、维护、背景(fumadocs 原理)。轨迹卡在 `docs-site/trajectory/`。
|
|
50
49
|
随插件发布的说明书正本在 `docs-site/content/docs/outputs/skills/`,生成到 `plugins/docsite/skills/` 与 `plugins/collab/skills/`。
|
|
51
50
|
|
|
52
51
|
**这份 README 与仓根 `CLAUDE.md` 是生成物**(正本在 `docs-site/content/docs/outputs/`),`AGENTS.md` 手写;改 mdx 正本再在 `docs-site/` 里 `pnpm doc:render`。
|
|
@@ -59,8 +58,8 @@ pnpm build # 按依赖序构建全部包、两个插件与各站
|
|
|
59
58
|
pnpm check # 逐个包跑 check(typecheck、快的一级测试,有 lint 的再 lint),一分钟左右
|
|
60
59
|
pnpm test:slow # 慢的一级测试(各包的 *.slow.test.ts:真建站、大量调 git 的),只在显式要求时跑,近三分钟
|
|
61
60
|
pnpm test:deps # 包之间的依赖方向、包内分层、模块之间不许有环(规则在 tests/dependency-rules.cjs,要先 pnpm build)
|
|
62
|
-
pnpm verify #
|
|
63
|
-
pnpm test:pack #
|
|
61
|
+
pnpm verify # 全量:build → check → test:slow → test:deps → test:scaffold → test:e2e;只在升共用依赖时跑,平时看改到了什么只跑相关的几项(见 AGENTS.md)
|
|
62
|
+
pnpm test:pack # 把包打成 tarball 装进测试站的副本,跑同样的端到端检查;改了包的 exports、files、依赖时跑
|
|
64
63
|
|
|
65
64
|
cd docs-site
|
|
66
65
|
pnpm doc
|
|
@@ -371,13 +371,90 @@ const schema = {
|
|
|
371
371
|
"$ref": "#/$defs/inlines",
|
|
372
372
|
"minItems": 1
|
|
373
373
|
},
|
|
374
|
-
"alt": { "$ref": "#/$defs/line" }
|
|
374
|
+
"alt": { "$ref": "#/$defs/line" },
|
|
375
|
+
"rows": {
|
|
376
|
+
"type": "integer",
|
|
377
|
+
"minimum": 1,
|
|
378
|
+
"maximum": 6
|
|
379
|
+
},
|
|
380
|
+
"columns": {
|
|
381
|
+
"type": "integer",
|
|
382
|
+
"minimum": 1,
|
|
383
|
+
"maximum": 6
|
|
384
|
+
},
|
|
385
|
+
"panels": {
|
|
386
|
+
"type": "array",
|
|
387
|
+
"items": { "$ref": "#/$defs/figurePanel" },
|
|
388
|
+
"minItems": 2,
|
|
389
|
+
"maxItems": 36
|
|
390
|
+
}
|
|
375
391
|
},
|
|
376
392
|
"required": [
|
|
377
393
|
"type",
|
|
378
394
|
"id",
|
|
395
|
+
"caption"
|
|
396
|
+
],
|
|
397
|
+
"additionalProperties": false,
|
|
398
|
+
"allOf": [
|
|
399
|
+
{
|
|
400
|
+
"if": { "required": ["panels"] },
|
|
401
|
+
"then": { "required": ["rows", "columns"] }
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"if": { "required": ["panels"] },
|
|
405
|
+
"then": { "not": { "required": ["src"] } }
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"if": { "required": ["panels"] },
|
|
409
|
+
"then": { "not": { "required": ["alt"] } }
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
"if": { "not": { "required": ["panels"] } },
|
|
413
|
+
"then": { "required": ["src", "alt"] }
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
"if": { "not": { "required": ["panels"] } },
|
|
417
|
+
"then": { "not": { "required": ["rows"] } }
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"if": { "not": { "required": ["panels"] } },
|
|
421
|
+
"then": { "not": { "required": ["columns"] } }
|
|
422
|
+
}
|
|
423
|
+
],
|
|
424
|
+
"description": "A single image (src, alt) or several images under one figure number (rows, columns, panels). Each panel names its own grid cell and label; cells nobody names stay empty. Cells inside the grid, one panel per cell and unique labels are semantic checks."
|
|
425
|
+
},
|
|
426
|
+
"figurePanel": {
|
|
427
|
+
"type": "object",
|
|
428
|
+
"properties": {
|
|
429
|
+
"id": { "$ref": "#/$defs/id" },
|
|
430
|
+
"row": {
|
|
431
|
+
"type": "integer",
|
|
432
|
+
"minimum": 1,
|
|
433
|
+
"maximum": 6
|
|
434
|
+
},
|
|
435
|
+
"column": {
|
|
436
|
+
"type": "integer",
|
|
437
|
+
"minimum": 1,
|
|
438
|
+
"maximum": 6
|
|
439
|
+
},
|
|
440
|
+
"label": {
|
|
441
|
+
"type": "string",
|
|
442
|
+
"pattern": "^[^\\s()()]{1,8}$",
|
|
443
|
+
"description": "Shown under the image as (label) and in references as figure number + (label); written without parentheses."
|
|
444
|
+
},
|
|
445
|
+
"src": { "$ref": "#/$defs/assetPath" },
|
|
446
|
+
"alt": { "$ref": "#/$defs/line" },
|
|
447
|
+
"caption": {
|
|
448
|
+
"$ref": "#/$defs/inlines",
|
|
449
|
+
"minItems": 1
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
"required": [
|
|
453
|
+
"id",
|
|
454
|
+
"row",
|
|
455
|
+
"column",
|
|
456
|
+
"label",
|
|
379
457
|
"src",
|
|
380
|
-
"caption",
|
|
381
458
|
"alt"
|
|
382
459
|
],
|
|
383
460
|
"additionalProperties": false
|
|
@@ -675,8 +752,10 @@ function shape(value, rule, pointer, file) {
|
|
|
675
752
|
if (rule.format === "date" && (Number.isNaN(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value)) fail("Invalid calendar date");
|
|
676
753
|
}
|
|
677
754
|
if (typeof value === "number" && rule.minimum !== void 0 && value < rule.minimum) fail(`Minimum is ${rule.minimum}`);
|
|
755
|
+
if (typeof value === "number" && rule.maximum !== void 0 && value > rule.maximum) fail(`Maximum is ${rule.maximum}`);
|
|
678
756
|
if (Array.isArray(value)) {
|
|
679
|
-
if (rule.minItems && value.length < rule.minItems) fail("Array must not be empty");
|
|
757
|
+
if (rule.minItems && value.length < rule.minItems) fail(rule.minItems === 1 ? "Array must not be empty" : `At least ${rule.minItems} items`);
|
|
758
|
+
if (rule.maxItems && value.length > rule.maxItems) fail(`At most ${rule.maxItems} items`);
|
|
680
759
|
if (rule.uniqueItems && new Set(value.map(stableValue)).size !== value.length) fail("Duplicate array values");
|
|
681
760
|
if (rule.items) value.forEach((v, i) => shape(v, rule.items, `${pointer}/${i}`, file));
|
|
682
761
|
if (rule.contains && !value.some((v) => {
|
|
@@ -767,6 +846,23 @@ function assembleDocument(entry, files, options = {}) {
|
|
|
767
846
|
const pending = /* @__PURE__ */ new Map();
|
|
768
847
|
const resourceNames = /* @__PURE__ */ new Map();
|
|
769
848
|
let rootBibliography;
|
|
849
|
+
/** 图片路径改成按文档 id 分开的 assets/<文档 id>/…,同名的章节图片不互相覆盖 */
|
|
850
|
+
function registerAsset(image, file, srcPointer) {
|
|
851
|
+
try {
|
|
852
|
+
safeRelativePath(image.src, true);
|
|
853
|
+
} catch (e) {
|
|
854
|
+
throw new ReportContentError(e.message, file, srcPointer);
|
|
855
|
+
}
|
|
856
|
+
const original = join(file, image.src);
|
|
857
|
+
const output = `assets/${documents.get(file).id}/${image.src.slice(7)}`;
|
|
858
|
+
for (const resource of [original, output]) {
|
|
859
|
+
const key = resource.normalize("NFC").toLowerCase(), previous = resourceNames.get(key);
|
|
860
|
+
if (previous && previous !== resource) throw new ReportContentError("Asset names collide on Windows", file, srcPointer);
|
|
861
|
+
resourceNames.set(key, resource);
|
|
862
|
+
}
|
|
863
|
+
assets[output] = original;
|
|
864
|
+
image.src = output;
|
|
865
|
+
}
|
|
770
866
|
function register(value, file, prefix) {
|
|
771
867
|
for (const { node, pointer } of walk(value, prefix)) {
|
|
772
868
|
if (node.id !== void 0) {
|
|
@@ -778,20 +874,8 @@ function assembleDocument(entry, files, options = {}) {
|
|
|
778
874
|
};
|
|
779
875
|
}
|
|
780
876
|
if (node.type === "figure") {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
} catch (e) {
|
|
784
|
-
throw new ReportContentError(e.message, file, pointer + "/src");
|
|
785
|
-
}
|
|
786
|
-
const original = join(file, node.src);
|
|
787
|
-
const output = `assets/${documents.get(file).id}/${node.src.slice(7)}`;
|
|
788
|
-
for (const resource of [original, output]) {
|
|
789
|
-
const key = resource.normalize("NFC").toLowerCase(), previous = resourceNames.get(key);
|
|
790
|
-
if (previous && previous !== resource) throw new ReportContentError("Asset names collide on Windows", file, pointer + "/src");
|
|
791
|
-
resourceNames.set(key, resource);
|
|
792
|
-
}
|
|
793
|
-
assets[output] = original;
|
|
794
|
-
node.src = output;
|
|
877
|
+
const images = node.panels ? node.panels.map((panel, k) => [panel, `${pointer}/panels/${k}/src`]) : [[node, `${pointer}/src`]];
|
|
878
|
+
for (const [image, srcPointer] of images) registerAsset(image, file, srcPointer);
|
|
795
879
|
}
|
|
796
880
|
}
|
|
797
881
|
}
|
|
@@ -892,6 +976,7 @@ function assembleDocument(entry, files, options = {}) {
|
|
|
892
976
|
const source = sources[node.id];
|
|
893
977
|
validateTable(node, source.file, source.pointer);
|
|
894
978
|
}
|
|
979
|
+
if (node.type === "figure" && node.panels) validatePanelGrid(node, fail);
|
|
895
980
|
if (node.type === "math" || node.type === "equation") {
|
|
896
981
|
const latex = node.latex.trim();
|
|
897
982
|
if (!latex || /[^\x00-\x7f]/.test(latex) || /^(\$|\\\(|\\\[)/.test(latex) || /\\(?:tag|label|documentclass)\b|\\begin\s*\{document\}/.test(latex)) fail(node, "MathType requires nonempty ASCII TeX without delimiters or manual numbering");
|
|
@@ -964,6 +1049,15 @@ function assembleDocument(entry, files, options = {}) {
|
|
|
964
1049
|
label,
|
|
965
1050
|
title
|
|
966
1051
|
};
|
|
1052
|
+
if (node.type === "figure" && node.panels) for (const panel of node.panels) {
|
|
1053
|
+
const panelNumber = `${number}(${panel.label})`;
|
|
1054
|
+
index[panel.id] = {
|
|
1055
|
+
type: "figure-panel",
|
|
1056
|
+
number: panelNumber,
|
|
1057
|
+
label: `图 ${panelNumber}`,
|
|
1058
|
+
title: plain(panel.caption)
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
967
1061
|
if (node.type === "table") {
|
|
968
1062
|
if (tables >= 6) fail(node, "At most six table levels");
|
|
969
1063
|
for (const row of node.rows) for (const cell of row.cells) blockNumbers(cell.blocks, depth, chapter, parentNumber, parentMode, tables + 1);
|
|
@@ -1033,6 +1127,11 @@ function assembleDocument(entry, files, options = {}) {
|
|
|
1033
1127
|
}
|
|
1034
1128
|
fail(node, `Unknown reference target: ${node.target}`);
|
|
1035
1129
|
}
|
|
1130
|
+
if (index[node.target]?.type === "figure-panel") {
|
|
1131
|
+
if (node.display === "title" && (!target.caption?.length || target.caption.some((x) => !["text", "break"].includes(x.type)))) fail(node, "Title reference requires plain caption");
|
|
1132
|
+
delete node.externalLabel;
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1036
1135
|
if (![
|
|
1037
1136
|
"section",
|
|
1038
1137
|
"figure",
|
|
@@ -1065,6 +1164,18 @@ function assembleDocument(entry, files, options = {}) {
|
|
|
1065
1164
|
warnings
|
|
1066
1165
|
};
|
|
1067
1166
|
}
|
|
1167
|
+
/** 多图的每张都要在 rows × columns 的网格里、一格只放一张、编号不重复 */
|
|
1168
|
+
function validatePanelGrid(figure, fail) {
|
|
1169
|
+
const occupied = /* @__PURE__ */ new Map(), labels = /* @__PURE__ */ new Map();
|
|
1170
|
+
for (const panel of figure.panels) {
|
|
1171
|
+
if (panel.row > figure.rows || panel.column > figure.columns) fail(panel, `Panel cell row ${panel.row}, column ${panel.column} is outside the ${figure.rows} x ${figure.columns} grid of ${figure.id}`);
|
|
1172
|
+
const cell = `${panel.row},${panel.column}`;
|
|
1173
|
+
if (occupied.has(cell)) fail(panel, `Panel shares row ${panel.row}, column ${panel.column} with ${occupied.get(cell)}`);
|
|
1174
|
+
if (labels.has(panel.label)) fail(panel, `Panel label (${panel.label}) is also used by ${labels.get(panel.label)}`);
|
|
1175
|
+
occupied.set(cell, panel.id);
|
|
1176
|
+
labels.set(panel.label, panel.id);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1068
1179
|
function alphabet(n) {
|
|
1069
1180
|
let s = "";
|
|
1070
1181
|
while (n > 0) {
|
|
@@ -1075,4 +1186,4 @@ function alphabet(n) {
|
|
|
1075
1186
|
return s;
|
|
1076
1187
|
}
|
|
1077
1188
|
//#endregion
|
|
1078
|
-
export {
|
|
1189
|
+
export { parseReportJson as a, walk as i, safeRelativePath as n, ReportContentError as o, validateDocument as r, assembleDocument as t };
|
package/dist/cli.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ReportExportResult, o as ReportCheckResult, t as ReportExportOptions } from "./export-
|
|
1
|
+
import { n as ReportExportResult, o as ReportCheckResult, t as ReportExportOptions } from "./export-DC764Umz.mjs";
|
|
2
2
|
//#region src/node/cli.d.ts
|
|
3
3
|
type CliCommand = {
|
|
4
4
|
command: 'export' | 'status';
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as reportExport, r as checkReports } from "./export-
|
|
3
|
-
import { r as reportEntryOf } from "./report-files-
|
|
2
|
+
import { n as reportExport, r as checkReports } from "./export-TbclsIsE.mjs";
|
|
3
|
+
import { r as reportEntryOf } from "./report-files-DM9Ea6yK.mjs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { realpathSync } from "node:fs";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
package/dist/core.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as TableCell, S as Table, _ as
|
|
1
|
+
import { C as SingleImageFigure, E as TableCell, S as Section, T as Table, _ as ReportContentError, a as CellBlock, b as ReportInline, c as ContentWarning, d as FigurePanel, f as Include, g as ReportBlock, h as Paragraph, i as Bibliography, l as Equation, m as MultiImageFigure, n as AssemblyOptions, o as Code, p as IndexEntry, r as AssemblyResult, s as CompiledReport, t as Abstract, u as Figure, v as ReportDocument, w as SourceLocation, x as ReportList, y as ReportIndex } from "./types-Bcl48xQN.mjs";
|
|
2
2
|
//#region src/core/assemble.d.ts
|
|
3
3
|
declare function assembleDocument(entry: string, files: Record<string, string | ReportDocument>, options?: AssemblyOptions): AssemblyResult;
|
|
4
4
|
//#endregion
|
|
@@ -10,4 +10,4 @@ declare function parseReportJson(text: string, file?: string): unknown;
|
|
|
10
10
|
declare function validateDocument(value: unknown, file?: string): asserts value is ReportDocument;
|
|
11
11
|
declare function safeRelativePath(raw: string, asset?: boolean): string;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { Abstract, AssemblyOptions, AssemblyResult, Bibliography, CellBlock, Code, CompiledReport, ContentWarning, Equation, Figure, Include, IndexEntry, Paragraph, ReportBlock, ReportContentError, ReportDocument, ReportIndex, ReportInline, ReportList, Section, SourceLocation, Table, TableCell, assembleDocument, parseReportJson, safeRelativePath, validateDocument };
|
|
13
|
+
export { Abstract, AssemblyOptions, AssemblyResult, Bibliography, CellBlock, Code, CompiledReport, ContentWarning, Equation, Figure, FigurePanel, Include, IndexEntry, MultiImageFigure, Paragraph, ReportBlock, ReportContentError, ReportDocument, ReportIndex, ReportInline, ReportList, Section, SingleImageFigure, SourceLocation, Table, TableCell, assembleDocument, parseReportJson, safeRelativePath, validateDocument };
|
package/dist/core.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { o as ReportContentError, t as assembleDocument, a as parseReportJson, n as safeRelativePath, r as validateDocument } from "./assemble-3cx8L2vo.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as reportsDirName } from "./report-files-
|
|
1
|
+
import { a as parseReportJson, i as walk, n as safeRelativePath, o as ReportContentError, r as validateDocument, t as assembleDocument } from "./assemble-3cx8L2vo.mjs";
|
|
2
|
+
import { a as reportsDirName } from "./report-files-DM9Ea6yK.mjs";
|
|
3
3
|
import { access, mkdir, open, readFile, readdir, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -283,12 +283,17 @@ async function layoutOf(root, entry) {
|
|
|
283
283
|
value: JSON.parse(bytes.toString("utf8"))
|
|
284
284
|
};
|
|
285
285
|
}
|
|
286
|
+
/** Word 引擎还不会排一个图号下的多张图:报告里有多图就在启动 Word 之前拒绝 */
|
|
287
|
+
function rejectMultiImageFigures(document) {
|
|
288
|
+
for (const { node } of walk(document)) if (node.type === "figure" && node.panels) throw new Error(`图 ${node.id} 是多图(panels),导出 Word 还不支持多图;网页上照常显示`);
|
|
289
|
+
}
|
|
286
290
|
async function capture(site, engine, compiler, entry, mode) {
|
|
287
291
|
const root = site;
|
|
288
292
|
const report = await readReport(path.join(root, entry), {
|
|
289
293
|
root,
|
|
290
294
|
mode
|
|
291
295
|
});
|
|
296
|
+
rejectMultiImageFigures(report.document);
|
|
292
297
|
const layout = await layoutOf(root, entry);
|
|
293
298
|
const engineHash = await engineFingerprint(engine, compiler);
|
|
294
299
|
return {
|
package/dist/node.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as AssemblyOptions, r as AssemblyResult } from "./types-
|
|
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-
|
|
1
|
+
import { n as AssemblyOptions, r as AssemblyResult } from "./types-Bcl48xQN.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-DC764Umz.mjs";
|
|
3
3
|
//#region src/node/read-report.d.ts
|
|
4
4
|
interface SnapshotEntry {
|
|
5
5
|
path: string;
|
package/dist/node.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as readReport, n as reportExport, r as checkReports, t as reportEntry } from "./export-
|
|
2
|
-
import { a as reportsDirName, i as reportIdsOf, n as readReportFiles, r as reportEntryOf, t as REPORT_ID_PATTERN } from "./report-files-
|
|
1
|
+
import { i as readReport, n as reportExport, r as checkReports, t as reportEntry } from "./export-TbclsIsE.mjs";
|
|
2
|
+
import { a as reportsDirName, i as reportIdsOf, n as readReportFiles, r as reportEntryOf, t as REPORT_ID_PATTERN } from "./report-files-DM9Ea6yK.mjs";
|
|
3
3
|
export { REPORT_ID_PATTERN, checkReports, readReport, readReportFiles, reportEntry, reportEntryOf, reportExport, reportIdsOf, reportsDirName };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { r as AssemblyResult } from "./types-
|
|
1
|
+
import { r as AssemblyResult } from "./types-Bcl48xQN.mjs";
|
|
2
|
+
import { CommentsAccess, PageThreadSummary } from "@godcreator/comments/site";
|
|
2
3
|
//#region src/site/page-data.d.ts
|
|
3
4
|
/** 报告列表里的一行:报告读不对时 problem 是原话,标题退回报告 id */
|
|
4
5
|
type ReportSummary = {
|
|
@@ -24,16 +25,22 @@ type ReportTocItem = {
|
|
|
24
25
|
url: string;
|
|
25
26
|
depth: number;
|
|
26
27
|
};
|
|
28
|
+
/** 报告页上的评论:归属这一页的评论串,以及本机身份能不能写 */
|
|
29
|
+
type ReportPageComments = {
|
|
30
|
+
threads: PageThreadSummary[];
|
|
31
|
+
access: CommentsAccess;
|
|
32
|
+
};
|
|
27
33
|
type ReportPageData = {
|
|
28
34
|
siteName: string;
|
|
29
35
|
hrefBase: string;
|
|
30
36
|
reportId: string;
|
|
31
37
|
report: AssemblyResult;
|
|
32
38
|
toc: ReportTocItem[];
|
|
39
|
+
comments: ReportPageComments;
|
|
33
40
|
};
|
|
34
41
|
/** 报告页地址 */
|
|
35
42
|
declare const reportHrefOf: (hrefBase: string, reportId: string) => string;
|
|
36
43
|
/** 报告里一张图的地址:组装后图的路径是 assets/<文档 id>/<文件名>,挂在报告页地址下面 */
|
|
37
44
|
declare const reportAssetHrefOf: (hrefBase: string, reportId: string, compiledPath: string) => string;
|
|
38
45
|
//#endregion
|
|
39
|
-
export {
|
|
46
|
+
export { ReportTocItem as a, ReportSummary as i, ReportPageComments as n, reportAssetHrefOf as o, ReportPageData as r, reportHrefOf as s, ReportListData as t };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/site/page-data.ts
|
|
2
|
+
/** 报告页地址 */
|
|
3
|
+
const reportHrefOf = (hrefBase, reportId) => `${hrefBase}/${reportId}`;
|
|
4
|
+
/** 报告里一张图的地址:组装后图的路径是 assets/<文档 id>/<文件名>,挂在报告页地址下面 */
|
|
5
|
+
const reportAssetHrefOf = (hrefBase, reportId, compiledPath) => `${reportHrefOf(hrefBase, reportId)}/${compiledPath}`;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { reportHrefOf as n, reportAssetHrefOf as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as parseReportJson, n as safeRelativePath, o as ReportContentError, r as validateDocument } from "./assemble-3cx8L2vo.mjs";
|
|
2
2
|
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
//#region src/node/report-files.ts
|
package/dist/site-server.d.mts
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as ReportTocItem, i as ReportSummary, n as ReportPageComments, r as ReportPageData, t as ReportListData } from "./page-data-CJ6nCBnW.mjs";
|
|
2
2
|
//#region src/site/loaders.server.d.ts
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* 报告功能的站点信息:站点目录(报告在它的 reports/ 下)、站名、报告页的地址前缀,
|
|
5
|
+
* 以及按评论归属键取一页评论的函数(报告页的归属键是它的地址,如 /reports/sample)
|
|
6
|
+
*/
|
|
4
7
|
type WordReportSite = {
|
|
5
8
|
siteDir: string;
|
|
6
9
|
siteName: string;
|
|
7
10
|
hrefBase: string;
|
|
11
|
+
pageComments: (page: string) => ReportPageComments;
|
|
8
12
|
};
|
|
9
|
-
/** loader 用到的路由参数:报告页是 :report,图片再加
|
|
13
|
+
/** loader 用到的路由参数:报告页是 :report,图片再加 *;图片的 loader 收到 request 时按 If-None-Match 回 304 */
|
|
10
14
|
type ReportRouteArgs = {
|
|
11
15
|
params: Partial<Record<string, string>>;
|
|
16
|
+
request?: Request;
|
|
12
17
|
};
|
|
13
18
|
/** 报告列表:站点 reports/ 下的每份报告各一行;读不对的报告也列出来,带上原话 */
|
|
14
19
|
declare function reportListLoader(_args: ReportRouteArgs, site: WordReportSite): Promise<ReportListData>;
|
|
15
20
|
/** 报告页:整份报告组装成一页(各章按 include 的顺序接在一起);报告读不对直接报错,原话在错误页上 */
|
|
16
21
|
declare function reportPageLoader({ params }: ReportRouteArgs, site: WordReportSite): Promise<ReportPageData>;
|
|
17
|
-
/**
|
|
18
|
-
|
|
22
|
+
/**
|
|
23
|
+
* 报告里的一张图:地址是组装后的路径 assets/<文档 id>/<文件名>,只给这份报告用到的图。
|
|
24
|
+
* 浏览器每次都来问(no-cache),ETag 由文件大小与修改时间算出,没变就回 304、不再传图
|
|
25
|
+
*/
|
|
26
|
+
declare function reportAssetLoader({ params, request }: ReportRouteArgs, site: WordReportSite): Promise<Response>;
|
|
19
27
|
//#endregion
|
|
20
|
-
export { type ReportListData, type ReportPageData, ReportRouteArgs, type ReportSummary, type ReportTocItem, WordReportSite, reportAssetLoader, reportListLoader, reportPageLoader };
|
|
28
|
+
export { type ReportListData, type ReportPageComments, type ReportPageData, ReportRouteArgs, type ReportSummary, type ReportTocItem, WordReportSite, reportAssetLoader, reportListLoader, reportPageLoader };
|
package/dist/site-server.mjs
CHANGED
|
@@ -1,17 +1,60 @@
|
|
|
1
|
-
import { t as assembleDocument } from "./assemble-
|
|
2
|
-
import { i as reportIdsOf, n as readReportFiles, r as reportEntryOf, t as REPORT_ID_PATTERN } from "./report-files-
|
|
1
|
+
import { t as assembleDocument } from "./assemble-3cx8L2vo.mjs";
|
|
2
|
+
import { i as reportIdsOf, n as readReportFiles, r as reportEntryOf, t as REPORT_ID_PATTERN } from "./report-files-DM9Ea6yK.mjs";
|
|
3
|
+
import { n as reportHrefOf } from "./page-data-DLPoVgXy.mjs";
|
|
4
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
5
|
import path from "node:path";
|
|
4
6
|
import { createReadStream } from "node:fs";
|
|
5
7
|
import { Readable } from "node:stream";
|
|
6
8
|
//#region src/site/loaders.server.ts
|
|
7
9
|
/**
|
|
8
|
-
* `@godcreator/word-report/site/server`:报告列表、报告页与报告里图片的 loader
|
|
10
|
+
* `@godcreator/word-report/site/server`:报告列表、报告页与报告里图片的 loader。报告从站点 reports/ 现读,
|
|
11
|
+
* 报告页与图片在 JSON 原文没变时沿用上一次的组装结果。
|
|
9
12
|
*/
|
|
10
13
|
/** 组装结果里有 Object.create(null) 的对象:换成普通对象再交给 React Router 序列化 */
|
|
11
14
|
const plainOf = (result) => JSON.parse(JSON.stringify(result));
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* 每份报告最近一次组装,键是站点目录加报告 id。一页报告的几十张图同时请求,组装一次要几百毫秒到几秒、
|
|
17
|
+
* 全在一个线程上排队:不共用的话,最后一张图要等几十次组装
|
|
18
|
+
*/
|
|
19
|
+
const latestAssemblies = /* @__PURE__ */ new Map();
|
|
20
|
+
async function assembleFromDisk(siteDir, reportId) {
|
|
13
21
|
const entry = reportEntryOf(reportId);
|
|
14
|
-
|
|
22
|
+
const files = await readReportFiles(siteDir, entry);
|
|
23
|
+
return {
|
|
24
|
+
files,
|
|
25
|
+
result: assembleDocument(entry, files, { mode: "complete" })
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** 上次组装读过的每个 JSON 文件原文都没变;文件没了也算变了,交给重新组装去报错 */
|
|
29
|
+
async function sameFilesOnDisk(siteDir, files) {
|
|
30
|
+
const root = await realpath(siteDir);
|
|
31
|
+
return (await Promise.all(Object.entries(files).map(async ([relative, text]) => {
|
|
32
|
+
try {
|
|
33
|
+
return await readFile(path.join(root, relative), "utf8") === text;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.code === "ENOENT") return false;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}))).every(Boolean);
|
|
39
|
+
}
|
|
40
|
+
/** 报告的整份组装结果:JSON 原文和上次一样就用上次的,同时到的请求共用同一次组装;结果只读,要改先复制 */
|
|
41
|
+
async function assembledReport(siteDir, reportId) {
|
|
42
|
+
const key = `${path.resolve(siteDir)}\0${reportId}`;
|
|
43
|
+
const latest = latestAssemblies.get(key);
|
|
44
|
+
if (latest !== void 0) {
|
|
45
|
+
const assembly = await latest;
|
|
46
|
+
if (await sameFilesOnDisk(siteDir, assembly.files)) return assembly.result;
|
|
47
|
+
}
|
|
48
|
+
const newer = latestAssemblies.get(key);
|
|
49
|
+
if (newer !== void 0 && newer !== latest) return (await newer).result;
|
|
50
|
+
const fresh = assembleFromDisk(siteDir, reportId);
|
|
51
|
+
latestAssemblies.set(key, fresh);
|
|
52
|
+
try {
|
|
53
|
+
return (await fresh).result;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (latestAssemblies.get(key) === fresh) latestAssemblies.delete(key);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
15
58
|
}
|
|
16
59
|
/** 地址里的报告 id:形状不对或站点里没有这份报告都是 404 */
|
|
17
60
|
async function reportIdOf(params, siteDir) {
|
|
@@ -78,7 +121,8 @@ async function reportPageLoader({ params }, site) {
|
|
|
78
121
|
hrefBase: site.hrefBase,
|
|
79
122
|
reportId,
|
|
80
123
|
report: result,
|
|
81
|
-
toc: tocOf(result)
|
|
124
|
+
toc: tocOf(result),
|
|
125
|
+
comments: site.pageComments(reportHrefOf(site.hrefBase, reportId))
|
|
82
126
|
};
|
|
83
127
|
}
|
|
84
128
|
const imageContentTypes = {
|
|
@@ -86,15 +130,29 @@ const imageContentTypes = {
|
|
|
86
130
|
".jpg": "image/jpeg",
|
|
87
131
|
".jpeg": "image/jpeg"
|
|
88
132
|
};
|
|
89
|
-
/**
|
|
90
|
-
|
|
133
|
+
/**
|
|
134
|
+
* 报告里的一张图:地址是组装后的路径 assets/<文档 id>/<文件名>,只给这份报告用到的图。
|
|
135
|
+
* 浏览器每次都来问(no-cache),ETag 由文件大小与修改时间算出,没变就回 304、不再传图
|
|
136
|
+
*/
|
|
137
|
+
async function reportAssetLoader({ params, request }, site) {
|
|
91
138
|
const reportId = await reportIdOf(params, site.siteDir);
|
|
92
139
|
const compiledPath = `assets/${params["*"] ?? ""}`;
|
|
93
140
|
const source = (await assembledReport(site.siteDir, reportId)).assets[compiledPath];
|
|
94
141
|
if (source === void 0) return new Response(`报告 ${reportId} 里没有图 ${compiledPath}`, { status: 404 });
|
|
95
142
|
const contentType = imageContentTypes[path.extname(source).toLowerCase()];
|
|
96
143
|
if (contentType === void 0) throw new Error(`报告 ${reportId} 的图 ${source} 不是 png 或 jpg`);
|
|
97
|
-
|
|
144
|
+
const file = path.join(site.siteDir, source);
|
|
145
|
+
const { size, mtimeMs } = await stat(file);
|
|
146
|
+
const headers = {
|
|
147
|
+
"content-type": contentType,
|
|
148
|
+
"cache-control": "no-cache",
|
|
149
|
+
etag: `"${size.toString(16)}-${Math.trunc(mtimeMs).toString(16)}"`
|
|
150
|
+
};
|
|
151
|
+
if (request?.headers.get("if-none-match") === headers.etag) return new Response(null, {
|
|
152
|
+
status: 304,
|
|
153
|
+
headers
|
|
154
|
+
});
|
|
155
|
+
return new Response(Readable.toWeb(createReadStream(file)), { headers });
|
|
98
156
|
}
|
|
99
157
|
//#endregion
|
|
100
158
|
export { reportAssetLoader, reportListLoader, reportPageLoader };
|
package/dist/site.d.mts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as
|
|
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";
|
|
3
|
+
import { CommentAvatar, CommentBodyComponent } from "@godcreator/comments/site";
|
|
3
4
|
//#region src/site/report-pages.d.ts
|
|
4
5
|
declare function ReportListPage(): import("react").JSX.Element;
|
|
5
|
-
|
|
6
|
+
interface ReportDocumentPageProps {
|
|
7
|
+
/** 评论表单提交到的地址:站点的评论写入路由(comments 功能的 /comments) */
|
|
8
|
+
commentAction: string;
|
|
9
|
+
/** 站点的评论正文组件与头像组件(站点 app/lib/content 里的 CommentBody、commentAvatar) */
|
|
10
|
+
CommentBody: CommentBodyComponent;
|
|
11
|
+
AvatarComponent?: CommentAvatar;
|
|
12
|
+
}
|
|
13
|
+
/** 报告页:正文是评论范围,右下角是本页评论;评论归属键是报告页的地址 */
|
|
14
|
+
declare function ReportDocumentPage({ commentAction, CommentBody, AvatarComponent }: ReportDocumentPageProps): import("react").JSX.Element;
|
|
6
15
|
//#endregion
|
|
7
16
|
//#region src/site/report-view.d.ts
|
|
8
17
|
type AssetUrl = (sourcePath: string, compiledPath: string) => string;
|
|
@@ -29,4 +38,4 @@ declare function reportMeta(input: ReportDocument | AssemblyResult): {
|
|
|
29
38
|
markdown: string;
|
|
30
39
|
};
|
|
31
40
|
//#endregion
|
|
32
|
-
export { type AssetUrl, ReportDocumentPage, type ReportListData, ReportListPage, ReportPage, type ReportPageData, type ReportPageProps, type ReportSummary, type ReportTocItem, ReportView, type ReportViewProps, reportAssetHrefOf, reportHrefOf, reportMeta };
|
|
41
|
+
export { type AssetUrl, ReportDocumentPage, type ReportDocumentPageProps, type ReportListData, ReportListPage, ReportPage, type ReportPageComments, type ReportPageData, type ReportPageProps, type ReportSummary, type ReportTocItem, ReportView, type ReportViewProps, reportAssetHrefOf, reportHrefOf, reportMeta };
|
package/dist/site.mjs
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { t as assembleDocument } from "./assemble-
|
|
1
|
+
import { t as assembleDocument } from "./assemble-3cx8L2vo.mjs";
|
|
2
|
+
import { n as reportHrefOf, t as reportAssetHrefOf } from "./page-data-DLPoVgXy.mjs";
|
|
2
3
|
import { Link, useLoaderData } from "react-router";
|
|
3
4
|
import { DocsBody, DocsPage, DocsTitle } from "@fumadocs/base-ui/layouts/docs/page";
|
|
5
|
+
import { CommentsDock, commentScopeProps } from "@godcreator/comments/site";
|
|
4
6
|
import { Fragment } from "react";
|
|
5
7
|
import katex from "katex";
|
|
6
8
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
//#region src/site/page-data.ts
|
|
8
|
-
/** 报告页地址 */
|
|
9
|
-
const reportHrefOf = (hrefBase, reportId) => `${hrefBase}/${reportId}`;
|
|
10
|
-
/** 报告里一张图的地址:组装后图的路径是 assets/<文档 id>/<文件名>,挂在报告页地址下面 */
|
|
11
|
-
const reportAssetHrefOf = (hrefBase, reportId, compiledPath) => `${reportHrefOf(hrefBase, reportId)}/${compiledPath}`;
|
|
12
|
-
//#endregion
|
|
13
9
|
//#region src/site/report-view.tsx
|
|
14
10
|
function RenderError({ children }) {
|
|
15
11
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -182,19 +178,56 @@ function ReportView({ report, assetUrl }) {
|
|
|
182
178
|
})]
|
|
183
179
|
}, node.id);
|
|
184
180
|
case "figure": {
|
|
185
|
-
const
|
|
181
|
+
const image = (src, id, alt) => {
|
|
182
|
+
const source = report.assets[src] ?? src;
|
|
183
|
+
return /* @__PURE__ */ jsx("img", {
|
|
184
|
+
id,
|
|
185
|
+
src: assetUrl?.(source, src) ?? source,
|
|
186
|
+
alt,
|
|
187
|
+
loading: "lazy"
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const caption = /* @__PURE__ */ jsxs("figcaption", {
|
|
191
|
+
id: `${node.id}-caption`,
|
|
192
|
+
children: [label?.number && /* @__PURE__ */ jsxs("span", { children: [label.label, " "] }), inlines(node.caption)]
|
|
193
|
+
});
|
|
194
|
+
if (!node.panels) return /* @__PURE__ */ jsxs("figure", {
|
|
195
|
+
"data-report-source": origin?.file,
|
|
196
|
+
"data-report-pointer": origin?.pointer,
|
|
197
|
+
children: [image(node.src, node.id, node.alt), caption]
|
|
198
|
+
}, node.id);
|
|
186
199
|
return /* @__PURE__ */ jsxs("figure", {
|
|
200
|
+
className: "report-multi-figure",
|
|
187
201
|
"data-report-source": origin?.file,
|
|
188
202
|
"data-report-pointer": origin?.pointer,
|
|
189
|
-
children: [/* @__PURE__ */ jsx("
|
|
203
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
190
204
|
id: node.id,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
children:
|
|
197
|
-
|
|
205
|
+
className: "report-panels",
|
|
206
|
+
style: {
|
|
207
|
+
gridTemplateColumns: `repeat(${node.columns}, minmax(0, 1fr))`,
|
|
208
|
+
gridTemplateRows: `repeat(${node.rows}, auto)`
|
|
209
|
+
},
|
|
210
|
+
children: node.panels.map((panel) => /* @__PURE__ */ jsxs("div", {
|
|
211
|
+
className: "report-panel",
|
|
212
|
+
style: {
|
|
213
|
+
gridRow: panel.row,
|
|
214
|
+
gridColumn: panel.column
|
|
215
|
+
},
|
|
216
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
217
|
+
className: "report-panel-image",
|
|
218
|
+
children: image(panel.src, panel.id, panel.alt)
|
|
219
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
220
|
+
id: `${panel.id}-caption`,
|
|
221
|
+
className: "report-panel-caption",
|
|
222
|
+
children: [
|
|
223
|
+
"(",
|
|
224
|
+
panel.label,
|
|
225
|
+
")",
|
|
226
|
+
panel.caption && /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", inlines(panel.caption)] })
|
|
227
|
+
]
|
|
228
|
+
})]
|
|
229
|
+
}, panel.id))
|
|
230
|
+
}), caption]
|
|
198
231
|
}, node.id);
|
|
199
232
|
}
|
|
200
233
|
case "code": return /* @__PURE__ */ jsx("pre", {
|
|
@@ -340,15 +373,16 @@ function ReportListPage() {
|
|
|
340
373
|
}) })
|
|
341
374
|
] });
|
|
342
375
|
}
|
|
343
|
-
|
|
344
|
-
|
|
376
|
+
/** 报告页:正文是评论范围,右下角是本页评论;评论归属键是报告页的地址 */
|
|
377
|
+
function ReportDocumentPage({ commentAction, CommentBody, AvatarComponent }) {
|
|
378
|
+
const { report, toc, reportId, siteName, hrefBase, comments } = useLoaderData();
|
|
345
379
|
const { metadata } = report.document;
|
|
346
380
|
const metaLine = [
|
|
347
381
|
metadata.revision,
|
|
348
382
|
metadata.date,
|
|
349
383
|
metadata.authors?.join("、")
|
|
350
384
|
].filter((part) => part !== void 0 && part !== "");
|
|
351
|
-
return /* @__PURE__ */ jsxs(DocsPage, {
|
|
385
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(DocsPage, {
|
|
352
386
|
toc,
|
|
353
387
|
children: [
|
|
354
388
|
/* @__PURE__ */ jsx("title", { children: `${metadata.title} · ${siteName}` }),
|
|
@@ -362,12 +396,22 @@ function ReportDocumentPage() {
|
|
|
362
396
|
to: hrefBase,
|
|
363
397
|
children: "← 全部报告"
|
|
364
398
|
}),
|
|
365
|
-
/* @__PURE__ */ jsx(DocsBody, {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
399
|
+
/* @__PURE__ */ jsx(DocsBody, {
|
|
400
|
+
...commentScopeProps,
|
|
401
|
+
children: /* @__PURE__ */ jsx(ReportView, {
|
|
402
|
+
report,
|
|
403
|
+
assetUrl: (_source, compiledPath) => reportAssetHrefOf(hrefBase, reportId, compiledPath)
|
|
404
|
+
})
|
|
405
|
+
})
|
|
369
406
|
]
|
|
370
|
-
})
|
|
407
|
+
}), /* @__PURE__ */ jsx(CommentsDock, {
|
|
408
|
+
pagePath: reportHrefOf(hrefBase, reportId),
|
|
409
|
+
action: commentAction,
|
|
410
|
+
threads: comments.threads,
|
|
411
|
+
access: comments.access,
|
|
412
|
+
BodyComponent: CommentBody,
|
|
413
|
+
...AvatarComponent !== void 0 ? { AvatarComponent } : {}
|
|
414
|
+
})] });
|
|
371
415
|
}
|
|
372
416
|
//#endregion
|
|
373
417
|
//#region src/site/report-meta.ts
|
|
@@ -396,7 +440,7 @@ function reportMeta(input) {
|
|
|
396
440
|
case "abstract": return `## ${node.title ?? "摘要"}\n\n${node.paragraphs.map((p) => block(p)).join("\n\n")}\n\n关键词:${node.keywords.join(";")}`;
|
|
397
441
|
case "paragraph": return inline(node.content);
|
|
398
442
|
case "equation": return `$$\n${node.latex}\n$$${report?.index[node.id]?.number ? `\n(${report.index[node.id].number})` : ""}`;
|
|
399
|
-
case "figure": return `\n\n${report?.index[node.id]?.label ?? ""} ${inline(node.caption)}`;
|
|
443
|
+
case "figure": return `${node.panels ? node.panels.map((panel) => `\n${report?.index[panel.id]?.label ?? panel.id}${panel.caption ? " " + inline(panel.caption) : ""}`).join("\n\n") : ``}\n\n${report?.index[node.id]?.label ?? ""} ${inline(node.caption)}`;
|
|
400
444
|
case "table": return `${node.caption ? `${report?.index[node.id]?.label ?? ""} ${inline(node.caption)}\n\n` : ""}${node.rows.map((row) => row.cells.map((cell) => cell.blocks.map((child) => block(child, depth)).join("\n")).join(" | ")).join("\n")}`;
|
|
401
445
|
case "list": return node.items.map((item, i) => `${node.ordered ? `${(node.start ?? 1) + i}.` : "-"} ${item.blocks.map((child) => block(child, depth)).join("\n")}`).join("\n");
|
|
402
446
|
case "code": return `\`\`\`${node.language}\n${node.text}\n\`\`\``;
|
|
@@ -51,13 +51,38 @@ interface ReportList {
|
|
|
51
51
|
blocks: (Paragraph | ReportList)[];
|
|
52
52
|
}[];
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
/** 多图里的一张:放在第 row 行第 column 列(从 1 起),图下显示「(label)」,caption 是写在编号后面的小标题;label 由人写,不自动编 */
|
|
55
|
+
interface FigurePanel {
|
|
56
|
+
id: string;
|
|
57
|
+
row: number;
|
|
58
|
+
column: number;
|
|
59
|
+
label: string;
|
|
60
|
+
src: string;
|
|
61
|
+
alt: string;
|
|
62
|
+
caption?: ReportInline[];
|
|
63
|
+
}
|
|
64
|
+
interface SingleImageFigure {
|
|
55
65
|
type: 'figure';
|
|
56
66
|
id: string;
|
|
57
67
|
src: string;
|
|
58
68
|
caption: ReportInline[];
|
|
59
69
|
alt: string;
|
|
70
|
+
rows?: never;
|
|
71
|
+
columns?: never;
|
|
72
|
+
panels?: never;
|
|
73
|
+
}
|
|
74
|
+
/** 一个图号下的多张图:rows 行 columns 列的网格,每张图写明自己的格子,没写到的格子留空;caption 是整个图的图题 */
|
|
75
|
+
interface MultiImageFigure {
|
|
76
|
+
type: 'figure';
|
|
77
|
+
id: string;
|
|
78
|
+
caption: ReportInline[];
|
|
79
|
+
rows: number;
|
|
80
|
+
columns: number;
|
|
81
|
+
panels: FigurePanel[];
|
|
82
|
+
src?: never;
|
|
83
|
+
alt?: never;
|
|
60
84
|
}
|
|
85
|
+
type Figure = SingleImageFigure | MultiImageFigure;
|
|
61
86
|
interface Equation {
|
|
62
87
|
type: 'equation';
|
|
63
88
|
id: string;
|
|
@@ -169,4 +194,4 @@ declare class ReportContentError extends Error {
|
|
|
169
194
|
constructor(message: string, file?: string, pointer?: string);
|
|
170
195
|
}
|
|
171
196
|
//#endregion
|
|
172
|
-
export {
|
|
197
|
+
export { SingleImageFigure as C, TableCell as E, Section as S, Table as T, ReportContentError as _, CellBlock as a, ReportInline as b, ContentWarning as c, FigurePanel as d, Include as f, ReportBlock as g, Paragraph as h, Bibliography as i, Equation as l, MultiImageFigure as m, AssemblyOptions as n, Code as o, IndexEntry as p, AssemblyResult as r, CompiledReport as s, Abstract as t, Figure as u, ReportDocument as v, SourceLocation as w, ReportList as x, ReportIndex as y };
|
package/engine/README.md
CHANGED
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
.\.venv\Scripts\python.exe -m unittest discover -s tests -v
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## word-report 交给引擎的数据
|
|
17
17
|
|
|
18
|
-
网站只用于阅读和评论。`@godcreator/
|
|
18
|
+
网站只用于阅读和评论。`@godcreator/word-report` 的命令行 `report-docx` 与 `./node` 出口的 `reportExport` 先把分章内容编译成完整 `format: docreport` / `schemaVersion: 1` 文档与 `assets/`,交给以下入口。它们不依赖 gwb。`--data-only` 必须传递;这个模式不导入快照目录中的 `report.py`。未展开的文档引用不会通过正式 Schema。
|
|
19
19
|
|
|
20
20
|
```powershell
|
|
21
21
|
.\.venv\Scripts\python.exe -m docreport validate <快照目录> --data-only
|
|
22
22
|
.\.venv\Scripts\python.exe -m docreport build <快照目录> --data-only --out out\<新的运行目录>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
需要版式数据时,可加 `--layout <layout.json>`,内容为已有 `validate_layout` 支持的节点 ID 字典:段落样式、图片宽度、表格列宽与行高、顶层章节起页与方向。默认采用 `templates/project-report.dotx
|
|
25
|
+
需要版式数据时,可加 `--layout <layout.json>`,内容为已有 `validate_layout` 支持的节点 ID 字典:段落样式、图片宽度、表格列宽与行高、顶层章节起页与方向。默认采用 `templates/project-report.dotx`。word-report 的命令行与 `reportExport` 不接受任意 Python 布局程序。
|
|
26
26
|
|
|
27
27
|
已有 demo 保留其受信任的 `report.py` 与原入口,用于原有版式和专属验收回归:
|
|
28
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godcreator/word-report",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "godcreator"
|
|
6
6
|
},
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"LICENSE"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
+
"@godcreator/comments": "^0.1.3",
|
|
45
46
|
"sharp": "^0.35.5"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
package/styles/report.css
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
.report-document figure { margin: 1.5rem 0; text-align: center; }
|
|
7
7
|
.report-document img { margin: 0 auto; max-width: 100%; height: auto; }
|
|
8
8
|
.report-document figcaption, .report-document caption { color: var(--color-fd-muted-foreground); font-size: .9em; }
|
|
9
|
+
/* 多图:行列由报告写定(网格与每张的格子写在元素的 style 上),各列等宽、没写到的格子留空,任何宽度都不重排;同一行里图片上下居中、(a)(b) 对齐在同一条线上 */
|
|
10
|
+
.report-panels { display: grid; gap: .75rem 1rem; }
|
|
11
|
+
.report-panel { min-width: 0; display: flex; flex-direction: column; }
|
|
12
|
+
.report-panel-image { flex: 1; display: flex; align-items: center; justify-content: center; }
|
|
13
|
+
.report-panel-caption { margin-top: .25rem; font-size: .9em; color: var(--color-fd-muted-foreground); }
|
|
14
|
+
.report-multi-figure > figcaption { margin-top: .5rem; }
|
|
9
15
|
.report-document .report-equation { display: flex; align-items: center; gap: 1rem; margin: 1.25rem 0; }
|
|
10
16
|
.report-math-block { display: block; flex: 1; min-width: 0; overflow-x: auto; overflow-y: hidden; padding: .5em 0; }
|
|
11
17
|
.report-equation-number { flex-shrink: 0; }
|
package/template/report.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Route } from './+types/report'
|
|
2
2
|
import { ReportDocumentPage } from '@godcreator/word-report/site'
|
|
3
3
|
import { reportPageLoader } from '@godcreator/word-report/site/server'
|
|
4
|
+
import { commentAvatar, CommentBody } from '@/lib/content'
|
|
4
5
|
import { wordReportSite } from './site.server'
|
|
5
6
|
|
|
6
7
|
export const loader = (args: Route.LoaderArgs) => reportPageLoader(args, wordReportSite())
|
|
7
8
|
|
|
9
|
+
/** 评论写进站点的评论写入路由 /comments(comments 功能),和内容页一样 */
|
|
8
10
|
export default function Report() {
|
|
9
|
-
return <ReportDocumentPage />
|
|
11
|
+
return <ReportDocumentPage commentAction="/comments" CommentBody={CommentBody} AvatarComponent={commentAvatar} />
|
|
10
12
|
}
|
package/template/routes.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { relative, type RouteConfig } from '@react-router/dev/routes'
|
|
|
2
2
|
|
|
3
3
|
const { route } = relative(import.meta.dirname)
|
|
4
4
|
|
|
5
|
-
// 地址与
|
|
5
|
+
// 地址与 href.ts 的 reportHrefBase 一致;参数名 report、* 是 reportPageLoader、reportAssetLoader 读的
|
|
6
6
|
export default [
|
|
7
7
|
route('reports', './list.tsx'),
|
|
8
8
|
route('reports/:report', './report.tsx'),
|
package/template/site.server.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import { commentsAccessOf } from '@godcreator/comments/site'
|
|
2
|
+
import { pageThreadsOf } from '@godcreator/comments/site/server'
|
|
1
3
|
import type { WordReportSite } from '@godcreator/word-report/site/server'
|
|
2
4
|
import { site } from '@/lib/site.server'
|
|
3
5
|
import { reportHrefBase } from './href'
|
|
4
6
|
|
|
5
|
-
/** 报告功能的站点信息,每次请求现组装:报告在站点根下的 reports/,站名取页面树根(content/meta.json 的 title
|
|
7
|
+
/** 报告功能的站点信息,每次请求现组装:报告在站点根下的 reports/,站名取页面树根(content/meta.json 的 title),评论取站点的评论集合 */
|
|
6
8
|
export function wordReportSite(): WordReportSite {
|
|
7
9
|
const name = site.source.getPageTree().name
|
|
8
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
siteDir: site.dir,
|
|
12
|
+
siteName: typeof name === 'string' ? name : '',
|
|
13
|
+
hrefBase: reportHrefBase,
|
|
14
|
+
pageComments: (page) => ({
|
|
15
|
+
threads: pageThreadsOf({ entries: site.commentEntries, people: site.people(), page }),
|
|
16
|
+
access: commentsAccessOf(site.identity()),
|
|
17
|
+
}),
|
|
18
|
+
}
|
|
9
19
|
}
|