@docen/docx 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{blockquote-DY80QC06.d.mts → blockquote-D-1aSxEn.d.mts} +5 -1
- package/dist/converters/docx.d.mts +40 -66
- package/dist/converters/docx.mjs +248 -665
- package/dist/converters/html.d.mts +1 -1
- package/dist/converters/html.mjs +3 -4
- 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/prepare.d.mts +1 -1
- package/dist/converters/styles.d.mts +58 -14
- package/dist/converters/styles.mjs +124 -16
- package/dist/{core-DC0_-WcE.d.mts → core-BqyLL84S.d.mts} +34 -13
- package/dist/{core-BnF8XhVE.mjs → core-wNNPJiKr.mjs} +357 -25
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/details-DsJhDP5K.d.mts +31 -0
- package/dist/editor.d.mts +1 -1
- package/dist/editor.mjs +1 -1
- package/dist/extensions/blockquote.d.mts +2 -3
- package/dist/extensions/blockquote.mjs +50 -2
- package/dist/extensions/bullet-list.mjs +1 -1
- package/dist/extensions/code-block.d.mts +2 -2
- package/dist/extensions/code-block.mjs +51 -5
- package/dist/extensions/column-break.d.mts +2 -2
- package/dist/extensions/column-break.mjs +2 -2
- package/dist/extensions/details.d.mts +2 -3
- package/dist/extensions/details.mjs +48 -2
- package/dist/extensions/document.mjs +1 -1
- package/dist/extensions/extensions.d.mts +13 -9
- package/dist/extensions/extensions.mjs +7 -3
- package/dist/extensions/formatting-marks.d.mts +1 -1
- package/dist/extensions/formatting-marks.mjs +1 -1
- package/dist/extensions/heading.d.mts +2 -2
- package/dist/extensions/heading.mjs +61 -5
- package/dist/extensions/image.d.mts +2 -2
- package/dist/extensions/image.mjs +23 -4
- package/dist/extensions/index.d.mts +14 -10
- package/dist/extensions/index.mjs +8 -4
- package/dist/extensions/link.d.mts +2 -2
- package/dist/extensions/link.mjs +30 -2
- package/dist/extensions/list-aggregator.d.mts +8 -0
- package/dist/extensions/list-aggregator.mjs +2 -0
- package/dist/extensions/marks.d.mts +2 -0
- package/dist/extensions/marks.mjs +41 -0
- package/dist/extensions/mention.d.mts +2 -3
- package/dist/extensions/mention.mjs +16 -2
- package/dist/extensions/ordered-list.mjs +1 -1
- package/dist/extensions/page-break.d.mts +2 -2
- package/dist/extensions/page-break.mjs +2 -2
- package/dist/extensions/paragraph.mjs +3 -3
- package/dist/extensions/passthrough.d.mts +1 -1
- package/dist/extensions/passthrough.mjs +1 -1
- package/dist/extensions/scroll.d.mts +1 -1
- package/dist/extensions/scroll.mjs +30 -5
- package/dist/extensions/section-break.d.mts +1 -1
- package/dist/extensions/section-break.mjs +1 -1
- package/dist/extensions/strike.d.mts +2 -2
- package/dist/extensions/strike.mjs +5 -24
- package/dist/extensions/tab.d.mts +2 -2
- package/dist/extensions/tab.mjs +2 -2
- package/dist/extensions/table-cell.mjs +2 -2
- package/dist/extensions/table-header.mjs +2 -2
- package/dist/extensions/table-row.mjs +2 -2
- package/dist/extensions/table.d.mts +2 -2
- package/dist/extensions/table.mjs +122 -11
- package/dist/extensions/task-item.d.mts +2 -3
- package/dist/extensions/task-item.mjs +2 -2
- package/dist/extensions/text-style.d.mts +2 -2
- package/dist/extensions/text-style.mjs +27 -28
- package/dist/extensions/toc-field.d.mts +2 -2
- package/dist/extensions/toc-field.mjs +2 -2
- package/dist/extensions/track-change.d.mts +2 -0
- package/dist/extensions/track-change.mjs +2 -0
- package/dist/extensions/types.d.mts +127 -8
- package/dist/extensions/utils.d.mts +2 -2
- package/dist/extensions/utils.mjs +74 -1
- package/dist/extensions/wpg-group.d.mts +2 -2
- package/dist/extensions/wpg-group.mjs +2 -2
- package/dist/extensions/wps-shape.d.mts +2 -2
- package/dist/extensions/wps-shape.mjs +2 -2
- package/dist/heading-Bwpa8iZY.d.mts +24 -0
- package/dist/index.d.mts +29 -11
- package/dist/index.mjs +9 -5
- package/dist/{link-BawPjQZR.d.mts → link-gUqW45mE.d.mts} +3 -1
- package/dist/marks-Dz9Vb22Q.d.mts +10 -0
- package/dist/{mention-BGLzLVYw.d.mts → mention-CkONDrw9.d.mts} +5 -1
- package/dist/{scroll-ZNeThJsJ.d.mts → scroll-BARiZ5Gm.d.mts} +9 -3
- package/dist/strike-Brn9sWFy.d.mts +16 -0
- package/dist/table-CdcjR6HD.d.mts +18 -0
- package/dist/{task-item-B0ntvQ1Y.d.mts → task-item-CCAC4QLi.d.mts} +3 -1
- package/dist/text-style-BzfcbufI.d.mts +4 -0
- package/dist/{utils-BJwDQts7.d.mts → utils-CfwwOowz.d.mts} +25 -1
- package/package.json +1 -1
- package/dist/details-Dd5MqqmR.d.mts +0 -17
- package/dist/extensions/tiptap.d.mts +0 -2
- package/dist/extensions/tiptap.mjs +0 -31
- package/dist/heading-BvqBD2zX.d.mts +0 -8
- package/dist/strike-BgWGvjKr.d.mts +0 -33
- package/dist/table-BFkfeRp9.d.mts +0 -9
- package/dist/text-style-BHdtXkMb.d.mts +0 -8
- package/dist/tiptap-BKqn41uT.d.mts +0 -31
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./
|
|
1
|
+
import { attrNative, floatAnchorScope, floatingToStyles, normalizeColorToHex, renderRunStyles } from "./extensions/utils.mjs";
|
|
2
|
+
import { buildTextBlock, cleanAttrs, mergeTextNodes } from "./converters/styles.mjs";
|
|
3
|
+
import { Blockquote } from "./extensions/blockquote.mjs";
|
|
3
4
|
import { BulletList } from "./extensions/bullet-list.mjs";
|
|
4
5
|
import { CodeBlock } from "./extensions/code-block.mjs";
|
|
5
|
-
import "./extensions/details.mjs";
|
|
6
|
-
import { attrNative, floatAnchorScope, floatingToStyles, normalizeColorToHex, renderRunStyles } from "./extensions/utils.mjs";
|
|
6
|
+
import { Details, DetailsContent as DetailsContentBase, DetailsSummary as DetailsSummaryBase } from "./extensions/details.mjs";
|
|
7
7
|
import { Document } from "./extensions/document.mjs";
|
|
8
|
-
import { Heading } from "./extensions/heading.mjs";
|
|
8
|
+
import { Heading, detectHeadingLevel } from "./extensions/heading.mjs";
|
|
9
9
|
import { Image } from "./extensions/image.mjs";
|
|
10
10
|
import { Link } from "./extensions/link.mjs";
|
|
11
|
-
import "./extensions/
|
|
11
|
+
import { TaskItem as TaskItemBase, isTaskCheckbox, readCheckboxState } from "./extensions/task-item.mjs";
|
|
12
|
+
import { Bold, Code, Highlight, Italic, Subscript, Superscript, Underline } from "./extensions/marks.mjs";
|
|
13
|
+
import { Mention } from "./extensions/mention.mjs";
|
|
12
14
|
import { OrderedList } from "./extensions/ordered-list.mjs";
|
|
13
15
|
import { Paragraph } from "./extensions/paragraph.mjs";
|
|
14
16
|
import { Strike } from "./extensions/strike.mjs";
|
|
@@ -16,9 +18,17 @@ import { Table } from "./extensions/table.mjs";
|
|
|
16
18
|
import { TableCell } from "./extensions/table-cell.mjs";
|
|
17
19
|
import { TableHeader } from "./extensions/table-header.mjs";
|
|
18
20
|
import { TableRow } from "./extensions/table-row.mjs";
|
|
19
|
-
import "./extensions/task-item.mjs";
|
|
20
21
|
import { TextStyle } from "./extensions/text-style.mjs";
|
|
21
22
|
import { Editor, Extension, Mark, Node } from "@tiptap/core";
|
|
23
|
+
import { CodeBlockLowlight, CodeBlockLowlight as CodeBlockLowlight$1 } from "@tiptap/extension-code-block-lowlight";
|
|
24
|
+
import { Emoji, Emoji as Emoji$1 } from "@tiptap/extension-emoji";
|
|
25
|
+
import { HardBreak, HardBreak as HardBreak$1 } from "@tiptap/extension-hard-break";
|
|
26
|
+
import { HorizontalRule, HorizontalRule as HorizontalRule$1 } from "@tiptap/extension-horizontal-rule";
|
|
27
|
+
import { ListItem, ListItem as ListItem$1 } from "@tiptap/extension-list-item";
|
|
28
|
+
import { Mathematics, Mathematics as Mathematics$1 } from "@tiptap/extension-mathematics";
|
|
29
|
+
import { TaskList, TaskList as TaskList$1 } from "@tiptap/extension-task-list";
|
|
30
|
+
import { Text, Text as Text$1 } from "@tiptap/extension-text";
|
|
31
|
+
import { TextAlign, TextAlign as TextAlign$1 } from "@tiptap/extension-text-align";
|
|
22
32
|
import { all, createLowlight } from "lowlight";
|
|
23
33
|
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
24
34
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
@@ -33,13 +43,14 @@ import { convertEmuToPixels, encodeBase64 } from "@office-open/core";
|
|
|
33
43
|
* layout (paged.js multi-column) is a future concern; the node preserves the
|
|
34
44
|
* break losslessly regardless.
|
|
35
45
|
*
|
|
36
|
-
* The DOCX payload (`{ columnBreak: true }`) is inlined in DocxManager — a
|
|
37
|
-
* one-liner with no per-node variance, so no extension helper for it.
|
|
38
|
-
*
|
|
39
46
|
* `setColumnBreak` only inserts the atom (no paragraph split): a column break
|
|
40
47
|
* does not start a new page, and there is no column layout to reflow yet, so
|
|
41
48
|
* the node is purely for round-trip fidelity until multi-column lands.
|
|
42
49
|
*/
|
|
50
|
+
const parseDocxInline$4 = {
|
|
51
|
+
match: (child) => "columnBreak" in child,
|
|
52
|
+
convert: () => ({ type: "columnBreak" })
|
|
53
|
+
};
|
|
43
54
|
const ColumnBreak = Node.create({
|
|
44
55
|
name: "columnBreak",
|
|
45
56
|
inline: true,
|
|
@@ -54,6 +65,7 @@ const ColumnBreak = Node.create({
|
|
|
54
65
|
style: "break-after:column"
|
|
55
66
|
}];
|
|
56
67
|
},
|
|
68
|
+
parseDocxInline: parseDocxInline$4,
|
|
57
69
|
addCommands() {
|
|
58
70
|
return { setColumnBreak: () => ({ commands }) => commands.insertContent({ type: "columnBreak" }) };
|
|
59
71
|
}
|
|
@@ -146,6 +158,119 @@ const FormattingMarks = Extension.create({
|
|
|
146
158
|
}
|
|
147
159
|
});
|
|
148
160
|
//#endregion
|
|
161
|
+
//#region src/extensions/list-aggregator.ts
|
|
162
|
+
/** Classify a paragraph as a list item, or null if it isn't one. */
|
|
163
|
+
function detectList(para, ctx) {
|
|
164
|
+
const p = para;
|
|
165
|
+
const numbering = p.numbering;
|
|
166
|
+
const bullet = p.bullet;
|
|
167
|
+
let kind;
|
|
168
|
+
let level;
|
|
169
|
+
let reference;
|
|
170
|
+
let start;
|
|
171
|
+
if (numbering) {
|
|
172
|
+
reference = numbering.reference;
|
|
173
|
+
level = numbering.level ?? 0;
|
|
174
|
+
const cfg = reference ? ctx.numberingLookup?.get(reference) : void 0;
|
|
175
|
+
if (cfg && cfg.format && cfg.format !== "bullet") {
|
|
176
|
+
kind = "ordered";
|
|
177
|
+
start = cfg.start;
|
|
178
|
+
} else kind = "bullet";
|
|
179
|
+
} else if (bullet) {
|
|
180
|
+
kind = "bullet";
|
|
181
|
+
level = bullet.level ?? 0;
|
|
182
|
+
} else return null;
|
|
183
|
+
const first = p.children?.[0];
|
|
184
|
+
return {
|
|
185
|
+
kind: isTaskCheckbox(first) ? "task" : kind,
|
|
186
|
+
level,
|
|
187
|
+
reference,
|
|
188
|
+
start,
|
|
189
|
+
checked: readCheckboxState(first)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** Resolve a list-item paragraph to a Tiptap paragraph/heading node, stripping
|
|
193
|
+
* the list marker (bullet/numbering) and the leading task checkbox — those are
|
|
194
|
+
* expressed at the list/item level, not inside the paragraph. */
|
|
195
|
+
function resolveListItemParagraph(para, info, ctx) {
|
|
196
|
+
const resolved = typeof para === "string" ? { text: para } : para;
|
|
197
|
+
const headingLevel = detectHeadingLevel(resolved, ctx.styles);
|
|
198
|
+
return buildTextBlock(headingLevel ? "heading" : "paragraph", resolved, ctx, headingLevel, info.kind === "task" ? stripTaskCheckbox(resolved) : resolved);
|
|
199
|
+
}
|
|
200
|
+
/** Return a copy of `para` with its leading docen-task checkbox SDT removed. */
|
|
201
|
+
function stripTaskCheckbox(para) {
|
|
202
|
+
const children = para.children;
|
|
203
|
+
if (Array.isArray(children) && children.length > 0 && isTaskCheckbox(children[0])) return {
|
|
204
|
+
...para,
|
|
205
|
+
children: children.slice(1)
|
|
206
|
+
};
|
|
207
|
+
return para;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Rebuild nested Tiptap lists from a flat run of list paragraphs. Stack-based:
|
|
211
|
+
* each frame is an active list at a given depth; the `key` (level:type:
|
|
212
|
+
* reference) decides whether a paragraph continues the top list, starts a nested
|
|
213
|
+
* list, or splits off a new sibling list.
|
|
214
|
+
*/
|
|
215
|
+
function buildListTree(group, ctx) {
|
|
216
|
+
const topLevel = [];
|
|
217
|
+
const stack = [];
|
|
218
|
+
for (const { para, info } of group) {
|
|
219
|
+
const listType = info.kind === "ordered" ? "orderedList" : info.kind === "task" ? "taskList" : "bulletList";
|
|
220
|
+
const itemType = info.kind === "task" ? "taskItem" : "listItem";
|
|
221
|
+
const key = `${info.level}:${listType}:${info.reference ?? ""}`;
|
|
222
|
+
while (stack.length > 0) {
|
|
223
|
+
const top = stack[stack.length - 1];
|
|
224
|
+
if (top.level > info.level || top.level === info.level && top.key !== key) {
|
|
225
|
+
stack.pop();
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
const newItem = {
|
|
231
|
+
type: itemType,
|
|
232
|
+
content: [resolveListItemParagraph(para, info, ctx)]
|
|
233
|
+
};
|
|
234
|
+
if (itemType === "taskItem") newItem.attrs = { checked: info.checked };
|
|
235
|
+
const top = stack[stack.length - 1];
|
|
236
|
+
if (top && top.level === info.level && top.key === key) {
|
|
237
|
+
top.listNode.content.push(newItem);
|
|
238
|
+
top.currentItem = newItem;
|
|
239
|
+
} else {
|
|
240
|
+
const newList = {
|
|
241
|
+
type: listType,
|
|
242
|
+
content: [newItem]
|
|
243
|
+
};
|
|
244
|
+
const listAttrs = {};
|
|
245
|
+
if (listType === "orderedList" && info.level === 0 && typeof info.start === "number" && info.start !== 1) listAttrs.start = info.start;
|
|
246
|
+
if (info.reference) listAttrs.numbering = info.reference;
|
|
247
|
+
if (Object.keys(listAttrs).length > 0) newList.attrs = listAttrs;
|
|
248
|
+
if (top) top.currentItem.content.push(newList);
|
|
249
|
+
else topLevel.push(newList);
|
|
250
|
+
stack.push({
|
|
251
|
+
level: info.level,
|
|
252
|
+
key,
|
|
253
|
+
listNode: newList,
|
|
254
|
+
currentItem: newItem
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return topLevel;
|
|
259
|
+
}
|
|
260
|
+
const parseDocxAggregator = {
|
|
261
|
+
belongs: (para, ctx) => detectList(para, ctx) != null,
|
|
262
|
+
build: (group, ctx) => {
|
|
263
|
+
return buildListTree(group.map((para) => ({
|
|
264
|
+
para,
|
|
265
|
+
info: detectList(para, ctx)
|
|
266
|
+
})).filter((x) => x.info != null), ctx);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
const ListAggregator = Extension.create({
|
|
270
|
+
name: "listAggregator",
|
|
271
|
+
parseDocxAggregator
|
|
272
|
+
});
|
|
273
|
+
//#endregion
|
|
149
274
|
//#region src/extensions/page-break.ts
|
|
150
275
|
/**
|
|
151
276
|
* PageBreak — inline atom node for DOCX page breaks (`<w:br w:type="page"/>`).
|
|
@@ -166,6 +291,10 @@ const FormattingMarks = Extension.create({
|
|
|
166
291
|
* the paginator's `forcesPageBreakAfter` moves it to the next page (matching
|
|
167
292
|
* Word's Ctrl+Enter behavior).
|
|
168
293
|
*/
|
|
294
|
+
const parseDocxInline$3 = {
|
|
295
|
+
match: (child) => "pageBreak" in child,
|
|
296
|
+
convert: () => ({ type: "pageBreak" })
|
|
297
|
+
};
|
|
169
298
|
const PageBreak = Node.create({
|
|
170
299
|
name: "pageBreak",
|
|
171
300
|
inline: true,
|
|
@@ -180,6 +309,7 @@ const PageBreak = Node.create({
|
|
|
180
309
|
style: "break-after:page"
|
|
181
310
|
}];
|
|
182
311
|
},
|
|
312
|
+
parseDocxInline: parseDocxInline$3,
|
|
183
313
|
addProseMirrorPlugins() {
|
|
184
314
|
return [new Plugin({ appendTransaction: (transactions, _oldState, newState) => {
|
|
185
315
|
if (!transactions.some((tr) => tr.docChanged)) return null;
|
|
@@ -396,6 +526,10 @@ const SectionBreak = Extension.create({
|
|
|
396
526
|
* marks where a tab leader (e.g. a TOC's dotted leader) renders. compile turns it
|
|
397
527
|
* back into `{ tab: true }`.
|
|
398
528
|
*/
|
|
529
|
+
const parseDocxInline$2 = {
|
|
530
|
+
match: (child) => "tab" in child,
|
|
531
|
+
convert: () => ({ type: "tab" })
|
|
532
|
+
};
|
|
399
533
|
const Tab = Node.create({
|
|
400
534
|
name: "tab",
|
|
401
535
|
group: "inline",
|
|
@@ -409,7 +543,8 @@ const Tab = Node.create({
|
|
|
409
543
|
class: "docx-tab",
|
|
410
544
|
contenteditable: "false"
|
|
411
545
|
}];
|
|
412
|
-
}
|
|
546
|
+
},
|
|
547
|
+
parseDocxInline: parseDocxInline$2
|
|
413
548
|
});
|
|
414
549
|
//#endregion
|
|
415
550
|
//#region src/extensions/toc-field.ts
|
|
@@ -431,14 +566,44 @@ const Tab = Node.create({
|
|
|
431
566
|
* whose content-less runs (fldChar begin/separate/end) office-open parses as
|
|
432
567
|
* `null`. As opaque passthrough those nulls survived verbatim to
|
|
433
568
|
* `generateDocument`, where office-open's `stringifyRunInline(null).break`
|
|
434
|
-
* crashed.
|
|
435
|
-
* nulls (the
|
|
436
|
-
* and the generate path never sees a null — no
|
|
437
|
-
*
|
|
438
|
-
* DOCX serialization is inlined in DocxManager (resolve/compile read/write
|
|
439
|
-
* `attrs.options` + the entry content directly), so no renderDocx/parseDocx is
|
|
440
|
-
* needed here — the same pattern as the details extension.
|
|
569
|
+
* crashed. The `parseDocxBlock` rule resolves the entries through the shared
|
|
570
|
+
* block-stream path, which drops the nulls (the `child !== null` guard), so
|
|
571
|
+
* compile rebuilds clean entries and the generate path never sees a null — no
|
|
572
|
+
* office-open change required.
|
|
441
573
|
*/
|
|
574
|
+
/**
|
|
575
|
+
* Declarative block parse rule: recognize a table of contents SectionChild and
|
|
576
|
+
* rebuild it as an editable `tocField` container. DocxManager dispatches every
|
|
577
|
+
* SectionChild through this rule before the paragraph/passthrough fallbacks. */
|
|
578
|
+
const parseDocxBlock = {
|
|
579
|
+
match: (child) => "toc" in child,
|
|
580
|
+
convert: (child, ctx) => resolveToc(child.toc, ctx)
|
|
581
|
+
};
|
|
582
|
+
/** Resolve a table of contents into an editable `tocField` container:
|
|
583
|
+
* `attrs.options` carries the field switches, `content` is the entry
|
|
584
|
+
* paragraphs. Each entry's inner HYPERLINK field has content-less runs that
|
|
585
|
+
* office-open parses as `null`; resolving the entries through the shared
|
|
586
|
+
* block-stream path drops those nulls (the `stringifyRunInline(null).break`
|
|
587
|
+
* crash). When `entries` is absent/empty (a fresh, unrendered TOC), keep the
|
|
588
|
+
* node valid for `content: "block+"` with a placeholder empty paragraph. */
|
|
589
|
+
function resolveToc(toc, ctx) {
|
|
590
|
+
const { entries, ...options } = toc;
|
|
591
|
+
const content = [];
|
|
592
|
+
for (const entry of entries ?? []) {
|
|
593
|
+
const node = ctx.resolveBlock(entry);
|
|
594
|
+
if (!node) continue;
|
|
595
|
+
if (Array.isArray(node)) content.push(...node);
|
|
596
|
+
else content.push(node);
|
|
597
|
+
}
|
|
598
|
+
if (content.length === 0) content.push({ type: "paragraph" });
|
|
599
|
+
const node = {
|
|
600
|
+
type: "tocField",
|
|
601
|
+
content
|
|
602
|
+
};
|
|
603
|
+
const cleanOptions = cleanAttrs(options);
|
|
604
|
+
if (Object.keys(cleanOptions).length > 0) node.attrs = { options: cleanOptions };
|
|
605
|
+
return node;
|
|
606
|
+
}
|
|
442
607
|
const TocField = Node.create({
|
|
443
608
|
name: "tocField",
|
|
444
609
|
group: "block",
|
|
@@ -455,6 +620,119 @@ const TocField = Node.create({
|
|
|
455
620
|
{ class: "docx-toc" },
|
|
456
621
|
0
|
|
457
622
|
];
|
|
623
|
+
},
|
|
624
|
+
parseDocxBlock
|
|
625
|
+
});
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/extensions/track-change.ts
|
|
628
|
+
/**
|
|
629
|
+
* Track Changes marks (Word revision tracking).
|
|
630
|
+
*
|
|
631
|
+
* OOXML records inline revisions as `<w:ins>` / `<w:del>` containers wrapping
|
|
632
|
+
* runs (carrying w:author / w:date / w:id metadata). office-open models these
|
|
633
|
+
* as `ParagraphChild.insertion` / `.deletion` — `{ id, author, date, children }`
|
|
634
|
+
* — structurally identical to `hyperlink` (an inline container with attrs +
|
|
635
|
+
* child runs). docen mirrors that as two Tiptap marks applied to the contained
|
|
636
|
+
* text:
|
|
637
|
+
*
|
|
638
|
+
* - `insertion` — text added by a reviewer (Word renders colored + underlined)
|
|
639
|
+
* - `deletion` — text marked for removal (Word renders colored + strikethrough;
|
|
640
|
+
* the text stays visible until the change is accepted/rejected)
|
|
641
|
+
*
|
|
642
|
+
* Container-level, NOT rPr-level: like `link`, these wrap child runs, so resolve
|
|
643
|
+
* is declared via parseDocxInline (resolveTrackedChange) and compile via
|
|
644
|
+
* compileTrackedChangeRun (compileTextRun pushes `{insertion|deletion:{...}}`).
|
|
645
|
+
* They do NOT use the renderDocx/parseDocx mark hook — that is for rPr-level
|
|
646
|
+
* marks like strike/bold. The attrs (id/author/date) are round-tripped via
|
|
647
|
+
* resolve/compile and kept out of HTML (`rendered:false`): HTML paste loses
|
|
648
|
+
* the metadata but keeps the native `<ins>`/`<del>` tag (the class is a CSS
|
|
649
|
+
* hook); DOCX round-trip is byte-faithful.
|
|
650
|
+
*
|
|
651
|
+
* HTML tags: `<ins>`/`<del>` are HTML's native editorial-revision elements, so
|
|
652
|
+
* they are used instead of a bare span — semantic, accessible, and matching
|
|
653
|
+
* browser defaults (underlined / struck-through). `<ins>` has no competing
|
|
654
|
+
* mark, so both the classed tag and a bare pasted `<ins>` are claimed. `<del>`
|
|
655
|
+
* is also matched by the base Strike mark, so only the classed tag is claimed
|
|
656
|
+
* to avoid shadowing strike on a bare `<del>`.
|
|
657
|
+
*
|
|
658
|
+
* P1 scope: render + round-trip only. accept/reject commands, nested
|
|
659
|
+
* revisions, block-level revisions, and format-revision (markChange) are out
|
|
660
|
+
* of scope (office-open parses inline w:ins/w:del only).
|
|
661
|
+
*/
|
|
662
|
+
const trackChangeAttrs = () => ({
|
|
663
|
+
id: {
|
|
664
|
+
default: null,
|
|
665
|
+
rendered: false
|
|
666
|
+
},
|
|
667
|
+
author: {
|
|
668
|
+
default: null,
|
|
669
|
+
rendered: false
|
|
670
|
+
},
|
|
671
|
+
date: {
|
|
672
|
+
default: null,
|
|
673
|
+
rendered: false
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
/** ParagraphChild `{ insertion|deletion: {...} }` → text[] carrying the mark.
|
|
677
|
+
* Mirrors the old DocxManager.resolveTrackedChange: recurse the container's
|
|
678
|
+
* runs via ctx, merge adjacent text, then stamp every text node with the
|
|
679
|
+
* revision mark alongside any existing rPr marks. Returns null for an empty
|
|
680
|
+
* container. */
|
|
681
|
+
function resolveTrackedChange(opts, type, ctx) {
|
|
682
|
+
const content = ctx.resolveInlineChildren((opts.children ?? []).map((c) => c));
|
|
683
|
+
if (content.length === 0) return null;
|
|
684
|
+
const merged = mergeTextNodes(content);
|
|
685
|
+
const mark = {
|
|
686
|
+
type,
|
|
687
|
+
attrs: {
|
|
688
|
+
id: opts.id ?? null,
|
|
689
|
+
author: opts.author ?? null,
|
|
690
|
+
date: opts.date ?? null
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
for (const node of merged) if (node.type === "text") node.marks = [...node.marks ?? [], mark];
|
|
694
|
+
return merged;
|
|
695
|
+
}
|
|
696
|
+
const Insertion = Mark.create({
|
|
697
|
+
name: "insertion",
|
|
698
|
+
inclusive: false,
|
|
699
|
+
addAttributes() {
|
|
700
|
+
return trackChangeAttrs();
|
|
701
|
+
},
|
|
702
|
+
renderHTML() {
|
|
703
|
+
return [
|
|
704
|
+
"ins",
|
|
705
|
+
{ class: "docen-insertion" },
|
|
706
|
+
0
|
|
707
|
+
];
|
|
708
|
+
},
|
|
709
|
+
parseHTML() {
|
|
710
|
+
return [{ tag: "ins.docen-insertion" }, { tag: "ins" }];
|
|
711
|
+
},
|
|
712
|
+
parseDocxInline: {
|
|
713
|
+
match: (child) => "insertion" in child,
|
|
714
|
+
convert: (child, ctx) => resolveTrackedChange(child.insertion, "insertion", ctx)
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
const Deletion = Mark.create({
|
|
718
|
+
name: "deletion",
|
|
719
|
+
inclusive: false,
|
|
720
|
+
addAttributes() {
|
|
721
|
+
return trackChangeAttrs();
|
|
722
|
+
},
|
|
723
|
+
renderHTML() {
|
|
724
|
+
return [
|
|
725
|
+
"del",
|
|
726
|
+
{ class: "docen-deletion" },
|
|
727
|
+
0
|
|
728
|
+
];
|
|
729
|
+
},
|
|
730
|
+
parseHTML() {
|
|
731
|
+
return [{ tag: "del.docen-deletion" }];
|
|
732
|
+
},
|
|
733
|
+
parseDocxInline: {
|
|
734
|
+
match: (child) => "deletion" in child,
|
|
735
|
+
convert: (child, ctx) => resolveTrackedChange(child.deletion, "deletion", ctx)
|
|
458
736
|
}
|
|
459
737
|
});
|
|
460
738
|
//#endregion
|
|
@@ -698,6 +976,13 @@ const attrWpgGroup = () => ({
|
|
|
698
976
|
}
|
|
699
977
|
}
|
|
700
978
|
});
|
|
979
|
+
const parseDocxInline$1 = {
|
|
980
|
+
match: (child) => "wpgGroup" in child,
|
|
981
|
+
convert: (child) => ({
|
|
982
|
+
type: "wpgGroup",
|
|
983
|
+
attrs: { wpgGroup: child.wpgGroup }
|
|
984
|
+
})
|
|
985
|
+
};
|
|
701
986
|
const WpgGroup = Node.create({
|
|
702
987
|
name: "wpgGroup",
|
|
703
988
|
group: "inline",
|
|
@@ -718,7 +1003,8 @@ const WpgGroup = Node.create({
|
|
|
718
1003
|
`height:${h}px`
|
|
719
1004
|
].join(";"), floatAnchorScope(wpg.floating) === "paragraph" ? { "data-float-anchor": "paragraph" } : void 0);
|
|
720
1005
|
return renderGroup(wpg, w, h);
|
|
721
|
-
}
|
|
1006
|
+
},
|
|
1007
|
+
parseDocxInline: parseDocxInline$1
|
|
722
1008
|
});
|
|
723
1009
|
//#endregion
|
|
724
1010
|
//#region src/extensions/wps-shape.ts
|
|
@@ -745,6 +1031,48 @@ const attrWpsShape = () => ({
|
|
|
745
1031
|
}
|
|
746
1032
|
}
|
|
747
1033
|
});
|
|
1034
|
+
/** ParagraphChild `{ wpsShape: {...} }` → wpsShape node. Mirrors the old
|
|
1035
|
+
* DocxManager wpsShape branch: the shape's text body (children) becomes PM
|
|
1036
|
+
* content (one node per paragraph); geometry/styling ride on attrs.wpsShape.
|
|
1037
|
+
* Each paragraph's defRPr (para.run) is merged into its runs then dropped — it
|
|
1038
|
+
* is the box's default run-properties, not the ¶-mark rPr (see inline note). */
|
|
1039
|
+
function resolveWpsShape(ws, ctx) {
|
|
1040
|
+
const content = [];
|
|
1041
|
+
if (ws?.children) for (const para of ws.children) {
|
|
1042
|
+
if (typeof para !== "object" || para === null) {
|
|
1043
|
+
const node = ctx.resolveParagraph(para);
|
|
1044
|
+
if (node) content.push(node);
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
const defRPr = para.run ?? {};
|
|
1048
|
+
const children = Array.isArray(para.children) ? para.children.map((c) => typeof c !== "object" || c === null ? {
|
|
1049
|
+
...defRPr,
|
|
1050
|
+
text: c
|
|
1051
|
+
} : {
|
|
1052
|
+
...defRPr,
|
|
1053
|
+
...c
|
|
1054
|
+
}) : void 0;
|
|
1055
|
+
const node = ctx.resolveParagraph({
|
|
1056
|
+
...para,
|
|
1057
|
+
run: void 0,
|
|
1058
|
+
...children ? { children } : {}
|
|
1059
|
+
});
|
|
1060
|
+
if (node) content.push(node);
|
|
1061
|
+
}
|
|
1062
|
+
if (content.length === 0) content.push({ type: "paragraph" });
|
|
1063
|
+
const { children: _omit, ...geometry } = ws ?? {};
|
|
1064
|
+
const node = {
|
|
1065
|
+
type: "wpsShape",
|
|
1066
|
+
content
|
|
1067
|
+
};
|
|
1068
|
+
const cleanGeometry = cleanAttrs(geometry);
|
|
1069
|
+
if (Object.keys(cleanGeometry).length > 0) node.attrs = { wpsShape: cleanGeometry };
|
|
1070
|
+
return node;
|
|
1071
|
+
}
|
|
1072
|
+
const parseDocxInline = {
|
|
1073
|
+
match: (child) => "wpsShape" in child,
|
|
1074
|
+
convert: (child, ctx) => resolveWpsShape(child.wpsShape, ctx)
|
|
1075
|
+
};
|
|
748
1076
|
const WpsShape = Node.create({
|
|
749
1077
|
name: "wpsShape",
|
|
750
1078
|
group: "inline",
|
|
@@ -778,7 +1106,8 @@ const WpsShape = Node.create({
|
|
|
778
1106
|
0
|
|
779
1107
|
]
|
|
780
1108
|
];
|
|
781
|
-
}
|
|
1109
|
+
},
|
|
1110
|
+
parseDocxInline
|
|
782
1111
|
});
|
|
783
1112
|
//#endregion
|
|
784
1113
|
//#region src/extensions/extensions.ts
|
|
@@ -800,8 +1129,8 @@ const tiptapNodeExtensions = [
|
|
|
800
1129
|
ListItem,
|
|
801
1130
|
CodeBlock.configure({ lowlight: createLowlight(all) }),
|
|
802
1131
|
Details,
|
|
803
|
-
|
|
804
|
-
|
|
1132
|
+
DetailsSummaryBase,
|
|
1133
|
+
DetailsContentBase,
|
|
805
1134
|
Emoji,
|
|
806
1135
|
HorizontalRule,
|
|
807
1136
|
Image.configure({ inline: true }),
|
|
@@ -814,14 +1143,16 @@ const tiptapNodeExtensions = [
|
|
|
814
1143
|
TableCell,
|
|
815
1144
|
TableHeader,
|
|
816
1145
|
TaskList,
|
|
817
|
-
|
|
1146
|
+
TaskItemBase,
|
|
818
1147
|
Heading,
|
|
819
1148
|
TextAlign.configure({ types: ["heading", "paragraph"] })
|
|
820
1149
|
];
|
|
821
1150
|
const tiptapMarkExtensions = [
|
|
822
1151
|
Bold,
|
|
823
1152
|
Code,
|
|
1153
|
+
Deletion,
|
|
824
1154
|
Highlight,
|
|
1155
|
+
Insertion,
|
|
825
1156
|
Italic,
|
|
826
1157
|
Link,
|
|
827
1158
|
Strike,
|
|
@@ -833,7 +1164,8 @@ const tiptapMarkExtensions = [
|
|
|
833
1164
|
const docxExtensions = [
|
|
834
1165
|
...tiptapNodeExtensions,
|
|
835
1166
|
...tiptapMarkExtensions,
|
|
836
|
-
FormattingMarks
|
|
1167
|
+
FormattingMarks,
|
|
1168
|
+
ListAggregator
|
|
837
1169
|
];
|
|
838
1170
|
const DocxKit = Extension.create({
|
|
839
1171
|
name: "docxKit",
|
|
@@ -863,4 +1195,4 @@ const DocxKit = Extension.create({
|
|
|
863
1195
|
}
|
|
864
1196
|
});
|
|
865
1197
|
//#endregion
|
|
866
|
-
export {
|
|
1198
|
+
export { parseDocxBlock as A, FormattingMarks as B, renderWpsText as C, Deletion as D, wpsShapeStyles as E, Passthrough as F, parseDocxInline$4 as H, PageBreak as I, parseDocxInline$3 as L, parseDocxInline$2 as M, SectionBreak as N, Insertion as O, InlinePassthrough as P, ListAggregator as R, renderWpsInterior as S, wpsRotationVert as T, ColumnBreak as V, tiptapNodeExtensions as _, CodeBlockLowlight$1 as a, WpgGroup as b, HardBreak$1 as c, Mathematics$1 as d, TaskList$1 as f, tiptapMarkExtensions as g, docxExtensions as h, Node as i, Tab as j, TocField as k, HorizontalRule$1 as l, TextAlign$1 as m, Extension as n, DocxKit as o, Text$1 as p, Mark as r, Emoji$1 as s, Editor as t, ListItem$1 as u, WpsShape as v, wpsInnerStyle as w, parseDocxInline$1 as x, parseDocxInline as y, parseDocxAggregator as z };
|
package/dist/core.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as JSONContent, i as Extensions, n as Editor, o as Mark, r as Extension, s as Node, t as AnyExtension,
|
|
1
|
+
import { a as JSONContent, i as Extensions, n as Editor, o as Mark, r as Extension, s as Node, t as AnyExtension, y as docxExtensions } from "./core-BqyLL84S.mjs";
|
|
2
2
|
export { type AnyExtension, Editor, Extension, type Extensions, type JSONContent, Mark, Node, docxExtensions };
|
package/dist/core.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as docxExtensions, i as Node, n as Extension, r as Mark, t as Editor } from "./core-wNNPJiKr.mjs";
|
|
2
2
|
export { Editor, Extension, Mark, Node, docxExtensions };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ParseBlockRule } from "./extensions/types.mjs";
|
|
2
|
+
import { DetailsContent as DetailsContentBase, DetailsSummary as DetailsSummaryBase } from "@tiptap/extension-details";
|
|
3
|
+
|
|
4
|
+
//#region src/extensions/details.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Details extension — owns the DOCX expression of a collapsible details block.
|
|
7
|
+
*
|
|
8
|
+
* DOCX has no native collapsible region, but a block-level group-SDT is a
|
|
9
|
+
* reversible container. The details maps to one group-SDT tagged "docen-
|
|
10
|
+
* details"; the summary paragraph is marked with a fixed style so resolve can
|
|
11
|
+
* split it back out from the content paragraphs. Structure round-trips fully
|
|
12
|
+
* (summary + content); Word shows it expanded (no collapse) — an inherent
|
|
13
|
+
* DOCX limitation, not data loss.
|
|
14
|
+
*
|
|
15
|
+
* The `parseDocxBlock` rule (below) recognizes a details group-SDT during
|
|
16
|
+
* resolve and rebuilds the details/detailsSummary/detailsContent nodes; the
|
|
17
|
+
* extensions themselves carry no DOCX attrs of their own.
|
|
18
|
+
*/
|
|
19
|
+
/** SDT tag marking a details group content control. */
|
|
20
|
+
declare const DETAILS_TAG = "docen-details";
|
|
21
|
+
/** Paragraph style marking the summary line within a details group-SDT. */
|
|
22
|
+
declare const DETAILS_SUMMARY_STYLE = "DocenDetailsSummary";
|
|
23
|
+
/**
|
|
24
|
+
* Declarative block parse rule: recognize a group-SDT tagged "docen-details"
|
|
25
|
+
* and rebuild it as a details node (summary + content). DocxManager dispatches
|
|
26
|
+
* every SectionChild through this rule before the paragraph/passthrough
|
|
27
|
+
* fallbacks; a non-details SDT falls through to passthrough. */
|
|
28
|
+
declare const parseDocxBlock: ParseBlockRule;
|
|
29
|
+
declare const Details$1: import("@tiptap/core").Node<import("@tiptap/extension-details").DetailsOptions, any>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DetailsSummaryBase as a, DetailsContentBase as i, DETAILS_TAG as n, parseDocxBlock as o, Details$1 as r, DETAILS_SUMMARY_STYLE as t };
|
package/dist/editor.d.mts
CHANGED
package/dist/editor.mjs
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export { BLOCKQUOTE_BORDER, BLOCKQUOTE_INDENT_LEFT, Blockquote, applyBlockquoteStyle };
|
|
1
|
+
import { a as parseDocxAggregator, i as applyBlockquoteStyle, n as BLOCKQUOTE_INDENT_LEFT, r as Blockquote, t as BLOCKQUOTE_BORDER } from "../blockquote-D-1aSxEn.mjs";
|
|
2
|
+
export { BLOCKQUOTE_BORDER, BLOCKQUOTE_INDENT_LEFT, Blockquote, applyBlockquoteStyle, parseDocxAggregator };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cleanAttrs } from "../converters/styles.mjs";
|
|
2
|
+
import { Blockquote as Blockquote$1 } from "@tiptap/extension-blockquote";
|
|
2
3
|
//#region src/extensions/blockquote.ts
|
|
3
4
|
/**
|
|
4
5
|
* Blockquote extension — owns the DOCX expression of a blockquote.
|
|
@@ -27,5 +28,52 @@ function applyBlockquoteStyle(paraObj) {
|
|
|
27
28
|
left: BLOCKQUOTE_BORDER
|
|
28
29
|
};
|
|
29
30
|
}
|
|
31
|
+
/** Classify a paragraph as a blockquote member by its signature (left indent
|
|
32
|
+
* + left border). compile stamps this via applyBlockquoteStyle; this is the
|
|
33
|
+
* reverse predicate. Pure: reads only the paragraph opts. */
|
|
34
|
+
function detectBlockquote(para) {
|
|
35
|
+
const p = para;
|
|
36
|
+
const indent = p.indent;
|
|
37
|
+
const border = p.border;
|
|
38
|
+
if (!indent || indent.left !== 720) return false;
|
|
39
|
+
const bl = border?.left;
|
|
40
|
+
if (!bl) return false;
|
|
41
|
+
const sig = BLOCKQUOTE_BORDER;
|
|
42
|
+
return bl.style === sig.style && bl.size === sig.size && bl.space === sig.space && bl.color === sig.color;
|
|
43
|
+
}
|
|
44
|
+
/** Rebuild a blockquote node from a run of signature-carrying paragraphs,
|
|
45
|
+
* stripping the indent/border signature so child paragraphs render clean. */
|
|
46
|
+
function buildBlockquote(group, ctx) {
|
|
47
|
+
const content = [];
|
|
48
|
+
for (const para of group) {
|
|
49
|
+
const node = ctx.resolveParagraph(para);
|
|
50
|
+
const attrs = node.attrs;
|
|
51
|
+
if (attrs) {
|
|
52
|
+
if (attrs.indent) {
|
|
53
|
+
const indent = { ...attrs.indent };
|
|
54
|
+
delete indent.left;
|
|
55
|
+
attrs.indent = Object.keys(indent).length > 0 ? indent : void 0;
|
|
56
|
+
}
|
|
57
|
+
if (attrs.border) {
|
|
58
|
+
const border = { ...attrs.border };
|
|
59
|
+
delete border.left;
|
|
60
|
+
attrs.border = Object.keys(border).length > 0 ? border : void 0;
|
|
61
|
+
}
|
|
62
|
+
const cleaned = cleanAttrs(attrs);
|
|
63
|
+
if (Object.keys(cleaned).length > 0) node.attrs = cleaned;
|
|
64
|
+
else delete node.attrs;
|
|
65
|
+
}
|
|
66
|
+
content.push(node);
|
|
67
|
+
}
|
|
68
|
+
return [{
|
|
69
|
+
type: "blockquote",
|
|
70
|
+
content
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
const parseDocxAggregator = {
|
|
74
|
+
belongs: (para) => detectBlockquote(para),
|
|
75
|
+
build: (group, ctx) => buildBlockquote(group, ctx)
|
|
76
|
+
};
|
|
77
|
+
const Blockquote = Blockquote$1.extend({ parseDocxAggregator });
|
|
30
78
|
//#endregion
|
|
31
|
-
export { BLOCKQUOTE_BORDER, BLOCKQUOTE_INDENT_LEFT, Blockquote, applyBlockquoteStyle };
|
|
79
|
+
export { BLOCKQUOTE_BORDER, BLOCKQUOTE_INDENT_LEFT, Blockquote, applyBlockquoteStyle, parseDocxAggregator };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CodeBlock, renderDocx };
|
|
1
|
+
import { et as CodeBlock, nt as renderDocx, tt as parseDocxParagraph } from "../core-BqyLL84S.mjs";
|
|
2
|
+
export { CodeBlock, parseDocxParagraph, renderDocx };
|