@docen/docx 0.3.1 → 0.3.3
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/converters/docx.d.mts +220 -1
- package/dist/converters/docx.mjs +1352 -1
- package/dist/converters/html.d.mts +28 -1
- package/dist/converters/html.mjs +103 -1
- package/dist/converters/markdown.d.mts +12 -1
- package/dist/converters/markdown.mjs +17 -1
- package/dist/converters/patch.d.mts +52 -1
- package/dist/converters/patch.mjs +35 -1
- package/dist/converters/prepare.d.mts +62 -1
- package/dist/converters/prepare.mjs +121 -1
- package/dist/converters/styles.d.mts +91 -2
- package/dist/converters/styles.mjs +308 -2
- package/dist/core-B8ba_FNi.mjs +717 -0
- package/dist/core-DRaLI8nd.d.mts +203 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/document-Cws7XTYL.d.mts +28 -0
- package/dist/editor.d.mts +20 -1
- package/dist/editor.mjs +20 -1
- package/dist/extensions/blockquote.d.mts +1 -1
- package/dist/extensions/blockquote.mjs +30 -2
- package/dist/extensions/code-block.d.mts +1 -1
- package/dist/extensions/code-block.mjs +22 -1
- package/dist/extensions/column-break.d.mts +1 -1
- package/dist/extensions/column-break.mjs +1 -1
- package/dist/extensions/details.d.mts +1 -1
- package/dist/extensions/details.mjs +17 -2
- package/dist/extensions/document.d.mts +1 -1
- package/dist/extensions/document.mjs +45 -1
- package/dist/extensions/extensions.d.mts +4 -5
- package/dist/extensions/extensions.mjs +13 -14
- package/dist/extensions/formatting-marks.d.mts +7 -1
- package/dist/extensions/formatting-marks.mjs +1 -1
- package/dist/extensions/heading.mjs +187 -1
- package/dist/extensions/image.d.mts +1 -1
- package/dist/extensions/image.mjs +395 -1
- package/dist/extensions/index.d.mts +4 -3
- package/dist/extensions/index.mjs +4 -3
- package/dist/extensions/mention.d.mts +1 -1
- package/dist/extensions/mention.mjs +43 -2
- package/dist/extensions/ordered-list.d.mts +1 -1
- package/dist/extensions/ordered-list.mjs +40 -2
- package/dist/extensions/page-break.d.mts +1 -33
- package/dist/extensions/page-break.mjs +1 -1
- package/dist/extensions/paragraph.mjs +145 -1
- package/dist/extensions/passthrough.d.mts +1 -1
- package/dist/extensions/passthrough.mjs +1 -1
- package/dist/extensions/section-break.d.mts +19 -2
- package/dist/extensions/section-break.mjs +1 -1
- package/dist/extensions/strike.mjs +45 -1
- package/dist/extensions/table-cell.mjs +106 -1
- package/dist/extensions/table-header.mjs +106 -1
- package/dist/extensions/table-row.mjs +78 -1
- package/dist/extensions/table.mjs +154 -1
- package/dist/extensions/task-item.d.mts +1 -1
- package/dist/extensions/task-item.mjs +36 -2
- package/dist/extensions/text-style.mjs +145 -1
- package/dist/extensions/tiptap.d.mts +2 -2
- package/dist/extensions/tiptap.mjs +31 -2
- package/dist/extensions/toc-field.d.mts +2 -0
- package/dist/extensions/toc-field.mjs +2 -0
- package/dist/extensions/utils.d.mts +2 -62
- package/dist/extensions/utils.mjs +508 -2
- 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 +101 -29
- package/dist/index.mjs +13 -10
- package/dist/{tiptap-TErPjuNJ.d.mts → tiptap-pZsNPsvV.d.mts} +1 -3
- package/dist/utils-BJwDQts7.d.mts +137 -0
- package/package.json +39 -40
- package/dist/blockquote-Ky9xD_Q-.mjs +0 -31
- package/dist/code-block-BgNjhADy.mjs +0 -23
- package/dist/core-DEqBHRzE.mjs +0 -406
- package/dist/core-fBMCCj4D.d.mts +0 -97
- package/dist/details-Dh8luWb2.mjs +0 -18
- package/dist/dist-B6B3HnVZ.mjs +0 -1838
- package/dist/document-BH1y4qHM.d.mts +0 -6
- package/dist/document-CvTym-FM.mjs +0 -50
- package/dist/docx-C5wyjI-0.d.mts +0 -200
- package/dist/docx-CONyAedl.mjs +0 -1231
- package/dist/editor-Bf3CxQnY.mjs +0 -21
- package/dist/editor-DReY3Rb9.d.mts +0 -21
- package/dist/emoji-BZ1dJD7d.d.mts +0 -17
- package/dist/emoji-C2OsTWR7.mjs +0 -27
- package/dist/extensions/emoji.d.mts +0 -2
- package/dist/extensions/emoji.mjs +0 -2
- package/dist/extensions/image-group.d.mts +0 -6
- package/dist/extensions/image-group.mjs +0 -2
- package/dist/heading-DAubgJJN.mjs +0 -166
- package/dist/html-DCC_DSHv.d.mts +0 -18
- package/dist/html-DedDWPoO.mjs +0 -66
- package/dist/image-CuEIXoZl.mjs +0 -334
- package/dist/markdown-BzJmRsBs.mjs +0 -18
- package/dist/markdown-D0k0sRYS.d.mts +0 -13
- package/dist/mention-C-dTR6MK.mjs +0 -44
- package/dist/ordered-list-V-3xFQmV.mjs +0 -41
- package/dist/paragraph-BrWH-BOV.mjs +0 -147
- package/dist/patch-Cdp5WKJz.d.mts +0 -53
- package/dist/patch-IZ13tXTu.mjs +0 -36
- package/dist/prepare-Bdk8Cjkf.mjs +0 -122
- package/dist/prepare-CPAvcv6q.d.mts +0 -63
- package/dist/strike-hWxShOgb.mjs +0 -50
- package/dist/styles-QSX3RhxK.mjs +0 -285
- package/dist/styles-ugspkrxz.d.mts +0 -85
- package/dist/table-DrA12lxX.mjs +0 -159
- package/dist/table-cell-DmkfYwDc.mjs +0 -112
- package/dist/table-header-Bwr5GKWg.mjs +0 -112
- package/dist/table-row-vew1y7WH.mjs +0 -84
- package/dist/task-item-BnlXGrIG.mjs +0 -37
- package/dist/text-style-tMeBq5XQ.mjs +0 -151
- package/dist/tiptap-D7pl4I5U.mjs +0 -33
- package/dist/utils-BPT4tjcn.mjs +0 -333
|
@@ -1,2 +1,188 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Heading as Heading$1 } from "./tiptap.mjs";
|
|
2
|
+
import { alignmentFromElement, attrNative, bordersFromElement, indentFromElement, renderParagraphStyles, shadingFromElement, spacingFromElement } from "./utils.mjs";
|
|
3
|
+
//#region src/extensions/heading.ts
|
|
4
|
+
/**
|
|
5
|
+
* Heading extension with nested office-open attrs (mirrors Paragraph).
|
|
6
|
+
*
|
|
7
|
+
* Attrs mirror ParagraphPropertiesOptionsBase (alignment/indent/spacing/border/
|
|
8
|
+
* shading/frame as nested objects + scalar OOXML properties) plus the inherited
|
|
9
|
+
* Tiptap `level` (1-6, rendered: false). DOCX round-trip is near-identity:
|
|
10
|
+
* renderDocx/parseDocx pass attrs through and only map `level` ↔ OOXML
|
|
11
|
+
* `heading` (HeadingLevel literal). CSS conversion happens solely in
|
|
12
|
+
* renderHTML via utils mappers.
|
|
13
|
+
*/
|
|
14
|
+
const HEADING_COMPILE_MAP = {
|
|
15
|
+
1: "Heading1",
|
|
16
|
+
2: "Heading2",
|
|
17
|
+
3: "Heading3",
|
|
18
|
+
4: "Heading4",
|
|
19
|
+
5: "Heading5",
|
|
20
|
+
6: "Heading6",
|
|
21
|
+
7: "Heading7",
|
|
22
|
+
8: "Heading8",
|
|
23
|
+
9: "Heading9"
|
|
24
|
+
};
|
|
25
|
+
const HEADING_PARSE_MAP = {
|
|
26
|
+
Heading1: 1,
|
|
27
|
+
Heading2: 2,
|
|
28
|
+
Heading3: 3,
|
|
29
|
+
Heading4: 4,
|
|
30
|
+
Heading5: 5,
|
|
31
|
+
Heading6: 6,
|
|
32
|
+
Heading7: 7,
|
|
33
|
+
Heading8: 8,
|
|
34
|
+
Heading9: 9,
|
|
35
|
+
Title: 1
|
|
36
|
+
};
|
|
37
|
+
/** Runtime-only attrs the TableOfContents extension injects on each heading
|
|
38
|
+
* (`id` / `data-toc-id`). They are regenerated on every load, so never persist
|
|
39
|
+
* them to DOCX — skip them in renderDocx. */
|
|
40
|
+
const TOC_RUNTIME_KEYS = new Set(["id", "data-toc-id"]);
|
|
41
|
+
function renderDocx(node) {
|
|
42
|
+
const attrs = node.attrs ?? {};
|
|
43
|
+
const opts = {};
|
|
44
|
+
const level = typeof attrs.level === "number" ? attrs.level : void 0;
|
|
45
|
+
const styleId = attrs.styleId;
|
|
46
|
+
if (styleId) opts.heading = styleId;
|
|
47
|
+
else if (level) opts.heading = HEADING_COMPILE_MAP[level] ?? "Heading1";
|
|
48
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
49
|
+
if (key === "level" || key === "styleId" || TOC_RUNTIME_KEYS.has(key)) continue;
|
|
50
|
+
if (value !== null && value !== void 0) opts[key] = value;
|
|
51
|
+
}
|
|
52
|
+
return opts;
|
|
53
|
+
}
|
|
54
|
+
/** Structural/semantic keys expressed elsewhere (heading handled separately, list handling, run children). */
|
|
55
|
+
const SKIP_KEYS = new Set([
|
|
56
|
+
"heading",
|
|
57
|
+
"style",
|
|
58
|
+
"bullet",
|
|
59
|
+
"numbering",
|
|
60
|
+
"run",
|
|
61
|
+
"children",
|
|
62
|
+
"text",
|
|
63
|
+
"thematicBreak"
|
|
64
|
+
]);
|
|
65
|
+
function parseDocx(opts) {
|
|
66
|
+
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
67
|
+
const attrs = {};
|
|
68
|
+
if (resolved.heading) {
|
|
69
|
+
const level = HEADING_PARSE_MAP[resolved.heading];
|
|
70
|
+
if (level) attrs.level = level;
|
|
71
|
+
attrs.styleId = resolved.heading;
|
|
72
|
+
}
|
|
73
|
+
if (resolved.style) attrs.styleId = resolved.style;
|
|
74
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
75
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
76
|
+
attrs[key] = value ?? null;
|
|
77
|
+
}
|
|
78
|
+
return attrs;
|
|
79
|
+
}
|
|
80
|
+
const Heading = Heading$1.extend({
|
|
81
|
+
parseHTML() {
|
|
82
|
+
return [{
|
|
83
|
+
tag: "h6[data-heading-level]",
|
|
84
|
+
getAttrs: (el) => {
|
|
85
|
+
const level = Number(el.getAttribute("data-heading-level"));
|
|
86
|
+
return { level: Number.isInteger(level) && level >= 1 && level <= 9 ? level : 6 };
|
|
87
|
+
}
|
|
88
|
+
}, ...[
|
|
89
|
+
1,
|
|
90
|
+
2,
|
|
91
|
+
3,
|
|
92
|
+
4,
|
|
93
|
+
5,
|
|
94
|
+
6
|
|
95
|
+
].map((level) => ({
|
|
96
|
+
tag: `h${level}`,
|
|
97
|
+
attrs: { level }
|
|
98
|
+
}))];
|
|
99
|
+
},
|
|
100
|
+
addAttributes() {
|
|
101
|
+
return {
|
|
102
|
+
...this.parent?.(),
|
|
103
|
+
styleId: {
|
|
104
|
+
default: null,
|
|
105
|
+
parseHTML: (el) => {
|
|
106
|
+
const m = (el.getAttribute("class") || "").match(/(?:^|\s)docx-style-(\S+)/);
|
|
107
|
+
return m ? m[1] : null;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
alignment: {
|
|
111
|
+
default: null,
|
|
112
|
+
rendered: false,
|
|
113
|
+
parseHTML: (el) => alignmentFromElement(el)
|
|
114
|
+
},
|
|
115
|
+
indent: {
|
|
116
|
+
default: null,
|
|
117
|
+
rendered: false,
|
|
118
|
+
parseHTML: (el) => indentFromElement(el)
|
|
119
|
+
},
|
|
120
|
+
spacing: {
|
|
121
|
+
default: null,
|
|
122
|
+
rendered: false,
|
|
123
|
+
parseHTML: (el) => spacingFromElement(el)
|
|
124
|
+
},
|
|
125
|
+
shading: {
|
|
126
|
+
default: null,
|
|
127
|
+
rendered: false,
|
|
128
|
+
parseHTML: (el) => shadingFromElement(el)
|
|
129
|
+
},
|
|
130
|
+
border: {
|
|
131
|
+
default: null,
|
|
132
|
+
rendered: false,
|
|
133
|
+
parseHTML: (el) => bordersFromElement(el)
|
|
134
|
+
},
|
|
135
|
+
frame: attrNative(),
|
|
136
|
+
keepNext: attrNative(),
|
|
137
|
+
keepLines: attrNative(),
|
|
138
|
+
pageBreakBefore: attrNative(),
|
|
139
|
+
widowControl: attrNative(),
|
|
140
|
+
contextualSpacing: attrNative(),
|
|
141
|
+
bidirectional: attrNative(),
|
|
142
|
+
outlineLevel: attrNative(),
|
|
143
|
+
textDirection: attrNative(),
|
|
144
|
+
textAlignment: attrNative(),
|
|
145
|
+
suppressLineNumbers: attrNative(),
|
|
146
|
+
wordWrap: attrNative(),
|
|
147
|
+
overflowPunctuation: attrNative(),
|
|
148
|
+
autoSpaceEastAsianText: attrNative(),
|
|
149
|
+
suppressOverlap: attrNative(),
|
|
150
|
+
suppressAutoHyphens: attrNative(),
|
|
151
|
+
adjustRightInd: attrNative(),
|
|
152
|
+
snapToGrid: attrNative(),
|
|
153
|
+
mirrorIndents: attrNative(),
|
|
154
|
+
kinsoku: attrNative(),
|
|
155
|
+
topLinePunct: attrNative(),
|
|
156
|
+
autoSpaceDE: attrNative(),
|
|
157
|
+
textboxTightWrap: attrNative(),
|
|
158
|
+
rightTabStop: attrNative(),
|
|
159
|
+
leftTabStop: attrNative(),
|
|
160
|
+
divId: attrNative(),
|
|
161
|
+
tabStops: attrNative(),
|
|
162
|
+
cnfStyle: attrNative()
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
166
|
+
let hasContent = false;
|
|
167
|
+
node.forEach?.((child) => {
|
|
168
|
+
if (child.isText || child.type?.name === "hardBreak" || child.type?.name === "image") hasContent = true;
|
|
169
|
+
});
|
|
170
|
+
const styles = renderParagraphStyles(node.attrs, { empty: !hasContent });
|
|
171
|
+
const level = node.attrs?.level ?? 1;
|
|
172
|
+
const attrs = { ...HTMLAttributes };
|
|
173
|
+
const styleId = node.attrs.styleId;
|
|
174
|
+
if (styleId) attrs.class = `docx-style-${styleId}`;
|
|
175
|
+
if (styles.length > 0) attrs.style = styles.join(";");
|
|
176
|
+
const tag = level >= 1 && level <= 6 ? `h${level}` : "h6";
|
|
177
|
+
if (level >= 7) attrs["data-heading-level"] = String(level);
|
|
178
|
+
return [
|
|
179
|
+
tag,
|
|
180
|
+
attrs,
|
|
181
|
+
0
|
|
182
|
+
];
|
|
183
|
+
},
|
|
184
|
+
renderDocx,
|
|
185
|
+
parseDocx
|
|
186
|
+
});
|
|
187
|
+
//#endregion
|
|
2
188
|
export { Heading, parseDocx, renderDocx };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as Image, E as renderDocx, S as CropRenderContext, T as renderCropAttrs, w as parseDocx } from "../core-DRaLI8nd.mjs";
|
|
2
2
|
export { CropRenderContext, Image, parseDocx, renderCropAttrs, renderDocx };
|
|
@@ -1,2 +1,396 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Image as Image$1 } from "./tiptap.mjs";
|
|
2
|
+
import { floatAnchorScope, floatingToStyles } from "./utils.mjs";
|
|
3
|
+
import { convertEmuToPixels } from "@office-open/core";
|
|
4
|
+
//#region src/extensions/image.ts
|
|
5
|
+
/**
|
|
6
|
+
* Custom Image extension with node-level renderHTML + renderDocx/parseDocx.
|
|
7
|
+
*
|
|
8
|
+
* Attrs:
|
|
9
|
+
* - src/alt/title/width/height: Tiptap structural names (kept verbatim so base
|
|
10
|
+
* image commands work).
|
|
11
|
+
* - rotation: editor display only (CSS transform) but also carried through DOCX
|
|
12
|
+
* via transformation.rotation (MediaTransformation.rotation).
|
|
13
|
+
* - floating/outline: nested office-open objects (Floating / OutlineOptions).
|
|
14
|
+
* - crop: nested office-open SourceRectangleOptions (srcRect).
|
|
15
|
+
* - display: editor-only display hint, no OOXML equivalent.
|
|
16
|
+
*
|
|
17
|
+
* DOCX round-trip is near-identity: renderDocx packs attrs into CoreImageOptions;
|
|
18
|
+
* parseDocx unpacks them back. src is a data URL ↔ { type, data } base64.
|
|
19
|
+
* Node-level renderHTML solves the style merge problem (rotation + floating).
|
|
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
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Tiptap JSON image node → CoreImageOptions-shaped object.
|
|
37
|
+
*
|
|
38
|
+
* Returns `{ image: ImageOptions }` (structural wrapper) or null when no
|
|
39
|
+
* embedded image data is available (external URLs need pre-fetching).
|
|
40
|
+
* rotation is carried via transformation.rotation (not dropped).
|
|
41
|
+
*/
|
|
42
|
+
function renderDocx(node) {
|
|
43
|
+
const attrs = node.attrs ?? {};
|
|
44
|
+
const imageOpts = {};
|
|
45
|
+
const src = attrs.src;
|
|
46
|
+
if (src?.startsWith("data:image/")) {
|
|
47
|
+
const match = src.match(/^data:image\/([\w.+-]+);base64,(.+)$/);
|
|
48
|
+
if (match) {
|
|
49
|
+
imageOpts.type = match[1] === "jpeg" ? "jpg" : match[1];
|
|
50
|
+
const binary = atob(match[2]);
|
|
51
|
+
const bytes = new Uint8Array(binary.length);
|
|
52
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
53
|
+
imageOpts.data = bytes;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!imageOpts.data) return null;
|
|
57
|
+
const width = attrs.width ?? 600;
|
|
58
|
+
const height = attrs.height ?? 400;
|
|
59
|
+
const transformation = {
|
|
60
|
+
width: `${width}px`,
|
|
61
|
+
height: `${height}px`
|
|
62
|
+
};
|
|
63
|
+
const rotation = attrs.rotation;
|
|
64
|
+
if (rotation != null) transformation.rotation = rotation;
|
|
65
|
+
imageOpts.transformation = transformation;
|
|
66
|
+
const altText = {};
|
|
67
|
+
if (attrs.alt) altText.name = attrs.alt;
|
|
68
|
+
if (attrs.title) altText.description = attrs.title;
|
|
69
|
+
if (Object.keys(altText).length > 0) imageOpts.altText = altText;
|
|
70
|
+
if (attrs.floating) imageOpts.floating = attrs.floating;
|
|
71
|
+
if (attrs.crop) imageOpts.sourceRectangle = attrs.crop;
|
|
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;
|
|
82
|
+
return { image: imageOpts };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* ImageOptions-shaped object → Tiptap attrs.
|
|
86
|
+
*
|
|
87
|
+
* Near-identity unpack: transformation → width/height/rotation, altText → alt/title,
|
|
88
|
+
* floating/srcRect(→crop)/outline passed through verbatim. src is reconstructed by
|
|
89
|
+
* DocxManager from the image data bytes (kept out of parseDocx).
|
|
90
|
+
*/
|
|
91
|
+
function parseDocx(imageOpts) {
|
|
92
|
+
const opts = imageOpts ?? {};
|
|
93
|
+
const attrs = {};
|
|
94
|
+
const transformation = opts.transformation;
|
|
95
|
+
if (transformation) {
|
|
96
|
+
if (typeof transformation.width === "number") attrs.width = convertEmuToPixels(transformation.width);
|
|
97
|
+
if (typeof transformation.height === "number") attrs.height = convertEmuToPixels(transformation.height);
|
|
98
|
+
if (typeof transformation.rotation === "number") attrs.rotation = transformation.rotation;
|
|
99
|
+
}
|
|
100
|
+
const altText = opts.altText;
|
|
101
|
+
if (altText) {
|
|
102
|
+
if (altText.name) attrs.alt = altText.name;
|
|
103
|
+
if (altText.description) attrs.title = altText.description;
|
|
104
|
+
}
|
|
105
|
+
if (opts.floating) attrs.floating = opts.floating;
|
|
106
|
+
if (opts.sourceRectangle) attrs.crop = opts.sourceRectangle;
|
|
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;
|
|
118
|
+
return attrs;
|
|
119
|
+
}
|
|
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 = {}) {
|
|
132
|
+
const c = crop;
|
|
133
|
+
const leftPct = (c.left || 0) / 1e5;
|
|
134
|
+
const topPct = (c.top || 0) / 1e5;
|
|
135
|
+
const rightPct = (c.right || 0) / 1e5;
|
|
136
|
+
const bottomPct = (c.bottom || 0) / 1e5;
|
|
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(";") };
|
|
152
|
+
}
|
|
153
|
+
function renderImageStyles(attrs) {
|
|
154
|
+
const styles = [];
|
|
155
|
+
if (attrs.display) styles.push(`display:${attrs.display}`);
|
|
156
|
+
else if (!attrs.floating) styles.push("display:inline-block");
|
|
157
|
+
if (attrs.rotation) styles.push(`transform:rotate(${attrs.rotation}deg)`);
|
|
158
|
+
if (attrs.floating) styles.push(...floatingToStyles(attrs.floating, attrs.src, attrs.width));
|
|
159
|
+
return styles;
|
|
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
|
+
}
|
|
192
|
+
const Image = Image$1.extend({
|
|
193
|
+
addAttributes() {
|
|
194
|
+
return {
|
|
195
|
+
...this.parent?.(),
|
|
196
|
+
display: {
|
|
197
|
+
default: null,
|
|
198
|
+
rendered: false,
|
|
199
|
+
parseHTML: () => this.options.inline ? "inline-block" : null
|
|
200
|
+
},
|
|
201
|
+
rotation: {
|
|
202
|
+
default: null,
|
|
203
|
+
rendered: false,
|
|
204
|
+
parseHTML: (element) => {
|
|
205
|
+
const match = (element.getAttribute("style") || "").match(/transform:\s*rotate\(([\d.]+)deg\)/);
|
|
206
|
+
return match ? parseFloat(match[1]) : null;
|
|
207
|
+
}
|
|
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
|
+
} },
|
|
227
|
+
floating: {
|
|
228
|
+
default: null,
|
|
229
|
+
rendered: false,
|
|
230
|
+
parseHTML: (element) => {
|
|
231
|
+
const raw = element.getAttribute("data-floating");
|
|
232
|
+
if (!raw) return null;
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(raw);
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
outline: {
|
|
241
|
+
default: null,
|
|
242
|
+
rendered: false,
|
|
243
|
+
parseHTML: (element) => {
|
|
244
|
+
const raw = element.getAttribute("data-outline");
|
|
245
|
+
if (!raw) return null;
|
|
246
|
+
try {
|
|
247
|
+
return JSON.parse(raw);
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
crop: {
|
|
254
|
+
default: null,
|
|
255
|
+
rendered: false,
|
|
256
|
+
parseHTML: (element) => {
|
|
257
|
+
const raw = element.getAttribute("data-crop");
|
|
258
|
+
if (!raw) return null;
|
|
259
|
+
try {
|
|
260
|
+
return JSON.parse(raw);
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
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")
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
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
|
+
];
|
|
347
|
+
},
|
|
348
|
+
renderDocx,
|
|
349
|
+
parseDocx
|
|
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
|
+
}
|
|
395
|
+
//#endregion
|
|
2
396
|
export { Image, parseDocx, renderCropAttrs, renderDocx };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { n as createDocument, t as Document } from "../document-
|
|
2
|
-
import { c as
|
|
3
|
-
|
|
1
|
+
import { n as createDocument, t as Document } from "../document-Cws7XTYL.mjs";
|
|
2
|
+
import { c as PageBreak, f as docxExtensions, l as DocxKit, m as tiptapNodeExtensions, p as tiptapMarkExtensions, u as DocxKitOptions } from "../core-DRaLI8nd.mjs";
|
|
3
|
+
import { A as twipsToMm, S as sectionMarginCss, b as resolvePageSize, x as sectionLinePitchCss, y as resolveFontName } from "../utils-BJwDQts7.mjs";
|
|
4
|
+
export { Document, DocxKit, type DocxKitOptions, PageBreak, createDocument, docxExtensions, resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, tiptapMarkExtensions, tiptapNodeExtensions, twipsToMm };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { a as DocxKit, c as tiptapMarkExtensions, l as tiptapNodeExtensions, s as docxExtensions } from "../core-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { _ as PageBreak, a as DocxKit, c as tiptapMarkExtensions, l as tiptapNodeExtensions, s as docxExtensions } from "../core-B8ba_FNi.mjs";
|
|
2
|
+
import { resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, twipsToMm } from "./utils.mjs";
|
|
3
|
+
import { Document, createDocument } from "./document.mjs";
|
|
4
|
+
export { Document, DocxKit, PageBreak, createDocument, docxExtensions, resolveFontName, resolvePageSize, sectionLinePitchCss, sectionMarginCss, tiptapMarkExtensions, tiptapNodeExtensions, twipsToMm };
|
|
@@ -1,3 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
|
3
44
|
export { MENTION_TAG, Mention, createMention, isMention, readMention };
|