@docen/docx 0.3.3 → 0.3.5
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/dist/blockquote-DY80QC06.d.mts +21 -0
- package/dist/bullet-list-DF60pnSL.d.mts +12 -0
- package/dist/converters/docx.d.mts +1 -1
- package/dist/converters/docx.mjs +124 -25
- package/dist/converters/html.d.mts +1 -1
- package/dist/converters/html.mjs +1 -1
- 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/prepare.mjs +6 -9
- package/dist/{core-B8ba_FNi.mjs → core-BnF8XhVE.mjs} +180 -31
- package/dist/core-DC0_-WcE.d.mts +387 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/details-Dd5MqqmR.d.mts +17 -0
- package/dist/editor.d.mts +1 -1
- package/dist/editor.mjs +1 -1
- package/dist/extensions/blockquote.d.mts +2 -22
- package/dist/extensions/bullet-list.d.mts +2 -0
- package/dist/extensions/bullet-list.mjs +21 -0
- package/dist/extensions/code-block.d.mts +1 -18
- package/dist/extensions/column-break.d.mts +1 -30
- package/dist/extensions/column-break.mjs +1 -1
- package/dist/extensions/details.d.mts +2 -18
- package/dist/extensions/document.mjs +2 -1
- package/dist/extensions/extensions.d.mts +10 -6
- package/dist/extensions/extensions.mjs +9 -4
- package/dist/extensions/formatting-marks.d.mts +1 -37
- package/dist/extensions/formatting-marks.mjs +1 -1
- package/dist/extensions/image.d.mts +1 -1
- package/dist/extensions/image.mjs +20 -4
- package/dist/extensions/index.d.mts +15 -2
- package/dist/extensions/index.mjs +17 -2
- package/dist/extensions/link.d.mts +2 -0
- package/dist/extensions/link.mjs +56 -0
- package/dist/extensions/mention.d.mts +2 -24
- package/dist/extensions/ordered-list.d.mts +1 -24
- package/dist/extensions/ordered-list.mjs +10 -1
- package/dist/extensions/page-break.d.mts +1 -1
- package/dist/extensions/page-break.mjs +1 -1
- package/dist/extensions/paragraph.mjs +13 -0
- package/dist/extensions/passthrough.d.mts +2 -2
- package/dist/extensions/passthrough.mjs +2 -2
- package/dist/extensions/scroll.d.mts +2 -0
- package/dist/extensions/scroll.mjs +33 -0
- package/dist/extensions/section-break.d.mts +1 -43
- package/dist/extensions/section-break.mjs +1 -1
- package/dist/extensions/tab.d.mts +2 -0
- package/dist/extensions/tab.mjs +2 -0
- package/dist/extensions/task-item.d.mts +2 -26
- package/dist/extensions/tiptap.d.mts +1 -1
- package/dist/extensions/toc-field.d.mts +1 -1
- package/dist/extensions/toc-field.mjs +1 -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 +1 -1
- package/dist/extensions/wps-shape.mjs +1 -1
- package/dist/index.d.mts +31 -7
- package/dist/index.mjs +17 -2
- package/dist/link-BawPjQZR.d.mts +6 -0
- package/dist/mention-BGLzLVYw.d.mts +23 -0
- package/dist/ordered-list-DFAe-YEV.d.mts +25 -0
- package/dist/scroll-ZNeThJsJ.d.mts +18 -0
- package/dist/task-item-B0ntvQ1Y.d.mts +25 -0
- package/dist/{tiptap-pZsNPsvV.d.mts → tiptap-BKqn41uT.d.mts} +2 -2
- package/package.json +3 -3
- package/dist/core-DRaLI8nd.d.mts +0 -203
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/extensions/blockquote.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Blockquote extension — owns the DOCX expression of a blockquote.
|
|
4
|
+
*
|
|
5
|
+
* DOCX has no blockquote element; the convention is a left indent plus a left
|
|
6
|
+
* border on each contained paragraph. This module owns that signature; the
|
|
7
|
+
* DocxManager walks the blockquote's child paragraphs and applies it to each.
|
|
8
|
+
*/
|
|
9
|
+
/** Left indent (twips, ~0.5 inch) marking a blockquote. */
|
|
10
|
+
declare const BLOCKQUOTE_INDENT_LEFT = 720;
|
|
11
|
+
/** Left border marking a blockquote. */
|
|
12
|
+
declare const BLOCKQUOTE_BORDER: {
|
|
13
|
+
readonly style: "single";
|
|
14
|
+
readonly size: 18;
|
|
15
|
+
readonly space: 12;
|
|
16
|
+
readonly color: "CCCCCC";
|
|
17
|
+
};
|
|
18
|
+
/** Apply the blockquote signature (left indent + left border) to paragraph opts. */
|
|
19
|
+
declare function applyBlockquoteStyle(paraObj: Record<string, unknown>): void;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { BLOCKQUOTE_INDENT_LEFT as n, applyBlockquoteStyle as r, BLOCKQUOTE_BORDER as t };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/extensions/bullet-list.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* BulletList — carries the source DOCX abstractNum reference (when the list came
|
|
4
|
+
* from parseDOCX) so the round-trip reuses the original numbering definition
|
|
5
|
+
* (custom bullet glyph/font/indent, e.g. a Wingdings marker) instead of
|
|
6
|
+
* regenerating the default. The `numbering` attr is DOCX-only (not rendered
|
|
7
|
+
* to HTML); lists created in the editor carry null and compile to the default
|
|
8
|
+
* bullet.
|
|
9
|
+
*/
|
|
10
|
+
declare const BulletList: import("@tiptap/core").Node<import("@tiptap/extension-bullet-list").BulletListOptions, any>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { BulletList as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as JSONContent } from "../core-
|
|
1
|
+
import { a as JSONContent } from "../core-DC0_-WcE.mjs";
|
|
2
2
|
import { PrepareStep } from "./prepare.mjs";
|
|
3
3
|
import { DocumentOptions, OutputByType, OutputType, PackerOptions, parseDocument } from "@office-open/docx";
|
|
4
4
|
|
package/dist/converters/docx.mjs
CHANGED
|
@@ -120,13 +120,16 @@ var DocxManager = class {
|
|
|
120
120
|
const core = docAttrs.core ?? void 0;
|
|
121
121
|
const background = docAttrs.background ?? void 0;
|
|
122
122
|
const documentExtras = docAttrs.documentExtras ?? void 0;
|
|
123
|
+
const origNumberingConfig = docAttrs.numbering?.config ?? [];
|
|
124
|
+
const regeneratedRefs = new Set(this.numberingConfigs.map((c) => c.reference));
|
|
125
|
+
const numberingConfig = [...origNumberingConfig.filter((c) => !regeneratedRefs.has(c.reference)), ...this.numberingConfigs];
|
|
123
126
|
return {
|
|
124
127
|
sections,
|
|
125
128
|
...styles ? { styles } : {},
|
|
126
129
|
...core,
|
|
127
130
|
...background ? { background } : {},
|
|
128
131
|
...documentExtras,
|
|
129
|
-
...
|
|
132
|
+
...numberingConfig.length > 0 ? { numbering: { config: numberingConfig } } : {}
|
|
130
133
|
};
|
|
131
134
|
}
|
|
132
135
|
/** Assemble a SectionOptions from compiled children + optional layout/headers/footers. */
|
|
@@ -218,6 +221,7 @@ var DocxManager = class {
|
|
|
218
221
|
const attrs = {};
|
|
219
222
|
if (docOpts.styles) attrs.styles = docOpts.styles;
|
|
220
223
|
if (docOpts.background) attrs.background = docOpts.background;
|
|
224
|
+
if (docOpts.numbering) attrs.numbering = docOpts.numbering;
|
|
221
225
|
const core = extractCoreProperties(docOpts);
|
|
222
226
|
if (core) attrs.core = core;
|
|
223
227
|
const lastSection = sections[lastIndex];
|
|
@@ -339,6 +343,7 @@ var DocxManager = class {
|
|
|
339
343
|
compiledCells.push({
|
|
340
344
|
verticalMerge: "continue",
|
|
341
345
|
columnSpan: span.colspan,
|
|
346
|
+
...span.borders ? { borders: span.borders } : {},
|
|
342
347
|
children: [{ paragraph: "" }]
|
|
343
348
|
});
|
|
344
349
|
colIdx += span.colspan;
|
|
@@ -353,7 +358,8 @@ var DocxManager = class {
|
|
|
353
358
|
newSpans.push({
|
|
354
359
|
colStart: colIdx,
|
|
355
360
|
colspan: cs,
|
|
356
|
-
remainingRows: rs - 1
|
|
361
|
+
remainingRows: rs - 1,
|
|
362
|
+
borders: cell.borders
|
|
357
363
|
});
|
|
358
364
|
}
|
|
359
365
|
compiledCells.push(cell);
|
|
@@ -458,15 +464,20 @@ var DocxManager = class {
|
|
|
458
464
|
const items = [];
|
|
459
465
|
const isOrdered = node.type === "orderedList";
|
|
460
466
|
const isTask = node.type === "taskList";
|
|
467
|
+
const numbering = node.attrs?.numbering;
|
|
461
468
|
let ordered;
|
|
462
|
-
if (isOrdered) ordered = this.registerOrderedNumbering(node);
|
|
469
|
+
if (isOrdered && !numbering) ordered = this.registerOrderedNumbering(node);
|
|
463
470
|
for (const listItem of node.content ?? []) {
|
|
464
471
|
if (listItem.type !== "listItem" && listItem.type !== "taskItem") continue;
|
|
465
472
|
const checked = Boolean(listItem.attrs?.checked);
|
|
466
473
|
for (const child of listItem.content ?? []) if (child.type === "paragraph" || child.type === "heading") {
|
|
467
474
|
const para = child.type === "heading" ? this.compileHeadingNode(child) : this.compileParagraphNode(child);
|
|
468
475
|
const paraObj = typeof para === "string" ? { text: para } : para;
|
|
469
|
-
if (
|
|
476
|
+
if (numbering) paraObj.numbering = {
|
|
477
|
+
reference: numbering,
|
|
478
|
+
level
|
|
479
|
+
};
|
|
480
|
+
else if (ordered) paraObj.numbering = {
|
|
470
481
|
reference: ordered.reference,
|
|
471
482
|
instance: ordered.instance,
|
|
472
483
|
level
|
|
@@ -559,6 +570,17 @@ var DocxManager = class {
|
|
|
559
570
|
case "columnBreak":
|
|
560
571
|
children.push({ columnBreak: true });
|
|
561
572
|
break;
|
|
573
|
+
case "tab":
|
|
574
|
+
children.push({ tab: true });
|
|
575
|
+
break;
|
|
576
|
+
case "inlinePassthrough": {
|
|
577
|
+
const data = node.attrs?.data ?? "{}";
|
|
578
|
+
try {
|
|
579
|
+
const parsed = JSON.parse(data);
|
|
580
|
+
if (parsed) children.push(parsed);
|
|
581
|
+
} catch {}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
562
584
|
case "image": {
|
|
563
585
|
const imageRun = renderDocx$2(node);
|
|
564
586
|
if (imageRun) children.push(imageRun);
|
|
@@ -570,8 +592,18 @@ var DocxManager = class {
|
|
|
570
592
|
break;
|
|
571
593
|
}
|
|
572
594
|
case "wpsShape": {
|
|
573
|
-
const
|
|
574
|
-
|
|
595
|
+
const geometry = node.attrs?.wpsShape ?? {};
|
|
596
|
+
const body = [];
|
|
597
|
+
for (const child of node.content ?? []) {
|
|
598
|
+
const compiled = this.compileSectionChild(child);
|
|
599
|
+
if (!compiled) continue;
|
|
600
|
+
const items = Array.isArray(compiled) ? compiled : [compiled];
|
|
601
|
+
for (const it of items) if (it && typeof it === "object" && "paragraph" in it) body.push(it.paragraph);
|
|
602
|
+
}
|
|
603
|
+
children.push({ wpsShape: {
|
|
604
|
+
...geometry,
|
|
605
|
+
children: body
|
|
606
|
+
} });
|
|
575
607
|
break;
|
|
576
608
|
}
|
|
577
609
|
case "mention":
|
|
@@ -858,10 +890,7 @@ var DocxManager = class {
|
|
|
858
890
|
if (cfg && cfg.format && cfg.format !== "bullet") {
|
|
859
891
|
kind = "ordered";
|
|
860
892
|
start = cfg.start;
|
|
861
|
-
} else
|
|
862
|
-
kind = "bullet";
|
|
863
|
-
reference = void 0;
|
|
864
|
-
}
|
|
893
|
+
} else kind = "bullet";
|
|
865
894
|
} else if (bullet) {
|
|
866
895
|
kind = "bullet";
|
|
867
896
|
level = bullet.level ?? 0;
|
|
@@ -910,7 +939,10 @@ var DocxManager = class {
|
|
|
910
939
|
type: listType,
|
|
911
940
|
content: [newItem]
|
|
912
941
|
};
|
|
913
|
-
|
|
942
|
+
const listAttrs = {};
|
|
943
|
+
if (listType === "orderedList" && info.level === 0 && typeof info.start === "number" && info.start !== 1) listAttrs.start = info.start;
|
|
944
|
+
if (info.reference) listAttrs.numbering = info.reference;
|
|
945
|
+
if (Object.keys(listAttrs).length > 0) newList.attrs = listAttrs;
|
|
914
946
|
if (top) top.currentItem.content.push(newList);
|
|
915
947
|
else topLevel.push(newList);
|
|
916
948
|
stack.push({
|
|
@@ -1136,21 +1168,55 @@ var DocxManager = class {
|
|
|
1136
1168
|
return nodes;
|
|
1137
1169
|
}
|
|
1138
1170
|
resolveParagraphChild(child) {
|
|
1171
|
+
if ("tab" in child) return { type: "tab" };
|
|
1139
1172
|
if ("text" in child || "children" in child || "break" in child) return this.resolveRun(child);
|
|
1140
1173
|
if ("image" in child) return this.resolveImage(child.image);
|
|
1141
1174
|
if ("wpgGroup" in child) return {
|
|
1142
1175
|
type: "wpgGroup",
|
|
1143
1176
|
attrs: { wpgGroup: child.wpgGroup }
|
|
1144
1177
|
};
|
|
1145
|
-
if ("wpsShape" in child)
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1178
|
+
if ("wpsShape" in child) {
|
|
1179
|
+
const ws = child.wpsShape;
|
|
1180
|
+
const content = [];
|
|
1181
|
+
if (ws?.children) for (const para of ws.children) {
|
|
1182
|
+
if (typeof para !== "object" || para === null) {
|
|
1183
|
+
const node = this.resolveParagraph(para);
|
|
1184
|
+
if (node) content.push(node);
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
const defRPr = para.run ?? {};
|
|
1188
|
+
const children = Array.isArray(para.children) ? para.children.map((c) => typeof c !== "object" || c === null ? {
|
|
1189
|
+
...defRPr,
|
|
1190
|
+
text: c
|
|
1191
|
+
} : {
|
|
1192
|
+
...defRPr,
|
|
1193
|
+
...c
|
|
1194
|
+
}) : void 0;
|
|
1195
|
+
const node = this.resolveParagraph({
|
|
1196
|
+
...para,
|
|
1197
|
+
run: void 0,
|
|
1198
|
+
...children ? { children } : {}
|
|
1199
|
+
});
|
|
1200
|
+
if (node) content.push(node);
|
|
1201
|
+
}
|
|
1202
|
+
if (content.length === 0) content.push({ type: "paragraph" });
|
|
1203
|
+
const { children: _omit, ...geometry } = ws ?? {};
|
|
1204
|
+
const node = {
|
|
1205
|
+
type: "wpsShape",
|
|
1206
|
+
content
|
|
1207
|
+
};
|
|
1208
|
+
const cleanGeometry = cleanAttrs(geometry);
|
|
1209
|
+
if (Object.keys(cleanGeometry).length > 0) node.attrs = { wpsShape: cleanGeometry };
|
|
1210
|
+
return node;
|
|
1211
|
+
}
|
|
1149
1212
|
if ("sdt" in child) return this.resolveInlineSdt(child);
|
|
1150
1213
|
if ("hyperlink" in child) return this.resolveHyperlink(child.hyperlink);
|
|
1151
1214
|
if ("pageBreak" in child) return { type: "pageBreak" };
|
|
1152
1215
|
if ("columnBreak" in child) return { type: "columnBreak" };
|
|
1153
|
-
return
|
|
1216
|
+
return {
|
|
1217
|
+
type: "inlinePassthrough",
|
|
1218
|
+
attrs: { data: JSON.stringify(child) }
|
|
1219
|
+
};
|
|
1154
1220
|
}
|
|
1155
1221
|
/** Resolve an inline SDT (mention carrier; other inline SDTs unsupported). */
|
|
1156
1222
|
resolveInlineSdt(child) {
|
|
@@ -1170,15 +1236,48 @@ var DocxManager = class {
|
|
|
1170
1236
|
if (opts.break && opts.text === void 0 && !opts.children) return { type: "hardBreak" };
|
|
1171
1237
|
const text = opts.text;
|
|
1172
1238
|
if (text === void 0 && !opts.children) return null;
|
|
1173
|
-
if (opts.children
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1239
|
+
if (opts.children) {
|
|
1240
|
+
const marks = this.resolveMarks(opts);
|
|
1241
|
+
const nodes = [];
|
|
1242
|
+
let parts = [];
|
|
1243
|
+
const flushText = () => {
|
|
1244
|
+
if (parts.length > 0) {
|
|
1245
|
+
const node = {
|
|
1246
|
+
type: "text",
|
|
1247
|
+
text: parts.join("")
|
|
1248
|
+
};
|
|
1249
|
+
if (marks) node.marks = marks;
|
|
1250
|
+
nodes.push(node);
|
|
1251
|
+
parts = [];
|
|
1252
|
+
}
|
|
1181
1253
|
};
|
|
1254
|
+
for (const c of opts.children) if (typeof c === "string") parts.push(c);
|
|
1255
|
+
else if (c && typeof c === "object") {
|
|
1256
|
+
if ("pageBreak" in c) {
|
|
1257
|
+
flushText();
|
|
1258
|
+
nodes.push({ type: "pageBreak" });
|
|
1259
|
+
} else if ("columnBreak" in c) {
|
|
1260
|
+
flushText();
|
|
1261
|
+
nodes.push({ type: "columnBreak" });
|
|
1262
|
+
} else if ("break" in c) {
|
|
1263
|
+
flushText();
|
|
1264
|
+
nodes.push({ type: "hardBreak" });
|
|
1265
|
+
} else if ("tab" in c) {
|
|
1266
|
+
flushText();
|
|
1267
|
+
nodes.push({ type: "tab" });
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
flushText();
|
|
1271
|
+
if (text !== void 0) {
|
|
1272
|
+
const node = {
|
|
1273
|
+
type: "text",
|
|
1274
|
+
text,
|
|
1275
|
+
marks
|
|
1276
|
+
};
|
|
1277
|
+
nodes.push(node);
|
|
1278
|
+
}
|
|
1279
|
+
if (nodes.length === 0) return null;
|
|
1280
|
+
return nodes.length === 1 ? nodes[0] : nodes;
|
|
1182
1281
|
}
|
|
1183
1282
|
return {
|
|
1184
1283
|
type: "text",
|
|
@@ -1236,7 +1335,7 @@ var DocxManager = class {
|
|
|
1236
1335
|
type: "link",
|
|
1237
1336
|
attrs: {
|
|
1238
1337
|
href,
|
|
1239
|
-
target: "_blank",
|
|
1338
|
+
target: href.startsWith("#") ? null : "_blank",
|
|
1240
1339
|
rel: "noopener noreferrer nofollow",
|
|
1241
1340
|
class: null,
|
|
1242
1341
|
title: hyperlink.tooltip ?? null
|
package/dist/converters/html.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as docxExtensions } from "../core-BnF8XhVE.mjs";
|
|
2
2
|
import { sectionLinePitchCss, sectionMarginCss } from "../extensions/utils.mjs";
|
|
3
3
|
import { getSchema } from "@tiptap/core";
|
|
4
4
|
import { encodeBase64 } from "@office-open/core";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as docxExtensions } from "../core-BnF8XhVE.mjs";
|
|
2
2
|
import { Markdown, MarkdownManager } from "@tiptap/markdown";
|
|
3
3
|
//#region src/converters/markdown.ts
|
|
4
4
|
const markdownManager = new MarkdownManager({ extensions: [...docxExtensions, Markdown] });
|
|
@@ -67,15 +67,12 @@ async function prepareDocument(json, steps = DEFAULT_STEPS) {
|
|
|
67
67
|
}
|
|
68
68
|
async function toDataUrl(src, handler) {
|
|
69
69
|
const data = await handler(src);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
svg: "image/svg+xml",
|
|
77
|
-
webp: "image/webp"
|
|
78
|
-
}[src.split(".").pop()?.toLowerCase() ?? "png"] ?? "image/png"};base64,${encodeBase64(data)}`;
|
|
70
|
+
let mime = "image/png";
|
|
71
|
+
try {
|
|
72
|
+
const type = imageMeta(data).type;
|
|
73
|
+
if (type) mime = type === "jpg" ? "image/jpeg" : `image/${type}`;
|
|
74
|
+
} catch {}
|
|
75
|
+
return `data:${mime};base64,${encodeBase64(data)}`;
|
|
79
76
|
}
|
|
80
77
|
async function walkImages(node, handler) {
|
|
81
78
|
if (node.type === "image" && node.attrs) {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { Blockquote, Bold,
|
|
1
|
+
import { Blockquote, Bold, Code, CodeBlockLowlight, Details, DetailsContent, DetailsSummary, Emoji, HardBreak, Highlight, HorizontalRule, Italic, ListItem, Mathematics, Mention, Subscript, Superscript, TaskItem, TaskList, Text, TextAlign, Underline } from "./extensions/tiptap.mjs";
|
|
2
2
|
import "./extensions/blockquote.mjs";
|
|
3
|
+
import { BulletList } from "./extensions/bullet-list.mjs";
|
|
3
4
|
import { CodeBlock } from "./extensions/code-block.mjs";
|
|
4
5
|
import "./extensions/details.mjs";
|
|
5
6
|
import { attrNative, floatAnchorScope, floatingToStyles, normalizeColorToHex, renderRunStyles } from "./extensions/utils.mjs";
|
|
6
7
|
import { Document } from "./extensions/document.mjs";
|
|
7
8
|
import { Heading } from "./extensions/heading.mjs";
|
|
8
9
|
import { Image } from "./extensions/image.mjs";
|
|
10
|
+
import { Link } from "./extensions/link.mjs";
|
|
9
11
|
import "./extensions/mention.mjs";
|
|
10
|
-
import "./extensions/ordered-list.mjs";
|
|
12
|
+
import { OrderedList } from "./extensions/ordered-list.mjs";
|
|
11
13
|
import { Paragraph } from "./extensions/paragraph.mjs";
|
|
12
14
|
import { Strike } from "./extensions/strike.mjs";
|
|
13
15
|
import { Table } from "./extensions/table.mjs";
|
|
@@ -18,7 +20,6 @@ import "./extensions/task-item.mjs";
|
|
|
18
20
|
import { TextStyle } from "./extensions/text-style.mjs";
|
|
19
21
|
import { Editor, Extension, Mark, Node } from "@tiptap/core";
|
|
20
22
|
import { all, createLowlight } from "lowlight";
|
|
21
|
-
import { TextAlign } from "@tiptap/extension-text-align";
|
|
22
23
|
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
23
24
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
24
25
|
import { convertEmuToPixels, encodeBase64 } from "@office-open/core";
|
|
@@ -277,6 +278,46 @@ const Passthrough = Node.create({
|
|
|
277
278
|
];
|
|
278
279
|
}
|
|
279
280
|
});
|
|
281
|
+
/**
|
|
282
|
+
* InlinePassthrough — inline atom carrying an opaque inline ParagraphChild that
|
|
283
|
+
* has no native Tiptap representation (bookmarkStart/End, comment range markers,
|
|
284
|
+
* proofErr, track-change markers, …). The full ParagraphChild rides in
|
|
285
|
+
* `attrs.data` as JSON so DOCX→JSON→DOCX round-trips byte-faithful; the atom is
|
|
286
|
+
* zero-width (bookmark/range markers carry no layout box), matching Word's
|
|
287
|
+
* non-printing metadata. Mirrors the block-level Passthrough for inline children.
|
|
288
|
+
*/
|
|
289
|
+
const InlinePassthrough = Node.create({
|
|
290
|
+
name: "inlinePassthrough",
|
|
291
|
+
group: "inline",
|
|
292
|
+
inline: true,
|
|
293
|
+
atom: true,
|
|
294
|
+
addAttributes() {
|
|
295
|
+
return { data: {
|
|
296
|
+
default: "{}",
|
|
297
|
+
rendered: false,
|
|
298
|
+
parseHTML: (element) => element.getAttribute("data-inline-passthrough") ?? "{}"
|
|
299
|
+
} };
|
|
300
|
+
},
|
|
301
|
+
parseHTML() {
|
|
302
|
+
return [{ tag: "span[data-inline-passthrough]" }];
|
|
303
|
+
},
|
|
304
|
+
renderHTML({ node }) {
|
|
305
|
+
const data = node.attrs.data || "{}";
|
|
306
|
+
let bookmarkName;
|
|
307
|
+
try {
|
|
308
|
+
bookmarkName = JSON.parse(data).bookmarkStart?.name;
|
|
309
|
+
} catch {}
|
|
310
|
+
const attrs = {
|
|
311
|
+
"data-inline-passthrough": data,
|
|
312
|
+
contenteditable: "false"
|
|
313
|
+
};
|
|
314
|
+
if (bookmarkName) {
|
|
315
|
+
attrs.id = bookmarkName;
|
|
316
|
+
attrs.style = "display:inline-block;width:0;height:0;overflow:hidden;vertical-align:baseline";
|
|
317
|
+
} else attrs.style = "display:none";
|
|
318
|
+
return ["span", attrs];
|
|
319
|
+
}
|
|
320
|
+
});
|
|
280
321
|
//#endregion
|
|
281
322
|
//#region src/extensions/section-break.ts
|
|
282
323
|
/**
|
|
@@ -342,6 +383,35 @@ const SectionBreak = Extension.create({
|
|
|
342
383
|
}
|
|
343
384
|
});
|
|
344
385
|
//#endregion
|
|
386
|
+
//#region src/extensions/tab.ts
|
|
387
|
+
/**
|
|
388
|
+
* Tab — an inline atom representing a DOCX `<w:r><w:tab/></w:r>` tab character.
|
|
389
|
+
*
|
|
390
|
+
* office-open parses `<w:tab/>` as `{ tab: true }` inside a run's children. The
|
|
391
|
+
* resolve path turns that into this node so the tab is not lost: previously
|
|
392
|
+
* `<w:tab/>` was dropped, which let `mergeTextNodes` collapse a TOC entry's title
|
|
393
|
+
* and page number into adjacent text (no leader, no right alignment). The node is
|
|
394
|
+
* zero-width, non-editable, and carries no text height — measure skips it the
|
|
395
|
+
* same way it skips other inline atoms, so pagination is unaffected. It only
|
|
396
|
+
* marks where a tab leader (e.g. a TOC's dotted leader) renders. compile turns it
|
|
397
|
+
* back into `{ tab: true }`.
|
|
398
|
+
*/
|
|
399
|
+
const Tab = Node.create({
|
|
400
|
+
name: "tab",
|
|
401
|
+
group: "inline",
|
|
402
|
+
inline: true,
|
|
403
|
+
atom: true,
|
|
404
|
+
parseHTML() {
|
|
405
|
+
return [{ tag: "span.docx-tab" }];
|
|
406
|
+
},
|
|
407
|
+
renderHTML() {
|
|
408
|
+
return ["span", {
|
|
409
|
+
class: "docx-tab",
|
|
410
|
+
contenteditable: "false"
|
|
411
|
+
}];
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
//#endregion
|
|
345
415
|
//#region src/extensions/toc-field.ts
|
|
346
416
|
/**
|
|
347
417
|
* TOC field (`tocField`) — a block container representing a DOCX table of
|
|
@@ -499,6 +569,35 @@ function renderWpsText(children) {
|
|
|
499
569
|
];
|
|
500
570
|
});
|
|
501
571
|
}
|
|
572
|
+
/** Inner style of a wps text-box: fill + outline + textbox insets (padding).
|
|
573
|
+
* Lives on the contentDOM (the editable interior). Never carries rotation or
|
|
574
|
+
* writing-mode — those go on the outer positioning wrapper (see
|
|
575
|
+
* wpsRotationVert), because transform/writing-mode on an editable region
|
|
576
|
+
* distort the caret rect and break CJK IME composition. */
|
|
577
|
+
function wpsInnerStyle(data) {
|
|
578
|
+
const parts = [];
|
|
579
|
+
const fill = fillToCss(data.fill);
|
|
580
|
+
if (fill) parts.push(`background-color:${fill}`);
|
|
581
|
+
const outline = outlineToCss(data.outline);
|
|
582
|
+
if (outline) parts.push(outline);
|
|
583
|
+
const bp = data.bodyProperties;
|
|
584
|
+
if (bp) {
|
|
585
|
+
const ins = (v) => v != null ? `${(v / EMU_PER_PX).toFixed(1)}px` : "0px";
|
|
586
|
+
parts.push(`padding:${ins(bp.tIns)} ${ins(bp.rIns)} ${ins(bp.bIns)} ${ins(bp.lIns)}`);
|
|
587
|
+
}
|
|
588
|
+
return parts.join(";");
|
|
589
|
+
}
|
|
590
|
+
/** Rotation + writing-mode (text direction) for a wps shape. Lives on the
|
|
591
|
+
* positioning wrapper OUTSIDE the contentDOM. `rotationOverride` covers a
|
|
592
|
+
* group child whose rotation comes from the group transform, not bodyPr. */
|
|
593
|
+
function wpsRotationVert(data, rotationOverride) {
|
|
594
|
+
const parts = [];
|
|
595
|
+
const rotation = rotationOverride ?? data.bodyProperties?.rotation;
|
|
596
|
+
if (rotation) parts.push(`transform:rotate(${rotation}deg)`);
|
|
597
|
+
const vert = data.bodyProperties?.vert;
|
|
598
|
+
if (vert && vert !== "horz") parts.push(`writing-mode:${vert === "vert270" ? "vertical-lr" : "vertical-rl"}`);
|
|
599
|
+
return parts.join(";");
|
|
600
|
+
}
|
|
502
601
|
/**
|
|
503
602
|
* Render a wps shape's interior (fill/outline/insets/rotation/writing-mode + text
|
|
504
603
|
* body) as a positioned div. Shared by the wpg group's inline wps children and
|
|
@@ -507,23 +606,18 @@ function renderWpsText(children) {
|
|
|
507
606
|
* carries the placement — absolute group coords for a group child, the floating
|
|
508
607
|
* anchor CSS for a standalone shape. `opts.rotation` overrides bodyPr rotation
|
|
509
608
|
* (a group child's rotation may come from the group transform, not bodyPr).
|
|
609
|
+
*
|
|
610
|
+
* The wpg path merges placement + interior into ONE div (an atom has no
|
|
611
|
+
* contentDOM, so rotation/writing-mode on the element is safe — no caret inside).
|
|
612
|
+
* The editable wpsShape node instead splits these across two elements via
|
|
613
|
+
* wpsShapeStyles (outer = position+rotation+vert, inner = contentDOM).
|
|
510
614
|
*/
|
|
511
615
|
function renderWpsInterior(data, positionStyle, opts) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const bp = data.bodyProperties;
|
|
518
|
-
if (bp) {
|
|
519
|
-
const ins = (v) => v != null ? `${(v / EMU_PER_PX).toFixed(1)}px` : "0px";
|
|
520
|
-
styles.push(`padding:${ins(bp.tIns)} ${ins(bp.rIns)} ${ins(bp.bIns)} ${ins(bp.lIns)}`);
|
|
521
|
-
}
|
|
522
|
-
const rotation = opts?.rotation ?? bp?.rotation;
|
|
523
|
-
if (rotation) styles.push(`transform:rotate(${rotation}deg)`);
|
|
524
|
-
const vert = bp?.vert;
|
|
525
|
-
if (vert && vert !== "horz") styles.push(`writing-mode:${vert === "vert270" ? "vertical-lr" : "vertical-rl"}`);
|
|
526
|
-
const attrs = { style: styles.join(";") };
|
|
616
|
+
const attrs = { style: [
|
|
617
|
+
positionStyle,
|
|
618
|
+
wpsInnerStyle(data),
|
|
619
|
+
wpsRotationVert(data, opts?.rotation)
|
|
620
|
+
].filter(Boolean).join(";") };
|
|
527
621
|
if (opts?.attrs) Object.assign(attrs, opts.attrs);
|
|
528
622
|
return [
|
|
529
623
|
"div",
|
|
@@ -531,6 +625,37 @@ function renderWpsInterior(data, positionStyle, opts) {
|
|
|
531
625
|
...renderWpsText(data.children)
|
|
532
626
|
];
|
|
533
627
|
}
|
|
628
|
+
/** Two-element style split for an editable standalone wpsShape: `outer` for the
|
|
629
|
+
* positioning wrapper (dom), `inner` for the contentDOM. rotation/writing-mode
|
|
630
|
+
* stay on `outer` so the editable interior has a clean caret/IME rect. The
|
|
631
|
+
* geometry (EMU extent → px, floating anchor CSS) is computed here so the
|
|
632
|
+
* editor's NodeView and generateHTML render identically without re-deriving
|
|
633
|
+
* the engine's EMU/floating math. */
|
|
634
|
+
function wpsShapeStyles(ws) {
|
|
635
|
+
const sizeStyle = `width:${ws.transformation?.width != null ? convertEmuToPixels(ws.transformation.width) : 0}px;height:${ws.transformation?.height != null ? convertEmuToPixels(ws.transformation.height) : 0}px;box-sizing:border-box;overflow:hidden`;
|
|
636
|
+
const rotVert = wpsRotationVert(ws);
|
|
637
|
+
let outer;
|
|
638
|
+
let paragraphAnchor = false;
|
|
639
|
+
if (ws.floating) {
|
|
640
|
+
outer = [
|
|
641
|
+
...floatingToStyles(ws.floating, void 0, ws.transformation?.width),
|
|
642
|
+
sizeStyle,
|
|
643
|
+
rotVert
|
|
644
|
+
].filter(Boolean).join(";");
|
|
645
|
+
paragraphAnchor = floatAnchorScope(ws.floating) === "paragraph";
|
|
646
|
+
} else outer = [
|
|
647
|
+
`display:inline-block;vertical-align:middle`,
|
|
648
|
+
sizeStyle,
|
|
649
|
+
rotVert
|
|
650
|
+
].filter(Boolean).join(";");
|
|
651
|
+
const anchor = ws.bodyProperties?.anchor;
|
|
652
|
+
const inner = `box-sizing:border-box;display:flex;flex-direction:column;justify-content:${anchor === "ctr" ? "center" : anchor === "b" ? "flex-end" : "flex-start"};height:100%;${wpsInnerStyle(ws)}`;
|
|
653
|
+
return {
|
|
654
|
+
outer,
|
|
655
|
+
inner,
|
|
656
|
+
paragraphAnchor
|
|
657
|
+
};
|
|
658
|
+
}
|
|
534
659
|
/** Render a wpg group (top-level or nested) as a positioned container.
|
|
535
660
|
* actualW/H are the group's real pixel size — top-level groups read them from
|
|
536
661
|
* transformation.width/height; nested groups receive the box their parent
|
|
@@ -550,7 +675,7 @@ function renderGroup(group, actualW, actualH, containerStyle, extraAttrs) {
|
|
|
550
675
|
const style = containerStyle ?? `position:relative;display:inline-block;vertical-align:middle;width:${actualW}px;height:${actualH}px`;
|
|
551
676
|
const children = (group.children ?? []).map((c) => renderChild(c, chOff, scaleX, scaleY)).filter((c) => Array.isArray(c) && c.length > 0);
|
|
552
677
|
const attrs = {
|
|
553
|
-
"data-wpg-group":
|
|
678
|
+
"data-wpg-group": JSON.stringify(group),
|
|
554
679
|
style
|
|
555
680
|
};
|
|
556
681
|
if (extraAttrs) Object.assign(attrs, extraAttrs);
|
|
@@ -597,6 +722,16 @@ const WpgGroup = Node.create({
|
|
|
597
722
|
});
|
|
598
723
|
//#endregion
|
|
599
724
|
//#region src/extensions/wps-shape.ts
|
|
725
|
+
/**
|
|
726
|
+
* wpsShape — inline node carrying a standalone DOCX text-box shape
|
|
727
|
+
* (wp:anchor > wps:wsp > wps:txbx; NOT inside a wpg group). The shape geometry
|
|
728
|
+
* + styling (transformation/floating/fill/outline/bodyProperties) ride on
|
|
729
|
+
* attrs.wpsShape; the editable text body is PM content (block+), one paragraph
|
|
730
|
+
* per office-open ParagraphOptions. Unlike a group's interior wps children
|
|
731
|
+
* (laid out in the group's coordinate space), this shape floats on its own
|
|
732
|
+
* anchor. The engine node has no NodeView (UI-free); the editor layer extends
|
|
733
|
+
* it with a two-element NodeView (outer placement/rotation, inner contentDOM).
|
|
734
|
+
*/
|
|
600
735
|
const attrWpsShape = () => ({
|
|
601
736
|
default: null,
|
|
602
737
|
rendered: false,
|
|
@@ -614,23 +749,35 @@ const WpsShape = Node.create({
|
|
|
614
749
|
name: "wpsShape",
|
|
615
750
|
group: "inline",
|
|
616
751
|
inline: true,
|
|
617
|
-
|
|
752
|
+
content: "block+",
|
|
753
|
+
isolating: true,
|
|
754
|
+
defining: true,
|
|
618
755
|
addAttributes() {
|
|
619
756
|
return { wpsShape: attrWpsShape() };
|
|
620
757
|
},
|
|
621
758
|
parseHTML() {
|
|
622
|
-
return [{
|
|
759
|
+
return [{
|
|
760
|
+
tag: "div[data-wps-shape]",
|
|
761
|
+
contentElement: "div"
|
|
762
|
+
}];
|
|
623
763
|
},
|
|
624
764
|
renderHTML({ node }) {
|
|
625
765
|
const ws = node.attrs.wpsShape ?? {};
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
766
|
+
const { outer, inner, paragraphAnchor } = wpsShapeStyles(ws);
|
|
767
|
+
const attrs = {
|
|
768
|
+
"data-wps-shape": JSON.stringify(ws),
|
|
769
|
+
style: outer
|
|
770
|
+
};
|
|
771
|
+
if (paragraphAnchor) attrs["data-float-anchor"] = "paragraph";
|
|
772
|
+
return [
|
|
773
|
+
"div",
|
|
774
|
+
attrs,
|
|
775
|
+
[
|
|
776
|
+
"div",
|
|
777
|
+
{ style: inner },
|
|
778
|
+
0
|
|
779
|
+
]
|
|
780
|
+
];
|
|
634
781
|
}
|
|
635
782
|
});
|
|
636
783
|
//#endregion
|
|
@@ -642,8 +789,10 @@ const tiptapNodeExtensions = [
|
|
|
642
789
|
HardBreak,
|
|
643
790
|
PageBreak,
|
|
644
791
|
ColumnBreak,
|
|
792
|
+
Tab,
|
|
645
793
|
SectionBreak,
|
|
646
794
|
Passthrough,
|
|
795
|
+
InlinePassthrough,
|
|
647
796
|
TocField,
|
|
648
797
|
Blockquote,
|
|
649
798
|
OrderedList,
|
|
@@ -667,7 +816,7 @@ const tiptapNodeExtensions = [
|
|
|
667
816
|
TaskList,
|
|
668
817
|
TaskItem,
|
|
669
818
|
Heading,
|
|
670
|
-
TextAlign
|
|
819
|
+
TextAlign.configure({ types: ["heading", "paragraph"] })
|
|
671
820
|
];
|
|
672
821
|
const tiptapMarkExtensions = [
|
|
673
822
|
Bold,
|
|
@@ -714,4 +863,4 @@ const DocxKit = Extension.create({
|
|
|
714
863
|
}
|
|
715
864
|
});
|
|
716
865
|
//#endregion
|
|
717
|
-
export {
|
|
866
|
+
export { ColumnBreak as C, FormattingMarks as S, Tab as _, DocxKit as a, Passthrough as b, tiptapNodeExtensions as c, renderWpsInterior as d, renderWpsText as f, TocField as g, wpsShapeStyles as h, Node as i, WpsShape as l, wpsRotationVert as m, Extension as n, docxExtensions as o, wpsInnerStyle as p, Mark as r, tiptapMarkExtensions as s, Editor as t, WpgGroup as u, SectionBreak as v, PageBreak as x, InlinePassthrough as y };
|