mosage 0.2.0 → 0.8.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.
- package/README.md +16 -15
- package/bin.js +2 -0
- package/dist/build-C7NW_3Pk.js +14 -0
- package/dist/check-CP4873Wx.js +41 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +228 -0
- package/dist/config-DPm1BBAb.js +2619 -0
- package/dist/config-TlTe7Ona.d.ts +24 -0
- package/dist/context-BqsdSrAQ.js +1084 -0
- package/dist/dev-Biz42qlu.js +17 -0
- package/dist/diagram-xlVDekYk.js +763 -0
- package/dist/export-Bi6nuxjT.js +31 -0
- package/dist/import-D2jNB07F.js +25 -0
- package/dist/index.d.ts +455 -0
- package/dist/index.js +693 -0
- package/dist/{cli.js → init-Bbtj2pxF.js} +34 -68
- package/dist/preview-CLm51aRt.js +19 -0
- package/dist/sdk-DjpX6mCv.js +51 -0
- package/dist/vite/index.d.ts +25 -0
- package/dist/vite/index.js +2 -0
- package/env.d.ts +83 -0
- package/package.json +59 -12
- package/{template/.agents/skills → skills}/create-doc/SKILL.md +1 -1
- package/{template/.agents/skills → skills}/create-theme/SKILL.md +1 -1
- package/{template/.agents/skills → skills}/doc-authoring/SKILL.md +8 -9
- package/{template/.agents/skills → skills}/doc-authoring/references/assets.md +1 -1
- package/{template/.agents/skills → skills}/doc-authoring/references/design-system.md +1 -1
- package/{template/.agents/skills → skills}/doc-authoring/references/long-form.md +4 -4
- package/{template/.agents/skills → skills}/doc-authoring/references/pagination.md +1 -1
- package/src/app/app.tsx +42 -0
- package/src/app/components/data-table.tsx +196 -0
- package/src/app/components/design-panel/design-panel.tsx +318 -0
- package/src/app/components/design-panel/design-provider.tsx +121 -0
- package/src/app/components/design-panel/use-design.ts +85 -0
- package/src/app/components/diagram.tsx +76 -0
- package/src/app/components/doc-assets.tsx +129 -0
- package/src/app/components/doc-search.tsx +248 -0
- package/src/app/components/doc-sidebar.tsx +162 -0
- package/src/app/components/flow-page.tsx +93 -0
- package/src/app/components/footnote.tsx +204 -0
- package/src/app/components/image-placeholder.tsx +50 -0
- package/src/app/components/inspector/inspector.tsx +518 -0
- package/src/app/components/numbering.tsx +224 -0
- package/src/app/components/page-frame.tsx +70 -0
- package/src/app/components/sidebar/folder-item.tsx +212 -0
- package/src/app/components/sidebar/icon-picker.tsx +99 -0
- package/src/app/components/sidebar/sidebar.tsx +252 -0
- package/src/app/components/table-of-contents.tsx +93 -0
- package/src/app/components/theme-toggle.tsx +50 -0
- package/src/app/components/themes/markdown.tsx +249 -0
- package/src/app/components/themes/theme-preview.tsx +74 -0
- package/src/app/components/ui/menu.tsx +143 -0
- package/src/app/index.html +12 -0
- package/src/app/lib/agent-bridge.ts +140 -0
- package/src/app/lib/assets.ts +151 -0
- package/src/app/lib/design-presets.ts +109 -0
- package/src/app/lib/design.ts +88 -0
- package/src/app/lib/diagnostics.ts +282 -0
- package/src/app/lib/doc-preview.tsx +29 -0
- package/src/app/lib/docs.ts +26 -0
- package/src/app/lib/docx/extract.ts +1623 -0
- package/src/app/lib/docx/fonts.test.ts +136 -0
- package/src/app/lib/docx/fonts.ts +166 -0
- package/src/app/lib/docx/media.ts +102 -0
- package/src/app/lib/docx/model.ts +206 -0
- package/src/app/lib/docx/paragraph.test.ts +92 -0
- package/src/app/lib/docx/paragraph.ts +107 -0
- package/src/app/lib/docx/props.ts +187 -0
- package/src/app/lib/docx/styles.ts +306 -0
- package/src/app/lib/docx/units.ts +35 -0
- package/src/app/lib/docx/write.test.ts +507 -0
- package/src/app/lib/docx/write.ts +581 -0
- package/src/app/lib/docx/xml.ts +39 -0
- package/src/app/lib/export-docx.ts +289 -0
- package/src/app/lib/export-dom.ts +318 -0
- package/src/app/lib/export-html.ts +156 -0
- package/src/app/lib/export-image.ts +70 -0
- package/src/app/lib/export-pdf.ts +165 -0
- package/src/app/lib/flow-measure.test.ts +31 -0
- package/src/app/lib/flow-measure.ts +183 -0
- package/src/app/lib/flow.test.ts +110 -0
- package/src/app/lib/flow.ts +136 -0
- package/src/app/lib/folders.ts +192 -0
- package/src/app/lib/footnotes.test.tsx +102 -0
- package/src/app/lib/footnotes.ts +94 -0
- package/src/app/lib/inspector/fiber.ts +99 -0
- package/src/app/lib/labels.test.ts +18 -0
- package/src/app/lib/labels.ts +181 -0
- package/src/app/lib/outline.ts +118 -0
- package/src/app/lib/page-context.tsx +43 -0
- package/src/app/lib/page-range.test.ts +95 -0
- package/src/app/lib/page-range.ts +90 -0
- package/src/app/lib/print-ready.ts +69 -0
- package/src/app/lib/rasterize.ts +173 -0
- package/src/app/lib/scan.ts +26 -0
- package/src/app/lib/sdk.test.ts +32 -0
- package/src/app/lib/sdk.ts +115 -0
- package/src/app/lib/themes.ts +31 -0
- package/src/app/lib/use-doc-module.ts +53 -0
- package/src/app/lib/use-doc-pages.ts +147 -0
- package/src/app/lib/utils.ts +6 -0
- package/src/app/lib/view-mode.test.ts +91 -0
- package/src/app/lib/view-mode.ts +104 -0
- package/src/app/main.tsx +14 -0
- package/src/app/routes/assets.tsx +257 -0
- package/src/app/routes/doc.tsx +877 -0
- package/src/app/routes/home-shell.tsx +203 -0
- package/src/app/routes/home.tsx +269 -0
- package/src/app/routes/themes.tsx +121 -0
- package/src/app/styles.css +97 -0
- package/src/app/virtual.d.ts +30 -0
- package/template/AGENTS.md +1 -1
- package/template/README.md +24 -52
- package/template/docs/getting-started/index.tsx +2 -2
- package/template/mosage.config.ts +1 -1
- package/template/package.json +1 -1
- package/template/tsconfig.json +1 -1
- package/README.zh-TW.md +0 -28
- /package/{template/.agents/skills → skills}/apply-comments/SKILL.md +0 -0
- /package/{template/.agents/skills → skills}/current-doc/SKILL.md +0 -0
- /package/{template/.agents/skills → skills}/doc-authoring/references/tables-and-charts.md +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import { a as defaultDesign, i as cssVarsToString, n as DiagramSyntaxError, o as designToCssVars, r as parseDelimited, t as compileDiagram } from "./diagram-xlVDekYk.js";
|
|
2
|
+
import { a as PAGE_SIZE_NAMES, c as resolvePageGeometry, i as PAGE_SIZES, n as DEFAULT_PAGE_SIZE, o as isOrientation, r as ORIENTATIONS, s as isPageSizeName, t as DEFAULT_ORIENTATION } from "./sdk-DjpX6mCv.js";
|
|
3
|
+
import { Children, Fragment, createContext, isValidElement, useContext, useEffect, useId, useRef, useSyncExternalStore } from "react";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/app/lib/outline.ts
|
|
6
|
+
/** A contents list; headings inside one are never part of the outline. */
|
|
7
|
+
const TOC_ATTR = "data-od-toc";
|
|
8
|
+
const GLOBAL_KEY$3 = "__mosage_outline_store__";
|
|
9
|
+
const g$3 = globalThis;
|
|
10
|
+
if (!g$3[GLOBAL_KEY$3]) g$3[GLOBAL_KEY$3] = {
|
|
11
|
+
snapshot: [],
|
|
12
|
+
listeners: /* @__PURE__ */ new Set()
|
|
13
|
+
};
|
|
14
|
+
const store$1 = g$3[GLOBAL_KEY$3];
|
|
15
|
+
function getOutline() {
|
|
16
|
+
return store$1.snapshot;
|
|
17
|
+
}
|
|
18
|
+
function subscribe$1(listener) {
|
|
19
|
+
store$1.listeners.add(listener);
|
|
20
|
+
return () => store$1.listeners.delete(listener);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The document's headings, filled in after the pages render. Empty on the first
|
|
24
|
+
* pass — a `<TableOfContents>` renders its rows on the second, once the scan
|
|
25
|
+
* has run.
|
|
26
|
+
*/
|
|
27
|
+
function useDocOutline() {
|
|
28
|
+
return useSyncExternalStore(subscribe$1, getOutline, getOutline);
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/app/lib/labels.ts
|
|
32
|
+
const LABEL_ATTR = "data-od-label";
|
|
33
|
+
const LABEL_ID_ATTR = "data-od-label-id";
|
|
34
|
+
const LABEL_TEXT_ATTR = "data-od-label-text";
|
|
35
|
+
const EMPTY = {
|
|
36
|
+
entries: [],
|
|
37
|
+
vocabulary: {
|
|
38
|
+
figure: "Figure",
|
|
39
|
+
table: "Table",
|
|
40
|
+
footnotes: "",
|
|
41
|
+
onPage: " (p. {page})"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const GLOBAL_KEY$2 = "__mosage_labels_store__";
|
|
45
|
+
const g$2 = globalThis;
|
|
46
|
+
if (!g$2[GLOBAL_KEY$2]) g$2[GLOBAL_KEY$2] = {
|
|
47
|
+
snapshot: EMPTY,
|
|
48
|
+
listeners: /* @__PURE__ */ new Set()
|
|
49
|
+
};
|
|
50
|
+
const store = g$2[GLOBAL_KEY$2];
|
|
51
|
+
function getLabels() {
|
|
52
|
+
return store.snapshot;
|
|
53
|
+
}
|
|
54
|
+
function subscribe(listener) {
|
|
55
|
+
store.listeners.add(listener);
|
|
56
|
+
return () => store.listeners.delete(listener);
|
|
57
|
+
}
|
|
58
|
+
function useLabelSnapshot() {
|
|
59
|
+
return useSyncExternalStore(subscribe, getLabels, getLabels);
|
|
60
|
+
}
|
|
61
|
+
/** The document's numbered items of one kind, in order. Empty on the first render pass. */
|
|
62
|
+
function useDocLabels(kind) {
|
|
63
|
+
return useLabelSnapshot().entries.filter((entry) => entry.kind === kind);
|
|
64
|
+
}
|
|
65
|
+
/** One numbered item by id, once the scan has run. */
|
|
66
|
+
function useDocLabel(id) {
|
|
67
|
+
return useLabelSnapshot().entries.find((entry) => entry.id === id) ?? null;
|
|
68
|
+
}
|
|
69
|
+
function useLabelVocabulary() {
|
|
70
|
+
return useLabelSnapshot().vocabulary;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The words either side of the page number in a cross-reference's page suffix,
|
|
74
|
+
* or null when the suffix names no page — it is then printed as written.
|
|
75
|
+
*/
|
|
76
|
+
function onPageParts(vocabulary) {
|
|
77
|
+
const at = vocabulary.onPage.indexOf("{page}");
|
|
78
|
+
if (at < 0) return null;
|
|
79
|
+
return [vocabulary.onPage.slice(0, at), vocabulary.onPage.slice(at + 6)];
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/app/components/footnote.tsx
|
|
83
|
+
/** Stands in for a number the scan has not produced yet, or never will. */
|
|
84
|
+
const UNNUMBERED = "•";
|
|
85
|
+
const GLOBAL_KEY$1 = "__mosage_footnote_context__";
|
|
86
|
+
const g$1 = globalThis;
|
|
87
|
+
if (!g$1[GLOBAL_KEY$1]) g$1[GLOBAL_KEY$1] = createContext(null);
|
|
88
|
+
const FootnoteContext = g$1[GLOBAL_KEY$1];
|
|
89
|
+
const FOOTNOTE_MARKER_FLAG = "__odFootnote";
|
|
90
|
+
function markerStyle() {
|
|
91
|
+
return {
|
|
92
|
+
fontSize: "0.7em",
|
|
93
|
+
lineHeight: 0,
|
|
94
|
+
verticalAlign: "super",
|
|
95
|
+
fontVariantNumeric: "tabular-nums",
|
|
96
|
+
color: "var(--od-accent)",
|
|
97
|
+
padding: "0 1px"
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The superscript in the text. Its position in the document decides the number.
|
|
102
|
+
*
|
|
103
|
+
* The placeholder is the one the printed note uses, and deliberately not an
|
|
104
|
+
* empty string: the two describe the same unresolved state, a note whose number
|
|
105
|
+
* never arrives has to be visible in both places rather than silently blank,
|
|
106
|
+
* and the packer measures this glyph — an empty marker is narrower than the
|
|
107
|
+
* digit that replaces it.
|
|
108
|
+
*/
|
|
109
|
+
function FootnoteMarker({ id }) {
|
|
110
|
+
const entry = useDocLabel(id);
|
|
111
|
+
return /* @__PURE__ */ jsx("sup", {
|
|
112
|
+
[LABEL_ATTR]: "footnote",
|
|
113
|
+
[LABEL_ID_ATTR]: id,
|
|
114
|
+
style: markerStyle(),
|
|
115
|
+
children: entry?.number ?? UNNUMBERED
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A note anchored to this point in the text. Inside a `flow()` section the
|
|
120
|
+
* framework lifts it to the foot of whatever page the marker lands on; on a
|
|
121
|
+
* fixed page, put a `<Footnotes />` where you want them printed.
|
|
122
|
+
*/
|
|
123
|
+
function Footnote({ id, children }) {
|
|
124
|
+
const generated = useId();
|
|
125
|
+
const noteId = id ?? generated;
|
|
126
|
+
const collector = useContext(FootnoteContext);
|
|
127
|
+
const latest = useRef(null);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
latest.current = collector;
|
|
130
|
+
collector?.register(noteId, children);
|
|
131
|
+
});
|
|
132
|
+
useEffect(() => () => latest.current?.unregister(noteId), [noteId]);
|
|
133
|
+
return /* @__PURE__ */ jsx(FootnoteMarker, { id: noteId });
|
|
134
|
+
}
|
|
135
|
+
Footnote[FOOTNOTE_MARKER_FLAG] = true;
|
|
136
|
+
const FOOTNOTE_ROW_ATTR = "data-od-footnote-row";
|
|
137
|
+
const FOOTNOTE_BODY_ATTR = "data-od-footnote-body";
|
|
138
|
+
const FOOTNOTES_ATTR = "data-od-footnotes";
|
|
139
|
+
function footnoteAreaStyle() {
|
|
140
|
+
return {
|
|
141
|
+
borderTop: "1px solid var(--od-rule)",
|
|
142
|
+
paddingTop: 6,
|
|
143
|
+
marginTop: 12,
|
|
144
|
+
fontSize: "var(--od-size-caption)",
|
|
145
|
+
lineHeight: 1.45,
|
|
146
|
+
color: "var(--od-muted)"
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** One note's row. Shared with the measurement pass so the reserved space is real. */
|
|
150
|
+
function FootnoteRow({ id, content }) {
|
|
151
|
+
const entry = useDocLabel(id);
|
|
152
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
153
|
+
[FOOTNOTE_ROW_ATTR]: id,
|
|
154
|
+
style: {
|
|
155
|
+
display: "flex",
|
|
156
|
+
gap: 5,
|
|
157
|
+
marginBottom: 3
|
|
158
|
+
},
|
|
159
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
160
|
+
style: {
|
|
161
|
+
flex: "none",
|
|
162
|
+
fontVariantNumeric: "tabular-nums",
|
|
163
|
+
color: "var(--od-accent)"
|
|
164
|
+
},
|
|
165
|
+
children: entry?.number ?? UNNUMBERED
|
|
166
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
167
|
+
[FOOTNOTE_BODY_ATTR]: "",
|
|
168
|
+
style: { minWidth: 0 },
|
|
169
|
+
children: content
|
|
170
|
+
})]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function Footnotes({ notes, style, className }) {
|
|
174
|
+
const collector = useContext(FootnoteContext);
|
|
175
|
+
const vocabulary = useLabelVocabulary();
|
|
176
|
+
const list = notes ?? collector?.ids.map((id) => ({
|
|
177
|
+
id,
|
|
178
|
+
content: collector.contents.get(id)
|
|
179
|
+
})) ?? [];
|
|
180
|
+
if (list.length === 0) return null;
|
|
181
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
182
|
+
[FOOTNOTES_ATTR]: "",
|
|
183
|
+
className,
|
|
184
|
+
style: {
|
|
185
|
+
...footnoteAreaStyle(),
|
|
186
|
+
...style
|
|
187
|
+
},
|
|
188
|
+
children: [vocabulary.footnotes ? /* @__PURE__ */ jsx("div", {
|
|
189
|
+
style: {
|
|
190
|
+
fontWeight: 600,
|
|
191
|
+
marginBottom: 3
|
|
192
|
+
},
|
|
193
|
+
children: vocabulary.footnotes
|
|
194
|
+
}) : null, list.map((note) => /* @__PURE__ */ jsx(FootnoteRow, {
|
|
195
|
+
id: note.id,
|
|
196
|
+
content: note.content
|
|
197
|
+
}, note.id))]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/app/lib/page-context.tsx
|
|
202
|
+
const GLOBAL_KEY = "__mosage_page_context__";
|
|
203
|
+
const g = globalThis;
|
|
204
|
+
if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = createContext(null);
|
|
205
|
+
const PageContext = g[GLOBAL_KEY];
|
|
206
|
+
/** 1-based page number of the page currently rendering. `0` outside a page. */
|
|
207
|
+
function useDocPageNumber() {
|
|
208
|
+
const ctx = useContext(PageContext);
|
|
209
|
+
return ctx ? ctx.index + 1 : 0;
|
|
210
|
+
}
|
|
211
|
+
/** Total page count of the document being rendered. `0` outside a page. */
|
|
212
|
+
function useDocPageCount() {
|
|
213
|
+
const ctx = useContext(PageContext);
|
|
214
|
+
return ctx ? ctx.total : 0;
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/app/components/numbering.tsx
|
|
218
|
+
const REF_ATTR = "data-od-ref";
|
|
219
|
+
const LIST_OF_ATTR = "data-od-list-of";
|
|
220
|
+
function nameFor(kind, vocabulary) {
|
|
221
|
+
return kind === "table" ? vocabulary.table : vocabulary.figure;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* A numbered figure or table. The number comes from a scan of the rendered
|
|
225
|
+
* pages, not from a counter in the source, so inserting a figure halfway
|
|
226
|
+
* through a document renumbers everything after it — including the references
|
|
227
|
+
* to it — without anyone editing a number.
|
|
228
|
+
*
|
|
229
|
+
* Caption and content are one block, so a `flow()` section never separates them.
|
|
230
|
+
*/
|
|
231
|
+
function Figure({ id, caption, kind = "figure", captionPosition, captionText, style, captionStyle, className, children }) {
|
|
232
|
+
const generated = useId();
|
|
233
|
+
const labelId = id ?? generated;
|
|
234
|
+
const entry = useDocLabel(labelId);
|
|
235
|
+
const vocabulary = useLabelVocabulary();
|
|
236
|
+
const position = captionPosition ?? (kind === "table" ? "above" : "below");
|
|
237
|
+
const text = captionText ?? (typeof caption === "string" || typeof caption === "number" ? String(caption) : "");
|
|
238
|
+
const captionNode = caption ? /* @__PURE__ */ jsxs("figcaption", {
|
|
239
|
+
style: {
|
|
240
|
+
fontSize: "var(--od-size-caption)",
|
|
241
|
+
color: "var(--od-muted)",
|
|
242
|
+
lineHeight: 1.4,
|
|
243
|
+
margin: position === "above" ? "0 0 6px" : "6px 0 0",
|
|
244
|
+
...captionStyle
|
|
245
|
+
},
|
|
246
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
247
|
+
style: {
|
|
248
|
+
fontWeight: 600,
|
|
249
|
+
color: "var(--od-text)"
|
|
250
|
+
},
|
|
251
|
+
children: [
|
|
252
|
+
nameFor(kind, vocabulary),
|
|
253
|
+
" ",
|
|
254
|
+
entry?.number ?? ""
|
|
255
|
+
]
|
|
256
|
+
}), caption ? /* @__PURE__ */ jsxs("span", { children: [" — ", caption] }) : null]
|
|
257
|
+
}) : null;
|
|
258
|
+
return /* @__PURE__ */ jsxs("figure", {
|
|
259
|
+
[LABEL_ATTR]: kind,
|
|
260
|
+
[LABEL_ID_ATTR]: labelId,
|
|
261
|
+
[LABEL_TEXT_ATTR]: text,
|
|
262
|
+
className,
|
|
263
|
+
style: {
|
|
264
|
+
margin: "0 0 16px",
|
|
265
|
+
...style
|
|
266
|
+
},
|
|
267
|
+
children: [
|
|
268
|
+
position === "above" && captionNode,
|
|
269
|
+
children,
|
|
270
|
+
position === "below" && captionNode
|
|
271
|
+
]
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* A cross-reference that resolves after layout: "Figure 3", plus the page when
|
|
276
|
+
* the target is elsewhere. Never write "see Figure 3 on page 12" by hand — both
|
|
277
|
+
* numbers move.
|
|
278
|
+
*/
|
|
279
|
+
function Ref({ to, showPage = "auto", style, className }) {
|
|
280
|
+
const entry = useDocLabel(to);
|
|
281
|
+
const vocabulary = useLabelVocabulary();
|
|
282
|
+
const here = useDocPageNumber();
|
|
283
|
+
if (!entry) return /* @__PURE__ */ jsxs("span", {
|
|
284
|
+
"data-od-ref-unresolved": to,
|
|
285
|
+
className,
|
|
286
|
+
style: {
|
|
287
|
+
color: "var(--od-accent)",
|
|
288
|
+
...style
|
|
289
|
+
},
|
|
290
|
+
children: [
|
|
291
|
+
"[?",
|
|
292
|
+
to,
|
|
293
|
+
"]"
|
|
294
|
+
]
|
|
295
|
+
});
|
|
296
|
+
const name = entry.kind === "footnote" ? "" : `${nameFor(entry.kind, vocabulary)} `;
|
|
297
|
+
const withPage = showPage === "auto" ? entry.page !== here : showPage;
|
|
298
|
+
const parts = onPageParts(vocabulary);
|
|
299
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
300
|
+
[REF_ATTR]: to,
|
|
301
|
+
className,
|
|
302
|
+
style,
|
|
303
|
+
children: [
|
|
304
|
+
name,
|
|
305
|
+
entry.number,
|
|
306
|
+
withPage && parts && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
307
|
+
parts[0],
|
|
308
|
+
/* @__PURE__ */ jsx("span", {
|
|
309
|
+
["data-od-ref-page"]: "",
|
|
310
|
+
children: entry.page
|
|
311
|
+
}),
|
|
312
|
+
parts[1]
|
|
313
|
+
] }),
|
|
314
|
+
withPage && !parts && vocabulary.onPage
|
|
315
|
+
]
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The document's figures or tables as a contents list. Same lifecycle as
|
|
320
|
+
* `<TableOfContents>`: empty on the first render pass, filled once the scan has
|
|
321
|
+
* run — including before an export serializes.
|
|
322
|
+
*/
|
|
323
|
+
function ListOf({ kind = "figure", showPageNumbers = true, style, className }) {
|
|
324
|
+
const entries = useDocLabels(kind);
|
|
325
|
+
const vocabulary = useLabelVocabulary();
|
|
326
|
+
return /* @__PURE__ */ jsx("div", {
|
|
327
|
+
[LIST_OF_ATTR]: kind,
|
|
328
|
+
className,
|
|
329
|
+
style: {
|
|
330
|
+
fontFamily: "var(--od-font-body)",
|
|
331
|
+
fontSize: "var(--od-size-body)",
|
|
332
|
+
color: "var(--od-text)",
|
|
333
|
+
...style
|
|
334
|
+
},
|
|
335
|
+
children: entries.map((entry) => /* @__PURE__ */ jsxs("div", {
|
|
336
|
+
[REF_ATTR]: entry.id,
|
|
337
|
+
style: {
|
|
338
|
+
display: "flex",
|
|
339
|
+
alignItems: "baseline",
|
|
340
|
+
gap: 8,
|
|
341
|
+
marginBottom: 6
|
|
342
|
+
},
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ jsxs("span", {
|
|
345
|
+
style: {
|
|
346
|
+
flex: "none",
|
|
347
|
+
color: "var(--od-muted)"
|
|
348
|
+
},
|
|
349
|
+
children: [
|
|
350
|
+
nameFor(kind, vocabulary),
|
|
351
|
+
" ",
|
|
352
|
+
entry.number
|
|
353
|
+
]
|
|
354
|
+
}),
|
|
355
|
+
/* @__PURE__ */ jsx("span", { children: entry.text }),
|
|
356
|
+
showPageNumbers && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
357
|
+
"aria-hidden": true,
|
|
358
|
+
style: {
|
|
359
|
+
flex: 1,
|
|
360
|
+
borderBottom: "1px dotted var(--od-rule)",
|
|
361
|
+
transform: "translateY(-3px)"
|
|
362
|
+
}
|
|
363
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
364
|
+
["data-od-ref-page"]: "",
|
|
365
|
+
style: { fontVariantNumeric: "tabular-nums" },
|
|
366
|
+
children: entry.page
|
|
367
|
+
})] })
|
|
368
|
+
]
|
|
369
|
+
}, entry.id))
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function ListOfFigures(props) {
|
|
373
|
+
return /* @__PURE__ */ jsx(ListOf, {
|
|
374
|
+
...props,
|
|
375
|
+
kind: "figure"
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function ListOfTables(props) {
|
|
379
|
+
return /* @__PURE__ */ jsx(ListOf, {
|
|
380
|
+
...props,
|
|
381
|
+
kind: "table"
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/app/components/data-table.tsx
|
|
386
|
+
function normalizeColumn(column) {
|
|
387
|
+
return typeof column === "string" ? { key: column } : column;
|
|
388
|
+
}
|
|
389
|
+
function isNumber(value) {
|
|
390
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
391
|
+
}
|
|
392
|
+
/** A column of numbers aligns right; anything mixed stays left. */
|
|
393
|
+
function inferAlign(rows, key) {
|
|
394
|
+
let seen = 0;
|
|
395
|
+
for (const row of rows) {
|
|
396
|
+
const value = row[key];
|
|
397
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
398
|
+
if (!isNumber(value)) return "left";
|
|
399
|
+
seen++;
|
|
400
|
+
}
|
|
401
|
+
return seen > 0 ? "right" : "left";
|
|
402
|
+
}
|
|
403
|
+
function formatValue(value, format, row, emptyValue) {
|
|
404
|
+
if (typeof format === "function") return format(value, row);
|
|
405
|
+
if (value === null || value === void 0 || value === "") return emptyValue;
|
|
406
|
+
if (isNumber(value)) {
|
|
407
|
+
if (format === "integer") return Math.round(value).toLocaleString();
|
|
408
|
+
if (format === "percent") return `${(value * 100).toLocaleString(void 0, { maximumFractionDigits: 1 })}%`;
|
|
409
|
+
if (format === "text") return String(value);
|
|
410
|
+
return value.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
411
|
+
}
|
|
412
|
+
return String(value);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* A print-shaped table from data, so the numbers in a report come from a file
|
|
416
|
+
* instead of being retyped into JSX. Alignment and grouping follow the column's
|
|
417
|
+
* contents, which is the part people get wrong by hand.
|
|
418
|
+
*/
|
|
419
|
+
function DataTable({ rows, columns, caption, id, limit, compact = false, emptyValue = "—", style, className }) {
|
|
420
|
+
const resolved = (columns ?? Object.keys(rows[0] ?? {})).map(normalizeColumn);
|
|
421
|
+
const shown = limit !== void 0 ? rows.slice(0, limit) : rows;
|
|
422
|
+
const hidden = rows.length - shown.length;
|
|
423
|
+
const cellPadding = compact ? "4px 8px" : "7px 8px";
|
|
424
|
+
const table = /* @__PURE__ */ jsxs("table", {
|
|
425
|
+
className,
|
|
426
|
+
style: {
|
|
427
|
+
width: "100%",
|
|
428
|
+
borderCollapse: "collapse",
|
|
429
|
+
tableLayout: "fixed",
|
|
430
|
+
margin: 0,
|
|
431
|
+
...style
|
|
432
|
+
},
|
|
433
|
+
children: [
|
|
434
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: resolved.map((column) => {
|
|
435
|
+
const align = column.align ?? inferAlign(rows, column.key);
|
|
436
|
+
return /* @__PURE__ */ jsx("th", {
|
|
437
|
+
style: {
|
|
438
|
+
textAlign: align,
|
|
439
|
+
width: column.width,
|
|
440
|
+
fontFamily: "var(--od-font-heading)",
|
|
441
|
+
fontSize: 11,
|
|
442
|
+
fontWeight: 600,
|
|
443
|
+
letterSpacing: "0.04em",
|
|
444
|
+
textTransform: "uppercase",
|
|
445
|
+
color: "var(--od-muted)",
|
|
446
|
+
borderBottom: "1px solid var(--od-rule)",
|
|
447
|
+
padding: "0 8px 6px"
|
|
448
|
+
},
|
|
449
|
+
children: column.label ?? column.key
|
|
450
|
+
}, column.key);
|
|
451
|
+
}) }) }),
|
|
452
|
+
/* @__PURE__ */ jsx("tbody", { children: shown.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { children: resolved.map((column) => {
|
|
453
|
+
const align = column.align ?? inferAlign(rows, column.key);
|
|
454
|
+
return /* @__PURE__ */ jsx("td", {
|
|
455
|
+
style: {
|
|
456
|
+
textAlign: align,
|
|
457
|
+
fontSize: compact ? 11 : 12,
|
|
458
|
+
padding: cellPadding,
|
|
459
|
+
borderBottom: "1px solid var(--od-rule)",
|
|
460
|
+
fontVariantNumeric: align === "right" ? "tabular-nums" : void 0,
|
|
461
|
+
overflowWrap: "anywhere"
|
|
462
|
+
},
|
|
463
|
+
children: formatValue(row[column.key], column.format, row, emptyValue)
|
|
464
|
+
}, column.key);
|
|
465
|
+
}) }, rowIndex)) }),
|
|
466
|
+
hidden > 0 && /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
|
|
467
|
+
colSpan: resolved.length,
|
|
468
|
+
style: {
|
|
469
|
+
fontSize: "var(--od-size-caption)",
|
|
470
|
+
color: "var(--od-muted)",
|
|
471
|
+
padding: "6px 8px 0"
|
|
472
|
+
},
|
|
473
|
+
children: [
|
|
474
|
+
hidden,
|
|
475
|
+
" more row",
|
|
476
|
+
hidden === 1 ? "" : "s",
|
|
477
|
+
" not shown"
|
|
478
|
+
]
|
|
479
|
+
}) }) })
|
|
480
|
+
]
|
|
481
|
+
});
|
|
482
|
+
if (!caption) return table;
|
|
483
|
+
return /* @__PURE__ */ jsx(Figure, {
|
|
484
|
+
kind: "table",
|
|
485
|
+
caption,
|
|
486
|
+
...id !== void 0 ? { id } : {},
|
|
487
|
+
children: table
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/app/components/diagram.tsx
|
|
492
|
+
/**
|
|
493
|
+
* A diagram compiled from Mermaid-flavoured text at build time. The SVG is
|
|
494
|
+
* inlined rather than referenced through `<img>` so it inherits the document's
|
|
495
|
+
* theme variables — an `<img src="data:…">` would be painted in a document of
|
|
496
|
+
* its own, with none of this one's colours or faces.
|
|
497
|
+
*/
|
|
498
|
+
function Diagram({ chart, caption, captionText, kind = "figure", id, width, align = "center", style, className }) {
|
|
499
|
+
const drawing = /* @__PURE__ */ jsx("div", {
|
|
500
|
+
className: caption ? void 0 : className,
|
|
501
|
+
style: {
|
|
502
|
+
width: width ?? chart.width,
|
|
503
|
+
maxWidth: "100%",
|
|
504
|
+
marginLeft: align === "center" ? "auto" : void 0,
|
|
505
|
+
marginRight: align === "center" ? "auto" : void 0,
|
|
506
|
+
...caption ? void 0 : style
|
|
507
|
+
},
|
|
508
|
+
dangerouslySetInnerHTML: { __html: chart.svg }
|
|
509
|
+
});
|
|
510
|
+
if (!caption) return drawing;
|
|
511
|
+
return /* @__PURE__ */ jsx(Figure, {
|
|
512
|
+
...id ? { id } : {},
|
|
513
|
+
caption,
|
|
514
|
+
...captionText ? { captionText } : {},
|
|
515
|
+
kind,
|
|
516
|
+
className,
|
|
517
|
+
...style ? { style } : {},
|
|
518
|
+
children: drawing
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/app/components/image-placeholder.tsx
|
|
523
|
+
/**
|
|
524
|
+
* Stands in for an image the user has to supply (a chart from their data, a
|
|
525
|
+
* product screenshot). Use it instead of inventing filler — never for
|
|
526
|
+
* decoration.
|
|
527
|
+
*/
|
|
528
|
+
function ImagePlaceholder({ hint, width = "100%", height = 200, style, className }) {
|
|
529
|
+
return /* @__PURE__ */ jsx("div", {
|
|
530
|
+
"data-od-image-placeholder": "",
|
|
531
|
+
className,
|
|
532
|
+
style: {
|
|
533
|
+
width,
|
|
534
|
+
height,
|
|
535
|
+
display: "flex",
|
|
536
|
+
alignItems: "center",
|
|
537
|
+
justifyContent: "center",
|
|
538
|
+
textAlign: "center",
|
|
539
|
+
padding: 16,
|
|
540
|
+
boxSizing: "border-box",
|
|
541
|
+
border: "1px dashed var(--od-rule, #d4d4d8)",
|
|
542
|
+
borderRadius: "var(--od-radius, 6px)",
|
|
543
|
+
background: "repeating-linear-gradient(45deg, rgba(0,0,0,.02) 0 8px, transparent 8px 16px)",
|
|
544
|
+
color: "var(--od-muted, #6b7280)",
|
|
545
|
+
fontFamily: "var(--od-font-body, system-ui, sans-serif)",
|
|
546
|
+
fontSize: "var(--od-size-caption, 10px)",
|
|
547
|
+
lineHeight: 1.4,
|
|
548
|
+
...style
|
|
549
|
+
},
|
|
550
|
+
children: hint
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region src/app/components/table-of-contents.tsx
|
|
555
|
+
const TOC_ENTRY_ATTR = "data-od-toc-entry";
|
|
556
|
+
/** Read by the Word export, which rebuilds the list as a contents field. */
|
|
557
|
+
const TOC_DEPTH_ATTR = "data-od-toc-depth";
|
|
558
|
+
const TOC_LEVEL_ATTR = "data-od-toc-level";
|
|
559
|
+
/**
|
|
560
|
+
* Renders the document's headings as a contents list. The outline is filled in
|
|
561
|
+
* by a DOM scan after the pages render, so this component is empty on the very
|
|
562
|
+
* first pass and populated on the next — including during PDF/HTML export,
|
|
563
|
+
* which scans before serializing.
|
|
564
|
+
*/
|
|
565
|
+
function TableOfContents({ maxLevel = 2, showPageNumbers = true, includeOwnPage = false, indent = 20, style, className }) {
|
|
566
|
+
const outline = useDocOutline();
|
|
567
|
+
const ownPage = useDocPageNumber();
|
|
568
|
+
const entries = outline.filter((entry) => entry.level <= maxLevel && (includeOwnPage || entry.page !== ownPage));
|
|
569
|
+
return /* @__PURE__ */ jsx("div", {
|
|
570
|
+
[TOC_ATTR]: "",
|
|
571
|
+
[TOC_DEPTH_ATTR]: maxLevel,
|
|
572
|
+
className,
|
|
573
|
+
style: {
|
|
574
|
+
fontFamily: "var(--od-font-body)",
|
|
575
|
+
fontSize: "var(--od-size-body)",
|
|
576
|
+
color: "var(--od-text)",
|
|
577
|
+
...style
|
|
578
|
+
},
|
|
579
|
+
children: entries.map((entry) => /* @__PURE__ */ jsxs("div", {
|
|
580
|
+
[TOC_ENTRY_ATTR]: entry.id,
|
|
581
|
+
[TOC_LEVEL_ATTR]: entry.level,
|
|
582
|
+
style: {
|
|
583
|
+
display: "flex",
|
|
584
|
+
alignItems: "baseline",
|
|
585
|
+
gap: 8,
|
|
586
|
+
paddingLeft: (entry.level - 1) * indent,
|
|
587
|
+
marginBottom: entry.level === 1 ? 10 : 6,
|
|
588
|
+
fontWeight: entry.level === 1 ? 600 : 400,
|
|
589
|
+
color: entry.level === 1 ? "var(--od-text)" : "var(--od-muted)"
|
|
590
|
+
},
|
|
591
|
+
children: [/* @__PURE__ */ jsx("span", { children: entry.text }), showPageNumbers && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
592
|
+
"aria-hidden": true,
|
|
593
|
+
style: {
|
|
594
|
+
flex: 1,
|
|
595
|
+
borderBottom: "1px dotted var(--od-rule)",
|
|
596
|
+
transform: "translateY(-3px)"
|
|
597
|
+
}
|
|
598
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
599
|
+
["data-od-toc-page"]: "",
|
|
600
|
+
style: { fontVariantNumeric: "tabular-nums" },
|
|
601
|
+
children: entry.page
|
|
602
|
+
})] })]
|
|
603
|
+
}, entry.id))
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/app/lib/flow.ts
|
|
608
|
+
/**
|
|
609
|
+
* Flattens fragments so `flow(<>…</>)` yields one block per element the author
|
|
610
|
+
* wrote, not a single block containing the whole fragment.
|
|
611
|
+
*/
|
|
612
|
+
function toBlocks(node) {
|
|
613
|
+
const out = [];
|
|
614
|
+
for (const child of Children.toArray(node)) {
|
|
615
|
+
if (!isValidElement(child)) continue;
|
|
616
|
+
if (child.type === Fragment) {
|
|
617
|
+
out.push(...toBlocks(child.props.children));
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
out.push(child);
|
|
621
|
+
}
|
|
622
|
+
return out;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Wraps continuous content so the framework paginates it, instead of the author
|
|
626
|
+
* hand-splitting sections into fixed pages. Each direct child is one atomic
|
|
627
|
+
* block — a heading, a paragraph, a table — that never straddles a page break.
|
|
628
|
+
*/
|
|
629
|
+
function flow(children, opts = {}) {
|
|
630
|
+
return {
|
|
631
|
+
__odFlow: true,
|
|
632
|
+
blocks: toBlocks(children),
|
|
633
|
+
...opts
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function isFlowSection(entry) {
|
|
637
|
+
return typeof entry === "object" && entry !== null && entry.__odFlow === true;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Greedy top-to-bottom packing: fill a page until the next block would cross
|
|
641
|
+
* the bottom edge, then push that block — plus anything glued to it — onto the
|
|
642
|
+
* next page. Blocks are atomic; nothing is split mid-block.
|
|
643
|
+
*
|
|
644
|
+
* Footnotes are packed from the same budget: a block that carries notes brings
|
|
645
|
+
* their height with it, because those notes print at the foot of whichever page
|
|
646
|
+
* the block lands on.
|
|
647
|
+
*/
|
|
648
|
+
function paginateBlocks(blocks, available, opts = {}) {
|
|
649
|
+
const overhead = opts.footnoteOverhead ?? 0;
|
|
650
|
+
const pages = [];
|
|
651
|
+
const overflowing = [];
|
|
652
|
+
let current = [];
|
|
653
|
+
let used = 0;
|
|
654
|
+
let notes = 0;
|
|
655
|
+
const heightOf = (indices) => indices.reduce((sum, index) => sum + (blocks[index]?.height ?? 0), 0);
|
|
656
|
+
const notesOf = (indices) => indices.reduce((sum, index) => sum + (blocks[index]?.footnoteHeight ?? 0), 0);
|
|
657
|
+
/** A page with no notes pays nothing; the first note also pays for the rule. */
|
|
658
|
+
const reserve = (total) => total > 0 ? total + overhead : 0;
|
|
659
|
+
const closePage = () => {
|
|
660
|
+
if (current.length === 0) return;
|
|
661
|
+
pages.push(current);
|
|
662
|
+
current = [];
|
|
663
|
+
used = 0;
|
|
664
|
+
notes = 0;
|
|
665
|
+
};
|
|
666
|
+
blocks.forEach((block, index) => {
|
|
667
|
+
const own = block.footnoteHeight ?? 0;
|
|
668
|
+
if (block.height + reserve(own) > available) overflowing.push(index);
|
|
669
|
+
if (block.breakBefore) closePage();
|
|
670
|
+
if (current.length > 0 && used + block.height + reserve(notes + own) > available) {
|
|
671
|
+
let splitAt = current.length;
|
|
672
|
+
while (splitAt > 0 && blocks[current[splitAt - 1]]?.keepWithNext) splitAt--;
|
|
673
|
+
if (block.keepWithPrevious && splitAt === current.length) splitAt--;
|
|
674
|
+
if (splitAt > 0 && splitAt < current.length) {
|
|
675
|
+
const moved = current.slice(splitAt);
|
|
676
|
+
pages.push(current.slice(0, splitAt));
|
|
677
|
+
current = moved;
|
|
678
|
+
used = heightOf(moved);
|
|
679
|
+
notes = notesOf(moved);
|
|
680
|
+
} else closePage();
|
|
681
|
+
}
|
|
682
|
+
current.push(index);
|
|
683
|
+
used += block.height;
|
|
684
|
+
notes += own;
|
|
685
|
+
});
|
|
686
|
+
closePage();
|
|
687
|
+
return {
|
|
688
|
+
pages,
|
|
689
|
+
overflowing
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
//#endregion
|
|
693
|
+
export { DEFAULT_ORIENTATION, DEFAULT_PAGE_SIZE, DataTable, Diagram, DiagramSyntaxError, Figure, Footnote, Footnotes, ImagePlaceholder, ListOf, ListOfFigures, ListOfTables, ORIENTATIONS, PAGE_SIZES, PAGE_SIZE_NAMES, Ref, TableOfContents, compileDiagram, cssVarsToString, defaultDesign, designToCssVars, flow, isFlowSection, isOrientation, isPageSizeName, paginateBlocks, parseDelimited, resolvePageGeometry, useDocLabel, useDocLabels, useDocOutline, useDocPageCount, useDocPageNumber };
|