@docen/docx 0.3.3 → 0.3.4
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 +121 -24
- 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];
|
|
@@ -458,15 +462,20 @@ var DocxManager = class {
|
|
|
458
462
|
const items = [];
|
|
459
463
|
const isOrdered = node.type === "orderedList";
|
|
460
464
|
const isTask = node.type === "taskList";
|
|
465
|
+
const numbering = node.attrs?.numbering;
|
|
461
466
|
let ordered;
|
|
462
|
-
if (isOrdered) ordered = this.registerOrderedNumbering(node);
|
|
467
|
+
if (isOrdered && !numbering) ordered = this.registerOrderedNumbering(node);
|
|
463
468
|
for (const listItem of node.content ?? []) {
|
|
464
469
|
if (listItem.type !== "listItem" && listItem.type !== "taskItem") continue;
|
|
465
470
|
const checked = Boolean(listItem.attrs?.checked);
|
|
466
471
|
for (const child of listItem.content ?? []) if (child.type === "paragraph" || child.type === "heading") {
|
|
467
472
|
const para = child.type === "heading" ? this.compileHeadingNode(child) : this.compileParagraphNode(child);
|
|
468
473
|
const paraObj = typeof para === "string" ? { text: para } : para;
|
|
469
|
-
if (
|
|
474
|
+
if (numbering) paraObj.numbering = {
|
|
475
|
+
reference: numbering,
|
|
476
|
+
level
|
|
477
|
+
};
|
|
478
|
+
else if (ordered) paraObj.numbering = {
|
|
470
479
|
reference: ordered.reference,
|
|
471
480
|
instance: ordered.instance,
|
|
472
481
|
level
|
|
@@ -559,6 +568,17 @@ var DocxManager = class {
|
|
|
559
568
|
case "columnBreak":
|
|
560
569
|
children.push({ columnBreak: true });
|
|
561
570
|
break;
|
|
571
|
+
case "tab":
|
|
572
|
+
children.push({ tab: true });
|
|
573
|
+
break;
|
|
574
|
+
case "inlinePassthrough": {
|
|
575
|
+
const data = node.attrs?.data ?? "{}";
|
|
576
|
+
try {
|
|
577
|
+
const parsed = JSON.parse(data);
|
|
578
|
+
if (parsed) children.push(parsed);
|
|
579
|
+
} catch {}
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
562
582
|
case "image": {
|
|
563
583
|
const imageRun = renderDocx$2(node);
|
|
564
584
|
if (imageRun) children.push(imageRun);
|
|
@@ -570,8 +590,18 @@ var DocxManager = class {
|
|
|
570
590
|
break;
|
|
571
591
|
}
|
|
572
592
|
case "wpsShape": {
|
|
573
|
-
const
|
|
574
|
-
|
|
593
|
+
const geometry = node.attrs?.wpsShape ?? {};
|
|
594
|
+
const body = [];
|
|
595
|
+
for (const child of node.content ?? []) {
|
|
596
|
+
const compiled = this.compileSectionChild(child);
|
|
597
|
+
if (!compiled) continue;
|
|
598
|
+
const items = Array.isArray(compiled) ? compiled : [compiled];
|
|
599
|
+
for (const it of items) if (it && typeof it === "object" && "paragraph" in it) body.push(it.paragraph);
|
|
600
|
+
}
|
|
601
|
+
children.push({ wpsShape: {
|
|
602
|
+
...geometry,
|
|
603
|
+
children: body
|
|
604
|
+
} });
|
|
575
605
|
break;
|
|
576
606
|
}
|
|
577
607
|
case "mention":
|
|
@@ -858,10 +888,7 @@ var DocxManager = class {
|
|
|
858
888
|
if (cfg && cfg.format && cfg.format !== "bullet") {
|
|
859
889
|
kind = "ordered";
|
|
860
890
|
start = cfg.start;
|
|
861
|
-
} else
|
|
862
|
-
kind = "bullet";
|
|
863
|
-
reference = void 0;
|
|
864
|
-
}
|
|
891
|
+
} else kind = "bullet";
|
|
865
892
|
} else if (bullet) {
|
|
866
893
|
kind = "bullet";
|
|
867
894
|
level = bullet.level ?? 0;
|
|
@@ -910,7 +937,10 @@ var DocxManager = class {
|
|
|
910
937
|
type: listType,
|
|
911
938
|
content: [newItem]
|
|
912
939
|
};
|
|
913
|
-
|
|
940
|
+
const listAttrs = {};
|
|
941
|
+
if (listType === "orderedList" && info.level === 0 && typeof info.start === "number" && info.start !== 1) listAttrs.start = info.start;
|
|
942
|
+
if (info.reference) listAttrs.numbering = info.reference;
|
|
943
|
+
if (Object.keys(listAttrs).length > 0) newList.attrs = listAttrs;
|
|
914
944
|
if (top) top.currentItem.content.push(newList);
|
|
915
945
|
else topLevel.push(newList);
|
|
916
946
|
stack.push({
|
|
@@ -1136,21 +1166,55 @@ var DocxManager = class {
|
|
|
1136
1166
|
return nodes;
|
|
1137
1167
|
}
|
|
1138
1168
|
resolveParagraphChild(child) {
|
|
1169
|
+
if ("tab" in child) return { type: "tab" };
|
|
1139
1170
|
if ("text" in child || "children" in child || "break" in child) return this.resolveRun(child);
|
|
1140
1171
|
if ("image" in child) return this.resolveImage(child.image);
|
|
1141
1172
|
if ("wpgGroup" in child) return {
|
|
1142
1173
|
type: "wpgGroup",
|
|
1143
1174
|
attrs: { wpgGroup: child.wpgGroup }
|
|
1144
1175
|
};
|
|
1145
|
-
if ("wpsShape" in child)
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1176
|
+
if ("wpsShape" in child) {
|
|
1177
|
+
const ws = child.wpsShape;
|
|
1178
|
+
const content = [];
|
|
1179
|
+
if (ws?.children) for (const para of ws.children) {
|
|
1180
|
+
if (typeof para !== "object" || para === null) {
|
|
1181
|
+
const node = this.resolveParagraph(para);
|
|
1182
|
+
if (node) content.push(node);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
const defRPr = para.run ?? {};
|
|
1186
|
+
const children = Array.isArray(para.children) ? para.children.map((c) => typeof c !== "object" || c === null ? {
|
|
1187
|
+
...defRPr,
|
|
1188
|
+
text: c
|
|
1189
|
+
} : {
|
|
1190
|
+
...defRPr,
|
|
1191
|
+
...c
|
|
1192
|
+
}) : void 0;
|
|
1193
|
+
const node = this.resolveParagraph({
|
|
1194
|
+
...para,
|
|
1195
|
+
run: void 0,
|
|
1196
|
+
...children ? { children } : {}
|
|
1197
|
+
});
|
|
1198
|
+
if (node) content.push(node);
|
|
1199
|
+
}
|
|
1200
|
+
if (content.length === 0) content.push({ type: "paragraph" });
|
|
1201
|
+
const { children: _omit, ...geometry } = ws ?? {};
|
|
1202
|
+
const node = {
|
|
1203
|
+
type: "wpsShape",
|
|
1204
|
+
content
|
|
1205
|
+
};
|
|
1206
|
+
const cleanGeometry = cleanAttrs(geometry);
|
|
1207
|
+
if (Object.keys(cleanGeometry).length > 0) node.attrs = { wpsShape: cleanGeometry };
|
|
1208
|
+
return node;
|
|
1209
|
+
}
|
|
1149
1210
|
if ("sdt" in child) return this.resolveInlineSdt(child);
|
|
1150
1211
|
if ("hyperlink" in child) return this.resolveHyperlink(child.hyperlink);
|
|
1151
1212
|
if ("pageBreak" in child) return { type: "pageBreak" };
|
|
1152
1213
|
if ("columnBreak" in child) return { type: "columnBreak" };
|
|
1153
|
-
return
|
|
1214
|
+
return {
|
|
1215
|
+
type: "inlinePassthrough",
|
|
1216
|
+
attrs: { data: JSON.stringify(child) }
|
|
1217
|
+
};
|
|
1154
1218
|
}
|
|
1155
1219
|
/** Resolve an inline SDT (mention carrier; other inline SDTs unsupported). */
|
|
1156
1220
|
resolveInlineSdt(child) {
|
|
@@ -1170,15 +1234,48 @@ var DocxManager = class {
|
|
|
1170
1234
|
if (opts.break && opts.text === void 0 && !opts.children) return { type: "hardBreak" };
|
|
1171
1235
|
const text = opts.text;
|
|
1172
1236
|
if (text === void 0 && !opts.children) return null;
|
|
1173
|
-
if (opts.children
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1237
|
+
if (opts.children) {
|
|
1238
|
+
const marks = this.resolveMarks(opts);
|
|
1239
|
+
const nodes = [];
|
|
1240
|
+
let parts = [];
|
|
1241
|
+
const flushText = () => {
|
|
1242
|
+
if (parts.length > 0) {
|
|
1243
|
+
const node = {
|
|
1244
|
+
type: "text",
|
|
1245
|
+
text: parts.join("")
|
|
1246
|
+
};
|
|
1247
|
+
if (marks) node.marks = marks;
|
|
1248
|
+
nodes.push(node);
|
|
1249
|
+
parts = [];
|
|
1250
|
+
}
|
|
1181
1251
|
};
|
|
1252
|
+
for (const c of opts.children) if (typeof c === "string") parts.push(c);
|
|
1253
|
+
else if (c && typeof c === "object") {
|
|
1254
|
+
if ("pageBreak" in c) {
|
|
1255
|
+
flushText();
|
|
1256
|
+
nodes.push({ type: "pageBreak" });
|
|
1257
|
+
} else if ("columnBreak" in c) {
|
|
1258
|
+
flushText();
|
|
1259
|
+
nodes.push({ type: "columnBreak" });
|
|
1260
|
+
} else if ("break" in c) {
|
|
1261
|
+
flushText();
|
|
1262
|
+
nodes.push({ type: "hardBreak" });
|
|
1263
|
+
} else if ("tab" in c) {
|
|
1264
|
+
flushText();
|
|
1265
|
+
nodes.push({ type: "tab" });
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
flushText();
|
|
1269
|
+
if (text !== void 0) {
|
|
1270
|
+
const node = {
|
|
1271
|
+
type: "text",
|
|
1272
|
+
text,
|
|
1273
|
+
marks
|
|
1274
|
+
};
|
|
1275
|
+
nodes.push(node);
|
|
1276
|
+
}
|
|
1277
|
+
if (nodes.length === 0) return null;
|
|
1278
|
+
return nodes.length === 1 ? nodes[0] : nodes;
|
|
1182
1279
|
}
|
|
1183
1280
|
return {
|
|
1184
1281
|
type: "text",
|
|
@@ -1236,7 +1333,7 @@ var DocxManager = class {
|
|
|
1236
1333
|
type: "link",
|
|
1237
1334
|
attrs: {
|
|
1238
1335
|
href,
|
|
1239
|
-
target: "_blank",
|
|
1336
|
+
target: href.startsWith("#") ? null : "_blank",
|
|
1240
1337
|
rel: "noopener noreferrer nofollow",
|
|
1241
1338
|
class: null,
|
|
1242
1339
|
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 };
|