@docen/docx 0.3.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 +2 -3
- package/dist/blockquote-Ky9xD_Q-.mjs +31 -0
- package/dist/code-block-BgNjhADy.mjs +23 -0
- package/dist/converters/docx.d.mts +1 -109
- package/dist/converters/docx.mjs +1 -623
- package/dist/converters/html.d.mts +1 -13
- package/dist/converters/html.mjs +1 -17
- package/dist/converters/markdown.d.mts +1 -12
- package/dist/converters/markdown.mjs +1 -17
- package/dist/converters/patch.d.mts +1 -52
- package/dist/converters/patch.mjs +1 -36
- package/dist/converters/prepare.d.mts +2 -51
- package/dist/converters/prepare.mjs +2 -76
- package/dist/converters/styles.d.mts +2 -0
- package/dist/converters/styles.mjs +2 -0
- package/dist/core-DEqBHRzE.mjs +406 -0
- package/dist/core-fBMCCj4D.d.mts +97 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/details-Dh8luWb2.mjs +18 -0
- package/dist/dist-B6B3HnVZ.mjs +1838 -0
- package/dist/document-BH1y4qHM.d.mts +6 -0
- package/dist/document-CvTym-FM.mjs +50 -0
- package/dist/docx-C5wyjI-0.d.mts +200 -0
- package/dist/docx-CONyAedl.mjs +1231 -0
- package/dist/editor-Bf3CxQnY.mjs +21 -0
- package/dist/editor-DReY3Rb9.d.mts +21 -0
- package/dist/editor.d.mts +1 -20
- package/dist/editor.mjs +1 -19
- package/dist/emoji-BZ1dJD7d.d.mts +17 -0
- package/dist/emoji-C2OsTWR7.mjs +27 -0
- package/dist/extensions/blockquote.d.mts +23 -0
- package/dist/extensions/blockquote.mjs +3 -0
- package/dist/extensions/code-block.d.mts +19 -0
- package/dist/extensions/code-block.mjs +2 -0
- package/dist/extensions/column-break.d.mts +31 -0
- package/dist/extensions/column-break.mjs +2 -0
- package/dist/extensions/details.d.mts +19 -0
- package/dist/extensions/details.mjs +3 -0
- package/dist/extensions/document.d.mts +2 -0
- package/dist/extensions/document.mjs +2 -0
- package/dist/extensions/emoji.d.mts +2 -0
- package/dist/extensions/emoji.mjs +2 -0
- package/dist/extensions/extensions.d.mts +6 -5
- package/dist/extensions/extensions.mjs +14 -12
- package/dist/extensions/formatting-marks.d.mts +32 -0
- package/dist/extensions/formatting-marks.mjs +2 -0
- package/dist/extensions/heading.mjs +1 -144
- package/dist/extensions/image-group.d.mts +6 -0
- package/dist/extensions/image-group.mjs +2 -0
- package/dist/extensions/image.d.mts +2 -2
- package/dist/extensions/image.mjs +1 -200
- package/dist/extensions/index.d.mts +3 -2
- package/dist/extensions/index.mjs +3 -2
- package/dist/extensions/mention.d.mts +25 -0
- package/dist/extensions/mention.mjs +3 -0
- package/dist/extensions/ordered-list.d.mts +25 -0
- package/dist/extensions/ordered-list.mjs +3 -0
- package/dist/extensions/page-break.d.mts +34 -0
- package/dist/extensions/page-break.mjs +2 -0
- package/dist/extensions/paragraph.d.mts +1 -1
- package/dist/extensions/paragraph.mjs +1 -115
- package/dist/extensions/passthrough.d.mts +2 -0
- package/dist/extensions/passthrough.mjs +2 -0
- package/dist/extensions/section-break.d.mts +27 -0
- package/dist/extensions/section-break.mjs +2 -0
- package/dist/extensions/strike.mjs +1 -49
- package/dist/extensions/table-cell.mjs +1 -111
- package/dist/extensions/table-header.mjs +1 -111
- package/dist/extensions/table-row.mjs +1 -83
- package/dist/extensions/table.mjs +1 -133
- package/dist/extensions/task-item.d.mts +27 -0
- package/dist/extensions/task-item.mjs +3 -0
- package/dist/extensions/text-style.mjs +1 -133
- package/dist/extensions/tiptap.mjs +1 -32
- package/dist/extensions/types.d.mts +7 -5
- package/dist/extensions/utils.d.mts +16 -3
- package/dist/extensions/utils.mjs +2 -289
- package/dist/heading-DAubgJJN.mjs +166 -0
- package/dist/html-DCC_DSHv.d.mts +18 -0
- package/dist/html-DedDWPoO.mjs +66 -0
- package/dist/image-CuEIXoZl.mjs +334 -0
- package/dist/index.d.mts +68 -18
- package/dist/index.mjs +10 -8
- package/dist/markdown-BzJmRsBs.mjs +18 -0
- package/dist/markdown-D0k0sRYS.d.mts +13 -0
- package/dist/mention-C-dTR6MK.mjs +44 -0
- package/dist/ordered-list-V-3xFQmV.mjs +41 -0
- package/dist/paragraph-BrWH-BOV.mjs +147 -0
- package/dist/paragraph-D8mpHo_o.d.mts +8 -0
- package/dist/patch-Cdp5WKJz.d.mts +53 -0
- package/dist/patch-IZ13tXTu.mjs +36 -0
- package/dist/prepare-Bdk8Cjkf.mjs +122 -0
- package/dist/prepare-CPAvcv6q.d.mts +63 -0
- package/dist/strike-hWxShOgb.mjs +50 -0
- package/dist/styles-QSX3RhxK.mjs +285 -0
- package/dist/styles-ugspkrxz.d.mts +85 -0
- package/dist/table-DrA12lxX.mjs +159 -0
- package/dist/table-cell-DmkfYwDc.mjs +112 -0
- package/dist/table-header-Bwr5GKWg.mjs +112 -0
- package/dist/table-row-vew1y7WH.mjs +84 -0
- package/dist/task-item-BnlXGrIG.mjs +37 -0
- package/dist/text-style-tMeBq5XQ.mjs +151 -0
- package/dist/tiptap-D7pl4I5U.mjs +33 -0
- package/dist/utils-BPT4tjcn.mjs +333 -0
- package/package.json +3 -2
- package/dist/core-CFIQVRfx.mjs +0 -88
- package/dist/core-omBKMRtl.d.mts +0 -34
- package/dist/image-Ge1y6uam.d.mts +0 -47
- package/dist/paragraph-fhEXtAN2.d.mts +0 -16
|
@@ -0,0 +1,1231 @@
|
|
|
1
|
+
import { r as applyBlockquoteStyle, t as BLOCKQUOTE_BORDER } from "./blockquote-Ky9xD_Q-.mjs";
|
|
2
|
+
import { n as renderDocx } from "./code-block-BgNjhADy.mjs";
|
|
3
|
+
import { n as DETAILS_TAG, t as DETAILS_SUMMARY_STYLE } from "./details-Dh8luWb2.mjs";
|
|
4
|
+
import { n as parseDocx, r as renderDocx$1 } from "./heading-DAubgJJN.mjs";
|
|
5
|
+
import { i as renderDocx$2, n as parseDocx$1 } from "./image-CuEIXoZl.mjs";
|
|
6
|
+
import { t as encodeBase64 } from "./dist-B6B3HnVZ.mjs";
|
|
7
|
+
import { i as readMention, n as createMention, r as isMention } from "./mention-C-dTR6MK.mjs";
|
|
8
|
+
import { n as ORDERED_REFERENCE_PREFIX, r as buildOrderedLevels } from "./ordered-list-V-3xFQmV.mjs";
|
|
9
|
+
import { n as parseDocx$2, r as renderDocx$3 } from "./paragraph-BrWH-BOV.mjs";
|
|
10
|
+
import { n as parseDocx$3, r as renderDocx$4 } from "./strike-hWxShOgb.mjs";
|
|
11
|
+
import { n as parseDocx$4, r as renderDocx$5 } from "./table-DrA12lxX.mjs";
|
|
12
|
+
import { n as parseDocx$5, r as renderDocx$6 } from "./table-cell-DmkfYwDc.mjs";
|
|
13
|
+
import { n as parseDocx$6, r as renderDocx$7 } from "./table-header-Bwr5GKWg.mjs";
|
|
14
|
+
import { n as parseDocx$7, r as renderDocx$8 } from "./table-row-vew1y7WH.mjs";
|
|
15
|
+
import { i as readCheckboxState, n as createTaskCheckbox, r as isTaskCheckbox } from "./task-item-BnlXGrIG.mjs";
|
|
16
|
+
import { n as parseDocx$8, r as renderDocx$9 } from "./text-style-tMeBq5XQ.mjs";
|
|
17
|
+
import { n as prepareDocument } from "./prepare-Bdk8Cjkf.mjs";
|
|
18
|
+
import { generateDocument, generateDocumentStream, generateDocumentSync, parseDocument } from "@office-open/docx";
|
|
19
|
+
//#region src/converters/docx.ts
|
|
20
|
+
/** Remove keys with null/undefined values */
|
|
21
|
+
function cleanAttrs(attrs) {
|
|
22
|
+
const result = {};
|
|
23
|
+
for (const [key, value] of Object.entries(attrs)) if (value !== null && value !== void 0) result[key] = value;
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
/** Keys round-tripped between DocumentOptions core properties and `doc.attrs.core`. */
|
|
27
|
+
const CORE_PROPERTY_KEYS = [
|
|
28
|
+
"title",
|
|
29
|
+
"subject",
|
|
30
|
+
"creator",
|
|
31
|
+
"keywords",
|
|
32
|
+
"description",
|
|
33
|
+
"lastModifiedBy",
|
|
34
|
+
"lastPrinted",
|
|
35
|
+
"created",
|
|
36
|
+
"modified",
|
|
37
|
+
"revision"
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* DocumentOptions keys that DocxManager reconstructs (sections/numbering) or
|
|
41
|
+
* carries in dedicated attrs (styles/background/core). Excluded from the
|
|
42
|
+
* documentExtras pass-through so they aren't duplicated.
|
|
43
|
+
*/
|
|
44
|
+
const COMPILE_OWNED_KEYS = new Set([
|
|
45
|
+
"sections",
|
|
46
|
+
"numbering",
|
|
47
|
+
"styles",
|
|
48
|
+
"background",
|
|
49
|
+
...CORE_PROPERTY_KEYS
|
|
50
|
+
]);
|
|
51
|
+
/** Collect core properties present on DocumentOptions into a plain object. */
|
|
52
|
+
function extractCoreProperties(docOpts) {
|
|
53
|
+
const source = docOpts;
|
|
54
|
+
const core = {};
|
|
55
|
+
for (const key of CORE_PROPERTY_KEYS) {
|
|
56
|
+
const value = source[key];
|
|
57
|
+
if (value !== void 0 && value !== null) core[key] = value;
|
|
58
|
+
}
|
|
59
|
+
return Object.keys(core).length > 0 ? core : null;
|
|
60
|
+
}
|
|
61
|
+
/** Merge consecutive text nodes with same marks */
|
|
62
|
+
function mergeTextNodes(nodes) {
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const node of nodes) {
|
|
65
|
+
if (node.type === "text" && result.length > 0 && result[result.length - 1].type === "text") {
|
|
66
|
+
const prev = result[result.length - 1];
|
|
67
|
+
if (JSON.stringify(prev.marks) === JSON.stringify(node.marks)) {
|
|
68
|
+
prev.text = (prev.text ?? "") + (node.text ?? "");
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
result.push({ ...node });
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Manages DOCX serialization (Tiptap JSON ↔ DocumentOptions).
|
|
78
|
+
*
|
|
79
|
+
* Each extension provides renderDocx/parseDocx for its own attrs mapping.
|
|
80
|
+
* DocxManager handles tree walking, child assembly, and dispatching.
|
|
81
|
+
*/
|
|
82
|
+
var DocxManager = class {
|
|
83
|
+
numberingConfigs = [];
|
|
84
|
+
orderedInstanceCounter = 0;
|
|
85
|
+
resolveStyles;
|
|
86
|
+
compile(json) {
|
|
87
|
+
this.numberingConfigs = [];
|
|
88
|
+
this.orderedInstanceCounter = 0;
|
|
89
|
+
const sections = [];
|
|
90
|
+
let currentChildren = [];
|
|
91
|
+
if (json.content) for (const node of json.content) {
|
|
92
|
+
const child = this.compileSectionChild(node);
|
|
93
|
+
if (child) if (Array.isArray(child)) currentChildren.push(...child);
|
|
94
|
+
else currentChildren.push(child);
|
|
95
|
+
if (node.type === "paragraph") {
|
|
96
|
+
const na = node.attrs ?? {};
|
|
97
|
+
if (na.sectionProperties != null) {
|
|
98
|
+
sections.push(this.buildSection(currentChildren, na.sectionProperties, this.compileHeaderFooter(na.sectionHeaders ?? null), this.compileHeaderFooter(na.sectionFooters ?? null)));
|
|
99
|
+
currentChildren = [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const docAttrs = json.attrs ?? {};
|
|
104
|
+
sections.push(this.buildSection(currentChildren, docAttrs.sectionProperties ?? null, this.compileHeaderFooter(docAttrs.sectionHeaders ?? null), this.compileHeaderFooter(docAttrs.sectionFooters ?? null)));
|
|
105
|
+
const styles = docAttrs.styles ?? void 0;
|
|
106
|
+
const core = docAttrs.core ?? void 0;
|
|
107
|
+
const background = docAttrs.background ?? void 0;
|
|
108
|
+
const documentExtras = docAttrs.documentExtras ?? void 0;
|
|
109
|
+
return {
|
|
110
|
+
sections,
|
|
111
|
+
...styles ? { styles } : {},
|
|
112
|
+
...core,
|
|
113
|
+
...background ? { background } : {},
|
|
114
|
+
...documentExtras,
|
|
115
|
+
...this.numberingConfigs.length > 0 ? { numbering: { config: this.numberingConfigs } } : {}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Assemble a SectionOptions from compiled children + optional layout/headers/footers. */
|
|
119
|
+
buildSection(children, properties, headers, footers) {
|
|
120
|
+
return {
|
|
121
|
+
children,
|
|
122
|
+
...properties ? { properties } : {},
|
|
123
|
+
...headers ? { headers } : {},
|
|
124
|
+
...footers ? { footers } : {}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Compile resolved header/footer slots (JSONContent[] per slot) back into
|
|
129
|
+
* SectionChild[] per slot. Returns undefined when no slot has content.
|
|
130
|
+
*/
|
|
131
|
+
compileHeaderFooter(slots) {
|
|
132
|
+
if (!slots) return void 0;
|
|
133
|
+
const group = {};
|
|
134
|
+
for (const slot of [
|
|
135
|
+
"default",
|
|
136
|
+
"first",
|
|
137
|
+
"even"
|
|
138
|
+
]) {
|
|
139
|
+
const json = slots[slot];
|
|
140
|
+
if (!json?.length) continue;
|
|
141
|
+
const children = [];
|
|
142
|
+
for (const node of json) {
|
|
143
|
+
const child = this.compileSectionChild(node);
|
|
144
|
+
if (!child) continue;
|
|
145
|
+
if (Array.isArray(child)) children.push(...child);
|
|
146
|
+
else children.push(child);
|
|
147
|
+
}
|
|
148
|
+
if (children.length > 0) group[slot] = children;
|
|
149
|
+
}
|
|
150
|
+
return Object.keys(group).length > 0 ? group : void 0;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a section's header/footer group (SectionChild[] per slot) into
|
|
154
|
+
* Tiptap JSON slots. Returns null when no slot has content.
|
|
155
|
+
*/
|
|
156
|
+
resolveHeaderFooter(group, numberingLookup) {
|
|
157
|
+
if (!group) return null;
|
|
158
|
+
const slots = {};
|
|
159
|
+
for (const slot of [
|
|
160
|
+
"default",
|
|
161
|
+
"first",
|
|
162
|
+
"even"
|
|
163
|
+
]) {
|
|
164
|
+
const children = group[slot];
|
|
165
|
+
if (children?.length) slots[slot] = this.resolveSectionChildren(children, numberingLookup);
|
|
166
|
+
}
|
|
167
|
+
return Object.keys(slots).length > 0 ? slots : null;
|
|
168
|
+
}
|
|
169
|
+
resolve(docOpts) {
|
|
170
|
+
this.resolveStyles = docOpts.styles ?? void 0;
|
|
171
|
+
const sections = docOpts.sections ?? [];
|
|
172
|
+
if (sections.length === 0) return {
|
|
173
|
+
type: "doc",
|
|
174
|
+
content: [{ type: "paragraph" }]
|
|
175
|
+
};
|
|
176
|
+
const numberingLookup = this.buildNumberingLookup(docOpts);
|
|
177
|
+
const content = [];
|
|
178
|
+
const lastIndex = sections.length - 1;
|
|
179
|
+
for (let i = 0; i < sections.length; i++) {
|
|
180
|
+
const section = sections[i];
|
|
181
|
+
const sectionContent = this.resolveSectionChildren(section.children ?? [], numberingLookup);
|
|
182
|
+
if (i < lastIndex) {
|
|
183
|
+
const sectAttrs = {
|
|
184
|
+
sectionProperties: section.properties ?? null,
|
|
185
|
+
sectionHeaders: this.resolveHeaderFooter(section.headers, numberingLookup),
|
|
186
|
+
sectionFooters: this.resolveHeaderFooter(section.footers, numberingLookup)
|
|
187
|
+
};
|
|
188
|
+
const last = sectionContent[sectionContent.length - 1];
|
|
189
|
+
if (last?.type === "paragraph") last.attrs = {
|
|
190
|
+
...last.attrs,
|
|
191
|
+
...sectAttrs
|
|
192
|
+
};
|
|
193
|
+
else sectionContent.push({
|
|
194
|
+
type: "paragraph",
|
|
195
|
+
attrs: sectAttrs
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
content.push(...sectionContent);
|
|
199
|
+
}
|
|
200
|
+
const doc = {
|
|
201
|
+
type: "doc",
|
|
202
|
+
content: content.length > 0 ? content : [{ type: "paragraph" }]
|
|
203
|
+
};
|
|
204
|
+
const attrs = {};
|
|
205
|
+
if (docOpts.styles) attrs.styles = docOpts.styles;
|
|
206
|
+
if (docOpts.background) attrs.background = docOpts.background;
|
|
207
|
+
const core = extractCoreProperties(docOpts);
|
|
208
|
+
if (core) attrs.core = core;
|
|
209
|
+
const lastSection = sections[lastIndex];
|
|
210
|
+
if (lastSection.properties) attrs.sectionProperties = lastSection.properties;
|
|
211
|
+
const lastHeaders = this.resolveHeaderFooter(lastSection.headers, numberingLookup);
|
|
212
|
+
if (lastHeaders) attrs.sectionHeaders = lastHeaders;
|
|
213
|
+
const lastFooters = this.resolveHeaderFooter(lastSection.footers, numberingLookup);
|
|
214
|
+
if (lastFooters) attrs.sectionFooters = lastFooters;
|
|
215
|
+
const documentExtras = {};
|
|
216
|
+
for (const [k, v] of Object.entries(docOpts)) {
|
|
217
|
+
if (COMPILE_OWNED_KEYS.has(k)) continue;
|
|
218
|
+
if (v === void 0 || v === null) continue;
|
|
219
|
+
documentExtras[k] = v;
|
|
220
|
+
}
|
|
221
|
+
if (Object.keys(documentExtras).length > 0) attrs.documentExtras = documentExtras;
|
|
222
|
+
if (Object.keys(attrs).length > 0) doc.attrs = attrs;
|
|
223
|
+
return doc;
|
|
224
|
+
}
|
|
225
|
+
compileSectionChild(node) {
|
|
226
|
+
switch (node.type) {
|
|
227
|
+
case "paragraph": return { paragraph: this.compileParagraphNode(node) };
|
|
228
|
+
case "heading": return { paragraph: this.compileHeadingNode(node) };
|
|
229
|
+
case "blockquote": {
|
|
230
|
+
const items = [];
|
|
231
|
+
for (const child of node.content ?? []) if (child.type === "paragraph" || child.type === "heading") {
|
|
232
|
+
const para = child.type === "heading" ? this.compileHeadingNode(child) : this.compileParagraphNode(child);
|
|
233
|
+
const paraObj = typeof para === "string" ? { text: para } : para;
|
|
234
|
+
applyBlockquoteStyle(paraObj);
|
|
235
|
+
items.push({ paragraph: paraObj });
|
|
236
|
+
}
|
|
237
|
+
return items.length > 0 ? items : null;
|
|
238
|
+
}
|
|
239
|
+
case "codeBlock": {
|
|
240
|
+
const opts = renderDocx(node);
|
|
241
|
+
const childList = this.compileInlineContent(node.content);
|
|
242
|
+
if (childList.length > 0) opts.children = childList;
|
|
243
|
+
return { paragraph: this.simplifyParagraph(opts) };
|
|
244
|
+
}
|
|
245
|
+
case "horizontalRule": return { paragraph: { thematicBreak: true } };
|
|
246
|
+
case "table": return { table: this.compileTableNode(node) };
|
|
247
|
+
case "image": {
|
|
248
|
+
const imageRun = renderDocx$2(node);
|
|
249
|
+
if (!imageRun) return null;
|
|
250
|
+
return { paragraph: { children: [imageRun] } };
|
|
251
|
+
}
|
|
252
|
+
case "bulletList":
|
|
253
|
+
case "orderedList":
|
|
254
|
+
case "taskList": return this.compileListFromNode(node, 0);
|
|
255
|
+
case "details": return this.compileDetailsNode(node);
|
|
256
|
+
case "passthrough": {
|
|
257
|
+
const data = node.attrs?.data ?? "{}";
|
|
258
|
+
try {
|
|
259
|
+
return JSON.parse(data);
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
default: return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
compileParagraphNode(node) {
|
|
268
|
+
const opts = renderDocx$3(node);
|
|
269
|
+
const childList = this.compileInlineContent(node.content);
|
|
270
|
+
if (childList.length > 0) opts.children = childList;
|
|
271
|
+
return this.simplifyParagraph(opts);
|
|
272
|
+
}
|
|
273
|
+
compileHeadingNode(node) {
|
|
274
|
+
const opts = renderDocx$1(node);
|
|
275
|
+
const childList = this.compileInlineContent(node.content);
|
|
276
|
+
if (childList.length > 0) opts.children = childList;
|
|
277
|
+
return this.simplifyParagraph(opts);
|
|
278
|
+
}
|
|
279
|
+
/** Simple text optimization: merge plain runs into text field */
|
|
280
|
+
simplifyParagraph(opts) {
|
|
281
|
+
const children = opts.children;
|
|
282
|
+
if (!children || children.length === 0) return opts;
|
|
283
|
+
if (children.every((c) => typeof c === "object" && c !== null && "text" in c && Object.keys(c).length === 1)) {
|
|
284
|
+
const combined = children.map((c) => c.text).join("");
|
|
285
|
+
delete opts.children;
|
|
286
|
+
if (combined && Object.keys(opts).length === 0) return combined;
|
|
287
|
+
if (combined) opts.text = combined;
|
|
288
|
+
}
|
|
289
|
+
return opts;
|
|
290
|
+
}
|
|
291
|
+
compileTableNode(node) {
|
|
292
|
+
const opts = renderDocx$5(node);
|
|
293
|
+
const colCount = this.getTableColumnCount(node);
|
|
294
|
+
const rows = [];
|
|
295
|
+
let activeSpans = [];
|
|
296
|
+
for (const rowNode of node.content ?? []) {
|
|
297
|
+
if (rowNode.type !== "tableRow") continue;
|
|
298
|
+
const rowOpts = renderDocx$8(rowNode);
|
|
299
|
+
const pmCells = (rowNode.content ?? []).filter((c) => c.type === "tableCell" || c.type === "tableHeader");
|
|
300
|
+
const currentSpans = [...activeSpans].sort((a, b) => a.colStart - b.colStart);
|
|
301
|
+
const newSpans = [];
|
|
302
|
+
const compiledCells = [];
|
|
303
|
+
let colIdx = 0;
|
|
304
|
+
let cellIdx = 0;
|
|
305
|
+
let spanIdx = 0;
|
|
306
|
+
while (spanIdx < currentSpans.length || cellIdx < pmCells.length) {
|
|
307
|
+
const nextSpanCol = spanIdx < currentSpans.length ? currentSpans[spanIdx].colStart : Infinity;
|
|
308
|
+
if (colIdx >= nextSpanCol) {
|
|
309
|
+
const span = currentSpans[spanIdx];
|
|
310
|
+
compiledCells.push({
|
|
311
|
+
verticalMerge: "continue",
|
|
312
|
+
columnSpan: span.colspan,
|
|
313
|
+
children: [{ paragraph: "" }]
|
|
314
|
+
});
|
|
315
|
+
colIdx += span.colspan;
|
|
316
|
+
spanIdx++;
|
|
317
|
+
} else {
|
|
318
|
+
const cell = this.compileTableCellNode(pmCells[cellIdx]);
|
|
319
|
+
const cs = cell.columnSpan ?? 1;
|
|
320
|
+
const rs = cell.rowSpan ?? 1;
|
|
321
|
+
if (rs > 1) {
|
|
322
|
+
delete cell.rowSpan;
|
|
323
|
+
cell.verticalMerge = "restart";
|
|
324
|
+
newSpans.push({
|
|
325
|
+
colStart: colIdx,
|
|
326
|
+
colspan: cs,
|
|
327
|
+
remainingRows: rs - 1
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
compiledCells.push(cell);
|
|
331
|
+
colIdx += cs;
|
|
332
|
+
cellIdx++;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
rowOpts.cells = compiledCells;
|
|
336
|
+
rows.push(rowOpts);
|
|
337
|
+
for (const span of currentSpans) span.remainingRows--;
|
|
338
|
+
activeSpans = [...currentSpans.filter((s) => s.remainingRows > 0), ...newSpans];
|
|
339
|
+
}
|
|
340
|
+
opts.rows = rows;
|
|
341
|
+
if (colCount > 0) {
|
|
342
|
+
const { columnWidths, tableWidth } = this.computeColumnWidths(node, colCount, opts);
|
|
343
|
+
opts.columnWidths = columnWidths;
|
|
344
|
+
if (!opts.width) opts.width = tableWidth;
|
|
345
|
+
if (!opts.layout) opts.layout = "autofit";
|
|
346
|
+
}
|
|
347
|
+
return opts;
|
|
348
|
+
}
|
|
349
|
+
/** Count grid columns from the first table row (summing colspan). */
|
|
350
|
+
getTableColumnCount(tableNode) {
|
|
351
|
+
for (const rowNode of tableNode.content ?? []) {
|
|
352
|
+
if (rowNode.type !== "tableRow") continue;
|
|
353
|
+
let count = 0;
|
|
354
|
+
for (const cell of rowNode.content ?? []) if (cell.type === "tableCell" || cell.type === "tableHeader") count += cell.attrs?.colspan ?? 1;
|
|
355
|
+
return count;
|
|
356
|
+
}
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Compute tblGrid column widths (twips) from first-row cell colwidth attrs.
|
|
361
|
+
* Returns columnWidths array and the appropriate tableWidth.
|
|
362
|
+
*/
|
|
363
|
+
computeColumnWidths(tableNode, colCount, _opts) {
|
|
364
|
+
const DEFAULT_CONTENT_TWIPS = 9026;
|
|
365
|
+
const tableColWidths = tableNode.attrs?.columnWidths;
|
|
366
|
+
if (tableColWidths && tableColWidths.length > 0) {
|
|
367
|
+
const filled = tableColWidths.slice(0, colCount);
|
|
368
|
+
while (filled.length < colCount) filled.push(filled[filled.length - 1] ?? Math.floor(DEFAULT_CONTENT_TWIPS / colCount));
|
|
369
|
+
return {
|
|
370
|
+
columnWidths: filled,
|
|
371
|
+
tableWidth: {
|
|
372
|
+
size: filled.reduce((a, b) => a + b, 0),
|
|
373
|
+
type: "dxa"
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const widths = Array.from({ length: colCount }, () => null);
|
|
378
|
+
for (const rowNode of tableNode.content ?? []) {
|
|
379
|
+
if (rowNode.type !== "tableRow") continue;
|
|
380
|
+
let colIdx = 0;
|
|
381
|
+
for (const cell of rowNode.content ?? []) {
|
|
382
|
+
if (cell.type !== "tableCell" && cell.type !== "tableHeader") continue;
|
|
383
|
+
const colspan = cell.attrs?.colspan ?? 1;
|
|
384
|
+
const colwidth = cell.attrs?.colwidth;
|
|
385
|
+
if (colwidth) for (let i = 0; i < colspan && colIdx + i < colCount; i++) {
|
|
386
|
+
const px = colwidth[i] ?? colwidth[0];
|
|
387
|
+
if (px) widths[colIdx + i] = px * 15;
|
|
388
|
+
}
|
|
389
|
+
colIdx += colspan;
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
if (!widths.some((w) => w !== null)) {
|
|
394
|
+
const equal = Math.floor(DEFAULT_CONTENT_TWIPS / colCount);
|
|
395
|
+
return {
|
|
396
|
+
columnWidths: Array(colCount).fill(equal),
|
|
397
|
+
tableWidth: {
|
|
398
|
+
size: 5e3,
|
|
399
|
+
type: "pct"
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const explicit = widths.filter((w) => w !== null);
|
|
404
|
+
const avg = Math.floor(explicit.reduce((a, b) => a + b, 0) / explicit.length);
|
|
405
|
+
const filled = widths.map((w) => w ?? avg);
|
|
406
|
+
return {
|
|
407
|
+
columnWidths: filled,
|
|
408
|
+
tableWidth: {
|
|
409
|
+
size: filled.reduce((a, b) => a + b, 0),
|
|
410
|
+
type: "dxa"
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
compileTableCellNode(cellNode) {
|
|
415
|
+
const cellOpts = cellNode.type === "tableHeader" ? renderDocx$7(cellNode) : renderDocx$6(cellNode);
|
|
416
|
+
const cellAlign = cellNode.attrs?.align ?? null;
|
|
417
|
+
const cellChildren = [];
|
|
418
|
+
for (const paraNode of cellNode.content ?? []) {
|
|
419
|
+
const para = this.compileParagraphNode(paraNode);
|
|
420
|
+
const paraObj = typeof para === "string" ? { text: para } : para;
|
|
421
|
+
if (cellAlign && !paraObj.alignment) paraObj.alignment = cellAlign;
|
|
422
|
+
cellChildren.push({ paragraph: paraObj });
|
|
423
|
+
}
|
|
424
|
+
if (cellChildren.length > 0) cellOpts.children = cellChildren;
|
|
425
|
+
return cellOpts;
|
|
426
|
+
}
|
|
427
|
+
compileListFromNode(node, level) {
|
|
428
|
+
const items = [];
|
|
429
|
+
const isOrdered = node.type === "orderedList";
|
|
430
|
+
const isTask = node.type === "taskList";
|
|
431
|
+
let ordered;
|
|
432
|
+
if (isOrdered) ordered = this.registerOrderedNumbering(node);
|
|
433
|
+
for (const listItem of node.content ?? []) {
|
|
434
|
+
if (listItem.type !== "listItem" && listItem.type !== "taskItem") continue;
|
|
435
|
+
const checked = Boolean(listItem.attrs?.checked);
|
|
436
|
+
for (const child of listItem.content ?? []) if (child.type === "paragraph" || child.type === "heading") {
|
|
437
|
+
const para = child.type === "heading" ? this.compileHeadingNode(child) : this.compileParagraphNode(child);
|
|
438
|
+
const paraObj = typeof para === "string" ? { text: para } : para;
|
|
439
|
+
if (ordered) paraObj.numbering = {
|
|
440
|
+
reference: ordered.reference,
|
|
441
|
+
instance: ordered.instance,
|
|
442
|
+
level
|
|
443
|
+
};
|
|
444
|
+
else paraObj.bullet = { level };
|
|
445
|
+
if (isTask) this.injectTaskCheckbox(paraObj, checked);
|
|
446
|
+
items.push({ paragraph: paraObj });
|
|
447
|
+
} else if (child.type === "bulletList" || child.type === "orderedList" || child.type === "taskList") {
|
|
448
|
+
const nested = this.compileListFromNode(child, level + 1);
|
|
449
|
+
if (nested) items.push(...nested);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return items.length > 0 ? items : null;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Register (or reuse) an abstractNum for an ordered list's `start`, and
|
|
456
|
+
* return a fresh instance so this list counts independently of other lists
|
|
457
|
+
* that share the same definition.
|
|
458
|
+
*/
|
|
459
|
+
registerOrderedNumbering(node) {
|
|
460
|
+
const start = Number(node.attrs?.start ?? 1) || 1;
|
|
461
|
+
let entry = this.numberingConfigs.find((c) => Number(c.levels[0]?.start ?? 1) === start);
|
|
462
|
+
if (!entry) {
|
|
463
|
+
entry = {
|
|
464
|
+
reference: `${ORDERED_REFERENCE_PREFIX}-${this.numberingConfigs.length + 1}`,
|
|
465
|
+
levels: buildOrderedLevels(start)
|
|
466
|
+
};
|
|
467
|
+
this.numberingConfigs.push(entry);
|
|
468
|
+
}
|
|
469
|
+
this.orderedInstanceCounter += 1;
|
|
470
|
+
return {
|
|
471
|
+
reference: entry.reference,
|
|
472
|
+
instance: this.orderedInstanceCounter
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Prepend an inline checkbox SDT to a task paragraph. The SDT is tagged
|
|
477
|
+
* "docen-task" so resolve can tell task items apart from ordinary paragraphs
|
|
478
|
+
* that happen to contain an SDT.
|
|
479
|
+
*/
|
|
480
|
+
injectTaskCheckbox(paraObj, checked) {
|
|
481
|
+
let existing = [];
|
|
482
|
+
if (Array.isArray(paraObj.children)) existing = paraObj.children;
|
|
483
|
+
else if (typeof paraObj.text === "string") {
|
|
484
|
+
if (paraObj.text) existing = [{ text: paraObj.text }];
|
|
485
|
+
delete paraObj.text;
|
|
486
|
+
}
|
|
487
|
+
paraObj.children = [createTaskCheckbox(checked), ...existing];
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* details → block-level group-SDT. The summary paragraph is tagged with a
|
|
491
|
+
* fixed style so resolve can split it back out; content blocks flatten in
|
|
492
|
+
* after it. (No native collapse in DOCX — structure round-trips, the view
|
|
493
|
+
* stays expanded.)
|
|
494
|
+
*/
|
|
495
|
+
compileDetailsNode(node) {
|
|
496
|
+
const sdtChildren = [];
|
|
497
|
+
for (const child of node.content ?? []) if (child.type === "detailsSummary") {
|
|
498
|
+
const inline = this.compileInlineContent(child.content);
|
|
499
|
+
const summaryPara = { style: DETAILS_SUMMARY_STYLE };
|
|
500
|
+
if (inline.length > 0) summaryPara.children = inline;
|
|
501
|
+
sdtChildren.push({ paragraph: summaryPara });
|
|
502
|
+
} else if (child.type === "detailsContent") for (const block of child.content ?? []) {
|
|
503
|
+
const compiled = this.compileSectionChild(block);
|
|
504
|
+
if (!compiled) continue;
|
|
505
|
+
if (Array.isArray(compiled)) sdtChildren.push(...compiled);
|
|
506
|
+
else sdtChildren.push(compiled);
|
|
507
|
+
}
|
|
508
|
+
return { sdt: {
|
|
509
|
+
properties: {
|
|
510
|
+
tag: DETAILS_TAG,
|
|
511
|
+
group: true
|
|
512
|
+
},
|
|
513
|
+
children: sdtChildren
|
|
514
|
+
} };
|
|
515
|
+
}
|
|
516
|
+
compileInlineContent(content) {
|
|
517
|
+
if (!content) return [];
|
|
518
|
+
const children = [];
|
|
519
|
+
for (const node of content) switch (node.type) {
|
|
520
|
+
case "text":
|
|
521
|
+
this.compileTextNode(node, children);
|
|
522
|
+
break;
|
|
523
|
+
case "hardBreak":
|
|
524
|
+
children.push({ break: 1 });
|
|
525
|
+
break;
|
|
526
|
+
case "pageBreak":
|
|
527
|
+
children.push({ pageBreak: true });
|
|
528
|
+
break;
|
|
529
|
+
case "columnBreak":
|
|
530
|
+
children.push({ columnBreak: true });
|
|
531
|
+
break;
|
|
532
|
+
case "image": {
|
|
533
|
+
const imageRun = renderDocx$2(node);
|
|
534
|
+
if (imageRun) children.push(imageRun);
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
case "imageGroup": {
|
|
538
|
+
const wpgGroup = node.attrs?.wpgGroup;
|
|
539
|
+
if (wpgGroup) children.push({ wpgGroup });
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
case "mention":
|
|
543
|
+
children.push(createMention(String(node.attrs?.id ?? ""), String(node.attrs?.label ?? "")));
|
|
544
|
+
break;
|
|
545
|
+
case "emoji": {
|
|
546
|
+
const attrs = node.attrs ?? {};
|
|
547
|
+
const text = attrs.emoji ?? (attrs.name ? `:${attrs.name}:` : "");
|
|
548
|
+
if (text) children.push({ text });
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return children;
|
|
553
|
+
}
|
|
554
|
+
compileTextNode(node, children) {
|
|
555
|
+
const text = node.text ?? "";
|
|
556
|
+
if (!text) return;
|
|
557
|
+
const segments = text.split("\n");
|
|
558
|
+
for (let i = 0; i < segments.length; i++) {
|
|
559
|
+
if (i > 0) children.push({ break: 1 });
|
|
560
|
+
if (segments[i]) this.compileTextRun(segments[i], node.marks, children);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/** Emit a single run for `text` with all inline marks applied. */
|
|
564
|
+
compileTextRun(text, marks, children) {
|
|
565
|
+
const runOpts = { text };
|
|
566
|
+
for (const mark of marks ?? []) switch (mark.type) {
|
|
567
|
+
case "bold":
|
|
568
|
+
runOpts.bold = true;
|
|
569
|
+
break;
|
|
570
|
+
case "italic":
|
|
571
|
+
runOpts.italic = true;
|
|
572
|
+
break;
|
|
573
|
+
case "underline":
|
|
574
|
+
runOpts.underline = { type: "single" };
|
|
575
|
+
break;
|
|
576
|
+
case "strike": {
|
|
577
|
+
const strikeProps = renderDocx$4(mark.attrs ?? {});
|
|
578
|
+
Object.assign(runOpts, strikeProps);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
case "subscript":
|
|
582
|
+
runOpts.subScript = true;
|
|
583
|
+
break;
|
|
584
|
+
case "superscript":
|
|
585
|
+
runOpts.superScript = true;
|
|
586
|
+
break;
|
|
587
|
+
case "highlight":
|
|
588
|
+
runOpts.highlight = mark.attrs?.color ?? "yellow";
|
|
589
|
+
break;
|
|
590
|
+
case "code":
|
|
591
|
+
runOpts.style = "CodeChar";
|
|
592
|
+
runOpts.font = "Consolas";
|
|
593
|
+
break;
|
|
594
|
+
case "textStyle": {
|
|
595
|
+
const tsProps = renderDocx$9(mark.attrs ?? {});
|
|
596
|
+
Object.assign(runOpts, tsProps);
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
case "link": {
|
|
600
|
+
const href = mark.attrs?.href;
|
|
601
|
+
if (href) {
|
|
602
|
+
const { text: _, ...runWithoutText } = runOpts;
|
|
603
|
+
const linkChildren = [];
|
|
604
|
+
if (text) linkChildren.push({
|
|
605
|
+
...runWithoutText,
|
|
606
|
+
text
|
|
607
|
+
});
|
|
608
|
+
children.push({ hyperlink: {
|
|
609
|
+
link: href.startsWith("#") ? void 0 : href,
|
|
610
|
+
anchor: href.startsWith("#") ? href.slice(1) : void 0,
|
|
611
|
+
children: linkChildren
|
|
612
|
+
} });
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
children.push(runOpts);
|
|
619
|
+
}
|
|
620
|
+
resolveSectionChild(child) {
|
|
621
|
+
if ("paragraph" in child) return this.resolveParagraph(child.paragraph);
|
|
622
|
+
if ("table" in child) return this.resolveTable(child.table);
|
|
623
|
+
if ("sdt" in child) {
|
|
624
|
+
const sdt = child.sdt;
|
|
625
|
+
if (sdt.properties?.tag === "docen-details") return this.resolveDetailsSdt(sdt);
|
|
626
|
+
return this.resolvePassthrough(child);
|
|
627
|
+
}
|
|
628
|
+
return this.resolvePassthrough(child);
|
|
629
|
+
}
|
|
630
|
+
/** Wrap an opaque SectionChild in a passthrough atom (attrs.data = JSON). */
|
|
631
|
+
resolvePassthrough(child) {
|
|
632
|
+
return {
|
|
633
|
+
type: "passthrough",
|
|
634
|
+
attrs: { data: JSON.stringify(child) }
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Resolve a details group-SDT: the summary-style paragraph becomes
|
|
639
|
+
* detailsSummary, the remaining blocks fold into detailsContent.
|
|
640
|
+
*/
|
|
641
|
+
resolveDetailsSdt(sdt) {
|
|
642
|
+
const content = [];
|
|
643
|
+
let summary = null;
|
|
644
|
+
for (const child of sdt.children ?? []) {
|
|
645
|
+
if ("paragraph" in child) {
|
|
646
|
+
const para = child.paragraph;
|
|
647
|
+
if (para.style === "DocenDetailsSummary") {
|
|
648
|
+
summary = this.resolveInlineContent(para);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const node = this.resolveSectionChild(child);
|
|
653
|
+
if (!node) continue;
|
|
654
|
+
if (Array.isArray(node)) content.push(...node);
|
|
655
|
+
else content.push(node);
|
|
656
|
+
}
|
|
657
|
+
const details = {
|
|
658
|
+
type: "details",
|
|
659
|
+
content: []
|
|
660
|
+
};
|
|
661
|
+
if (summary !== null) details.content.push({
|
|
662
|
+
type: "detailsSummary",
|
|
663
|
+
content: summary
|
|
664
|
+
});
|
|
665
|
+
if (content.length > 0) details.content.push({
|
|
666
|
+
type: "detailsContent",
|
|
667
|
+
content
|
|
668
|
+
});
|
|
669
|
+
return details;
|
|
670
|
+
}
|
|
671
|
+
resolveParagraph(opts) {
|
|
672
|
+
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
673
|
+
if (resolved.thematicBreak) return { type: "horizontalRule" };
|
|
674
|
+
if (resolved.style === "Code") return this.resolveCodeBlock(resolved);
|
|
675
|
+
let headingLevel = resolved.heading ? HEADING_LEVEL_MAP[resolved.heading] : void 0;
|
|
676
|
+
if (!headingLevel && resolved.style) {
|
|
677
|
+
const name = this.styleNameOf(resolved.style);
|
|
678
|
+
if (name) {
|
|
679
|
+
const m = /^heading\s+(\d)$/i.exec(name);
|
|
680
|
+
if (m) {
|
|
681
|
+
const lvl = Number(m[1]);
|
|
682
|
+
if (lvl >= 1 && lvl <= 6) headingLevel = lvl;
|
|
683
|
+
} else if (/^title$/i.test(name)) headingLevel = 1;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (headingLevel && !resolved.heading) resolved.heading = `Heading${headingLevel}`;
|
|
687
|
+
const nodeType = headingLevel ? "heading" : "paragraph";
|
|
688
|
+
const attrs = headingLevel ? parseDocx(resolved) : parseDocx$2(resolved);
|
|
689
|
+
const content = this.resolveInlineContent(resolved);
|
|
690
|
+
const cleanAttrsObj = cleanAttrs(attrs);
|
|
691
|
+
const node = { type: nodeType };
|
|
692
|
+
if (Object.keys(cleanAttrsObj).length > 0) node.attrs = cleanAttrsObj;
|
|
693
|
+
if (content.length > 0) node.content = content;
|
|
694
|
+
return node;
|
|
695
|
+
}
|
|
696
|
+
/** Look up a paragraph style's NAME from its styleId, via the styles table
|
|
697
|
+
* carried on the current resolve(). Returns undefined outside resolve. */
|
|
698
|
+
styleNameOf(styleId) {
|
|
699
|
+
return this.resolveStyles?.paragraphStyles?.find((p) => p.id === styleId)?.name;
|
|
700
|
+
}
|
|
701
|
+
/** reference → level-0 format/start, for classifying numbering paragraphs. */
|
|
702
|
+
buildNumberingLookup(docOpts) {
|
|
703
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
704
|
+
const config = docOpts.numbering?.config;
|
|
705
|
+
if (config) for (const entry of config) {
|
|
706
|
+
const lvl0 = entry.levels[0];
|
|
707
|
+
lookup.set(entry.reference, {
|
|
708
|
+
format: lvl0?.format,
|
|
709
|
+
start: lvl0?.start
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return lookup;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Walk section children, grouping consecutive list paragraphs into nested
|
|
716
|
+
* Tiptap lists. Non-list children resolve individually. DOCX flattens lists
|
|
717
|
+
* to a paragraph sequence (depth carried by `level`); this rebuilds the tree.
|
|
718
|
+
*/
|
|
719
|
+
resolveSectionChildren(children, numberingLookup) {
|
|
720
|
+
const content = [];
|
|
721
|
+
let i = 0;
|
|
722
|
+
while (i < children.length) {
|
|
723
|
+
const child = children[i];
|
|
724
|
+
const firstPara = "paragraph" in child ? child.paragraph : null;
|
|
725
|
+
if (!(firstPara && typeof firstPara !== "string" ? this.detectList(firstPara, numberingLookup) : null)) {
|
|
726
|
+
if (firstPara && typeof firstPara !== "string" && this.detectBlockquote(firstPara)) {
|
|
727
|
+
const group = [];
|
|
728
|
+
while (i < children.length) {
|
|
729
|
+
const member = children[i];
|
|
730
|
+
if (!("paragraph" in member)) break;
|
|
731
|
+
const para = member.paragraph;
|
|
732
|
+
if (typeof para === "string" || !this.detectBlockquote(para)) break;
|
|
733
|
+
group.push(para);
|
|
734
|
+
i++;
|
|
735
|
+
}
|
|
736
|
+
content.push(this.buildBlockquote(group));
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
const node = this.resolveSectionChild(child);
|
|
740
|
+
if (node) content.push(node);
|
|
741
|
+
i++;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
const group = [];
|
|
745
|
+
while (i < children.length) {
|
|
746
|
+
const member = children[i];
|
|
747
|
+
if (!("paragraph" in member)) break;
|
|
748
|
+
const para = member.paragraph;
|
|
749
|
+
if (typeof para === "string") break;
|
|
750
|
+
const info = this.detectList(para, numberingLookup);
|
|
751
|
+
if (!info) break;
|
|
752
|
+
group.push({
|
|
753
|
+
para,
|
|
754
|
+
info
|
|
755
|
+
});
|
|
756
|
+
i++;
|
|
757
|
+
}
|
|
758
|
+
content.push(...this.buildListTree(group));
|
|
759
|
+
}
|
|
760
|
+
return content;
|
|
761
|
+
}
|
|
762
|
+
/** Classify a paragraph as a list item, or null if it isn't one. */
|
|
763
|
+
detectList(para, lookup) {
|
|
764
|
+
const p = para;
|
|
765
|
+
const numbering = p.numbering;
|
|
766
|
+
const bullet = p.bullet;
|
|
767
|
+
let kind;
|
|
768
|
+
let level;
|
|
769
|
+
let reference;
|
|
770
|
+
let start;
|
|
771
|
+
if (numbering) {
|
|
772
|
+
reference = numbering.reference;
|
|
773
|
+
level = numbering.level ?? 0;
|
|
774
|
+
const cfg = reference ? lookup.get(reference) : void 0;
|
|
775
|
+
if (cfg && cfg.format && cfg.format !== "bullet") {
|
|
776
|
+
kind = "ordered";
|
|
777
|
+
start = cfg.start;
|
|
778
|
+
} else {
|
|
779
|
+
kind = "bullet";
|
|
780
|
+
reference = void 0;
|
|
781
|
+
}
|
|
782
|
+
} else if (bullet) {
|
|
783
|
+
kind = "bullet";
|
|
784
|
+
level = bullet.level ?? 0;
|
|
785
|
+
} else return null;
|
|
786
|
+
const first = p.children?.[0];
|
|
787
|
+
return {
|
|
788
|
+
kind: isTaskCheckbox(first) ? "task" : kind,
|
|
789
|
+
level,
|
|
790
|
+
reference,
|
|
791
|
+
start,
|
|
792
|
+
checked: readCheckboxState(first)
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Rebuild nested Tiptap lists from a flat run of list paragraphs. Stack-based:
|
|
797
|
+
* each frame is an active list at a given depth; the `key` (level:type:
|
|
798
|
+
* reference) decides whether a paragraph continues the top list, starts a
|
|
799
|
+
* nested list, or splits off a new sibling list.
|
|
800
|
+
*/
|
|
801
|
+
buildListTree(group) {
|
|
802
|
+
const topLevel = [];
|
|
803
|
+
const stack = [];
|
|
804
|
+
for (const { para, info } of group) {
|
|
805
|
+
const listType = info.kind === "ordered" ? "orderedList" : info.kind === "task" ? "taskList" : "bulletList";
|
|
806
|
+
const itemType = info.kind === "task" ? "taskItem" : "listItem";
|
|
807
|
+
const key = `${info.level}:${listType}:${info.reference ?? ""}`;
|
|
808
|
+
while (stack.length > 0) {
|
|
809
|
+
const top = stack[stack.length - 1];
|
|
810
|
+
if (top.level > info.level || top.level === info.level && top.key !== key) {
|
|
811
|
+
stack.pop();
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
const newItem = {
|
|
817
|
+
type: itemType,
|
|
818
|
+
content: [this.resolveListItemParagraph(para, info)]
|
|
819
|
+
};
|
|
820
|
+
if (itemType === "taskItem") newItem.attrs = { checked: info.checked };
|
|
821
|
+
const top = stack[stack.length - 1];
|
|
822
|
+
if (top && top.level === info.level && top.key === key) {
|
|
823
|
+
top.listNode.content.push(newItem);
|
|
824
|
+
top.currentItem = newItem;
|
|
825
|
+
} else {
|
|
826
|
+
const newList = {
|
|
827
|
+
type: listType,
|
|
828
|
+
content: [newItem]
|
|
829
|
+
};
|
|
830
|
+
if (listType === "orderedList" && info.level === 0 && typeof info.start === "number" && info.start !== 1) newList.attrs = { start: info.start };
|
|
831
|
+
if (top) top.currentItem.content.push(newList);
|
|
832
|
+
else topLevel.push(newList);
|
|
833
|
+
stack.push({
|
|
834
|
+
level: info.level,
|
|
835
|
+
key,
|
|
836
|
+
listNode: newList,
|
|
837
|
+
currentItem: newItem
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return topLevel;
|
|
842
|
+
}
|
|
843
|
+
/** Classify a paragraph as a blockquote member by its signature. */
|
|
844
|
+
detectBlockquote(para) {
|
|
845
|
+
const p = para;
|
|
846
|
+
const indent = p.indent;
|
|
847
|
+
const border = p.border;
|
|
848
|
+
if (!indent || indent.left !== 720) return false;
|
|
849
|
+
const bl = border?.left;
|
|
850
|
+
if (!bl) return false;
|
|
851
|
+
const sig = BLOCKQUOTE_BORDER;
|
|
852
|
+
return bl.style === sig.style && bl.size === sig.size && bl.space === sig.space && bl.color === sig.color;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Rebuild a blockquote node from a run of signature-carrying paragraphs,
|
|
856
|
+
* stripping the indent/border signature so child paragraphs render clean.
|
|
857
|
+
*/
|
|
858
|
+
buildBlockquote(group) {
|
|
859
|
+
const content = [];
|
|
860
|
+
for (const para of group) {
|
|
861
|
+
const node = this.resolveParagraph(para);
|
|
862
|
+
const attrs = node.attrs;
|
|
863
|
+
if (attrs) {
|
|
864
|
+
if (attrs.indent) {
|
|
865
|
+
const indent = { ...attrs.indent };
|
|
866
|
+
delete indent.left;
|
|
867
|
+
attrs.indent = Object.keys(indent).length > 0 ? indent : void 0;
|
|
868
|
+
}
|
|
869
|
+
if (attrs.border) {
|
|
870
|
+
const border = { ...attrs.border };
|
|
871
|
+
delete border.left;
|
|
872
|
+
attrs.border = Object.keys(border).length > 0 ? border : void 0;
|
|
873
|
+
}
|
|
874
|
+
const cleaned = cleanAttrs(attrs);
|
|
875
|
+
if (Object.keys(cleaned).length > 0) node.attrs = cleaned;
|
|
876
|
+
else delete node.attrs;
|
|
877
|
+
}
|
|
878
|
+
content.push(node);
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
type: "blockquote",
|
|
882
|
+
content
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Resolve a list-item paragraph to a Tiptap paragraph/heading node, stripping
|
|
887
|
+
* the list marker (bullet/numbering) and the leading task checkbox — those
|
|
888
|
+
* are expressed at the list/item level, not inside the paragraph.
|
|
889
|
+
*/
|
|
890
|
+
resolveListItemParagraph(para, info) {
|
|
891
|
+
const resolved = typeof para === "string" ? { text: para } : para;
|
|
892
|
+
const headingLevel = resolved.heading ? HEADING_LEVEL_MAP[resolved.heading] : void 0;
|
|
893
|
+
const nodeType = headingLevel ? "heading" : "paragraph";
|
|
894
|
+
const attrs = headingLevel ? parseDocx(resolved) : parseDocx$2(resolved);
|
|
895
|
+
const stripped = info.kind === "task" ? this.stripTaskCheckbox(resolved) : resolved;
|
|
896
|
+
const content = this.resolveInlineContent(stripped);
|
|
897
|
+
const node = { type: nodeType };
|
|
898
|
+
const cleanAttrsObj = cleanAttrs(attrs);
|
|
899
|
+
if (Object.keys(cleanAttrsObj).length > 0) node.attrs = cleanAttrsObj;
|
|
900
|
+
if (content.length > 0) node.content = content;
|
|
901
|
+
return node;
|
|
902
|
+
}
|
|
903
|
+
/** Return a copy of `para` with its leading docen-task checkbox SDT removed. */
|
|
904
|
+
stripTaskCheckbox(para) {
|
|
905
|
+
const children = para.children;
|
|
906
|
+
if (Array.isArray(children) && children.length > 0 && isTaskCheckbox(children[0])) return {
|
|
907
|
+
...para,
|
|
908
|
+
children: children.slice(1)
|
|
909
|
+
};
|
|
910
|
+
return para;
|
|
911
|
+
}
|
|
912
|
+
resolveCodeBlock(opts) {
|
|
913
|
+
const children = opts.children;
|
|
914
|
+
const content = [];
|
|
915
|
+
if (children) {
|
|
916
|
+
for (const child of children) if (typeof child === "string") {
|
|
917
|
+
if (child) content.push({
|
|
918
|
+
type: "text",
|
|
919
|
+
text: child
|
|
920
|
+
});
|
|
921
|
+
} else if (typeof child === "object" && child !== null) {
|
|
922
|
+
if ("break" in child) {
|
|
923
|
+
const prev = content[content.length - 1];
|
|
924
|
+
if (prev && prev.type === "text") prev.text = (prev.text ?? "") + "\n";
|
|
925
|
+
else content.push({
|
|
926
|
+
type: "text",
|
|
927
|
+
text: "\n"
|
|
928
|
+
});
|
|
929
|
+
} else if ("text" in child) {
|
|
930
|
+
const marks = this.resolveMarks(child);
|
|
931
|
+
const textNode = {
|
|
932
|
+
type: "text",
|
|
933
|
+
text: child.text
|
|
934
|
+
};
|
|
935
|
+
if (marks) textNode.marks = marks;
|
|
936
|
+
content.push(textNode);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
} else if (opts.text) content.push({
|
|
940
|
+
type: "text",
|
|
941
|
+
text: opts.text
|
|
942
|
+
});
|
|
943
|
+
const node = { type: "codeBlock" };
|
|
944
|
+
if (content.length > 0) node.content = content;
|
|
945
|
+
return node;
|
|
946
|
+
}
|
|
947
|
+
resolveTable(tableOpts) {
|
|
948
|
+
const attrs = parseDocx$4(tableOpts);
|
|
949
|
+
const rows = tableOpts.rows ?? [];
|
|
950
|
+
const content = [];
|
|
951
|
+
let activeSpans = /* @__PURE__ */ new Map();
|
|
952
|
+
for (const row of rows) {
|
|
953
|
+
const rowAttrs = parseDocx$7(row);
|
|
954
|
+
const cells = row.cells ?? [];
|
|
955
|
+
const cellNodes = [];
|
|
956
|
+
const nextActiveSpans = /* @__PURE__ */ new Map();
|
|
957
|
+
let colIdx = 0;
|
|
958
|
+
for (const cell of cells) {
|
|
959
|
+
const cellColspan = cell.columnSpan ?? 1;
|
|
960
|
+
const vMerge = cell.verticalMerge;
|
|
961
|
+
if (vMerge === "continue") {
|
|
962
|
+
const owner = activeSpans.get(colIdx);
|
|
963
|
+
if (owner) {
|
|
964
|
+
const ownerAttrs = owner.attrs ??= {};
|
|
965
|
+
ownerAttrs.rowspan = (ownerAttrs.rowspan ?? 1) + 1;
|
|
966
|
+
for (let c = colIdx; c < colIdx + cellColspan; c++) nextActiveSpans.set(c, owner);
|
|
967
|
+
}
|
|
968
|
+
colIdx += cellColspan;
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
const isHeader = row.tableHeader;
|
|
972
|
+
const cellAttrs = isHeader ? parseDocx$6(cell) : parseDocx$5(cell);
|
|
973
|
+
delete cellAttrs.verticalMerge;
|
|
974
|
+
const cellChildren = cell.children ?? [];
|
|
975
|
+
const cellContent = [];
|
|
976
|
+
for (const cc of cellChildren) {
|
|
977
|
+
const resolved = this.resolveSectionChild(cc);
|
|
978
|
+
if (resolved) cellContent.push(resolved);
|
|
979
|
+
}
|
|
980
|
+
const cellNode = { type: isHeader ? "tableHeader" : "tableCell" };
|
|
981
|
+
if (Object.keys(cellAttrs).length > 0) cellNode.attrs = cleanAttrs(cellAttrs);
|
|
982
|
+
if (cellContent.length > 0) cellNode.content = cellContent;
|
|
983
|
+
if (vMerge === "restart") for (let c = colIdx; c < colIdx + cellColspan; c++) nextActiveSpans.set(c, cellNode);
|
|
984
|
+
cellNodes.push(cellNode);
|
|
985
|
+
colIdx += cellColspan;
|
|
986
|
+
}
|
|
987
|
+
activeSpans = nextActiveSpans;
|
|
988
|
+
const rowNode = { type: "tableRow" };
|
|
989
|
+
if (Object.keys(rowAttrs).length > 0) rowNode.attrs = cleanAttrs(rowAttrs);
|
|
990
|
+
if (cellNodes.length > 0) rowNode.content = cellNodes;
|
|
991
|
+
content.push(rowNode);
|
|
992
|
+
}
|
|
993
|
+
const node = { type: "table" };
|
|
994
|
+
if (Object.keys(attrs).length > 0) node.attrs = cleanAttrs(attrs);
|
|
995
|
+
if (content.length > 0) node.content = content;
|
|
996
|
+
return node;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Resolve a paragraph's inline content. @office-open collapses a plain-text
|
|
1000
|
+
* paragraph (a single run with no properties) to a bare string or a `{ text }`
|
|
1001
|
+
* object with no `children` — recover that text here so it round-trips back to
|
|
1002
|
+
* a text node instead of being dropped.
|
|
1003
|
+
*/
|
|
1004
|
+
resolveInlineContent(opts) {
|
|
1005
|
+
const content = this.resolveParagraphChildren(opts.children);
|
|
1006
|
+
if (content.length === 0 && opts.text) {
|
|
1007
|
+
const marks = this.resolveMarks(opts);
|
|
1008
|
+
const node = {
|
|
1009
|
+
type: "text",
|
|
1010
|
+
text: opts.text
|
|
1011
|
+
};
|
|
1012
|
+
if (marks) node.marks = marks;
|
|
1013
|
+
return [node];
|
|
1014
|
+
}
|
|
1015
|
+
return content;
|
|
1016
|
+
}
|
|
1017
|
+
resolveParagraphChildren(children) {
|
|
1018
|
+
if (!children || children.length === 0) return [];
|
|
1019
|
+
const nodes = [];
|
|
1020
|
+
for (const child of children) {
|
|
1021
|
+
if (typeof child === "string") {
|
|
1022
|
+
if (child) nodes.push({
|
|
1023
|
+
type: "text",
|
|
1024
|
+
text: child
|
|
1025
|
+
});
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (typeof child === "object" && child !== null) {
|
|
1029
|
+
const resolved = this.resolveParagraphChild(child);
|
|
1030
|
+
if (resolved) nodes.push(...Array.isArray(resolved) ? resolved : [resolved]);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return nodes;
|
|
1034
|
+
}
|
|
1035
|
+
resolveParagraphChild(child) {
|
|
1036
|
+
if ("text" in child || "children" in child || "break" in child) return this.resolveRun(child);
|
|
1037
|
+
if ("image" in child) return this.resolveImage(child.image);
|
|
1038
|
+
if ("wpgGroup" in child) return {
|
|
1039
|
+
type: "imageGroup",
|
|
1040
|
+
attrs: { wpgGroup: child.wpgGroup }
|
|
1041
|
+
};
|
|
1042
|
+
if ("sdt" in child) return this.resolveInlineSdt(child);
|
|
1043
|
+
if ("hyperlink" in child) return this.resolveHyperlink(child.hyperlink);
|
|
1044
|
+
if ("pageBreak" in child) return { type: "pageBreak" };
|
|
1045
|
+
if ("columnBreak" in child) return { type: "columnBreak" };
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
/** Resolve an inline SDT (mention carrier; other inline SDTs unsupported). */
|
|
1049
|
+
resolveInlineSdt(child) {
|
|
1050
|
+
if (isMention(child)) {
|
|
1051
|
+
const { id, label } = readMention(child);
|
|
1052
|
+
return {
|
|
1053
|
+
type: "mention",
|
|
1054
|
+
attrs: {
|
|
1055
|
+
id,
|
|
1056
|
+
label
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
resolveRun(opts) {
|
|
1063
|
+
if (opts.break && opts.text === void 0 && !opts.children) return { type: "hardBreak" };
|
|
1064
|
+
const text = opts.text;
|
|
1065
|
+
if (text === void 0 && !opts.children) return null;
|
|
1066
|
+
if (opts.children && !text) {
|
|
1067
|
+
const parts = [];
|
|
1068
|
+
for (const c of opts.children) if (typeof c === "string") parts.push(c);
|
|
1069
|
+
if (parts.length === 0) return null;
|
|
1070
|
+
return {
|
|
1071
|
+
type: "text",
|
|
1072
|
+
text: parts.join(""),
|
|
1073
|
+
marks: this.resolveMarks(opts)
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
type: "text",
|
|
1078
|
+
text: text ?? "",
|
|
1079
|
+
marks: this.resolveMarks(opts)
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
resolveMarks(opts) {
|
|
1083
|
+
const marks = [];
|
|
1084
|
+
if (opts.bold) marks.push({ type: "bold" });
|
|
1085
|
+
if (opts.italic) marks.push({ type: "italic" });
|
|
1086
|
+
if (opts.underline) marks.push({ type: "underline" });
|
|
1087
|
+
if (opts.strike) {
|
|
1088
|
+
const strikeAttrs = parseDocx$3(opts);
|
|
1089
|
+
marks.push({
|
|
1090
|
+
type: "strike",
|
|
1091
|
+
attrs: { doubleStrike: strikeAttrs.doubleStrike ?? null }
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
if (opts.subScript) marks.push({ type: "subscript" });
|
|
1095
|
+
if (opts.superScript) marks.push({ type: "superscript" });
|
|
1096
|
+
if (opts.highlight) marks.push({
|
|
1097
|
+
type: "highlight",
|
|
1098
|
+
attrs: { color: opts.highlight }
|
|
1099
|
+
});
|
|
1100
|
+
if (opts.style === "CodeChar") marks.push({ type: "code" });
|
|
1101
|
+
const textStyleAttrs = parseDocx$8(opts);
|
|
1102
|
+
if (opts.style === "CodeChar") {
|
|
1103
|
+
delete textStyleAttrs.font;
|
|
1104
|
+
delete textStyleAttrs.styleId;
|
|
1105
|
+
}
|
|
1106
|
+
if (Object.keys(textStyleAttrs).length > 0) marks.push({
|
|
1107
|
+
type: "textStyle",
|
|
1108
|
+
attrs: textStyleAttrs
|
|
1109
|
+
});
|
|
1110
|
+
return marks.length > 0 ? marks : void 0;
|
|
1111
|
+
}
|
|
1112
|
+
resolveImage(imageOpts) {
|
|
1113
|
+
const attrs = parseDocx$1(imageOpts);
|
|
1114
|
+
const data = imageOpts.data;
|
|
1115
|
+
const type = imageOpts.type;
|
|
1116
|
+
if (data && type) attrs.src = `data:image/${type};base64,${encodeBase64(data instanceof ArrayBuffer ? new Uint8Array(data) : data)}`;
|
|
1117
|
+
return {
|
|
1118
|
+
type: "image",
|
|
1119
|
+
attrs
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
resolveHyperlink(hyperlink) {
|
|
1123
|
+
const href = hyperlink.link ?? (hyperlink.anchor ? `#${hyperlink.anchor}` : "");
|
|
1124
|
+
if (!href) return null;
|
|
1125
|
+
const content = this.resolveParagraphChildren((hyperlink.children ?? []).map((c) => c));
|
|
1126
|
+
if (content.length > 0) {
|
|
1127
|
+
const merged = mergeTextNodes(content);
|
|
1128
|
+
for (const node of merged) if (node.type === "text") node.marks = [...node.marks ?? [], {
|
|
1129
|
+
type: "link",
|
|
1130
|
+
attrs: {
|
|
1131
|
+
href,
|
|
1132
|
+
target: "_blank",
|
|
1133
|
+
rel: "noopener noreferrer nofollow",
|
|
1134
|
+
class: null,
|
|
1135
|
+
title: hyperlink.tooltip ?? null
|
|
1136
|
+
}
|
|
1137
|
+
}];
|
|
1138
|
+
return merged;
|
|
1139
|
+
}
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
const HEADING_LEVEL_MAP = {
|
|
1144
|
+
Heading1: 1,
|
|
1145
|
+
Heading2: 2,
|
|
1146
|
+
Heading3: 3,
|
|
1147
|
+
Heading4: 4,
|
|
1148
|
+
Heading5: 5,
|
|
1149
|
+
Heading6: 6,
|
|
1150
|
+
Title: 1
|
|
1151
|
+
};
|
|
1152
|
+
const defaultManager = new DocxManager();
|
|
1153
|
+
/**
|
|
1154
|
+
* Parse a DOCX file into Tiptap JSON (runtime model).
|
|
1155
|
+
*
|
|
1156
|
+
* Combines @office-open/docx's `parseDocument` (DOCX binary → DocumentOptions)
|
|
1157
|
+
* with `DocxManager.resolve` (DocumentOptions → Tiptap JSON).
|
|
1158
|
+
*/
|
|
1159
|
+
function parseDOCX(data) {
|
|
1160
|
+
return defaultManager.resolve(parseDocument(data));
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Merge {@link DocxGenerateOptions.document} into the `DocumentOptions`
|
|
1164
|
+
* compiled from Tiptap JSON.
|
|
1165
|
+
*
|
|
1166
|
+
* - `sections`/`numbering`: compile-owned (excluded from `document`, never
|
|
1167
|
+
* overridden).
|
|
1168
|
+
* - `styles`/`externalStyles`: option wins over `json.attrs.styles`; the two
|
|
1169
|
+
* are mutually exclusive, so `externalStyles` clears compiled `styles`.
|
|
1170
|
+
* - Everything else (core properties, background, features, …): injected.
|
|
1171
|
+
*/
|
|
1172
|
+
function applyDocumentOptions(base, document) {
|
|
1173
|
+
if (!document) return base;
|
|
1174
|
+
const merged = {
|
|
1175
|
+
...base,
|
|
1176
|
+
...document
|
|
1177
|
+
};
|
|
1178
|
+
if (document.externalStyles !== void 0) delete merged.styles;
|
|
1179
|
+
return merged;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Generate a DOCX file from Tiptap JSON (runtime model), asynchronously.
|
|
1183
|
+
*
|
|
1184
|
+
* Pipeline: `prepareDocument` (default: fetch http images, in place) →
|
|
1185
|
+
* `DocxManager.compile` → @office-open/docx's `generateDocument`. `packer.type`
|
|
1186
|
+
* controls the output format (default: `"nodebuffer"` → Buffer). Non-blocking
|
|
1187
|
+
* (fflate Web Workers). With the default `prepare`, the input `json` is mutated
|
|
1188
|
+
* in place (http image URLs become embedded data URLs).
|
|
1189
|
+
*/
|
|
1190
|
+
async function generateDOCX(json, options) {
|
|
1191
|
+
const { prepare = true, packer, document } = options ?? {};
|
|
1192
|
+
if (prepare !== false) await prepareDocument(json, prepare === true ? void 0 : prepare);
|
|
1193
|
+
return generateDocument(applyDocumentOptions(compileDocument(json), document), packer);
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Generate a DOCX file synchronously — fastest throughput, blocks the event loop.
|
|
1197
|
+
*
|
|
1198
|
+
* Pipeline: `DocxManager.compile` → `generateDocumentSync`. Does **not** run
|
|
1199
|
+
* `prepareDocument` (it is async); call `await prepareDocument(json)` first
|
|
1200
|
+
* when http images need embedding. `options.document` is still applied.
|
|
1201
|
+
*/
|
|
1202
|
+
function generateDOCXSync(json, options) {
|
|
1203
|
+
const { packer, document } = options ?? {};
|
|
1204
|
+
return generateDocumentSync(applyDocumentOptions(compileDocument(json), document), packer);
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Generate a DOCX file as a `ReadableStream<Uint8Array>` — for large documents
|
|
1208
|
+
* or streaming HTTP responses.
|
|
1209
|
+
*
|
|
1210
|
+
* Pipeline: `prepareDocument` (default: fetch http images, in place) →
|
|
1211
|
+
* `DocxManager.compile` → `generateDocumentStream`. Async due to preparation.
|
|
1212
|
+
*/
|
|
1213
|
+
async function generateDOCXStream(json, options) {
|
|
1214
|
+
const { prepare = true, packer, document } = options ?? {};
|
|
1215
|
+
if (prepare !== false) await prepareDocument(json, prepare === true ? void 0 : prepare);
|
|
1216
|
+
return generateDocumentStream(applyDocumentOptions(compileDocument(json), document), packer);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Convert DocumentOptions (persistence model) to Tiptap JSON (runtime model).
|
|
1220
|
+
*/
|
|
1221
|
+
function resolveDocument(docOpts) {
|
|
1222
|
+
return defaultManager.resolve(docOpts);
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Convert Tiptap JSON (runtime model) to DocumentOptions (persistence model).
|
|
1226
|
+
*/
|
|
1227
|
+
function compileDocument(json) {
|
|
1228
|
+
return defaultManager.compile(json);
|
|
1229
|
+
}
|
|
1230
|
+
//#endregion
|
|
1231
|
+
export { generateDOCXSync as a, generateDOCXStream as i, compileDocument as n, parseDOCX as o, generateDOCX as r, resolveDocument as s, DocxManager as t };
|