@docen/docx 0.3.0 → 0.3.2
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/converters/docx.d.mts +95 -5
- package/dist/converters/docx.mjs +732 -82
- package/dist/converters/html.d.mts +20 -5
- package/dist/converters/html.mjs +94 -8
- package/dist/converters/markdown.d.mts +1 -1
- package/dist/converters/markdown.mjs +1 -1
- package/dist/converters/patch.d.mts +1 -1
- package/dist/converters/patch.mjs +4 -5
- package/dist/converters/prepare.d.mts +15 -3
- package/dist/converters/prepare.mjs +50 -4
- package/dist/converters/styles.d.mts +85 -0
- package/dist/converters/styles.mjs +285 -0
- package/dist/core-C9VunJ8o.d.mts +174 -0
- package/dist/core-DT9IrTzN.mjs +670 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/document-BH1y4qHM.d.mts +6 -0
- package/dist/editor.d.mts +1 -1
- package/dist/editor.mjs +3 -2
- package/dist/extensions/blockquote.d.mts +23 -0
- package/dist/extensions/blockquote.mjs +31 -0
- package/dist/extensions/code-block.d.mts +19 -0
- package/dist/extensions/code-block.mjs +23 -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 +18 -0
- package/dist/extensions/document.d.mts +2 -0
- package/dist/extensions/document.mjs +50 -0
- package/dist/extensions/extensions.d.mts +5 -5
- package/dist/extensions/extensions.mjs +4 -3
- package/dist/extensions/formatting-marks.d.mts +38 -0
- package/dist/extensions/formatting-marks.mjs +2 -0
- package/dist/extensions/heading.mjs +24 -3
- package/dist/extensions/image.d.mts +2 -2
- package/dist/extensions/image.mjs +244 -49
- package/dist/extensions/index.d.mts +4 -2
- package/dist/extensions/index.mjs +4 -2
- package/dist/extensions/mention.d.mts +25 -0
- package/dist/extensions/mention.mjs +44 -0
- package/dist/extensions/ordered-list.d.mts +25 -0
- package/dist/extensions/ordered-list.mjs +41 -0
- package/dist/extensions/page-break.d.mts +2 -0
- package/dist/extensions/page-break.mjs +2 -0
- package/dist/extensions/paragraph.d.mts +1 -1
- package/dist/extensions/paragraph.mjs +39 -4
- package/dist/extensions/passthrough.d.mts +2 -0
- package/dist/extensions/passthrough.mjs +2 -0
- package/dist/extensions/section-break.d.mts +44 -0
- package/dist/extensions/section-break.mjs +2 -0
- package/dist/extensions/table.mjs +29 -3
- package/dist/extensions/task-item.d.mts +27 -0
- package/dist/extensions/task-item.mjs +37 -0
- package/dist/extensions/text-style.mjs +20 -3
- package/dist/extensions/tiptap.d.mts +2 -2
- package/dist/extensions/tiptap.mjs +1 -3
- package/dist/extensions/types.d.mts +7 -5
- package/dist/extensions/utils.d.mts +2 -49
- package/dist/extensions/utils.mjs +222 -12
- package/dist/extensions/wpg-group.d.mts +2 -0
- package/dist/extensions/wpg-group.mjs +2 -0
- package/dist/extensions/wps-shape.d.mts +2 -0
- package/dist/extensions/wps-shape.mjs +2 -0
- package/dist/index.d.mts +127 -18
- package/dist/index.mjs +7 -2
- package/dist/paragraph-D8mpHo_o.d.mts +8 -0
- package/dist/{tiptap-TErPjuNJ.d.mts → tiptap-pZsNPsvV.d.mts} +1 -3
- package/dist/utils-D87vukzp.d.mts +128 -0
- package/package.json +40 -40
- 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
package/dist/converters/docx.mjs
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { parseDocx
|
|
5
|
-
import { parseDocx as parseDocx$
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { parseDocx as parseDocx$
|
|
9
|
-
import { parseDocx as parseDocx$
|
|
1
|
+
import { BLOCKQUOTE_BORDER, applyBlockquoteStyle } from "../extensions/blockquote.mjs";
|
|
2
|
+
import { renderDocx } from "../extensions/code-block.mjs";
|
|
3
|
+
import { DETAILS_SUMMARY_STYLE, DETAILS_TAG } from "../extensions/details.mjs";
|
|
4
|
+
import { parseDocx, renderDocx as renderDocx$1 } from "../extensions/heading.mjs";
|
|
5
|
+
import { parseDocx as parseDocx$1, renderDocx as renderDocx$2 } from "../extensions/image.mjs";
|
|
6
|
+
import { createMention, isMention, readMention } from "../extensions/mention.mjs";
|
|
7
|
+
import { ORDERED_REFERENCE_PREFIX, buildOrderedLevels } from "../extensions/ordered-list.mjs";
|
|
8
|
+
import { parseDocx as parseDocx$2, renderDocx as renderDocx$3 } from "../extensions/paragraph.mjs";
|
|
9
|
+
import { parseDocx as parseDocx$3, renderDocx as renderDocx$4 } from "../extensions/strike.mjs";
|
|
10
|
+
import { parseDocx as parseDocx$4, renderDocx as renderDocx$5 } from "../extensions/table.mjs";
|
|
11
|
+
import { parseDocx as parseDocx$5, renderDocx as renderDocx$6 } from "../extensions/table-cell.mjs";
|
|
12
|
+
import { parseDocx as parseDocx$6, renderDocx as renderDocx$7 } from "../extensions/table-header.mjs";
|
|
13
|
+
import { parseDocx as parseDocx$7, renderDocx as renderDocx$8 } from "../extensions/table-row.mjs";
|
|
14
|
+
import { createTaskCheckbox, isTaskCheckbox, readCheckboxState } from "../extensions/task-item.mjs";
|
|
15
|
+
import { parseDocx as parseDocx$8, renderDocx as renderDocx$9 } from "../extensions/text-style.mjs";
|
|
10
16
|
import { prepareDocument } from "./prepare.mjs";
|
|
17
|
+
import { emojis, shortcodeToEmoji } from "@tiptap/extension-emoji";
|
|
11
18
|
import { generateDocument, generateDocumentStream, generateDocumentSync, parseDocument } from "@office-open/docx";
|
|
19
|
+
import { encodeBase64 } from "@office-open/core";
|
|
12
20
|
//#region src/converters/docx.ts
|
|
13
21
|
/** Remove keys with null/undefined values */
|
|
14
22
|
function cleanAttrs(attrs) {
|
|
@@ -16,6 +24,53 @@ function cleanAttrs(attrs) {
|
|
|
16
24
|
for (const [key, value] of Object.entries(attrs)) if (value !== null && value !== void 0) result[key] = value;
|
|
17
25
|
return result;
|
|
18
26
|
}
|
|
27
|
+
/** True when two cell-margin sets (w:tcMar / w:tblCellMar) match on every side's
|
|
28
|
+
* size — used to detect a cell that merely echoes the table's default so its
|
|
29
|
+
* redundant tcMar can be dropped for a near-identity round-trip. */
|
|
30
|
+
function sameCellMargins(a, b) {
|
|
31
|
+
const sz = (m, k) => m[k]?.size ?? null;
|
|
32
|
+
return [
|
|
33
|
+
"top",
|
|
34
|
+
"right",
|
|
35
|
+
"bottom",
|
|
36
|
+
"left"
|
|
37
|
+
].every((k) => sz(a, k) === sz(b, k));
|
|
38
|
+
}
|
|
39
|
+
/** Keys round-tripped between DocumentOptions core properties and `doc.attrs.core`. */
|
|
40
|
+
const CORE_PROPERTY_KEYS = [
|
|
41
|
+
"title",
|
|
42
|
+
"subject",
|
|
43
|
+
"creator",
|
|
44
|
+
"keywords",
|
|
45
|
+
"description",
|
|
46
|
+
"lastModifiedBy",
|
|
47
|
+
"lastPrinted",
|
|
48
|
+
"created",
|
|
49
|
+
"modified",
|
|
50
|
+
"revision"
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* DocumentOptions keys that DocxManager reconstructs (sections/numbering) or
|
|
54
|
+
* carries in dedicated attrs (styles/background/core). Excluded from the
|
|
55
|
+
* documentExtras pass-through so they aren't duplicated.
|
|
56
|
+
*/
|
|
57
|
+
const COMPILE_OWNED_KEYS = new Set([
|
|
58
|
+
"sections",
|
|
59
|
+
"numbering",
|
|
60
|
+
"styles",
|
|
61
|
+
"background",
|
|
62
|
+
...CORE_PROPERTY_KEYS
|
|
63
|
+
]);
|
|
64
|
+
/** Collect core properties present on DocumentOptions into a plain object. */
|
|
65
|
+
function extractCoreProperties(docOpts) {
|
|
66
|
+
const source = docOpts;
|
|
67
|
+
const core = {};
|
|
68
|
+
for (const key of CORE_PROPERTY_KEYS) {
|
|
69
|
+
const value = source[key];
|
|
70
|
+
if (value !== void 0 && value !== null) core[key] = value;
|
|
71
|
+
}
|
|
72
|
+
return Object.keys(core).length > 0 ? core : null;
|
|
73
|
+
}
|
|
19
74
|
/** Merge consecutive text nodes with same marks */
|
|
20
75
|
function mergeTextNodes(nodes) {
|
|
21
76
|
const result = [];
|
|
@@ -38,74 +93,202 @@ function mergeTextNodes(nodes) {
|
|
|
38
93
|
* DocxManager handles tree walking, child assembly, and dispatching.
|
|
39
94
|
*/
|
|
40
95
|
var DocxManager = class {
|
|
96
|
+
numberingConfigs = [];
|
|
97
|
+
orderedInstanceCounter = 0;
|
|
98
|
+
resolveStyles;
|
|
41
99
|
compile(json) {
|
|
42
|
-
|
|
100
|
+
this.numberingConfigs = [];
|
|
101
|
+
this.orderedInstanceCounter = 0;
|
|
102
|
+
const sections = [];
|
|
103
|
+
let currentChildren = [];
|
|
43
104
|
if (json.content) for (const node of json.content) {
|
|
44
105
|
const child = this.compileSectionChild(node);
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
106
|
+
if (child) if (Array.isArray(child)) currentChildren.push(...child);
|
|
107
|
+
else currentChildren.push(child);
|
|
108
|
+
if (node.type === "paragraph") {
|
|
109
|
+
const na = node.attrs ?? {};
|
|
110
|
+
if (na.sectionProperties != null) {
|
|
111
|
+
sections.push(this.buildSection(currentChildren, na.sectionProperties, this.compileHeaderFooter(na.sectionHeaders ?? null), this.compileHeaderFooter(na.sectionFooters ?? null)));
|
|
112
|
+
currentChildren = [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const docAttrs = json.attrs ?? {};
|
|
117
|
+
sections.push(this.buildSection(currentChildren, docAttrs.sectionProperties ?? null, this.compileHeaderFooter(docAttrs.sectionHeaders ?? null), this.compileHeaderFooter(docAttrs.sectionFooters ?? null)));
|
|
118
|
+
const styles = docAttrs.styles ?? void 0;
|
|
119
|
+
const core = docAttrs.core ?? void 0;
|
|
120
|
+
const background = docAttrs.background ?? void 0;
|
|
121
|
+
const documentExtras = docAttrs.documentExtras ?? void 0;
|
|
122
|
+
return {
|
|
123
|
+
sections,
|
|
124
|
+
...styles ? { styles } : {},
|
|
125
|
+
...core,
|
|
126
|
+
...background ? { background } : {},
|
|
127
|
+
...documentExtras,
|
|
128
|
+
...this.numberingConfigs.length > 0 ? { numbering: { config: this.numberingConfigs } } : {}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Assemble a SectionOptions from compiled children + optional layout/headers/footers. */
|
|
132
|
+
buildSection(children, properties, headers, footers) {
|
|
133
|
+
return {
|
|
134
|
+
children,
|
|
135
|
+
...properties ? { properties } : {},
|
|
136
|
+
...headers ? { headers } : {},
|
|
137
|
+
...footers ? { footers } : {}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Compile resolved header/footer slots (JSONContent[] per slot) back into
|
|
142
|
+
* SectionChild[] per slot. Returns undefined when no slot has content.
|
|
143
|
+
*/
|
|
144
|
+
compileHeaderFooter(slots) {
|
|
145
|
+
if (!slots) return void 0;
|
|
146
|
+
const group = {};
|
|
147
|
+
for (const slot of [
|
|
148
|
+
"default",
|
|
149
|
+
"first",
|
|
150
|
+
"even"
|
|
151
|
+
]) {
|
|
152
|
+
const json = slots[slot];
|
|
153
|
+
if (!json?.length) continue;
|
|
154
|
+
const children = [];
|
|
155
|
+
for (const node of json) {
|
|
156
|
+
const child = this.compileSectionChild(node);
|
|
157
|
+
if (!child) continue;
|
|
158
|
+
if (Array.isArray(child)) children.push(...child);
|
|
159
|
+
else children.push(child);
|
|
160
|
+
}
|
|
161
|
+
if (children.length > 0) group[slot] = children;
|
|
162
|
+
}
|
|
163
|
+
return Object.keys(group).length > 0 ? group : void 0;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve a section's header/footer group (SectionChild[] per slot) into
|
|
167
|
+
* Tiptap JSON slots. Returns null when no slot has content.
|
|
168
|
+
*/
|
|
169
|
+
resolveHeaderFooter(group, numberingLookup) {
|
|
170
|
+
if (!group) return null;
|
|
171
|
+
const slots = {};
|
|
172
|
+
for (const slot of [
|
|
173
|
+
"default",
|
|
174
|
+
"first",
|
|
175
|
+
"even"
|
|
176
|
+
]) {
|
|
177
|
+
const children = group[slot];
|
|
178
|
+
if (children?.length) slots[slot] = this.resolveSectionChildren(children, numberingLookup);
|
|
48
179
|
}
|
|
49
|
-
return
|
|
180
|
+
return Object.keys(slots).length > 0 ? slots : null;
|
|
50
181
|
}
|
|
51
182
|
resolve(docOpts) {
|
|
183
|
+
this.resolveStyles = docOpts.styles ?? void 0;
|
|
52
184
|
const sections = docOpts.sections ?? [];
|
|
53
185
|
if (sections.length === 0) return {
|
|
54
186
|
type: "doc",
|
|
55
187
|
content: [{ type: "paragraph" }]
|
|
56
188
|
};
|
|
57
|
-
const
|
|
189
|
+
const numberingLookup = this.buildNumberingLookup(docOpts);
|
|
58
190
|
const content = [];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
191
|
+
const lastIndex = sections.length - 1;
|
|
192
|
+
for (let i = 0; i < sections.length; i++) {
|
|
193
|
+
const section = sections[i];
|
|
194
|
+
const sectionContent = this.resolveSectionChildren(section.children ?? [], numberingLookup);
|
|
195
|
+
if (i < lastIndex) {
|
|
196
|
+
const sectAttrs = {
|
|
197
|
+
sectionProperties: section.properties ?? null,
|
|
198
|
+
sectionHeaders: this.resolveHeaderFooter(section.headers, numberingLookup),
|
|
199
|
+
sectionFooters: this.resolveHeaderFooter(section.footers, numberingLookup)
|
|
200
|
+
};
|
|
201
|
+
const last = sectionContent[sectionContent.length - 1];
|
|
202
|
+
if (last?.type === "paragraph") last.attrs = {
|
|
203
|
+
...last.attrs,
|
|
204
|
+
...sectAttrs
|
|
205
|
+
};
|
|
206
|
+
else sectionContent.push({
|
|
207
|
+
type: "paragraph",
|
|
208
|
+
attrs: sectAttrs
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
content.push(...sectionContent);
|
|
62
212
|
}
|
|
63
|
-
|
|
213
|
+
const doc = {
|
|
64
214
|
type: "doc",
|
|
65
215
|
content: content.length > 0 ? content : [{ type: "paragraph" }]
|
|
66
216
|
};
|
|
217
|
+
const attrs = {};
|
|
218
|
+
if (docOpts.styles) attrs.styles = docOpts.styles;
|
|
219
|
+
if (docOpts.background) attrs.background = docOpts.background;
|
|
220
|
+
const core = extractCoreProperties(docOpts);
|
|
221
|
+
if (core) attrs.core = core;
|
|
222
|
+
const lastSection = sections[lastIndex];
|
|
223
|
+
if (lastSection.properties) attrs.sectionProperties = lastSection.properties;
|
|
224
|
+
const lastHeaders = this.resolveHeaderFooter(lastSection.headers, numberingLookup);
|
|
225
|
+
if (lastHeaders) attrs.sectionHeaders = lastHeaders;
|
|
226
|
+
const lastFooters = this.resolveHeaderFooter(lastSection.footers, numberingLookup);
|
|
227
|
+
if (lastFooters) attrs.sectionFooters = lastFooters;
|
|
228
|
+
const documentExtras = {};
|
|
229
|
+
for (const [k, v] of Object.entries(docOpts)) {
|
|
230
|
+
if (COMPILE_OWNED_KEYS.has(k)) continue;
|
|
231
|
+
if (v === void 0 || v === null) continue;
|
|
232
|
+
documentExtras[k] = v;
|
|
233
|
+
}
|
|
234
|
+
if (Object.keys(documentExtras).length > 0) attrs.documentExtras = documentExtras;
|
|
235
|
+
if (Object.keys(attrs).length > 0) doc.attrs = attrs;
|
|
236
|
+
return doc;
|
|
67
237
|
}
|
|
68
238
|
compileSectionChild(node) {
|
|
69
239
|
switch (node.type) {
|
|
70
240
|
case "paragraph": return { paragraph: this.compileParagraphNode(node) };
|
|
71
241
|
case "heading": return { paragraph: this.compileHeadingNode(node) };
|
|
72
|
-
case "blockquote":
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
242
|
+
case "blockquote": {
|
|
243
|
+
const items = [];
|
|
244
|
+
for (const child of node.content ?? []) if (child.type === "paragraph" || child.type === "heading") {
|
|
245
|
+
const para = child.type === "heading" ? this.compileHeadingNode(child) : this.compileParagraphNode(child);
|
|
246
|
+
const paraObj = typeof para === "string" ? { text: para } : para;
|
|
247
|
+
applyBlockquoteStyle(paraObj);
|
|
248
|
+
items.push({ paragraph: paraObj });
|
|
249
|
+
}
|
|
250
|
+
return items.length > 0 ? items : null;
|
|
251
|
+
}
|
|
252
|
+
case "codeBlock": {
|
|
253
|
+
const opts = renderDocx(node);
|
|
254
|
+
const childList = this.compileInlineContent(node.content);
|
|
255
|
+
if (childList.length > 0) opts.children = childList;
|
|
256
|
+
return { paragraph: this.simplifyParagraph(opts) };
|
|
257
|
+
}
|
|
76
258
|
case "horizontalRule": return { paragraph: { thematicBreak: true } };
|
|
77
259
|
case "table": return { table: this.compileTableNode(node) };
|
|
78
260
|
case "image": {
|
|
79
|
-
const imageRun = renderDocx$
|
|
261
|
+
const imageRun = renderDocx$2(node);
|
|
80
262
|
if (!imageRun) return null;
|
|
81
263
|
return { paragraph: { children: [imageRun] } };
|
|
82
264
|
}
|
|
83
265
|
case "bulletList":
|
|
84
266
|
case "orderedList":
|
|
85
|
-
case "taskList": return this.compileListFromNode(node);
|
|
267
|
+
case "taskList": return this.compileListFromNode(node, 0);
|
|
268
|
+
case "details": return this.compileDetailsNode(node);
|
|
269
|
+
case "passthrough": {
|
|
270
|
+
const data = node.attrs?.data ?? "{}";
|
|
271
|
+
try {
|
|
272
|
+
return JSON.parse(data);
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
86
277
|
default: return null;
|
|
87
278
|
}
|
|
88
279
|
}
|
|
89
280
|
compileParagraphNode(node) {
|
|
90
|
-
const opts = renderDocx$
|
|
281
|
+
const opts = renderDocx$3(node);
|
|
91
282
|
const childList = this.compileInlineContent(node.content);
|
|
92
283
|
if (childList.length > 0) opts.children = childList;
|
|
93
284
|
return this.simplifyParagraph(opts);
|
|
94
285
|
}
|
|
95
286
|
compileHeadingNode(node) {
|
|
96
|
-
const opts = renderDocx(node);
|
|
287
|
+
const opts = renderDocx$1(node);
|
|
97
288
|
const childList = this.compileInlineContent(node.content);
|
|
98
289
|
if (childList.length > 0) opts.children = childList;
|
|
99
290
|
return this.simplifyParagraph(opts);
|
|
100
291
|
}
|
|
101
|
-
compileCodeBlock(node) {
|
|
102
|
-
const text = (node.content ?? []).map((c) => c.text ?? "").join("");
|
|
103
|
-
return {
|
|
104
|
-
style: "Code",
|
|
105
|
-
...node.attrs?.language ? { run: { font: "Consolas" } } : {},
|
|
106
|
-
text
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
292
|
/** Simple text optimization: merge plain runs into text field */
|
|
110
293
|
simplifyParagraph(opts) {
|
|
111
294
|
const children = opts.children;
|
|
@@ -119,13 +302,14 @@ var DocxManager = class {
|
|
|
119
302
|
return opts;
|
|
120
303
|
}
|
|
121
304
|
compileTableNode(node) {
|
|
122
|
-
const opts = renderDocx$
|
|
305
|
+
const opts = renderDocx$5(node);
|
|
123
306
|
const colCount = this.getTableColumnCount(node);
|
|
307
|
+
const tableCellMargins = opts.cellMargin ?? opts.margins ?? null;
|
|
124
308
|
const rows = [];
|
|
125
309
|
let activeSpans = [];
|
|
126
310
|
for (const rowNode of node.content ?? []) {
|
|
127
311
|
if (rowNode.type !== "tableRow") continue;
|
|
128
|
-
const rowOpts = renderDocx$
|
|
312
|
+
const rowOpts = renderDocx$8(rowNode);
|
|
129
313
|
const pmCells = (rowNode.content ?? []).filter((c) => c.type === "tableCell" || c.type === "tableHeader");
|
|
130
314
|
const currentSpans = [...activeSpans].sort((a, b) => a.colStart - b.colStart);
|
|
131
315
|
const newSpans = [];
|
|
@@ -145,7 +329,7 @@ var DocxManager = class {
|
|
|
145
329
|
colIdx += span.colspan;
|
|
146
330
|
spanIdx++;
|
|
147
331
|
} else {
|
|
148
|
-
const cell = this.compileTableCellNode(pmCells[cellIdx]);
|
|
332
|
+
const cell = this.compileTableCellNode(pmCells[cellIdx], tableCellMargins);
|
|
149
333
|
const cs = cell.columnSpan ?? 1;
|
|
150
334
|
const rs = cell.rowSpan ?? 1;
|
|
151
335
|
if (rs > 1) {
|
|
@@ -241,8 +425,9 @@ var DocxManager = class {
|
|
|
241
425
|
}
|
|
242
426
|
};
|
|
243
427
|
}
|
|
244
|
-
compileTableCellNode(cellNode) {
|
|
245
|
-
const cellOpts = cellNode.type === "tableHeader" ? renderDocx$
|
|
428
|
+
compileTableCellNode(cellNode, tableMargins) {
|
|
429
|
+
const cellOpts = cellNode.type === "tableHeader" ? renderDocx$7(cellNode) : renderDocx$6(cellNode);
|
|
430
|
+
if (tableMargins && cellOpts.margins && sameCellMargins(cellOpts.margins, tableMargins)) delete cellOpts.margins;
|
|
246
431
|
const cellAlign = cellNode.attrs?.align ?? null;
|
|
247
432
|
const cellChildren = [];
|
|
248
433
|
for (const paraNode of cellNode.content ?? []) {
|
|
@@ -254,23 +439,95 @@ var DocxManager = class {
|
|
|
254
439
|
if (cellChildren.length > 0) cellOpts.children = cellChildren;
|
|
255
440
|
return cellOpts;
|
|
256
441
|
}
|
|
257
|
-
compileListFromNode(node) {
|
|
442
|
+
compileListFromNode(node, level) {
|
|
258
443
|
const items = [];
|
|
444
|
+
const isOrdered = node.type === "orderedList";
|
|
445
|
+
const isTask = node.type === "taskList";
|
|
446
|
+
let ordered;
|
|
447
|
+
if (isOrdered) ordered = this.registerOrderedNumbering(node);
|
|
259
448
|
for (const listItem of node.content ?? []) {
|
|
260
449
|
if (listItem.type !== "listItem" && listItem.type !== "taskItem") continue;
|
|
450
|
+
const checked = Boolean(listItem.attrs?.checked);
|
|
261
451
|
for (const child of listItem.content ?? []) if (child.type === "paragraph" || child.type === "heading") {
|
|
262
|
-
const para = this.compileParagraphNode(child);
|
|
452
|
+
const para = child.type === "heading" ? this.compileHeadingNode(child) : this.compileParagraphNode(child);
|
|
263
453
|
const paraObj = typeof para === "string" ? { text: para } : para;
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
level
|
|
454
|
+
if (ordered) paraObj.numbering = {
|
|
455
|
+
reference: ordered.reference,
|
|
456
|
+
instance: ordered.instance,
|
|
457
|
+
level
|
|
268
458
|
};
|
|
459
|
+
else paraObj.bullet = { level };
|
|
460
|
+
if (isTask) this.injectTaskCheckbox(paraObj, checked);
|
|
269
461
|
items.push({ paragraph: paraObj });
|
|
462
|
+
} else if (child.type === "bulletList" || child.type === "orderedList" || child.type === "taskList") {
|
|
463
|
+
const nested = this.compileListFromNode(child, level + 1);
|
|
464
|
+
if (nested) items.push(...nested);
|
|
270
465
|
}
|
|
271
466
|
}
|
|
272
467
|
return items.length > 0 ? items : null;
|
|
273
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Register (or reuse) an abstractNum for an ordered list's `start`, and
|
|
471
|
+
* return a fresh instance so this list counts independently of other lists
|
|
472
|
+
* that share the same definition.
|
|
473
|
+
*/
|
|
474
|
+
registerOrderedNumbering(node) {
|
|
475
|
+
const start = Number(node.attrs?.start ?? 1) || 1;
|
|
476
|
+
let entry = this.numberingConfigs.find((c) => Number(c.levels[0]?.start ?? 1) === start);
|
|
477
|
+
if (!entry) {
|
|
478
|
+
entry = {
|
|
479
|
+
reference: `${ORDERED_REFERENCE_PREFIX}-${this.numberingConfigs.length + 1}`,
|
|
480
|
+
levels: buildOrderedLevels(start)
|
|
481
|
+
};
|
|
482
|
+
this.numberingConfigs.push(entry);
|
|
483
|
+
}
|
|
484
|
+
this.orderedInstanceCounter += 1;
|
|
485
|
+
return {
|
|
486
|
+
reference: entry.reference,
|
|
487
|
+
instance: this.orderedInstanceCounter
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Prepend an inline checkbox SDT to a task paragraph. The SDT is tagged
|
|
492
|
+
* "docen-task" so resolve can tell task items apart from ordinary paragraphs
|
|
493
|
+
* that happen to contain an SDT.
|
|
494
|
+
*/
|
|
495
|
+
injectTaskCheckbox(paraObj, checked) {
|
|
496
|
+
let existing = [];
|
|
497
|
+
if (Array.isArray(paraObj.children)) existing = paraObj.children;
|
|
498
|
+
else if (typeof paraObj.text === "string") {
|
|
499
|
+
if (paraObj.text) existing = [{ text: paraObj.text }];
|
|
500
|
+
delete paraObj.text;
|
|
501
|
+
}
|
|
502
|
+
paraObj.children = [createTaskCheckbox(checked), ...existing];
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* details → block-level group-SDT. The summary paragraph is tagged with a
|
|
506
|
+
* fixed style so resolve can split it back out; content blocks flatten in
|
|
507
|
+
* after it. (No native collapse in DOCX — structure round-trips, the view
|
|
508
|
+
* stays expanded.)
|
|
509
|
+
*/
|
|
510
|
+
compileDetailsNode(node) {
|
|
511
|
+
const sdtChildren = [];
|
|
512
|
+
for (const child of node.content ?? []) if (child.type === "detailsSummary") {
|
|
513
|
+
const inline = this.compileInlineContent(child.content);
|
|
514
|
+
const summaryPara = { style: DETAILS_SUMMARY_STYLE };
|
|
515
|
+
if (inline.length > 0) summaryPara.children = inline;
|
|
516
|
+
sdtChildren.push({ paragraph: summaryPara });
|
|
517
|
+
} else if (child.type === "detailsContent") for (const block of child.content ?? []) {
|
|
518
|
+
const compiled = this.compileSectionChild(block);
|
|
519
|
+
if (!compiled) continue;
|
|
520
|
+
if (Array.isArray(compiled)) sdtChildren.push(...compiled);
|
|
521
|
+
else sdtChildren.push(compiled);
|
|
522
|
+
}
|
|
523
|
+
return { sdt: {
|
|
524
|
+
properties: {
|
|
525
|
+
tag: DETAILS_TAG,
|
|
526
|
+
group: true
|
|
527
|
+
},
|
|
528
|
+
children: sdtChildren
|
|
529
|
+
} };
|
|
530
|
+
}
|
|
274
531
|
compileInlineContent(content) {
|
|
275
532
|
if (!content) return [];
|
|
276
533
|
const children = [];
|
|
@@ -281,20 +538,52 @@ var DocxManager = class {
|
|
|
281
538
|
case "hardBreak":
|
|
282
539
|
children.push({ break: 1 });
|
|
283
540
|
break;
|
|
541
|
+
case "pageBreak":
|
|
542
|
+
children.push({ pageBreak: true });
|
|
543
|
+
break;
|
|
544
|
+
case "columnBreak":
|
|
545
|
+
children.push({ columnBreak: true });
|
|
546
|
+
break;
|
|
284
547
|
case "image": {
|
|
285
|
-
const imageRun = renderDocx$
|
|
548
|
+
const imageRun = renderDocx$2(node);
|
|
286
549
|
if (imageRun) children.push(imageRun);
|
|
287
550
|
break;
|
|
288
551
|
}
|
|
552
|
+
case "wpgGroup": {
|
|
553
|
+
const wpgGroup = node.attrs?.wpgGroup;
|
|
554
|
+
if (wpgGroup) children.push({ wpgGroup });
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
case "wpsShape": {
|
|
558
|
+
const wpsShape = node.attrs?.wpsShape;
|
|
559
|
+
if (wpsShape) children.push({ wpsShape });
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case "mention":
|
|
563
|
+
children.push(createMention(String(node.attrs?.id ?? ""), String(node.attrs?.label ?? "")));
|
|
564
|
+
break;
|
|
565
|
+
case "emoji": {
|
|
566
|
+
const name = String(node.attrs?.name ?? "");
|
|
567
|
+
const text = (name ? shortcodeToEmoji(name, emojis)?.emoji : void 0) ?? (name ? `:${name}:` : "");
|
|
568
|
+
if (text) children.push({ text });
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
289
571
|
}
|
|
290
572
|
return children;
|
|
291
573
|
}
|
|
292
574
|
compileTextNode(node, children) {
|
|
293
575
|
const text = node.text ?? "";
|
|
294
576
|
if (!text) return;
|
|
295
|
-
const
|
|
577
|
+
const segments = text.split("\n");
|
|
578
|
+
for (let i = 0; i < segments.length; i++) {
|
|
579
|
+
if (i > 0) children.push({ break: 1 });
|
|
580
|
+
if (segments[i]) this.compileTextRun(segments[i], node.marks, children);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/** Emit a single run for `text` with all inline marks applied. */
|
|
584
|
+
compileTextRun(text, marks, children) {
|
|
296
585
|
const runOpts = { text };
|
|
297
|
-
for (const mark of marks) switch (mark.type) {
|
|
586
|
+
for (const mark of marks ?? []) switch (mark.type) {
|
|
298
587
|
case "bold":
|
|
299
588
|
runOpts.bold = true;
|
|
300
589
|
break;
|
|
@@ -305,7 +594,7 @@ var DocxManager = class {
|
|
|
305
594
|
runOpts.underline = { type: "single" };
|
|
306
595
|
break;
|
|
307
596
|
case "strike": {
|
|
308
|
-
const strikeProps = renderDocx$
|
|
597
|
+
const strikeProps = renderDocx$4(mark.attrs ?? {});
|
|
309
598
|
Object.assign(runOpts, strikeProps);
|
|
310
599
|
break;
|
|
311
600
|
}
|
|
@@ -319,10 +608,11 @@ var DocxManager = class {
|
|
|
319
608
|
runOpts.highlight = mark.attrs?.color ?? "yellow";
|
|
320
609
|
break;
|
|
321
610
|
case "code":
|
|
611
|
+
runOpts.style = "CodeChar";
|
|
322
612
|
runOpts.font = "Consolas";
|
|
323
613
|
break;
|
|
324
614
|
case "textStyle": {
|
|
325
|
-
const tsProps = renderDocx$
|
|
615
|
+
const tsProps = renderDocx$9(mark.attrs ?? {});
|
|
326
616
|
Object.assign(runOpts, tsProps);
|
|
327
617
|
break;
|
|
328
618
|
}
|
|
@@ -350,15 +640,72 @@ var DocxManager = class {
|
|
|
350
640
|
resolveSectionChild(child) {
|
|
351
641
|
if ("paragraph" in child) return this.resolveParagraph(child.paragraph);
|
|
352
642
|
if ("table" in child) return this.resolveTable(child.table);
|
|
353
|
-
|
|
643
|
+
if ("sdt" in child) {
|
|
644
|
+
const sdt = child.sdt;
|
|
645
|
+
if (sdt.properties?.tag === "docen-details") return this.resolveDetailsSdt(sdt);
|
|
646
|
+
return this.resolvePassthrough(child);
|
|
647
|
+
}
|
|
648
|
+
return this.resolvePassthrough(child);
|
|
649
|
+
}
|
|
650
|
+
/** Wrap an opaque SectionChild in a passthrough atom (attrs.data = JSON). */
|
|
651
|
+
resolvePassthrough(child) {
|
|
652
|
+
return {
|
|
653
|
+
type: "passthrough",
|
|
654
|
+
attrs: { data: JSON.stringify(child) }
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Resolve a details group-SDT: the summary-style paragraph becomes
|
|
659
|
+
* detailsSummary, the remaining blocks fold into detailsContent.
|
|
660
|
+
*/
|
|
661
|
+
resolveDetailsSdt(sdt) {
|
|
662
|
+
const content = [];
|
|
663
|
+
let summary = null;
|
|
664
|
+
for (const child of sdt.children ?? []) {
|
|
665
|
+
if ("paragraph" in child) {
|
|
666
|
+
const para = child.paragraph;
|
|
667
|
+
if (para.style === "DocenDetailsSummary") {
|
|
668
|
+
summary = this.resolveInlineContent(para);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const node = this.resolveSectionChild(child);
|
|
673
|
+
if (!node) continue;
|
|
674
|
+
if (Array.isArray(node)) content.push(...node);
|
|
675
|
+
else content.push(node);
|
|
676
|
+
}
|
|
677
|
+
const details = {
|
|
678
|
+
type: "details",
|
|
679
|
+
content: []
|
|
680
|
+
};
|
|
681
|
+
if (summary !== null) details.content.push({
|
|
682
|
+
type: "detailsSummary",
|
|
683
|
+
content: summary
|
|
684
|
+
});
|
|
685
|
+
if (content.length > 0) details.content.push({
|
|
686
|
+
type: "detailsContent",
|
|
687
|
+
content
|
|
688
|
+
});
|
|
689
|
+
return details;
|
|
354
690
|
}
|
|
355
691
|
resolveParagraph(opts) {
|
|
356
692
|
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
357
|
-
|
|
693
|
+
if (resolved.thematicBreak) return { type: "horizontalRule" };
|
|
694
|
+
if (resolved.style === "Code") return this.resolveCodeBlock(resolved);
|
|
695
|
+
let headingLevel = resolved.heading ? HEADING_LEVEL_MAP[resolved.heading] : void 0;
|
|
696
|
+
if (!headingLevel && resolved.style) {
|
|
697
|
+
const name = this.styleNameOf(resolved.style);
|
|
698
|
+
if (name) {
|
|
699
|
+
const m = /^heading\s+(\d)$/i.exec(name);
|
|
700
|
+
if (m) {
|
|
701
|
+
const lvl = Number(m[1]);
|
|
702
|
+
if (lvl >= 1 && lvl <= 6) headingLevel = lvl;
|
|
703
|
+
} else if (/^title$/i.test(name)) headingLevel = 1;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (headingLevel && !resolved.heading) resolved.heading = `Heading${headingLevel}`;
|
|
358
707
|
const nodeType = headingLevel ? "heading" : "paragraph";
|
|
359
708
|
const attrs = headingLevel ? parseDocx(resolved) : parseDocx$2(resolved);
|
|
360
|
-
if (resolved.bullet) return this.resolveListItem(resolved, "bulletList", attrs);
|
|
361
|
-
if (resolved.numbering) return this.resolveListItem(resolved, "orderedList", attrs);
|
|
362
709
|
const content = this.resolveInlineContent(resolved);
|
|
363
710
|
const cleanAttrsObj = cleanAttrs(attrs);
|
|
364
711
|
const node = { type: nodeType };
|
|
@@ -366,24 +713,262 @@ var DocxManager = class {
|
|
|
366
713
|
if (content.length > 0) node.content = content;
|
|
367
714
|
return node;
|
|
368
715
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
716
|
+
/** Look up a paragraph style's NAME from its styleId, via the styles table
|
|
717
|
+
* carried on the current resolve(). Returns undefined outside resolve. */
|
|
718
|
+
styleNameOf(styleId) {
|
|
719
|
+
return this.resolveStyles?.paragraphStyles?.find((p) => p.id === styleId)?.name;
|
|
720
|
+
}
|
|
721
|
+
/** reference → level-0 format/start, for classifying numbering paragraphs. */
|
|
722
|
+
buildNumberingLookup(docOpts) {
|
|
723
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
724
|
+
const config = docOpts.numbering?.config;
|
|
725
|
+
if (config) for (const entry of config) {
|
|
726
|
+
const lvl0 = entry.levels[0];
|
|
727
|
+
lookup.set(entry.reference, {
|
|
728
|
+
format: lvl0?.format,
|
|
729
|
+
start: lvl0?.start
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
return lookup;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Walk section children, grouping consecutive list paragraphs into nested
|
|
736
|
+
* Tiptap lists. Non-list children resolve individually. DOCX flattens lists
|
|
737
|
+
* to a paragraph sequence (depth carried by `level`); this rebuilds the tree.
|
|
738
|
+
*/
|
|
739
|
+
resolveSectionChildren(children, numberingLookup) {
|
|
740
|
+
const content = [];
|
|
741
|
+
let i = 0;
|
|
742
|
+
while (i < children.length) {
|
|
743
|
+
const child = children[i];
|
|
744
|
+
const firstPara = "paragraph" in child ? child.paragraph : null;
|
|
745
|
+
if (!(firstPara && typeof firstPara !== "string" ? this.detectList(firstPara, numberingLookup) : null)) {
|
|
746
|
+
if (firstPara && typeof firstPara !== "string" && this.detectBlockquote(firstPara)) {
|
|
747
|
+
const group = [];
|
|
748
|
+
while (i < children.length) {
|
|
749
|
+
const member = children[i];
|
|
750
|
+
if (!("paragraph" in member)) break;
|
|
751
|
+
const para = member.paragraph;
|
|
752
|
+
if (typeof para === "string" || !this.detectBlockquote(para)) break;
|
|
753
|
+
group.push(para);
|
|
754
|
+
i++;
|
|
755
|
+
}
|
|
756
|
+
content.push(this.buildBlockquote(group));
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const node = this.resolveSectionChild(child);
|
|
760
|
+
if (node) content.push(node);
|
|
761
|
+
i++;
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const group = [];
|
|
765
|
+
while (i < children.length) {
|
|
766
|
+
const member = children[i];
|
|
767
|
+
if (!("paragraph" in member)) break;
|
|
768
|
+
const para = member.paragraph;
|
|
769
|
+
if (typeof para === "string") break;
|
|
770
|
+
const info = this.detectList(para, numberingLookup);
|
|
771
|
+
if (!info) break;
|
|
772
|
+
group.push({
|
|
773
|
+
para,
|
|
774
|
+
info
|
|
775
|
+
});
|
|
776
|
+
i++;
|
|
777
|
+
}
|
|
778
|
+
content.push(...this.buildListTree(group));
|
|
779
|
+
}
|
|
780
|
+
return content;
|
|
781
|
+
}
|
|
782
|
+
/** Classify a paragraph as a list item, or null if it isn't one. */
|
|
783
|
+
detectList(para, lookup) {
|
|
784
|
+
const p = para;
|
|
785
|
+
const numbering = p.numbering;
|
|
786
|
+
const bullet = p.bullet;
|
|
787
|
+
let kind;
|
|
788
|
+
let level;
|
|
789
|
+
let reference;
|
|
790
|
+
let start;
|
|
791
|
+
if (numbering) {
|
|
792
|
+
reference = numbering.reference;
|
|
793
|
+
level = numbering.level ?? 0;
|
|
794
|
+
const cfg = reference ? lookup.get(reference) : void 0;
|
|
795
|
+
if (cfg && cfg.format && cfg.format !== "bullet") {
|
|
796
|
+
kind = "ordered";
|
|
797
|
+
start = cfg.start;
|
|
798
|
+
} else {
|
|
799
|
+
kind = "bullet";
|
|
800
|
+
reference = void 0;
|
|
801
|
+
}
|
|
802
|
+
} else if (bullet) {
|
|
803
|
+
kind = "bullet";
|
|
804
|
+
level = bullet.level ?? 0;
|
|
805
|
+
} else return null;
|
|
806
|
+
const first = p.children?.[0];
|
|
375
807
|
return {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
808
|
+
kind: isTaskCheckbox(first) ? "task" : kind,
|
|
809
|
+
level,
|
|
810
|
+
reference,
|
|
811
|
+
start,
|
|
812
|
+
checked: readCheckboxState(first)
|
|
381
813
|
};
|
|
382
814
|
}
|
|
815
|
+
/**
|
|
816
|
+
* Rebuild nested Tiptap lists from a flat run of list paragraphs. Stack-based:
|
|
817
|
+
* each frame is an active list at a given depth; the `key` (level:type:
|
|
818
|
+
* reference) decides whether a paragraph continues the top list, starts a
|
|
819
|
+
* nested list, or splits off a new sibling list.
|
|
820
|
+
*/
|
|
821
|
+
buildListTree(group) {
|
|
822
|
+
const topLevel = [];
|
|
823
|
+
const stack = [];
|
|
824
|
+
for (const { para, info } of group) {
|
|
825
|
+
const listType = info.kind === "ordered" ? "orderedList" : info.kind === "task" ? "taskList" : "bulletList";
|
|
826
|
+
const itemType = info.kind === "task" ? "taskItem" : "listItem";
|
|
827
|
+
const key = `${info.level}:${listType}:${info.reference ?? ""}`;
|
|
828
|
+
while (stack.length > 0) {
|
|
829
|
+
const top = stack[stack.length - 1];
|
|
830
|
+
if (top.level > info.level || top.level === info.level && top.key !== key) {
|
|
831
|
+
stack.pop();
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
const newItem = {
|
|
837
|
+
type: itemType,
|
|
838
|
+
content: [this.resolveListItemParagraph(para, info)]
|
|
839
|
+
};
|
|
840
|
+
if (itemType === "taskItem") newItem.attrs = { checked: info.checked };
|
|
841
|
+
const top = stack[stack.length - 1];
|
|
842
|
+
if (top && top.level === info.level && top.key === key) {
|
|
843
|
+
top.listNode.content.push(newItem);
|
|
844
|
+
top.currentItem = newItem;
|
|
845
|
+
} else {
|
|
846
|
+
const newList = {
|
|
847
|
+
type: listType,
|
|
848
|
+
content: [newItem]
|
|
849
|
+
};
|
|
850
|
+
if (listType === "orderedList" && info.level === 0 && typeof info.start === "number" && info.start !== 1) newList.attrs = { start: info.start };
|
|
851
|
+
if (top) top.currentItem.content.push(newList);
|
|
852
|
+
else topLevel.push(newList);
|
|
853
|
+
stack.push({
|
|
854
|
+
level: info.level,
|
|
855
|
+
key,
|
|
856
|
+
listNode: newList,
|
|
857
|
+
currentItem: newItem
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return topLevel;
|
|
862
|
+
}
|
|
863
|
+
/** Classify a paragraph as a blockquote member by its signature. */
|
|
864
|
+
detectBlockquote(para) {
|
|
865
|
+
const p = para;
|
|
866
|
+
const indent = p.indent;
|
|
867
|
+
const border = p.border;
|
|
868
|
+
if (!indent || indent.left !== 720) return false;
|
|
869
|
+
const bl = border?.left;
|
|
870
|
+
if (!bl) return false;
|
|
871
|
+
const sig = BLOCKQUOTE_BORDER;
|
|
872
|
+
return bl.style === sig.style && bl.size === sig.size && bl.space === sig.space && bl.color === sig.color;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Rebuild a blockquote node from a run of signature-carrying paragraphs,
|
|
876
|
+
* stripping the indent/border signature so child paragraphs render clean.
|
|
877
|
+
*/
|
|
878
|
+
buildBlockquote(group) {
|
|
879
|
+
const content = [];
|
|
880
|
+
for (const para of group) {
|
|
881
|
+
const node = this.resolveParagraph(para);
|
|
882
|
+
const attrs = node.attrs;
|
|
883
|
+
if (attrs) {
|
|
884
|
+
if (attrs.indent) {
|
|
885
|
+
const indent = { ...attrs.indent };
|
|
886
|
+
delete indent.left;
|
|
887
|
+
attrs.indent = Object.keys(indent).length > 0 ? indent : void 0;
|
|
888
|
+
}
|
|
889
|
+
if (attrs.border) {
|
|
890
|
+
const border = { ...attrs.border };
|
|
891
|
+
delete border.left;
|
|
892
|
+
attrs.border = Object.keys(border).length > 0 ? border : void 0;
|
|
893
|
+
}
|
|
894
|
+
const cleaned = cleanAttrs(attrs);
|
|
895
|
+
if (Object.keys(cleaned).length > 0) node.attrs = cleaned;
|
|
896
|
+
else delete node.attrs;
|
|
897
|
+
}
|
|
898
|
+
content.push(node);
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
type: "blockquote",
|
|
902
|
+
content
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Resolve a list-item paragraph to a Tiptap paragraph/heading node, stripping
|
|
907
|
+
* the list marker (bullet/numbering) and the leading task checkbox — those
|
|
908
|
+
* are expressed at the list/item level, not inside the paragraph.
|
|
909
|
+
*/
|
|
910
|
+
resolveListItemParagraph(para, info) {
|
|
911
|
+
const resolved = typeof para === "string" ? { text: para } : para;
|
|
912
|
+
const headingLevel = resolved.heading ? HEADING_LEVEL_MAP[resolved.heading] : void 0;
|
|
913
|
+
const nodeType = headingLevel ? "heading" : "paragraph";
|
|
914
|
+
const attrs = headingLevel ? parseDocx(resolved) : parseDocx$2(resolved);
|
|
915
|
+
const stripped = info.kind === "task" ? this.stripTaskCheckbox(resolved) : resolved;
|
|
916
|
+
const content = this.resolveInlineContent(stripped);
|
|
917
|
+
const node = { type: nodeType };
|
|
918
|
+
const cleanAttrsObj = cleanAttrs(attrs);
|
|
919
|
+
if (Object.keys(cleanAttrsObj).length > 0) node.attrs = cleanAttrsObj;
|
|
920
|
+
if (content.length > 0) node.content = content;
|
|
921
|
+
return node;
|
|
922
|
+
}
|
|
923
|
+
/** Return a copy of `para` with its leading docen-task checkbox SDT removed. */
|
|
924
|
+
stripTaskCheckbox(para) {
|
|
925
|
+
const children = para.children;
|
|
926
|
+
if (Array.isArray(children) && children.length > 0 && isTaskCheckbox(children[0])) return {
|
|
927
|
+
...para,
|
|
928
|
+
children: children.slice(1)
|
|
929
|
+
};
|
|
930
|
+
return para;
|
|
931
|
+
}
|
|
932
|
+
resolveCodeBlock(opts) {
|
|
933
|
+
const children = opts.children;
|
|
934
|
+
const content = [];
|
|
935
|
+
if (children) {
|
|
936
|
+
for (const child of children) if (typeof child === "string") {
|
|
937
|
+
if (child) content.push({
|
|
938
|
+
type: "text",
|
|
939
|
+
text: child
|
|
940
|
+
});
|
|
941
|
+
} else if (typeof child === "object" && child !== null) {
|
|
942
|
+
if ("break" in child) {
|
|
943
|
+
const prev = content[content.length - 1];
|
|
944
|
+
if (prev && prev.type === "text") prev.text = (prev.text ?? "") + "\n";
|
|
945
|
+
else content.push({
|
|
946
|
+
type: "text",
|
|
947
|
+
text: "\n"
|
|
948
|
+
});
|
|
949
|
+
} else if ("text" in child) {
|
|
950
|
+
const marks = this.resolveMarks(child);
|
|
951
|
+
const textNode = {
|
|
952
|
+
type: "text",
|
|
953
|
+
text: child.text
|
|
954
|
+
};
|
|
955
|
+
if (marks) textNode.marks = marks;
|
|
956
|
+
content.push(textNode);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
} else if (opts.text) content.push({
|
|
960
|
+
type: "text",
|
|
961
|
+
text: opts.text
|
|
962
|
+
});
|
|
963
|
+
const node = { type: "codeBlock" };
|
|
964
|
+
if (content.length > 0) node.content = content;
|
|
965
|
+
return node;
|
|
966
|
+
}
|
|
383
967
|
resolveTable(tableOpts) {
|
|
384
968
|
const attrs = parseDocx$4(tableOpts);
|
|
385
969
|
const rows = tableOpts.rows ?? [];
|
|
386
970
|
const content = [];
|
|
971
|
+
const tableCellMargins = tableOpts.cellMargin ?? tableOpts.margins ?? null;
|
|
387
972
|
let activeSpans = /* @__PURE__ */ new Map();
|
|
388
973
|
for (const row of rows) {
|
|
389
974
|
const rowAttrs = parseDocx$7(row);
|
|
@@ -407,6 +992,7 @@ var DocxManager = class {
|
|
|
407
992
|
const isHeader = row.tableHeader;
|
|
408
993
|
const cellAttrs = isHeader ? parseDocx$6(cell) : parseDocx$5(cell);
|
|
409
994
|
delete cellAttrs.verticalMerge;
|
|
995
|
+
if (!cellAttrs.margins && tableCellMargins) cellAttrs.margins = tableCellMargins;
|
|
410
996
|
const cellChildren = cell.children ?? [];
|
|
411
997
|
const cellContent = [];
|
|
412
998
|
for (const cc of cellChildren) {
|
|
@@ -416,10 +1002,27 @@ var DocxManager = class {
|
|
|
416
1002
|
const cellNode = { type: isHeader ? "tableHeader" : "tableCell" };
|
|
417
1003
|
if (Object.keys(cellAttrs).length > 0) cellNode.attrs = cleanAttrs(cellAttrs);
|
|
418
1004
|
if (cellContent.length > 0) cellNode.content = cellContent;
|
|
1005
|
+
else cellNode.content = [{ type: "paragraph" }];
|
|
419
1006
|
if (vMerge === "restart") for (let c = colIdx; c < colIdx + cellColspan; c++) nextActiveSpans.set(c, cellNode);
|
|
420
1007
|
cellNodes.push(cellNode);
|
|
421
1008
|
colIdx += cellColspan;
|
|
422
1009
|
}
|
|
1010
|
+
const gridAfter = row.gridAfter ?? 0;
|
|
1011
|
+
if (gridAfter > 0) {
|
|
1012
|
+
const trailingType = row.tableHeader ? "tableHeader" : "tableCell";
|
|
1013
|
+
const nilBorders = {
|
|
1014
|
+
top: { style: "nil" },
|
|
1015
|
+
right: { style: "nil" },
|
|
1016
|
+
bottom: { style: "nil" },
|
|
1017
|
+
left: { style: "nil" }
|
|
1018
|
+
};
|
|
1019
|
+
for (let c = 0; c < gridAfter; c++) cellNodes.push({
|
|
1020
|
+
type: trailingType,
|
|
1021
|
+
attrs: { borders: nilBorders },
|
|
1022
|
+
content: [{ type: "paragraph" }]
|
|
1023
|
+
});
|
|
1024
|
+
colIdx += gridAfter;
|
|
1025
|
+
}
|
|
423
1026
|
activeSpans = nextActiveSpans;
|
|
424
1027
|
const rowNode = { type: "tableRow" };
|
|
425
1028
|
if (Object.keys(rowAttrs).length > 0) rowNode.attrs = cleanAttrs(rowAttrs);
|
|
@@ -471,15 +1074,36 @@ var DocxManager = class {
|
|
|
471
1074
|
resolveParagraphChild(child) {
|
|
472
1075
|
if ("text" in child || "children" in child || "break" in child) return this.resolveRun(child);
|
|
473
1076
|
if ("image" in child) return this.resolveImage(child.image);
|
|
474
|
-
if ("
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
attrs: { pageBreak: true }
|
|
1077
|
+
if ("wpgGroup" in child) return {
|
|
1078
|
+
type: "wpgGroup",
|
|
1079
|
+
attrs: { wpgGroup: child.wpgGroup }
|
|
478
1080
|
};
|
|
479
|
-
if ("
|
|
1081
|
+
if ("wpsShape" in child) return {
|
|
1082
|
+
type: "wpsShape",
|
|
1083
|
+
attrs: { wpsShape: child.wpsShape }
|
|
1084
|
+
};
|
|
1085
|
+
if ("sdt" in child) return this.resolveInlineSdt(child);
|
|
1086
|
+
if ("hyperlink" in child) return this.resolveHyperlink(child.hyperlink);
|
|
1087
|
+
if ("pageBreak" in child) return { type: "pageBreak" };
|
|
1088
|
+
if ("columnBreak" in child) return { type: "columnBreak" };
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
/** Resolve an inline SDT (mention carrier; other inline SDTs unsupported). */
|
|
1092
|
+
resolveInlineSdt(child) {
|
|
1093
|
+
if (isMention(child)) {
|
|
1094
|
+
const { id, label } = readMention(child);
|
|
1095
|
+
return {
|
|
1096
|
+
type: "mention",
|
|
1097
|
+
attrs: {
|
|
1098
|
+
id,
|
|
1099
|
+
label
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
480
1103
|
return null;
|
|
481
1104
|
}
|
|
482
1105
|
resolveRun(opts) {
|
|
1106
|
+
if (opts.break && opts.text === void 0 && !opts.children) return { type: "hardBreak" };
|
|
483
1107
|
const text = opts.text;
|
|
484
1108
|
if (text === void 0 && !opts.children) return null;
|
|
485
1109
|
if (opts.children && !text) {
|
|
@@ -512,10 +1136,16 @@ var DocxManager = class {
|
|
|
512
1136
|
}
|
|
513
1137
|
if (opts.subScript) marks.push({ type: "subscript" });
|
|
514
1138
|
if (opts.superScript) marks.push({ type: "superscript" });
|
|
515
|
-
if (opts.highlight) marks.push({
|
|
516
|
-
|
|
1139
|
+
if (opts.highlight) marks.push({
|
|
1140
|
+
type: "highlight",
|
|
1141
|
+
attrs: { color: opts.highlight }
|
|
1142
|
+
});
|
|
1143
|
+
if (opts.style === "CodeChar") marks.push({ type: "code" });
|
|
517
1144
|
const textStyleAttrs = parseDocx$8(opts);
|
|
518
|
-
if (opts.
|
|
1145
|
+
if (opts.style === "CodeChar") {
|
|
1146
|
+
delete textStyleAttrs.font;
|
|
1147
|
+
delete textStyleAttrs.styleId;
|
|
1148
|
+
}
|
|
519
1149
|
if (Object.keys(textStyleAttrs).length > 0) marks.push({
|
|
520
1150
|
type: "textStyle",
|
|
521
1151
|
attrs: textStyleAttrs
|
|
@@ -526,7 +1156,7 @@ var DocxManager = class {
|
|
|
526
1156
|
const attrs = parseDocx$1(imageOpts);
|
|
527
1157
|
const data = imageOpts.data;
|
|
528
1158
|
const type = imageOpts.type;
|
|
529
|
-
if (data && type) attrs.src = `data:image/${type};base64,${
|
|
1159
|
+
if (data && type) attrs.src = `data:image/${type};base64,${encodeBase64(data instanceof ArrayBuffer ? new Uint8Array(data) : data)}`;
|
|
530
1160
|
return {
|
|
531
1161
|
type: "image",
|
|
532
1162
|
attrs
|
|
@@ -573,6 +1203,25 @@ function parseDOCX(data) {
|
|
|
573
1203
|
return defaultManager.resolve(parseDocument(data));
|
|
574
1204
|
}
|
|
575
1205
|
/**
|
|
1206
|
+
* Merge {@link DocxGenerateOptions.document} into the `DocumentOptions`
|
|
1207
|
+
* compiled from Tiptap JSON.
|
|
1208
|
+
*
|
|
1209
|
+
* - `sections`/`numbering`: compile-owned (excluded from `document`, never
|
|
1210
|
+
* overridden).
|
|
1211
|
+
* - `styles`/`externalStyles`: option wins over `json.attrs.styles`; the two
|
|
1212
|
+
* are mutually exclusive, so `externalStyles` clears compiled `styles`.
|
|
1213
|
+
* - Everything else (core properties, background, features, …): injected.
|
|
1214
|
+
*/
|
|
1215
|
+
function applyDocumentOptions(base, document) {
|
|
1216
|
+
if (!document) return base;
|
|
1217
|
+
const merged = {
|
|
1218
|
+
...base,
|
|
1219
|
+
...document
|
|
1220
|
+
};
|
|
1221
|
+
if (document.externalStyles !== void 0) delete merged.styles;
|
|
1222
|
+
return merged;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
576
1225
|
* Generate a DOCX file from Tiptap JSON (runtime model), asynchronously.
|
|
577
1226
|
*
|
|
578
1227
|
* Pipeline: `prepareDocument` (default: fetch http images, in place) →
|
|
@@ -582,19 +1231,20 @@ function parseDOCX(data) {
|
|
|
582
1231
|
* in place (http image URLs become embedded data URLs).
|
|
583
1232
|
*/
|
|
584
1233
|
async function generateDOCX(json, options) {
|
|
585
|
-
const { prepare = true, packer } = options ?? {};
|
|
1234
|
+
const { prepare = true, packer, document } = options ?? {};
|
|
586
1235
|
if (prepare !== false) await prepareDocument(json, prepare === true ? void 0 : prepare);
|
|
587
|
-
return generateDocument(compileDocument(json), packer);
|
|
1236
|
+
return generateDocument(applyDocumentOptions(compileDocument(json), document), packer);
|
|
588
1237
|
}
|
|
589
1238
|
/**
|
|
590
1239
|
* Generate a DOCX file synchronously — fastest throughput, blocks the event loop.
|
|
591
1240
|
*
|
|
592
1241
|
* Pipeline: `DocxManager.compile` → `generateDocumentSync`. Does **not** run
|
|
593
1242
|
* `prepareDocument` (it is async); call `await prepareDocument(json)` first
|
|
594
|
-
* when http images need embedding.
|
|
1243
|
+
* when http images need embedding. `options.document` is still applied.
|
|
595
1244
|
*/
|
|
596
|
-
function generateDOCXSync(json,
|
|
597
|
-
|
|
1245
|
+
function generateDOCXSync(json, options) {
|
|
1246
|
+
const { packer, document } = options ?? {};
|
|
1247
|
+
return generateDocumentSync(applyDocumentOptions(compileDocument(json), document), packer);
|
|
598
1248
|
}
|
|
599
1249
|
/**
|
|
600
1250
|
* Generate a DOCX file as a `ReadableStream<Uint8Array>` — for large documents
|
|
@@ -604,9 +1254,9 @@ function generateDOCXSync(json, packerOptions) {
|
|
|
604
1254
|
* `DocxManager.compile` → `generateDocumentStream`. Async due to preparation.
|
|
605
1255
|
*/
|
|
606
1256
|
async function generateDOCXStream(json, options) {
|
|
607
|
-
const { prepare = true, packer } = options ?? {};
|
|
1257
|
+
const { prepare = true, packer, document } = options ?? {};
|
|
608
1258
|
if (prepare !== false) await prepareDocument(json, prepare === true ? void 0 : prepare);
|
|
609
|
-
return generateDocumentStream(compileDocument(json), packer);
|
|
1259
|
+
return generateDocumentStream(applyDocumentOptions(compileDocument(json), document), packer);
|
|
610
1260
|
}
|
|
611
1261
|
/**
|
|
612
1262
|
* Convert DocumentOptions (persistence model) to Tiptap JSON (runtime model).
|