@docen/docx 0.3.0
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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/converters/docx.d.mts +110 -0
- package/dist/converters/docx.mjs +624 -0
- package/dist/converters/html.d.mts +14 -0
- package/dist/converters/html.mjs +18 -0
- package/dist/converters/markdown.d.mts +13 -0
- package/dist/converters/markdown.mjs +18 -0
- package/dist/converters/patch.d.mts +53 -0
- package/dist/converters/patch.mjs +37 -0
- package/dist/converters/prepare.d.mts +51 -0
- package/dist/converters/prepare.mjs +76 -0
- package/dist/core-CFIQVRfx.mjs +88 -0
- package/dist/core-omBKMRtl.d.mts +34 -0
- package/dist/core.d.mts +2 -0
- package/dist/core.mjs +2 -0
- package/dist/editor.d.mts +21 -0
- package/dist/editor.mjs +20 -0
- package/dist/extensions/extensions.d.mts +12 -0
- package/dist/extensions/extensions.mjs +12 -0
- package/dist/extensions/heading.d.mts +2 -0
- package/dist/extensions/heading.mjs +145 -0
- package/dist/extensions/image.d.mts +2 -0
- package/dist/extensions/image.mjs +201 -0
- package/dist/extensions/index.d.mts +2 -0
- package/dist/extensions/index.mjs +2 -0
- package/dist/extensions/paragraph.d.mts +2 -0
- package/dist/extensions/paragraph.mjs +116 -0
- package/dist/extensions/strike.d.mts +2 -0
- package/dist/extensions/strike.mjs +50 -0
- package/dist/extensions/table-cell.d.mts +2 -0
- package/dist/extensions/table-cell.mjs +112 -0
- package/dist/extensions/table-header.d.mts +2 -0
- package/dist/extensions/table-header.mjs +112 -0
- package/dist/extensions/table-row.d.mts +2 -0
- package/dist/extensions/table-row.mjs +84 -0
- package/dist/extensions/table.d.mts +2 -0
- package/dist/extensions/table.mjs +134 -0
- package/dist/extensions/text-style.d.mts +2 -0
- package/dist/extensions/text-style.mjs +134 -0
- package/dist/extensions/tiptap.d.mts +2 -0
- package/dist/extensions/tiptap.mjs +33 -0
- package/dist/extensions/types.d.mts +30 -0
- package/dist/extensions/utils.d.mts +49 -0
- package/dist/extensions/utils.mjs +289 -0
- package/dist/heading-BvqBD2zX.d.mts +8 -0
- package/dist/image-Ge1y6uam.d.mts +47 -0
- package/dist/index.d.mts +213 -0
- package/dist/index.mjs +8 -0
- package/dist/paragraph-fhEXtAN2.d.mts +16 -0
- package/dist/strike-BgWGvjKr.d.mts +33 -0
- package/dist/table-BFkfeRp9.d.mts +9 -0
- package/dist/table-cell-D_FV4D2h.d.mts +8 -0
- package/dist/table-header-KGQ2aEkP.d.mts +8 -0
- package/dist/table-row-kgzYkZlW.d.mts +9 -0
- package/dist/text-style-BHdtXkMb.d.mts +8 -0
- package/dist/tiptap-TErPjuNJ.d.mts +33 -0
- package/package.json +106 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Image as Image$1 } from "./tiptap.mjs";
|
|
2
|
+
//#region src/extensions/image.ts
|
|
3
|
+
/**
|
|
4
|
+
* Custom Image extension with node-level renderHTML + renderDocx/parseDocx.
|
|
5
|
+
*
|
|
6
|
+
* Attrs:
|
|
7
|
+
* - src/alt/title/width/height: Tiptap structural names (kept verbatim so base
|
|
8
|
+
* image commands work).
|
|
9
|
+
* - rotation: editor display only (CSS transform) but also carried through DOCX
|
|
10
|
+
* via transformation.rotation (MediaTransformation.rotation).
|
|
11
|
+
* - floating/outline: nested office-open objects (Floating / OutlineOptions).
|
|
12
|
+
* - crop: nested office-open SourceRectangleOptions (srcRect).
|
|
13
|
+
* - display: editor-only display hint, no OOXML equivalent.
|
|
14
|
+
*
|
|
15
|
+
* DOCX round-trip is near-identity: renderDocx packs attrs into CoreImageOptions;
|
|
16
|
+
* parseDocx unpacks them back. src is a data URL ↔ { type, data } base64.
|
|
17
|
+
* Node-level renderHTML solves the style merge problem (rotation + floating).
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Tiptap JSON image node → CoreImageOptions-shaped object.
|
|
21
|
+
*
|
|
22
|
+
* Returns `{ image: ImageOptions }` (structural wrapper) or null when no
|
|
23
|
+
* embedded image data is available (external URLs need pre-fetching).
|
|
24
|
+
* rotation is carried via transformation.rotation (not dropped).
|
|
25
|
+
*/
|
|
26
|
+
function renderDocx(node) {
|
|
27
|
+
const attrs = node.attrs ?? {};
|
|
28
|
+
const imageOpts = {};
|
|
29
|
+
const src = attrs.src;
|
|
30
|
+
if (src?.startsWith("data:image/")) {
|
|
31
|
+
const match = src.match(/^data:image\/([\w.+-]+);base64,(.+)$/);
|
|
32
|
+
if (match) {
|
|
33
|
+
imageOpts.type = match[1] === "jpeg" ? "jpg" : match[1];
|
|
34
|
+
const binary = atob(match[2]);
|
|
35
|
+
const bytes = new Uint8Array(binary.length);
|
|
36
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
37
|
+
imageOpts.data = bytes;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!imageOpts.data) return null;
|
|
41
|
+
const transformation = {
|
|
42
|
+
width: attrs.width ?? 600,
|
|
43
|
+
height: attrs.height ?? 400
|
|
44
|
+
};
|
|
45
|
+
const rotation = attrs.rotation;
|
|
46
|
+
if (rotation != null) transformation.rotation = rotation;
|
|
47
|
+
imageOpts.transformation = transformation;
|
|
48
|
+
const altText = {};
|
|
49
|
+
if (attrs.alt) altText.name = attrs.alt;
|
|
50
|
+
if (attrs.title) altText.description = attrs.title;
|
|
51
|
+
if (Object.keys(altText).length > 0) imageOpts.altText = altText;
|
|
52
|
+
if (attrs.floating) imageOpts.floating = attrs.floating;
|
|
53
|
+
if (attrs.crop) imageOpts.srcRect = attrs.crop;
|
|
54
|
+
if (attrs.outline) imageOpts.outline = attrs.outline;
|
|
55
|
+
return { image: imageOpts };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ImageOptions-shaped object → Tiptap attrs.
|
|
59
|
+
*
|
|
60
|
+
* Near-identity unpack: transformation → width/height/rotation, altText → alt/title,
|
|
61
|
+
* floating/srcRect(→crop)/outline passed through verbatim. src is reconstructed by
|
|
62
|
+
* DocxManager from the image data bytes (kept out of parseDocx).
|
|
63
|
+
*/
|
|
64
|
+
function parseDocx(imageOpts) {
|
|
65
|
+
const opts = imageOpts ?? {};
|
|
66
|
+
const attrs = {};
|
|
67
|
+
const transformation = opts.transformation;
|
|
68
|
+
if (transformation) {
|
|
69
|
+
if (typeof transformation.width === "number") attrs.width = transformation.width;
|
|
70
|
+
if (typeof transformation.height === "number") attrs.height = transformation.height;
|
|
71
|
+
if (typeof transformation.rotation === "number") attrs.rotation = transformation.rotation;
|
|
72
|
+
}
|
|
73
|
+
const altText = opts.altText;
|
|
74
|
+
if (altText) {
|
|
75
|
+
if (altText.name) attrs.alt = altText.name;
|
|
76
|
+
if (altText.description) attrs.title = altText.description;
|
|
77
|
+
}
|
|
78
|
+
if (opts.floating) attrs.floating = opts.floating;
|
|
79
|
+
if (opts.srcRect) attrs.crop = opts.srcRect;
|
|
80
|
+
if (opts.outline) attrs.outline = opts.outline;
|
|
81
|
+
return attrs;
|
|
82
|
+
}
|
|
83
|
+
function renderCropAttrs(crop) {
|
|
84
|
+
const c = crop;
|
|
85
|
+
const leftPct = (c.left || 0) / 1e5;
|
|
86
|
+
const topPct = (c.top || 0) / 1e5;
|
|
87
|
+
const rightPct = (c.right || 0) / 1e5;
|
|
88
|
+
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)}%` };
|
|
94
|
+
}
|
|
95
|
+
function renderImageStyles(attrs) {
|
|
96
|
+
const styles = [];
|
|
97
|
+
if (attrs.display) styles.push(`display:${attrs.display}`);
|
|
98
|
+
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
|
+
}
|
|
128
|
+
return styles;
|
|
129
|
+
}
|
|
130
|
+
const Image = Image$1.extend({
|
|
131
|
+
addAttributes() {
|
|
132
|
+
return {
|
|
133
|
+
...this.parent?.(),
|
|
134
|
+
display: {
|
|
135
|
+
default: null,
|
|
136
|
+
rendered: false,
|
|
137
|
+
parseHTML: () => this.options.inline ? "inline-block" : null
|
|
138
|
+
},
|
|
139
|
+
rotation: {
|
|
140
|
+
default: null,
|
|
141
|
+
rendered: false,
|
|
142
|
+
parseHTML: (element) => {
|
|
143
|
+
const match = (element.getAttribute("style") || "").match(/transform:\s*rotate\(([\d.]+)deg\)/);
|
|
144
|
+
return match ? parseFloat(match[1]) : null;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
floating: {
|
|
148
|
+
default: null,
|
|
149
|
+
rendered: false,
|
|
150
|
+
parseHTML: (element) => {
|
|
151
|
+
const raw = element.getAttribute("data-floating");
|
|
152
|
+
if (!raw) return null;
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(raw);
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
outline: {
|
|
161
|
+
default: null,
|
|
162
|
+
rendered: false,
|
|
163
|
+
parseHTML: (element) => {
|
|
164
|
+
const raw = element.getAttribute("data-outline");
|
|
165
|
+
if (!raw) return null;
|
|
166
|
+
try {
|
|
167
|
+
return JSON.parse(raw);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
crop: {
|
|
174
|
+
default: null,
|
|
175
|
+
rendered: false,
|
|
176
|
+
parseHTML: (element) => {
|
|
177
|
+
const raw = element.getAttribute("data-crop");
|
|
178
|
+
if (!raw) return null;
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(raw);
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
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];
|
|
196
|
+
},
|
|
197
|
+
renderDocx,
|
|
198
|
+
parseDocx
|
|
199
|
+
});
|
|
200
|
+
//#endregion
|
|
201
|
+
export { Image, parseDocx, renderCropAttrs, renderDocx };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Paragraph as Paragraph$1 } from "./tiptap.mjs";
|
|
2
|
+
import { alignmentFromElement, bordersFromElement, indentFromElement, renderParagraphStyles, shadingFromElement, spacingFromElement } from "./utils.mjs";
|
|
3
|
+
//#region src/extensions/paragraph.ts
|
|
4
|
+
/**
|
|
5
|
+
* Paragraph extension with nested office-open attrs.
|
|
6
|
+
*
|
|
7
|
+
* Attrs mirror ParagraphPropertiesOptionsBase (alignment/indent/spacing/border/
|
|
8
|
+
* shading/frame as nested objects + scalar OOXML properties). DOCX round-trip is
|
|
9
|
+
* near-identity: renderDocx/parseDocx pass attrs through; CSS conversion happens
|
|
10
|
+
* only in renderHTML via utils mappers.
|
|
11
|
+
*/
|
|
12
|
+
function renderDocx(node) {
|
|
13
|
+
const attrs = node.attrs ?? {};
|
|
14
|
+
const opts = {};
|
|
15
|
+
for (const [key, value] of Object.entries(attrs)) if (value !== null && value !== void 0) opts[key] = value;
|
|
16
|
+
return opts;
|
|
17
|
+
}
|
|
18
|
+
/** Structural/semantic keys expressed elsewhere (heading ext, list handling, run children). */
|
|
19
|
+
const SKIP_KEYS = new Set([
|
|
20
|
+
"heading",
|
|
21
|
+
"style",
|
|
22
|
+
"bullet",
|
|
23
|
+
"numbering",
|
|
24
|
+
"run",
|
|
25
|
+
"children",
|
|
26
|
+
"text",
|
|
27
|
+
"thematicBreak"
|
|
28
|
+
]);
|
|
29
|
+
function parseDocx(opts) {
|
|
30
|
+
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
31
|
+
const attrs = {};
|
|
32
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
33
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
34
|
+
attrs[key] = value ?? null;
|
|
35
|
+
}
|
|
36
|
+
return attrs;
|
|
37
|
+
}
|
|
38
|
+
const attrNative = () => ({
|
|
39
|
+
default: null,
|
|
40
|
+
parseHTML: () => null,
|
|
41
|
+
rendered: false
|
|
42
|
+
});
|
|
43
|
+
const Paragraph = Paragraph$1.extend({
|
|
44
|
+
addAttributes() {
|
|
45
|
+
return {
|
|
46
|
+
...this.parent?.(),
|
|
47
|
+
alignment: {
|
|
48
|
+
default: null,
|
|
49
|
+
rendered: false,
|
|
50
|
+
parseHTML: (el) => alignmentFromElement(el)
|
|
51
|
+
},
|
|
52
|
+
indent: {
|
|
53
|
+
default: null,
|
|
54
|
+
rendered: false,
|
|
55
|
+
parseHTML: (el) => indentFromElement(el)
|
|
56
|
+
},
|
|
57
|
+
spacing: {
|
|
58
|
+
default: null,
|
|
59
|
+
rendered: false,
|
|
60
|
+
parseHTML: (el) => spacingFromElement(el)
|
|
61
|
+
},
|
|
62
|
+
shading: {
|
|
63
|
+
default: null,
|
|
64
|
+
rendered: false,
|
|
65
|
+
parseHTML: (el) => shadingFromElement(el)
|
|
66
|
+
},
|
|
67
|
+
border: {
|
|
68
|
+
default: null,
|
|
69
|
+
rendered: false,
|
|
70
|
+
parseHTML: (el) => bordersFromElement(el)
|
|
71
|
+
},
|
|
72
|
+
frame: attrNative(),
|
|
73
|
+
keepNext: attrNative(),
|
|
74
|
+
keepLines: attrNative(),
|
|
75
|
+
pageBreakBefore: attrNative(),
|
|
76
|
+
widowControl: attrNative(),
|
|
77
|
+
contextualSpacing: attrNative(),
|
|
78
|
+
bidirectional: attrNative(),
|
|
79
|
+
outlineLevel: attrNative(),
|
|
80
|
+
textDirection: attrNative(),
|
|
81
|
+
textAlignment: attrNative(),
|
|
82
|
+
suppressLineNumbers: attrNative(),
|
|
83
|
+
wordWrap: attrNative(),
|
|
84
|
+
overflowPunctuation: attrNative(),
|
|
85
|
+
autoSpaceEastAsianText: attrNative(),
|
|
86
|
+
suppressOverlap: attrNative(),
|
|
87
|
+
suppressAutoHyphens: attrNative(),
|
|
88
|
+
adjustRightInd: attrNative(),
|
|
89
|
+
snapToGrid: attrNative(),
|
|
90
|
+
mirrorIndents: attrNative(),
|
|
91
|
+
kinsoku: attrNative(),
|
|
92
|
+
topLinePunct: attrNative(),
|
|
93
|
+
autoSpaceDE: attrNative(),
|
|
94
|
+
textboxTightWrap: attrNative(),
|
|
95
|
+
rightTabStop: attrNative(),
|
|
96
|
+
leftTabStop: attrNative(),
|
|
97
|
+
divId: attrNative(),
|
|
98
|
+
tabStops: attrNative(),
|
|
99
|
+
cnfStyle: attrNative()
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
103
|
+
const styles = renderParagraphStyles(node.attrs);
|
|
104
|
+
const attrs = { ...HTMLAttributes };
|
|
105
|
+
if (styles.length > 0) attrs.style = styles.join(";");
|
|
106
|
+
return [
|
|
107
|
+
"p",
|
|
108
|
+
attrs,
|
|
109
|
+
0
|
|
110
|
+
];
|
|
111
|
+
},
|
|
112
|
+
renderDocx,
|
|
113
|
+
parseDocx
|
|
114
|
+
});
|
|
115
|
+
//#endregion
|
|
116
|
+
export { Paragraph, parseDocx, renderDocx };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Strike as Strike$1 } from "./tiptap.mjs";
|
|
2
|
+
//#region src/extensions/strike.ts
|
|
3
|
+
/**
|
|
4
|
+
* Strike mark extension with nested office-open attrs.
|
|
5
|
+
*
|
|
6
|
+
* OOXML represents strikethrough on a run via two mutually exclusive booleans:
|
|
7
|
+
* `strike` (single) and `doubleStrike` (double). The mark itself is "single
|
|
8
|
+
* strikethrough"; the `doubleStrike` attr flips it to double. DOCX round-trip
|
|
9
|
+
* is near-identity for the doubleStrike flag; `strike` itself is implied by the
|
|
10
|
+
* mark's presence and handled in renderDocx/parseDocx.
|
|
11
|
+
*
|
|
12
|
+
* Mark attribute-level renderHTML is delegated to the base Strike extension
|
|
13
|
+
* (renders `<s>`); only the DOCX flag needs custom handling.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* attrs → run properties.
|
|
17
|
+
*
|
|
18
|
+
* The mark presence itself means single strikethrough. When `doubleStrike` is
|
|
19
|
+
* set, emit the OOXML double-strike flag instead (the two are mutually
|
|
20
|
+
* exclusive in OOXML). DocxManager calls this with `mark.attrs`.
|
|
21
|
+
*/
|
|
22
|
+
function renderDocx(attrs) {
|
|
23
|
+
return attrs.doubleStrike ? { doubleStrike: true } : { strike: true };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* run properties → attrs.
|
|
27
|
+
*
|
|
28
|
+
* Only the `doubleStrike` flag needs to round-trip; single strike is implied by
|
|
29
|
+
* the mark's existence. `strike` is structural/semantic (the mark) and skipped.
|
|
30
|
+
*/
|
|
31
|
+
function parseDocx(runOpts) {
|
|
32
|
+
return { doubleStrike: runOpts.doubleStrike ?? null };
|
|
33
|
+
}
|
|
34
|
+
const attrNative = () => ({
|
|
35
|
+
default: null,
|
|
36
|
+
parseHTML: () => null,
|
|
37
|
+
rendered: false
|
|
38
|
+
});
|
|
39
|
+
const Strike = Strike$1.extend({
|
|
40
|
+
addAttributes() {
|
|
41
|
+
return {
|
|
42
|
+
...this.parent?.(),
|
|
43
|
+
doubleStrike: attrNative()
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
renderDocx,
|
|
47
|
+
parseDocx
|
|
48
|
+
});
|
|
49
|
+
//#endregion
|
|
50
|
+
export { Strike, parseDocx, renderDocx };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { TableCell as TableCell$1 } from "./tiptap.mjs";
|
|
2
|
+
import { bordersFromElement, renderTableCellStyles, shadingFromElement } from "./utils.mjs";
|
|
3
|
+
//#region src/extensions/table-cell.ts
|
|
4
|
+
/**
|
|
5
|
+
* Table cell extension with nested office-open attrs.
|
|
6
|
+
*
|
|
7
|
+
* Attrs mirror TableCellPropertiesOptionsBase (shading/margins/borders/width as
|
|
8
|
+
* nested objects + scalar OOXML properties) plus the inherited Tiptap structural
|
|
9
|
+
* names colspan/rowspan/colwidth/align (rendered: false for the OOXML-named ones).
|
|
10
|
+
* DOCX round-trip is near-identity: renderDocx/parseDocx pass OOXML-native attrs
|
|
11
|
+
* through and only map the Tiptap structural names colspan/rowspan/colwidth to
|
|
12
|
+
* OOXML columnSpan/rowSpan/width. CSS conversion happens solely in renderHTML via
|
|
13
|
+
* utils.renderTableCellStyles (consuming nested shading/verticalAlign/noWrap).
|
|
14
|
+
*/
|
|
15
|
+
/** Tiptap structural attrs expressed via OOXML structural fields, not passed through. */
|
|
16
|
+
const SKIP_KEYS = new Set([
|
|
17
|
+
"colspan",
|
|
18
|
+
"rowspan",
|
|
19
|
+
"colwidth",
|
|
20
|
+
"children"
|
|
21
|
+
]);
|
|
22
|
+
function renderDocx(node) {
|
|
23
|
+
const attrs = node.attrs ?? {};
|
|
24
|
+
const opts = {};
|
|
25
|
+
const colspan = attrs.colspan;
|
|
26
|
+
if (colspan && colspan > 1) opts.columnSpan = colspan;
|
|
27
|
+
const rowspan = attrs.rowspan;
|
|
28
|
+
if (rowspan && rowspan > 1) opts.rowSpan = rowspan;
|
|
29
|
+
const colwidth = attrs.colwidth;
|
|
30
|
+
if (colwidth && colwidth.length > 0) {
|
|
31
|
+
const totalPx = colwidth.reduce((sum, w) => sum + (w || 0), 0);
|
|
32
|
+
if (totalPx > 0) opts.width = {
|
|
33
|
+
size: totalPx * 15,
|
|
34
|
+
type: "dxa"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
38
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
39
|
+
if (value !== null && value !== void 0) opts[key] = value;
|
|
40
|
+
}
|
|
41
|
+
return opts;
|
|
42
|
+
}
|
|
43
|
+
function parseDocx(opts) {
|
|
44
|
+
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
45
|
+
const attrs = {};
|
|
46
|
+
if (resolved.columnSpan != null) attrs.colspan = resolved.columnSpan;
|
|
47
|
+
if (resolved.rowSpan != null) attrs.rowspan = resolved.rowSpan;
|
|
48
|
+
if (resolved.width) {
|
|
49
|
+
const twips = resolved.width.size ?? 0;
|
|
50
|
+
if (twips) attrs.colwidth = [Math.round(twips / 15)];
|
|
51
|
+
}
|
|
52
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
53
|
+
if (key === "columnSpan" || key === "rowSpan" || key === "width" || key === "children") continue;
|
|
54
|
+
attrs[key] = value ?? null;
|
|
55
|
+
}
|
|
56
|
+
return attrs;
|
|
57
|
+
}
|
|
58
|
+
const attrNative = () => ({
|
|
59
|
+
default: null,
|
|
60
|
+
parseHTML: () => null,
|
|
61
|
+
rendered: false
|
|
62
|
+
});
|
|
63
|
+
const TableCell = TableCell$1.extend({
|
|
64
|
+
addAttributes() {
|
|
65
|
+
return {
|
|
66
|
+
...this.parent?.(),
|
|
67
|
+
shading: {
|
|
68
|
+
default: null,
|
|
69
|
+
rendered: false,
|
|
70
|
+
parseHTML: (el) => shadingFromElement(el)
|
|
71
|
+
},
|
|
72
|
+
borders: {
|
|
73
|
+
default: null,
|
|
74
|
+
rendered: false,
|
|
75
|
+
parseHTML: (el) => bordersFromElement(el)
|
|
76
|
+
},
|
|
77
|
+
verticalAlign: {
|
|
78
|
+
default: null,
|
|
79
|
+
rendered: false,
|
|
80
|
+
parseHTML: (el) => el.style.verticalAlign || null
|
|
81
|
+
},
|
|
82
|
+
textDirection: attrNative(),
|
|
83
|
+
width: attrNative(),
|
|
84
|
+
margins: attrNative(),
|
|
85
|
+
noWrap: {
|
|
86
|
+
default: null,
|
|
87
|
+
rendered: false,
|
|
88
|
+
parseHTML: (el) => el.style.whiteSpace === "nowrap" ? true : null
|
|
89
|
+
},
|
|
90
|
+
verticalMerge: attrNative(),
|
|
91
|
+
horizontalMerge: attrNative(),
|
|
92
|
+
fitText: attrNative(),
|
|
93
|
+
hideMark: attrNative(),
|
|
94
|
+
headers: attrNative(),
|
|
95
|
+
cnfStyle: attrNative()
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
99
|
+
const styles = renderTableCellStyles(node.attrs);
|
|
100
|
+
const attrs = { ...HTMLAttributes };
|
|
101
|
+
if (styles.length > 0) attrs.style = styles.join(";");
|
|
102
|
+
return [
|
|
103
|
+
"td",
|
|
104
|
+
attrs,
|
|
105
|
+
0
|
|
106
|
+
];
|
|
107
|
+
},
|
|
108
|
+
renderDocx,
|
|
109
|
+
parseDocx
|
|
110
|
+
});
|
|
111
|
+
//#endregion
|
|
112
|
+
export { TableCell, parseDocx, renderDocx };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { TableHeader as TableHeader$1 } from "./tiptap.mjs";
|
|
2
|
+
import { bordersFromElement, renderTableCellStyles, shadingFromElement } from "./utils.mjs";
|
|
3
|
+
//#region src/extensions/table-header.ts
|
|
4
|
+
/**
|
|
5
|
+
* Table header extension with nested office-open attrs (mirrors TableCell).
|
|
6
|
+
*
|
|
7
|
+
* Attrs mirror TableCellPropertiesOptionsBase (shading/margins/borders/width as
|
|
8
|
+
* nested objects + scalar OOXML properties) plus the inherited Tiptap structural
|
|
9
|
+
* names colspan/rowspan/colwidth/align (rendered: false for the OOXML-named ones).
|
|
10
|
+
* DOCX round-trip is near-identity: renderDocx/parseDocx pass OOXML-native attrs
|
|
11
|
+
* through and only map the Tiptap structural names colspan/rowspan/colwidth to
|
|
12
|
+
* OOXML columnSpan/rowSpan/width. CSS conversion happens solely in renderHTML via
|
|
13
|
+
* utils.renderTableCellStyles (consuming nested shading/verticalAlign/noWrap).
|
|
14
|
+
*/
|
|
15
|
+
/** Tiptap structural attrs expressed via OOXML structural fields, not passed through. */
|
|
16
|
+
const SKIP_KEYS = new Set([
|
|
17
|
+
"colspan",
|
|
18
|
+
"rowspan",
|
|
19
|
+
"colwidth",
|
|
20
|
+
"children"
|
|
21
|
+
]);
|
|
22
|
+
function renderDocx(node) {
|
|
23
|
+
const attrs = node.attrs ?? {};
|
|
24
|
+
const opts = {};
|
|
25
|
+
const colspan = attrs.colspan;
|
|
26
|
+
if (colspan && colspan > 1) opts.columnSpan = colspan;
|
|
27
|
+
const rowspan = attrs.rowspan;
|
|
28
|
+
if (rowspan && rowspan > 1) opts.rowSpan = rowspan;
|
|
29
|
+
const colwidth = attrs.colwidth;
|
|
30
|
+
if (colwidth && colwidth.length > 0) {
|
|
31
|
+
const totalPx = colwidth.reduce((sum, w) => sum + (w || 0), 0);
|
|
32
|
+
if (totalPx > 0) opts.width = {
|
|
33
|
+
size: totalPx * 15,
|
|
34
|
+
type: "dxa"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
38
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
39
|
+
if (value !== null && value !== void 0) opts[key] = value;
|
|
40
|
+
}
|
|
41
|
+
return opts;
|
|
42
|
+
}
|
|
43
|
+
function parseDocx(opts) {
|
|
44
|
+
const resolved = typeof opts === "string" ? { text: opts } : opts;
|
|
45
|
+
const attrs = {};
|
|
46
|
+
if (resolved.columnSpan != null) attrs.colspan = resolved.columnSpan;
|
|
47
|
+
if (resolved.rowSpan != null) attrs.rowspan = resolved.rowSpan;
|
|
48
|
+
if (resolved.width) {
|
|
49
|
+
const twips = resolved.width.size ?? 0;
|
|
50
|
+
if (twips) attrs.colwidth = [Math.round(twips / 15)];
|
|
51
|
+
}
|
|
52
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
53
|
+
if (key === "columnSpan" || key === "rowSpan" || key === "width" || key === "children") continue;
|
|
54
|
+
attrs[key] = value ?? null;
|
|
55
|
+
}
|
|
56
|
+
return attrs;
|
|
57
|
+
}
|
|
58
|
+
const attrNative = () => ({
|
|
59
|
+
default: null,
|
|
60
|
+
parseHTML: () => null,
|
|
61
|
+
rendered: false
|
|
62
|
+
});
|
|
63
|
+
const TableHeader = TableHeader$1.extend({
|
|
64
|
+
addAttributes() {
|
|
65
|
+
return {
|
|
66
|
+
...this.parent?.(),
|
|
67
|
+
shading: {
|
|
68
|
+
default: null,
|
|
69
|
+
rendered: false,
|
|
70
|
+
parseHTML: (el) => shadingFromElement(el)
|
|
71
|
+
},
|
|
72
|
+
borders: {
|
|
73
|
+
default: null,
|
|
74
|
+
rendered: false,
|
|
75
|
+
parseHTML: (el) => bordersFromElement(el)
|
|
76
|
+
},
|
|
77
|
+
verticalAlign: {
|
|
78
|
+
default: null,
|
|
79
|
+
rendered: false,
|
|
80
|
+
parseHTML: (el) => el.style.verticalAlign || null
|
|
81
|
+
},
|
|
82
|
+
textDirection: attrNative(),
|
|
83
|
+
width: attrNative(),
|
|
84
|
+
margins: attrNative(),
|
|
85
|
+
noWrap: {
|
|
86
|
+
default: null,
|
|
87
|
+
rendered: false,
|
|
88
|
+
parseHTML: (el) => el.style.whiteSpace === "nowrap" ? true : null
|
|
89
|
+
},
|
|
90
|
+
verticalMerge: attrNative(),
|
|
91
|
+
horizontalMerge: attrNative(),
|
|
92
|
+
fitText: attrNative(),
|
|
93
|
+
hideMark: attrNative(),
|
|
94
|
+
headers: attrNative(),
|
|
95
|
+
cnfStyle: attrNative()
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
99
|
+
const styles = renderTableCellStyles(node.attrs);
|
|
100
|
+
const attrs = { ...HTMLAttributes };
|
|
101
|
+
if (styles.length > 0) attrs.style = styles.join(";");
|
|
102
|
+
return [
|
|
103
|
+
"th",
|
|
104
|
+
attrs,
|
|
105
|
+
0
|
|
106
|
+
];
|
|
107
|
+
},
|
|
108
|
+
renderDocx,
|
|
109
|
+
parseDocx
|
|
110
|
+
});
|
|
111
|
+
//#endregion
|
|
112
|
+
export { TableHeader, parseDocx, renderDocx };
|