@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.
Files changed (74) hide show
  1. package/README.md +2 -3
  2. package/dist/converters/docx.d.mts +95 -5
  3. package/dist/converters/docx.mjs +732 -82
  4. package/dist/converters/html.d.mts +20 -5
  5. package/dist/converters/html.mjs +94 -8
  6. package/dist/converters/markdown.d.mts +1 -1
  7. package/dist/converters/markdown.mjs +1 -1
  8. package/dist/converters/patch.d.mts +1 -1
  9. package/dist/converters/patch.mjs +4 -5
  10. package/dist/converters/prepare.d.mts +15 -3
  11. package/dist/converters/prepare.mjs +50 -4
  12. package/dist/converters/styles.d.mts +85 -0
  13. package/dist/converters/styles.mjs +285 -0
  14. package/dist/core-C9VunJ8o.d.mts +174 -0
  15. package/dist/core-DT9IrTzN.mjs +670 -0
  16. package/dist/core.d.mts +1 -1
  17. package/dist/core.mjs +1 -1
  18. package/dist/document-BH1y4qHM.d.mts +6 -0
  19. package/dist/editor.d.mts +1 -1
  20. package/dist/editor.mjs +3 -2
  21. package/dist/extensions/blockquote.d.mts +23 -0
  22. package/dist/extensions/blockquote.mjs +31 -0
  23. package/dist/extensions/code-block.d.mts +19 -0
  24. package/dist/extensions/code-block.mjs +23 -0
  25. package/dist/extensions/column-break.d.mts +31 -0
  26. package/dist/extensions/column-break.mjs +2 -0
  27. package/dist/extensions/details.d.mts +19 -0
  28. package/dist/extensions/details.mjs +18 -0
  29. package/dist/extensions/document.d.mts +2 -0
  30. package/dist/extensions/document.mjs +50 -0
  31. package/dist/extensions/extensions.d.mts +5 -5
  32. package/dist/extensions/extensions.mjs +4 -3
  33. package/dist/extensions/formatting-marks.d.mts +38 -0
  34. package/dist/extensions/formatting-marks.mjs +2 -0
  35. package/dist/extensions/heading.mjs +24 -3
  36. package/dist/extensions/image.d.mts +2 -2
  37. package/dist/extensions/image.mjs +244 -49
  38. package/dist/extensions/index.d.mts +4 -2
  39. package/dist/extensions/index.mjs +4 -2
  40. package/dist/extensions/mention.d.mts +25 -0
  41. package/dist/extensions/mention.mjs +44 -0
  42. package/dist/extensions/ordered-list.d.mts +25 -0
  43. package/dist/extensions/ordered-list.mjs +41 -0
  44. package/dist/extensions/page-break.d.mts +2 -0
  45. package/dist/extensions/page-break.mjs +2 -0
  46. package/dist/extensions/paragraph.d.mts +1 -1
  47. package/dist/extensions/paragraph.mjs +39 -4
  48. package/dist/extensions/passthrough.d.mts +2 -0
  49. package/dist/extensions/passthrough.mjs +2 -0
  50. package/dist/extensions/section-break.d.mts +44 -0
  51. package/dist/extensions/section-break.mjs +2 -0
  52. package/dist/extensions/table.mjs +29 -3
  53. package/dist/extensions/task-item.d.mts +27 -0
  54. package/dist/extensions/task-item.mjs +37 -0
  55. package/dist/extensions/text-style.mjs +20 -3
  56. package/dist/extensions/tiptap.d.mts +2 -2
  57. package/dist/extensions/tiptap.mjs +1 -3
  58. package/dist/extensions/types.d.mts +7 -5
  59. package/dist/extensions/utils.d.mts +2 -49
  60. package/dist/extensions/utils.mjs +222 -12
  61. package/dist/extensions/wpg-group.d.mts +2 -0
  62. package/dist/extensions/wpg-group.mjs +2 -0
  63. package/dist/extensions/wps-shape.d.mts +2 -0
  64. package/dist/extensions/wps-shape.mjs +2 -0
  65. package/dist/index.d.mts +127 -18
  66. package/dist/index.mjs +7 -2
  67. package/dist/paragraph-D8mpHo_o.d.mts +8 -0
  68. package/dist/{tiptap-TErPjuNJ.d.mts → tiptap-pZsNPsvV.d.mts} +1 -3
  69. package/dist/utils-D87vukzp.d.mts +128 -0
  70. package/package.json +40 -40
  71. package/dist/core-CFIQVRfx.mjs +0 -88
  72. package/dist/core-omBKMRtl.d.mts +0 -34
  73. package/dist/image-Ge1y6uam.d.mts +0 -47
  74. 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: attrs.width ?? 600,
43
- height: attrs.height ?? 400
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.srcRect = attrs.crop;
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.srcRect) attrs.crop = opts.srcRect;
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
- function renderCropAttrs(crop) {
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 visibleWidthPct = 1 - leftPct - rightPct;
90
- const visibleHeightPct = 1 - topPct - bottomPct;
91
- const posX = visibleWidthPct > 0 ? leftPct / visibleWidthPct * 100 : 0;
92
- const posY = visibleHeightPct > 0 ? topPct / visibleHeightPct * 100 : 0;
93
- return { style: `object-fit:cover;object-position:${posX.toFixed(2)}% ${posY.toFixed(2)}%` };
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
- const f = attrs.floating;
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 styles = renderImageStyles(node.attrs);
190
- const attrs = { ...HTMLAttributes };
191
- if (styles.length > 0) attrs.style = styles.join(";");
192
- if (node.attrs.floating) attrs["data-floating"] = JSON.stringify(node.attrs.floating);
193
- if (node.attrs.outline) attrs["data-outline"] = JSON.stringify(node.attrs.outline);
194
- if (node.attrs.crop) attrs["data-crop"] = JSON.stringify(node.attrs.crop);
195
- return ["img", attrs];
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 { c as StarterKit, d as docxExtensions, f as tiptapMarkExtensions, l as StarterKitOptions, p as tiptapNodeExtensions } from "../core-omBKMRtl.mjs";
2
- export { StarterKit, type StarterKitOptions, docxExtensions, tiptapMarkExtensions, tiptapNodeExtensions };
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 StarterKit, c as tiptapMarkExtensions, l as tiptapNodeExtensions, s as docxExtensions } from "../core-CFIQVRfx.mjs";
2
- export { StarterKit, docxExtensions, tiptapMarkExtensions, tiptapNodeExtensions };
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 };
@@ -0,0 +1,2 @@
1
+ import { c as PageBreak } from "../core-C9VunJ8o.mjs";
2
+ export { PageBreak };
@@ -0,0 +1,2 @@
1
+ import { g as PageBreak } from "../core-DT9IrTzN.mjs";
2
+ export { PageBreak };
@@ -1,2 +1,2 @@
1
- import { n as parseDocx, r as renderDocx, t as Paragraph } from "../paragraph-fhEXtAN2.mjs";
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)) if (value !== null && value !== void 0) opts[key] = value;
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
- /** Structural/semantic keys expressed elsewhere (heading ext, list handling, run children). */
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
- const styles = renderParagraphStyles(node.attrs);
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",
@@ -0,0 +1,2 @@
1
+ import { h as Passthrough } from "../core-C9VunJ8o.mjs";
2
+ export { Passthrough };
@@ -0,0 +1,2 @@
1
+ import { h as Passthrough } from "../core-DT9IrTzN.mjs";
2
+ export { Passthrough };