@docen/docx 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/converters/docx.d.mts +95 -5
- package/dist/converters/docx.mjs +732 -82
- package/dist/converters/html.d.mts +20 -5
- package/dist/converters/html.mjs +94 -8
- package/dist/converters/markdown.d.mts +1 -1
- package/dist/converters/markdown.mjs +1 -1
- package/dist/converters/patch.d.mts +1 -1
- package/dist/converters/patch.mjs +4 -5
- package/dist/converters/prepare.d.mts +15 -3
- package/dist/converters/prepare.mjs +50 -4
- package/dist/converters/styles.d.mts +85 -0
- package/dist/converters/styles.mjs +285 -0
- package/dist/core-C9VunJ8o.d.mts +174 -0
- package/dist/core-DT9IrTzN.mjs +670 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/document-BH1y4qHM.d.mts +6 -0
- package/dist/editor.d.mts +1 -1
- package/dist/editor.mjs +3 -2
- package/dist/extensions/blockquote.d.mts +23 -0
- package/dist/extensions/blockquote.mjs +31 -0
- package/dist/extensions/code-block.d.mts +19 -0
- package/dist/extensions/code-block.mjs +23 -0
- package/dist/extensions/column-break.d.mts +31 -0
- package/dist/extensions/column-break.mjs +2 -0
- package/dist/extensions/details.d.mts +19 -0
- package/dist/extensions/details.mjs +18 -0
- package/dist/extensions/document.d.mts +2 -0
- package/dist/extensions/document.mjs +50 -0
- package/dist/extensions/extensions.d.mts +5 -5
- package/dist/extensions/extensions.mjs +4 -3
- package/dist/extensions/formatting-marks.d.mts +38 -0
- package/dist/extensions/formatting-marks.mjs +2 -0
- package/dist/extensions/heading.mjs +24 -3
- package/dist/extensions/image.d.mts +2 -2
- package/dist/extensions/image.mjs +244 -49
- package/dist/extensions/index.d.mts +4 -2
- package/dist/extensions/index.mjs +4 -2
- package/dist/extensions/mention.d.mts +25 -0
- package/dist/extensions/mention.mjs +44 -0
- package/dist/extensions/ordered-list.d.mts +25 -0
- package/dist/extensions/ordered-list.mjs +41 -0
- package/dist/extensions/page-break.d.mts +2 -0
- package/dist/extensions/page-break.mjs +2 -0
- package/dist/extensions/paragraph.d.mts +1 -1
- package/dist/extensions/paragraph.mjs +39 -4
- package/dist/extensions/passthrough.d.mts +2 -0
- package/dist/extensions/passthrough.mjs +2 -0
- package/dist/extensions/section-break.d.mts +44 -0
- package/dist/extensions/section-break.mjs +2 -0
- package/dist/extensions/table.mjs +29 -3
- package/dist/extensions/task-item.d.mts +27 -0
- package/dist/extensions/task-item.mjs +37 -0
- package/dist/extensions/text-style.mjs +20 -3
- package/dist/extensions/tiptap.d.mts +2 -2
- package/dist/extensions/tiptap.mjs +1 -3
- package/dist/extensions/types.d.mts +7 -5
- package/dist/extensions/utils.d.mts +2 -49
- package/dist/extensions/utils.mjs +222 -12
- package/dist/extensions/wpg-group.d.mts +2 -0
- package/dist/extensions/wpg-group.mjs +2 -0
- package/dist/extensions/wps-shape.d.mts +2 -0
- package/dist/extensions/wps-shape.mjs +2 -0
- package/dist/index.d.mts +127 -18
- package/dist/index.mjs +7 -2
- package/dist/paragraph-D8mpHo_o.d.mts +8 -0
- package/dist/{tiptap-TErPjuNJ.d.mts → tiptap-pZsNPsvV.d.mts} +1 -3
- package/dist/utils-D87vukzp.d.mts +128 -0
- package/package.json +40 -40
- package/dist/core-CFIQVRfx.mjs +0 -88
- package/dist/core-omBKMRtl.d.mts +0 -34
- package/dist/image-Ge1y6uam.d.mts +0 -47
- package/dist/paragraph-fhEXtAN2.d.mts +0 -16
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Image as Image$1 } from "./tiptap.mjs";
|
|
2
|
+
import { floatAnchorScope, floatingToStyles } from "./utils.mjs";
|
|
3
|
+
import { convertEmuToPixels } from "@office-open/core";
|
|
2
4
|
//#region src/extensions/image.ts
|
|
3
5
|
/**
|
|
4
6
|
* Custom Image extension with node-level renderHTML + renderDocx/parseDocx.
|
|
@@ -16,6 +18,20 @@ import { Image as Image$1 } from "./tiptap.mjs";
|
|
|
16
18
|
* parseDocx unpacks them back. src is a data URL ↔ { type, data } base64.
|
|
17
19
|
* Node-level renderHTML solves the style merge problem (rotation + floating).
|
|
18
20
|
*/
|
|
21
|
+
/** Attribute spec for a nested office-open value stored as JSON in a data-* attr. */
|
|
22
|
+
const attrDataJson = (name) => ({
|
|
23
|
+
default: null,
|
|
24
|
+
rendered: false,
|
|
25
|
+
parseHTML: (element) => {
|
|
26
|
+
const raw = element.getAttribute(name);
|
|
27
|
+
if (!raw) return null;
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
19
35
|
/**
|
|
20
36
|
* Tiptap JSON image node → CoreImageOptions-shaped object.
|
|
21
37
|
*
|
|
@@ -38,9 +54,11 @@ function renderDocx(node) {
|
|
|
38
54
|
}
|
|
39
55
|
}
|
|
40
56
|
if (!imageOpts.data) return null;
|
|
57
|
+
const width = attrs.width ?? 600;
|
|
58
|
+
const height = attrs.height ?? 400;
|
|
41
59
|
const transformation = {
|
|
42
|
-
width:
|
|
43
|
-
height:
|
|
60
|
+
width: `${width}px`,
|
|
61
|
+
height: `${height}px`
|
|
44
62
|
};
|
|
45
63
|
const rotation = attrs.rotation;
|
|
46
64
|
if (rotation != null) transformation.rotation = rotation;
|
|
@@ -50,8 +68,17 @@ function renderDocx(node) {
|
|
|
50
68
|
if (attrs.title) altText.description = attrs.title;
|
|
51
69
|
if (Object.keys(altText).length > 0) imageOpts.altText = altText;
|
|
52
70
|
if (attrs.floating) imageOpts.floating = attrs.floating;
|
|
53
|
-
if (attrs.crop) imageOpts.
|
|
71
|
+
if (attrs.crop) imageOpts.sourceRectangle = attrs.crop;
|
|
54
72
|
if (attrs.outline) imageOpts.outline = attrs.outline;
|
|
73
|
+
if (attrs.nonVisualProperties) imageOpts.nonVisualProperties = attrs.nonVisualProperties;
|
|
74
|
+
if (attrs.effectExtent) transformation.effectExtent = attrs.effectExtent;
|
|
75
|
+
if (attrs.graphicFrameLocks) imageOpts.graphicFrameLocks = attrs.graphicFrameLocks;
|
|
76
|
+
if (attrs.blipEffects) imageOpts.blipEffects = attrs.blipEffects;
|
|
77
|
+
if (attrs.useLocalDpi !== null && attrs.useLocalDpi !== void 0) imageOpts.useLocalDpi = attrs.useLocalDpi;
|
|
78
|
+
if (attrs.fill) imageOpts.fill = attrs.fill;
|
|
79
|
+
if (attrs.effects) imageOpts.effects = attrs.effects;
|
|
80
|
+
if (attrs.tile) imageOpts.tile = attrs.tile;
|
|
81
|
+
if (attrs.runPropertiesRawXml) imageOpts.runPropertiesRawXml = attrs.runPropertiesRawXml;
|
|
55
82
|
return { image: imageOpts };
|
|
56
83
|
}
|
|
57
84
|
/**
|
|
@@ -66,8 +93,8 @@ function parseDocx(imageOpts) {
|
|
|
66
93
|
const attrs = {};
|
|
67
94
|
const transformation = opts.transformation;
|
|
68
95
|
if (transformation) {
|
|
69
|
-
if (typeof transformation.width === "number") attrs.width = transformation.width;
|
|
70
|
-
if (typeof transformation.height === "number") attrs.height = transformation.height;
|
|
96
|
+
if (typeof transformation.width === "number") attrs.width = convertEmuToPixels(transformation.width);
|
|
97
|
+
if (typeof transformation.height === "number") attrs.height = convertEmuToPixels(transformation.height);
|
|
71
98
|
if (typeof transformation.rotation === "number") attrs.rotation = transformation.rotation;
|
|
72
99
|
}
|
|
73
100
|
const altText = opts.altText;
|
|
@@ -76,57 +103,92 @@ function parseDocx(imageOpts) {
|
|
|
76
103
|
if (altText.description) attrs.title = altText.description;
|
|
77
104
|
}
|
|
78
105
|
if (opts.floating) attrs.floating = opts.floating;
|
|
79
|
-
if (opts.
|
|
106
|
+
if (opts.sourceRectangle) attrs.crop = opts.sourceRectangle;
|
|
80
107
|
if (opts.outline) attrs.outline = opts.outline;
|
|
108
|
+
if (opts.nonVisualProperties) attrs.nonVisualProperties = opts.nonVisualProperties;
|
|
109
|
+
const effectExtent = opts.transformation?.effectExtent;
|
|
110
|
+
if (effectExtent) attrs.effectExtent = effectExtent;
|
|
111
|
+
if (opts.graphicFrameLocks) attrs.graphicFrameLocks = opts.graphicFrameLocks;
|
|
112
|
+
if (opts.blipEffects) attrs.blipEffects = opts.blipEffects;
|
|
113
|
+
if (opts.useLocalDpi !== void 0) attrs.useLocalDpi = opts.useLocalDpi;
|
|
114
|
+
if (opts.fill) attrs.fill = opts.fill;
|
|
115
|
+
if (opts.effects) attrs.effects = opts.effects;
|
|
116
|
+
if (opts.tile) attrs.tile = opts.tile;
|
|
117
|
+
if (opts.runPropertiesRawXml) attrs.runPropertiesRawXml = opts.runPropertiesRawXml;
|
|
81
118
|
return attrs;
|
|
82
119
|
}
|
|
83
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Render crop as the inner <img> style for byte-accurate four-sided srcRect.
|
|
122
|
+
*
|
|
123
|
+
* object-fit:cover scales uniformly, so it only matches single-axis crops.
|
|
124
|
+
* Instead the inner <img> is sized to the un-cropped display size per axis
|
|
125
|
+
* (imgW = W/visibleW, imgH = H/visibleH) and translated so the visible srcRect
|
|
126
|
+
* region maps exactly onto the extent box; the outer box clips (overflow:hidden)
|
|
127
|
+
* the cropped-out left/top region. Mathematically equivalent to a
|
|
128
|
+
* background-size/background-position crop, but keeps a real <img> (alt,
|
|
129
|
+
* accessibility, semantics, drag-to-save).
|
|
130
|
+
*/
|
|
131
|
+
function renderCropAttrs(crop, ctx = {}) {
|
|
84
132
|
const c = crop;
|
|
85
133
|
const leftPct = (c.left || 0) / 1e5;
|
|
86
134
|
const topPct = (c.top || 0) / 1e5;
|
|
87
135
|
const rightPct = (c.right || 0) / 1e5;
|
|
88
136
|
const bottomPct = (c.bottom || 0) / 1e5;
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
137
|
+
const visibleW = 1 - leftPct - rightPct;
|
|
138
|
+
const visibleH = 1 - topPct - bottomPct;
|
|
139
|
+
const w = ctx.width ?? 0;
|
|
140
|
+
const h = ctx.height ?? 0;
|
|
141
|
+
const imgW = visibleW > 0 ? w / visibleW : w;
|
|
142
|
+
const imgH = visibleH > 0 ? h / visibleH : h;
|
|
143
|
+
const offX = -(leftPct * imgW);
|
|
144
|
+
const offY = -(topPct * imgH);
|
|
145
|
+
return { style: [
|
|
146
|
+
"display:block",
|
|
147
|
+
`width:${imgW.toFixed(2)}px`,
|
|
148
|
+
`height:${imgH.toFixed(2)}px`,
|
|
149
|
+
`transform:translate(${offX.toFixed(2)}px,${offY.toFixed(2)}px)`,
|
|
150
|
+
"transform-origin:0 0"
|
|
151
|
+
].join(";") };
|
|
94
152
|
}
|
|
95
153
|
function renderImageStyles(attrs) {
|
|
96
154
|
const styles = [];
|
|
97
155
|
if (attrs.display) styles.push(`display:${attrs.display}`);
|
|
156
|
+
else if (!attrs.floating) styles.push("display:inline-block");
|
|
98
157
|
if (attrs.rotation) styles.push(`transform:rotate(${attrs.rotation}deg)`);
|
|
99
|
-
|
|
100
|
-
if (f) {
|
|
101
|
-
styles.push(f.behindDocument ? "z-index:-1" : "z-index:1");
|
|
102
|
-
const wrapType = f.wrap?.type ?? 0;
|
|
103
|
-
if (wrapType === 0) styles.push("position:absolute");
|
|
104
|
-
else if (wrapType === 1) {
|
|
105
|
-
const align = f.horizontalPosition?.align;
|
|
106
|
-
if (align === "left" || align === "inside") styles.push("float:left");
|
|
107
|
-
else if (align === "right" || align === "outside") styles.push("float:right");
|
|
108
|
-
else styles.push("float:left");
|
|
109
|
-
} else if (wrapType === 2) {
|
|
110
|
-
styles.push("float:left");
|
|
111
|
-
styles.push("shape-outside:margin-box");
|
|
112
|
-
} else if (wrapType === 3) {
|
|
113
|
-
styles.push("display:block");
|
|
114
|
-
styles.push("clear:both");
|
|
115
|
-
} else styles.push("display:inline-block");
|
|
116
|
-
const m = f.margins;
|
|
117
|
-
if (m) {
|
|
118
|
-
if (m.top) styles.push(`margin-top:${(m.top * 96 / 1440).toFixed(1)}pt`);
|
|
119
|
-
if (m.bottom) styles.push(`margin-bottom:${(m.bottom * 96 / 1440).toFixed(1)}pt`);
|
|
120
|
-
if (m.left) styles.push(`margin-left:${(m.left * 96 / 1440).toFixed(1)}pt`);
|
|
121
|
-
if (m.right) styles.push(`margin-right:${(m.right * 96 / 1440).toFixed(1)}pt`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (attrs.crop) {
|
|
125
|
-
const cropAttrs = renderCropAttrs(attrs.crop);
|
|
126
|
-
styles.push(cropAttrs.style);
|
|
127
|
-
}
|
|
158
|
+
if (attrs.floating) styles.push(...floatingToStyles(attrs.floating, attrs.src, attrs.width));
|
|
128
159
|
return styles;
|
|
129
160
|
}
|
|
161
|
+
/** Office vector (GDI) formats the browser cannot decode — EMF/WMF render as
|
|
162
|
+
* a labeled placeholder (Image.renderHTML) instead of an <img> that decodes
|
|
163
|
+
* to naturalWidth 0 (an empty box). */
|
|
164
|
+
function isVectorImage(src) {
|
|
165
|
+
return typeof src === "string" && /^data:image\/(?:x-)?(?:emf|wmf)/i.test(src);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Stamp the nested office-open attrs onto an HTML attribute map as JSON
|
|
169
|
+
* data-* pairs. Shared by the cropped-div and plain-img render branches so the
|
|
170
|
+
* fidelity fields (floating/outline/nonVisualProperties/…) round-trip through
|
|
171
|
+
* HTML identically. `crop` is handled separately (crop branch only).
|
|
172
|
+
*/
|
|
173
|
+
const RAW_ATTR_DATA = [
|
|
174
|
+
["floating", "data-floating"],
|
|
175
|
+
["outline", "data-outline"],
|
|
176
|
+
["nonVisualProperties", "data-non-visual"],
|
|
177
|
+
["effectExtent", "data-effect-extent"],
|
|
178
|
+
["graphicFrameLocks", "data-graphic-frame-locks"],
|
|
179
|
+
["blipEffects", "data-blip-effects"],
|
|
180
|
+
["useLocalDpi", "data-use-local-dpi"],
|
|
181
|
+
["fill", "data-fill"],
|
|
182
|
+
["effects", "data-effects"],
|
|
183
|
+
["tile", "data-tile"],
|
|
184
|
+
["runPropertiesRawXml", "data-run-properties-raw-xml"]
|
|
185
|
+
];
|
|
186
|
+
function attachRawAttrs(target, attrs) {
|
|
187
|
+
for (const [attr, data] of RAW_ATTR_DATA) {
|
|
188
|
+
const value = attrs[attr];
|
|
189
|
+
if (value !== null && value !== void 0) target[data] = JSON.stringify(value);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
130
192
|
const Image = Image$1.extend({
|
|
131
193
|
addAttributes() {
|
|
132
194
|
return {
|
|
@@ -144,6 +206,24 @@ const Image = Image$1.extend({
|
|
|
144
206
|
return match ? parseFloat(match[1]) : null;
|
|
145
207
|
}
|
|
146
208
|
},
|
|
209
|
+
width: { parseHTML: (element) => {
|
|
210
|
+
const attr = element.getAttribute("width");
|
|
211
|
+
if (attr != null) {
|
|
212
|
+
const n = parseFloat(attr);
|
|
213
|
+
if (!Number.isNaN(n)) return n;
|
|
214
|
+
}
|
|
215
|
+
const m = (element.getAttribute("style") || "").match(/(?:^|;)\s*width:\s*([\d.]+)px/);
|
|
216
|
+
return m ? parseFloat(m[1]) : null;
|
|
217
|
+
} },
|
|
218
|
+
height: { parseHTML: (element) => {
|
|
219
|
+
const attr = element.getAttribute("height");
|
|
220
|
+
if (attr != null) {
|
|
221
|
+
const n = parseFloat(attr);
|
|
222
|
+
if (!Number.isNaN(n)) return n;
|
|
223
|
+
}
|
|
224
|
+
const m = (element.getAttribute("style") || "").match(/(?:^|;)\s*height:\s*([\d.]+)px/);
|
|
225
|
+
return m ? parseFloat(m[1]) : null;
|
|
226
|
+
} },
|
|
147
227
|
floating: {
|
|
148
228
|
default: null,
|
|
149
229
|
rendered: false,
|
|
@@ -182,20 +262,135 @@ const Image = Image$1.extend({
|
|
|
182
262
|
return null;
|
|
183
263
|
}
|
|
184
264
|
}
|
|
185
|
-
}
|
|
265
|
+
},
|
|
266
|
+
nonVisualProperties: attrDataJson("data-non-visual"),
|
|
267
|
+
effectExtent: attrDataJson("data-effect-extent"),
|
|
268
|
+
graphicFrameLocks: attrDataJson("data-graphic-frame-locks"),
|
|
269
|
+
blipEffects: attrDataJson("data-blip-effects"),
|
|
270
|
+
useLocalDpi: attrDataJson("data-use-local-dpi"),
|
|
271
|
+
fill: attrDataJson("data-fill"),
|
|
272
|
+
effects: attrDataJson("data-effects"),
|
|
273
|
+
tile: attrDataJson("data-tile"),
|
|
274
|
+
runPropertiesRawXml: attrDataJson("data-run-properties-raw-xml")
|
|
186
275
|
};
|
|
187
276
|
},
|
|
188
277
|
renderHTML({ node, HTMLAttributes }) {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
278
|
+
const attrs = node.attrs;
|
|
279
|
+
const floatAnchor = attrs.floating && floatAnchorScope(attrs.floating) === "paragraph" ? "paragraph" : null;
|
|
280
|
+
if (isVectorImage(attrs.src)) {
|
|
281
|
+
const styles = renderImageStyles(attrs);
|
|
282
|
+
if (attrs.width != null) styles.push(`width:${attrs.width}px`);
|
|
283
|
+
if (attrs.height != null) styles.push(`height:${attrs.height}px`);
|
|
284
|
+
const divAttrs = {
|
|
285
|
+
"data-image": "vector",
|
|
286
|
+
role: "img",
|
|
287
|
+
"data-vector-src": attrs.src,
|
|
288
|
+
style: styles.join(";")
|
|
289
|
+
};
|
|
290
|
+
if (attrs.alt) divAttrs["aria-label"] = attrs.alt;
|
|
291
|
+
if (attrs.title) divAttrs["title"] = attrs.title;
|
|
292
|
+
attachRawAttrs(divAttrs, attrs);
|
|
293
|
+
if (floatAnchor) divAttrs["data-float-anchor"] = floatAnchor;
|
|
294
|
+
return [
|
|
295
|
+
"div",
|
|
296
|
+
divAttrs,
|
|
297
|
+
"Vector image"
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
if (attrs.crop) {
|
|
301
|
+
const width = attrs.width;
|
|
302
|
+
const height = attrs.height;
|
|
303
|
+
const boxStyles = renderImageStyles(attrs);
|
|
304
|
+
boxStyles.push("overflow:hidden", `width:${width}px`, `height:${height}px`);
|
|
305
|
+
const crop = renderCropAttrs(attrs.crop, {
|
|
306
|
+
width,
|
|
307
|
+
height
|
|
308
|
+
});
|
|
309
|
+
const boxAttrs = {
|
|
310
|
+
"data-image": "crop",
|
|
311
|
+
style: boxStyles.join(";")
|
|
312
|
+
};
|
|
313
|
+
attachRawAttrs(boxAttrs, attrs);
|
|
314
|
+
boxAttrs["data-crop"] = JSON.stringify(attrs.crop);
|
|
315
|
+
if (floatAnchor) boxAttrs["data-float-anchor"] = floatAnchor;
|
|
316
|
+
const imgAttrs = {
|
|
317
|
+
src: attrs.src,
|
|
318
|
+
style: crop.style
|
|
319
|
+
};
|
|
320
|
+
if (attrs.alt) imgAttrs.alt = attrs.alt;
|
|
321
|
+
if (attrs.title) imgAttrs.title = attrs.title;
|
|
322
|
+
return [
|
|
323
|
+
"span",
|
|
324
|
+
boxAttrs,
|
|
325
|
+
["img", imgAttrs]
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
const htmlAttrs = { ...HTMLAttributes };
|
|
329
|
+
const styles = renderImageStyles(attrs);
|
|
330
|
+
if (styles.length > 0) htmlAttrs.style = styles.join(";");
|
|
331
|
+
attachRawAttrs(htmlAttrs, attrs);
|
|
332
|
+
if (floatAnchor) htmlAttrs["data-float-anchor"] = floatAnchor;
|
|
333
|
+
return ["img", htmlAttrs];
|
|
334
|
+
},
|
|
335
|
+
parseHTML() {
|
|
336
|
+
return [
|
|
337
|
+
{
|
|
338
|
+
tag: "span[data-image=crop]",
|
|
339
|
+
getAttrs: (el) => parseCropDiv(el)
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
tag: "div[data-image=vector]",
|
|
343
|
+
getAttrs: (el) => parseVectorDiv(el)
|
|
344
|
+
},
|
|
345
|
+
{ tag: "img[src]" }
|
|
346
|
+
];
|
|
196
347
|
},
|
|
197
348
|
renderDocx,
|
|
198
349
|
parseDocx
|
|
199
350
|
});
|
|
351
|
+
/**
|
|
352
|
+
* Reverse-parse a cropped span[extent-box] back into image attrs.
|
|
353
|
+
*
|
|
354
|
+
* src/alt/title live on the inner <img>; width/height on the outer box inline
|
|
355
|
+
* style (the extent box, not the img's un-cropped display size).
|
|
356
|
+
* rotation/crop/floating/outline are left to their attribute parseHTML rules,
|
|
357
|
+
* which read the style/data-* the box carries.
|
|
358
|
+
*/
|
|
359
|
+
function parseCropDiv(el) {
|
|
360
|
+
const attrs = {};
|
|
361
|
+
const img = el.querySelector("img");
|
|
362
|
+
if (img) {
|
|
363
|
+
const src = img.getAttribute("src");
|
|
364
|
+
if (src) attrs.src = src;
|
|
365
|
+
const alt = img.getAttribute("alt");
|
|
366
|
+
if (alt) attrs.alt = alt;
|
|
367
|
+
const title = img.getAttribute("title");
|
|
368
|
+
if (title) attrs.title = title;
|
|
369
|
+
}
|
|
370
|
+
const style = el.getAttribute("style") || "";
|
|
371
|
+
const wMatch = style.match(/(?:^|;)\s*width:\s*([\d.]+)px/);
|
|
372
|
+
const hMatch = style.match(/(?:^|;)\s*height:\s*([\d.]+)px/);
|
|
373
|
+
if (wMatch) attrs.width = parseFloat(wMatch[1]);
|
|
374
|
+
if (hMatch) attrs.height = parseFloat(hMatch[1]);
|
|
375
|
+
return attrs;
|
|
376
|
+
}
|
|
377
|
+
/** Reverse-parse an EMF/WMF placeholder div back into image attrs: src from
|
|
378
|
+
* data-vector-src, extent from the inline width/height, alt/title from
|
|
379
|
+
* aria-label/title. Floating/rotation/etc. round-trip via attribute rules. */
|
|
380
|
+
function parseVectorDiv(el) {
|
|
381
|
+
const attrs = {};
|
|
382
|
+
const src = el.getAttribute("data-vector-src");
|
|
383
|
+
if (src) attrs.src = src;
|
|
384
|
+
const style = el.getAttribute("style") || "";
|
|
385
|
+
const wMatch = style.match(/(?:^|;)\s*width:\s*([\d.]+)px/);
|
|
386
|
+
const hMatch = style.match(/(?:^|;)\s*height:\s*([\d.]+)px/);
|
|
387
|
+
if (wMatch) attrs.width = parseFloat(wMatch[1]);
|
|
388
|
+
if (hMatch) attrs.height = parseFloat(hMatch[1]);
|
|
389
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
390
|
+
if (ariaLabel) attrs.alt = ariaLabel;
|
|
391
|
+
const title = el.getAttribute("title");
|
|
392
|
+
if (title) attrs.title = title;
|
|
393
|
+
return attrs;
|
|
394
|
+
}
|
|
200
395
|
//#endregion
|
|
201
396
|
export { Image, parseDocx, renderCropAttrs, renderDocx };
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { n as createDocument, t as Document } from "../document-BH1y4qHM.mjs";
|
|
2
|
+
import { c as PageBreak, f as docxExtensions, l as DocxKit, m as tiptapNodeExtensions, p as tiptapMarkExtensions, u as DocxKitOptions } from "../core-C9VunJ8o.mjs";
|
|
3
|
+
import { b as sectionLinePitchCss, k as twipsToMm, v as resolveFontName, x as sectionMarginCss, y as resolvePageSize } from "../utils-D87vukzp.mjs";
|
|
4
|
+
export { Document, DocxKit, type DocxKitOptions, PageBreak, createDocument, docxExtensions, resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, tiptapMarkExtensions, tiptapNodeExtensions, twipsToMm };
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
1
|
+
import { a as DocxKit, c as tiptapMarkExtensions, g as PageBreak, l as tiptapNodeExtensions, s as docxExtensions } from "../core-DT9IrTzN.mjs";
|
|
2
|
+
import { Document, createDocument } from "./document.mjs";
|
|
3
|
+
import { resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, twipsToMm } from "./utils.mjs";
|
|
4
|
+
export { Document, DocxKit, PageBreak, createDocument, docxExtensions, resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, tiptapMarkExtensions, tiptapNodeExtensions, twipsToMm };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { b as Mention } from "../tiptap-pZsNPsvV.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/extensions/mention.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Mention extension — owns the DOCX expression of an inline mention.
|
|
6
|
+
*
|
|
7
|
+
* A mention is an atom node carrying `{ id, label }`. DOCX has no mention
|
|
8
|
+
* element, but an inline text-SDT (CT_SdtRun) is a reversible carrier: the
|
|
9
|
+
* `id` rides in the SDT alias, the `label` as the SDT's run text, and a fixed
|
|
10
|
+
* tag marks the type so resolve can recover the mention. (customXml would also
|
|
11
|
+
* carry the id but triggers Word's i4i patent warning; SDT does not.)
|
|
12
|
+
*/
|
|
13
|
+
/** SDT tag marking a mention content control. */
|
|
14
|
+
declare const MENTION_TAG = "docen-mention";
|
|
15
|
+
/** Inline text-SDT carrying a mention (id in alias, label as run text). */
|
|
16
|
+
declare function createMention(id: string, label: string): Record<string, unknown>;
|
|
17
|
+
/** True if an inline SDT child carries a mention. */
|
|
18
|
+
declare function isMention(child: unknown): boolean;
|
|
19
|
+
/** Read a mention SDT → `{ id, label }`. */
|
|
20
|
+
declare function readMention(child: unknown): {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { MENTION_TAG, Mention, createMention, isMention, readMention };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Mention } from "./tiptap.mjs";
|
|
2
|
+
//#region src/extensions/mention.ts
|
|
3
|
+
/**
|
|
4
|
+
* Mention extension — owns the DOCX expression of an inline mention.
|
|
5
|
+
*
|
|
6
|
+
* A mention is an atom node carrying `{ id, label }`. DOCX has no mention
|
|
7
|
+
* element, but an inline text-SDT (CT_SdtRun) is a reversible carrier: the
|
|
8
|
+
* `id` rides in the SDT alias, the `label` as the SDT's run text, and a fixed
|
|
9
|
+
* tag marks the type so resolve can recover the mention. (customXml would also
|
|
10
|
+
* carry the id but triggers Word's i4i patent warning; SDT does not.)
|
|
11
|
+
*/
|
|
12
|
+
/** SDT tag marking a mention content control. */
|
|
13
|
+
const MENTION_TAG = "docen-mention";
|
|
14
|
+
/** Inline text-SDT carrying a mention (id in alias, label as run text). */
|
|
15
|
+
function createMention(id, label) {
|
|
16
|
+
return { sdt: {
|
|
17
|
+
properties: {
|
|
18
|
+
tag: MENTION_TAG,
|
|
19
|
+
alias: id,
|
|
20
|
+
text: {}
|
|
21
|
+
},
|
|
22
|
+
children: [{ text: label }]
|
|
23
|
+
} };
|
|
24
|
+
}
|
|
25
|
+
/** True if an inline SDT child carries a mention. */
|
|
26
|
+
function isMention(child) {
|
|
27
|
+
if (typeof child !== "object" || child === null || !("sdt" in child)) return false;
|
|
28
|
+
return child.sdt?.properties?.tag === MENTION_TAG;
|
|
29
|
+
}
|
|
30
|
+
/** Read a mention SDT → `{ id, label }`. */
|
|
31
|
+
function readMention(child) {
|
|
32
|
+
const sdt = child.sdt;
|
|
33
|
+
const id = sdt?.properties?.alias ?? "";
|
|
34
|
+
let label = "";
|
|
35
|
+
const first = sdt?.children?.[0];
|
|
36
|
+
if (typeof first === "string") label = first;
|
|
37
|
+
else if (first && typeof first === "object" && "text" in first) label = String(first.text ?? "");
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
label
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { MENTION_TAG, Mention, createMention, isMention, readMention };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { x as OrderedList } from "../tiptap-pZsNPsvV.mjs";
|
|
2
|
+
import { LevelsOptions } from "@office-open/docx";
|
|
3
|
+
|
|
4
|
+
//#region src/extensions/ordered-list.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* OrderedList extension — owns the DOCX expression of an ordered list.
|
|
7
|
+
*
|
|
8
|
+
* A Tiptap orderedList maps to a sequence of paragraphs referencing one
|
|
9
|
+
* abstractNum (decimal). The reference is keyed by `start` so lists with the
|
|
10
|
+
* same start share a definition; DocxManager gives each list its own instance
|
|
11
|
+
* for independent counting. This module owns that abstractNum shape; DocxManager
|
|
12
|
+
* owns the cross-paragraph tree walk, start recovery, and numbering-instance
|
|
13
|
+
* bookkeeping.
|
|
14
|
+
*/
|
|
15
|
+
/** Reference prefix for generated ordered-list abstractNum definitions. */
|
|
16
|
+
declare const ORDERED_REFERENCE_PREFIX = "docen-ordered";
|
|
17
|
+
/** lvlText per nesting depth (level 0 → "%1.", … level 8 → "%9."). */
|
|
18
|
+
declare const ORDERED_LEVEL_TEXT: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Build nine decimal numbering levels for an ordered list. Level 0 carries
|
|
21
|
+
* `start`; deeper levels restart at 1 (Word convention).
|
|
22
|
+
*/
|
|
23
|
+
declare function buildOrderedLevels(start: number): LevelsOptions[];
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ORDERED_LEVEL_TEXT, ORDERED_REFERENCE_PREFIX, OrderedList, buildOrderedLevels };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { OrderedList } from "./tiptap.mjs";
|
|
2
|
+
import { LevelFormat } from "@office-open/docx";
|
|
3
|
+
//#region src/extensions/ordered-list.ts
|
|
4
|
+
/**
|
|
5
|
+
* OrderedList extension — owns the DOCX expression of an ordered list.
|
|
6
|
+
*
|
|
7
|
+
* A Tiptap orderedList maps to a sequence of paragraphs referencing one
|
|
8
|
+
* abstractNum (decimal). The reference is keyed by `start` so lists with the
|
|
9
|
+
* same start share a definition; DocxManager gives each list its own instance
|
|
10
|
+
* for independent counting. This module owns that abstractNum shape; DocxManager
|
|
11
|
+
* owns the cross-paragraph tree walk, start recovery, and numbering-instance
|
|
12
|
+
* bookkeeping.
|
|
13
|
+
*/
|
|
14
|
+
/** Reference prefix for generated ordered-list abstractNum definitions. */
|
|
15
|
+
const ORDERED_REFERENCE_PREFIX = "docen-ordered";
|
|
16
|
+
/** lvlText per nesting depth (level 0 → "%1.", … level 8 → "%9."). */
|
|
17
|
+
const ORDERED_LEVEL_TEXT = [
|
|
18
|
+
"%1.",
|
|
19
|
+
"%2.",
|
|
20
|
+
"%3.",
|
|
21
|
+
"%4.",
|
|
22
|
+
"%5.",
|
|
23
|
+
"%6.",
|
|
24
|
+
"%7.",
|
|
25
|
+
"%8.",
|
|
26
|
+
"%9."
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Build nine decimal numbering levels for an ordered list. Level 0 carries
|
|
30
|
+
* `start`; deeper levels restart at 1 (Word convention).
|
|
31
|
+
*/
|
|
32
|
+
function buildOrderedLevels(start) {
|
|
33
|
+
return Array.from({ length: 9 }, (_, level) => ({
|
|
34
|
+
level,
|
|
35
|
+
format: LevelFormat.DECIMAL,
|
|
36
|
+
start: level === 0 ? start : 1,
|
|
37
|
+
text: ORDERED_LEVEL_TEXT[level]
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { ORDERED_LEVEL_TEXT, ORDERED_REFERENCE_PREFIX, OrderedList, buildOrderedLevels };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as parseDocx, r as renderDocx, t as Paragraph } from "../paragraph-
|
|
1
|
+
import { n as parseDocx, r as renderDocx, t as Paragraph } from "../paragraph-D8mpHo_o.mjs";
|
|
2
2
|
export { Paragraph, parseDocx, renderDocx };
|
|
@@ -9,19 +9,36 @@ import { alignmentFromElement, bordersFromElement, indentFromElement, renderPara
|
|
|
9
9
|
* near-identity: renderDocx/parseDocx pass attrs through; CSS conversion happens
|
|
10
10
|
* only in renderHTML via utils mappers.
|
|
11
11
|
*/
|
|
12
|
+
const SECTION_ATTR_KEYS = new Set([
|
|
13
|
+
"sectionProperties",
|
|
14
|
+
"sectionHeaders",
|
|
15
|
+
"sectionFooters"
|
|
16
|
+
]);
|
|
12
17
|
function renderDocx(node) {
|
|
13
18
|
const attrs = node.attrs ?? {};
|
|
14
19
|
const opts = {};
|
|
15
|
-
for (const [key, value] of Object.entries(attrs))
|
|
20
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
21
|
+
if (value === null || value === void 0) continue;
|
|
22
|
+
if (SECTION_ATTR_KEYS.has(key)) continue;
|
|
23
|
+
if (key === "styleId") {
|
|
24
|
+
opts.style = value;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
opts[key] = value;
|
|
28
|
+
}
|
|
16
29
|
return opts;
|
|
17
30
|
}
|
|
18
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Structural/semantic keys handled elsewhere (heading ext, list handling, run/text
|
|
33
|
+
* children). NOTE: `run` is intentionally NOT skipped — ParagraphOptions.run (the
|
|
34
|
+
* paragraph's default run properties: font/size/color) is kept as an attr for
|
|
35
|
+
* lossless round-trip (e.g. header/footer paragraphs whose styling lives there).
|
|
36
|
+
*/
|
|
19
37
|
const SKIP_KEYS = new Set([
|
|
20
38
|
"heading",
|
|
21
39
|
"style",
|
|
22
40
|
"bullet",
|
|
23
41
|
"numbering",
|
|
24
|
-
"run",
|
|
25
42
|
"children",
|
|
26
43
|
"text",
|
|
27
44
|
"thematicBreak"
|
|
@@ -29,6 +46,7 @@ const SKIP_KEYS = new Set([
|
|
|
29
46
|
function parseDocx(opts) {
|
|
30
47
|
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
31
48
|
const attrs = {};
|
|
49
|
+
if (resolved.style) attrs.styleId = resolved.style;
|
|
32
50
|
for (const [key, value] of Object.entries(resolved)) {
|
|
33
51
|
if (SKIP_KEYS.has(key)) continue;
|
|
34
52
|
attrs[key] = value ?? null;
|
|
@@ -44,6 +62,13 @@ const Paragraph = Paragraph$1.extend({
|
|
|
44
62
|
addAttributes() {
|
|
45
63
|
return {
|
|
46
64
|
...this.parent?.(),
|
|
65
|
+
styleId: {
|
|
66
|
+
default: null,
|
|
67
|
+
parseHTML: (el) => {
|
|
68
|
+
const m = (el.getAttribute("class") || "").match(/(?:^|\s)docx-style-(\S+)/);
|
|
69
|
+
return m ? m[1] : null;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
47
72
|
alignment: {
|
|
48
73
|
default: null,
|
|
49
74
|
rendered: false,
|
|
@@ -70,6 +95,10 @@ const Paragraph = Paragraph$1.extend({
|
|
|
70
95
|
parseHTML: (el) => bordersFromElement(el)
|
|
71
96
|
},
|
|
72
97
|
frame: attrNative(),
|
|
98
|
+
run: attrNative(),
|
|
99
|
+
sectionProperties: attrNative(),
|
|
100
|
+
sectionHeaders: attrNative(),
|
|
101
|
+
sectionFooters: attrNative(),
|
|
73
102
|
keepNext: attrNative(),
|
|
74
103
|
keepLines: attrNative(),
|
|
75
104
|
pageBreakBefore: attrNative(),
|
|
@@ -100,8 +129,14 @@ const Paragraph = Paragraph$1.extend({
|
|
|
100
129
|
};
|
|
101
130
|
},
|
|
102
131
|
renderHTML({ node, HTMLAttributes }) {
|
|
103
|
-
|
|
132
|
+
let hasContent = false;
|
|
133
|
+
node.forEach?.((child) => {
|
|
134
|
+
if (child.isText || child.type?.name === "hardBreak" || child.type?.name === "image") hasContent = true;
|
|
135
|
+
});
|
|
136
|
+
const styles = renderParagraphStyles(node.attrs, { empty: !hasContent });
|
|
104
137
|
const attrs = { ...HTMLAttributes };
|
|
138
|
+
const styleId = node.attrs.styleId;
|
|
139
|
+
attrs.class = styleId ? `docx-style-${styleId}` : "docx-default";
|
|
105
140
|
if (styles.length > 0) attrs.style = styles.join(";");
|
|
106
141
|
return [
|
|
107
142
|
"p",
|