@portone/docx-editor 0.2.0 → 0.2.1
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/CHANGELOG.md +65 -0
- package/CONTRIBUTING.md +4 -0
- package/dist/docx/commentOnlyChange.d.ts +3 -1
- package/dist/docx/commentOnlyChange.js +38 -21
- package/dist/docx/comments/constants.d.ts +3 -3
- package/dist/docx/comments/constants.js +4 -3
- package/dist/docx/comments/grammar.d.ts +80 -0
- package/dist/docx/comments/grammar.js +213 -0
- package/dist/docx/comments/people.js +12 -14
- package/dist/docx/comments/reading.d.ts +5 -3
- package/dist/docx/comments/reading.js +23 -20
- package/dist/docx/comments/verifying.d.ts +44 -0
- package/dist/docx/comments/verifying.js +206 -0
- package/dist/docx/comments/writing.js +73 -46
- package/dist/docx/exportDocx.js +2 -1
- package/dist/docx/formatting/direct.js +3 -6
- package/dist/docx/headersFooters.js +8 -10
- package/dist/docx/notes.js +3 -5
- package/dist/docx/paraProps.js +51 -46
- package/dist/docx/propsXml.d.ts +20 -2
- package/dist/docx/propsXml.js +72 -32
- package/dist/docx/relationships.js +8 -3
- package/dist/docx/runProps.js +34 -34
- package/dist/docx/sdt.js +16 -14
- package/dist/docx/serializeBlock.js +1 -2
- package/dist/docx/serializeParagraph.d.ts +2 -1
- package/dist/docx/serializeParagraph.js +25 -14
- package/dist/docx/serializeTable.js +38 -24
- package/dist/docx/storyProjection.d.ts +36 -0
- package/dist/docx/storyProjection.js +23 -0
- package/dist/docx/tableFormatting/editing.js +49 -73
- package/dist/docx/tableTemplate.js +24 -3
- package/dist/docx/theme.d.ts +0 -6
- package/dist/docx/theme.js +0 -8
- package/dist/editor/clipboard/images.js +17 -3
- package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
- package/dist/editor/clipboard/inlineFormatting.js +1 -0
- package/dist/editor/commands/breakCommands.js +4 -3
- package/dist/editor/commands/canRunCommand.d.ts +2 -2
- package/dist/editor/commands/canRunCommand.js +1 -1
- package/dist/editor/commands/comments/editing.js +3 -6
- package/dist/editor/commands/historyCommands.js +2 -1
- package/dist/editor/commands/lockCommands.js +10 -1
- package/dist/editor/commands/tabCommands.js +4 -3
- package/dist/editor/createEditor.js +3 -9
- package/dist/editor/externalClipboard.js +166 -4
- package/dist/editor/insertImage.js +4 -3
- package/dist/editor/plugins/documentProtection.d.ts +3 -3
- package/dist/editor/plugins/lockedContent.d.ts +4 -4
- package/dist/editor/plugins/lockedContent.js +1 -1
- package/dist/numbering/listTemplate.js +32 -10
- package/dist/ooxml/element.d.ts +52 -0
- package/dist/ooxml/element.js +49 -0
- package/dist/ooxml/fragment.d.ts +53 -0
- package/dist/ooxml/fragment.js +76 -0
- package/dist/ooxml/image.js +4 -3
- package/dist/ooxml/names.d.ts +27 -0
- package/dist/ooxml/names.js +29 -0
- package/dist/ooxml/precedence.d.ts +31 -0
- package/dist/ooxml/precedence.js +51 -0
- package/dist/ooxml/xml.d.ts +31 -0
- package/dist/ooxml/xml.js +27 -0
- package/dist/page/blockKinds.d.ts +42 -0
- package/dist/page/blockKinds.js +0 -0
- package/dist/page/measureBlocks.d.ts +14 -1
- package/dist/page/measureBlocks.js +31 -11
- package/dist/page/pageDecorations.d.ts +16 -0
- package/dist/page/pageDecorations.js +1 -0
- package/dist/page/pageLayout.d.ts +8 -43
- package/dist/page/pageLayout.js +28 -51
- package/dist/page/tableMeasurements.d.ts +10 -2
- package/dist/page/tableMeasurements.js +20 -16
- package/dist/page/usePageLayout.d.ts +15 -0
- package/dist/page/usePageLayout.js +30 -2
- package/dist/schema/attrRoles.d.ts +35 -0
- package/dist/schema/attrRoles.js +106 -0
- package/dist/schema/docxSchema.d.ts +6 -0
- package/dist/schema/docxSchema.js +212 -101
- package/dist/schema/editGuard.d.ts +99 -0
- package/dist/schema/editGuard.js +43 -0
- package/dist/schema/guards.d.ts +68 -0
- package/dist/schema/guards.js +82 -0
- package/dist/schema/locks.d.ts +8 -35
- package/dist/schema/locks.js +29 -64
- package/dist/schema/preservedGuards.d.ts +30 -0
- package/dist/schema/preservedGuards.js +50 -0
- package/dist/schema/protection.d.ts +10 -3
- package/dist/schema/protection.js +2 -1
- package/dist/schema/sourceEquality.d.ts +27 -0
- package/dist/schema/sourceEquality.js +65 -0
- package/dist/table/cellFormatting.js +2 -1
- package/dist/table/commands.d.ts +2 -2
- package/dist/table/commands.js +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/merge.d.ts +2 -2
- package/dist/table/merge.js +1 -1
- package/package.json +4 -1
- package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
- package/dist/editor/plugins/bookmarkProtection.js +0 -28
- package/dist/editor/plugins/noteProtection.d.ts +0 -3
- package/dist/editor/plugins/noteProtection.js +0 -25
package/dist/docx/sdt.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// src/docx/sdt.ts
|
|
2
|
+
import { elementXml, emptyTagXml, openTagXml } from "../ooxml/element.js";
|
|
3
|
+
import { wName } from "../ooxml/names.js";
|
|
2
4
|
import { wAttr } from "../ooxml/units.js";
|
|
3
5
|
import { attrString, elementChildren, serializeXml } from "../ooxml/xml.js";
|
|
4
6
|
import {
|
|
@@ -31,7 +33,7 @@ function readSdtWrapper(el) {
|
|
|
31
33
|
const attrs = attrString(el);
|
|
32
34
|
const val = lockValue(sdtPr);
|
|
33
35
|
return {
|
|
34
|
-
prefix: (
|
|
36
|
+
prefix: openTagXml(wName("sdt"), attrs) + head.map(serializeXml).join(""),
|
|
35
37
|
content,
|
|
36
38
|
contentsLocked: val !== null && CONTENTS_LOCKED.includes(val),
|
|
37
39
|
deletionLocked: val !== null && DELETION_LOCKED.includes(val)
|
|
@@ -53,8 +55,8 @@ function newControlId() {
|
|
|
53
55
|
return Math.floor(Math.random() * 2147483647);
|
|
54
56
|
}
|
|
55
57
|
function renderPrefix(props) {
|
|
56
|
-
const
|
|
57
|
-
return
|
|
58
|
+
const inner = props.children.map((child) => (child.before ?? "") + child.xml).join("");
|
|
59
|
+
return openTagXml(props.tag, props.attrs) + inner + (props.tail ?? "");
|
|
58
60
|
}
|
|
59
61
|
var NAMES_NOTHING = ["id", "lock"];
|
|
60
62
|
function namesNothing(prefix) {
|
|
@@ -66,31 +68,31 @@ function namesNothing(prefix) {
|
|
|
66
68
|
return props.children.every((child) => NAMES_NOTHING.includes(child.name));
|
|
67
69
|
}
|
|
68
70
|
function emptyProps(props) {
|
|
69
|
-
return props.
|
|
71
|
+
return emptyTagXml(props.tag, props.attrs);
|
|
70
72
|
}
|
|
71
73
|
function editSdtPrefix(prefix, edits) {
|
|
72
74
|
const sdt = parseProps(`${prefix}</w:sdt>`);
|
|
73
75
|
const sdtPr = sdt && propsChild(sdt.children, "sdtPr");
|
|
74
76
|
const props = sdtPr ? parseProps(sdtPr.xml) : null;
|
|
75
77
|
if (!sdt || !props) return null;
|
|
76
|
-
const
|
|
77
|
-
(
|
|
78
|
-
|
|
78
|
+
const rendered = renderProps(
|
|
79
|
+
edits.reduce(
|
|
80
|
+
(kept, [name, xml]) => setPropsChild(kept, name, xml, SDT_PR_ORDER),
|
|
81
|
+
props
|
|
82
|
+
)
|
|
79
83
|
);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
children: setPropsChild(
|
|
84
|
-
sdt.children,
|
|
84
|
+
return renderPrefix(
|
|
85
|
+
setPropsChild(
|
|
86
|
+
sdt,
|
|
85
87
|
"sdtPr",
|
|
86
88
|
rendered === "" ? emptyProps(props) : rendered,
|
|
87
89
|
SDT_ORDER
|
|
88
90
|
)
|
|
89
|
-
|
|
91
|
+
);
|
|
90
92
|
}
|
|
91
93
|
function copiedControlPrefix(prefix, id) {
|
|
92
94
|
const copied = editSdtPrefix(prefix, [
|
|
93
|
-
["id",
|
|
95
|
+
["id", elementXml(wName("id"), [[wName("val"), `${id}`]])],
|
|
94
96
|
["dataBinding", null]
|
|
95
97
|
]);
|
|
96
98
|
return copied ?? prefix;
|
|
@@ -18,8 +18,7 @@ function serializeBlock(node, session, refs = NO_EXPORT_REFS) {
|
|
|
18
18
|
if (node.type.name === "paragraph") return serializeParagraph(node, refs);
|
|
19
19
|
if (node.type.name === "table") return serializeTable(node, refs);
|
|
20
20
|
if (node.type.name === "rawBlock") return preservedXml(node);
|
|
21
|
-
if (node.type.
|
|
22
|
-
if (node.type.name === "docxRaw") return serializeRaw(node, session);
|
|
21
|
+
if (node.type.isInGroup("preserved")) return serializeRaw(node, session);
|
|
23
22
|
throw new DocxExportError(
|
|
24
23
|
"unsupported-content",
|
|
25
24
|
`block we cannot serialize: ${node.type.name}`
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { Node as PMNode } from "prosemirror-model";
|
|
12
12
|
import { type ExportRefs } from "./exportRefs";
|
|
13
|
-
|
|
13
|
+
/** The attribute text a node attr holds, which is either what the original wrote or nothing */
|
|
14
|
+
export declare function rawAttrsOf(value: unknown): string | null;
|
|
14
15
|
export declare function preservedXml(node: PMNode): string;
|
|
15
16
|
export declare function serializeParagraph(node: PMNode, refs?: ExportRefs): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/docx/serializeParagraph.ts
|
|
2
|
+
import { elementXml, emptyTagXml, openTagXml } from "../ooxml/element.js";
|
|
2
3
|
import { DocxExportError } from "../ooxml/errors.js";
|
|
3
4
|
import {
|
|
4
5
|
imageDrawingXml,
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
toImageSrc,
|
|
7
8
|
withExtent
|
|
8
9
|
} from "../ooxml/image.js";
|
|
10
|
+
import { wName } from "../ooxml/names.js";
|
|
9
11
|
import { escapeXml } from "../ooxml/xml.js";
|
|
10
12
|
import { NO_EXPORT_REFS } from "./exportRefs.js";
|
|
11
13
|
import { relIdIn, withRelId } from "./hyperlink.js";
|
|
@@ -16,11 +18,8 @@ function sameMark(a, b) {
|
|
|
16
18
|
function markOf(node, name) {
|
|
17
19
|
return node.marks.find((mark) => mark.type.name === name) ?? null;
|
|
18
20
|
}
|
|
19
|
-
function
|
|
20
|
-
return typeof
|
|
21
|
-
}
|
|
22
|
-
function emptyTag(name, attrs) {
|
|
23
|
-
return typeof attrs === "string" ? `<${name} ${attrs}/>` : `<${name}/>`;
|
|
21
|
+
function rawAttrsOf(value) {
|
|
22
|
+
return typeof value === "string" ? value : null;
|
|
24
23
|
}
|
|
25
24
|
function renderImage(node, images) {
|
|
26
25
|
const extent = toImageExtent(node.attrs.extent);
|
|
@@ -51,19 +50,28 @@ function renderInline(node, images) {
|
|
|
51
50
|
if (node.isText) {
|
|
52
51
|
const tab = markOf(node, "tab");
|
|
53
52
|
return (node.text ?? "").split(" ").flatMap((text, index, pieces) => {
|
|
54
|
-
const rendered = text ? [
|
|
55
|
-
|
|
53
|
+
const rendered = text ? [
|
|
54
|
+
elementXml(
|
|
55
|
+
wName("t"),
|
|
56
|
+
[["xml:space", "preserve"]],
|
|
57
|
+
[escapeXml(text)]
|
|
58
|
+
)
|
|
59
|
+
] : [];
|
|
60
|
+
return index < pieces.length - 1 ? [
|
|
61
|
+
...rendered,
|
|
62
|
+
emptyTagXml(wName("tab"), rawAttrsOf(tab?.attrs.tabAttrs))
|
|
63
|
+
] : rendered;
|
|
56
64
|
}).join("");
|
|
57
65
|
}
|
|
58
66
|
if (node.type.name === "hardBreak")
|
|
59
|
-
return
|
|
67
|
+
return emptyTagXml(wName("br"), rawAttrsOf(node.attrs.brAttrs));
|
|
60
68
|
if (node.type.name === "image") return renderImage(node, images);
|
|
61
69
|
if (node.type.name === "commentReference") {
|
|
62
70
|
const original = node.attrs.referenceXml;
|
|
63
71
|
if (typeof original === "string") return original;
|
|
64
72
|
const id = node.attrs.id;
|
|
65
73
|
if (typeof id === "string") {
|
|
66
|
-
return
|
|
74
|
+
return elementXml(wName("commentReference"), [[wName("id"), id]]);
|
|
67
75
|
}
|
|
68
76
|
throw new DocxExportError(
|
|
69
77
|
"lost-original",
|
|
@@ -76,7 +84,7 @@ function renderInline(node, images) {
|
|
|
76
84
|
const id = node.attrs.id;
|
|
77
85
|
const name = node.attrs.kind === "endnote" ? "endnoteReference" : "footnoteReference";
|
|
78
86
|
if (typeof id === "string") {
|
|
79
|
-
return
|
|
87
|
+
return elementXml(wName(name), [[wName("id"), id]]);
|
|
80
88
|
}
|
|
81
89
|
throw new DocxExportError(
|
|
82
90
|
"lost-original",
|
|
@@ -117,7 +125,10 @@ function addInline(parts, child, images) {
|
|
|
117
125
|
);
|
|
118
126
|
}
|
|
119
127
|
const name = child.type.name === "commentStart" ? "commentRangeStart" : "commentRangeEnd";
|
|
120
|
-
parts.push({
|
|
128
|
+
parts.push({
|
|
129
|
+
kind: "raw",
|
|
130
|
+
xml: elementXml(wName(name), [[wName("id"), id]])
|
|
131
|
+
});
|
|
121
132
|
return;
|
|
122
133
|
}
|
|
123
134
|
const piece = renderInline(child, images);
|
|
@@ -150,7 +161,7 @@ function splitParagraphGroups(paragraph, refs) {
|
|
|
150
161
|
}
|
|
151
162
|
function renderParagraphPart(part) {
|
|
152
163
|
if (part.kind === "raw") return part.xml;
|
|
153
|
-
const open =
|
|
164
|
+
const open = openTagXml(wName("r"), rawAttrsOf(part.mark?.attrs.rAttrs));
|
|
154
165
|
const rPr = part.mark?.attrs.rPr;
|
|
155
166
|
return open + (typeof rPr === "string" ? rPr : "") + part.pieces.join("") + "</w:r>";
|
|
156
167
|
}
|
|
@@ -196,13 +207,13 @@ function renderParagraphGroup(group, refs) {
|
|
|
196
207
|
return `${prefix}<w:sdtContent>${body}</w:sdtContent></w:sdt>`;
|
|
197
208
|
}
|
|
198
209
|
function serializeParagraph(node, refs = NO_EXPORT_REFS) {
|
|
199
|
-
const open =
|
|
210
|
+
const open = openTagXml(wName("p"), rawAttrsOf(node.attrs.pAttrs));
|
|
200
211
|
const pPr = node.attrs.pPr;
|
|
201
212
|
const body = splitParagraphGroups(node, refs).map((group) => renderParagraphGroup(group, refs)).join("");
|
|
202
213
|
return open + (typeof pPr === "string" ? pPr : "") + body + "</w:p>";
|
|
203
214
|
}
|
|
204
215
|
export {
|
|
205
|
-
openTag,
|
|
206
216
|
preservedXml,
|
|
217
|
+
rawAttrsOf,
|
|
207
218
|
serializeParagraph
|
|
208
219
|
};
|
|
@@ -5,18 +5,22 @@ import {
|
|
|
5
5
|
toTableWidth,
|
|
6
6
|
widthNumber
|
|
7
7
|
} from "../model/format.js";
|
|
8
|
+
import { elementXml, openTagXml } from "../ooxml/element.js";
|
|
8
9
|
import { DocxExportError } from "../ooxml/errors.js";
|
|
10
|
+
import { wName } from "../ooxml/names.js";
|
|
9
11
|
import { NO_EXPORT_REFS } from "./exportRefs.js";
|
|
10
12
|
import {
|
|
13
|
+
innerXml,
|
|
11
14
|
parseProps,
|
|
15
|
+
propsChild,
|
|
12
16
|
renderProps,
|
|
13
17
|
setPropsChild,
|
|
14
18
|
TBL_PR_ORDER,
|
|
15
19
|
TC_PR_ORDER
|
|
16
20
|
} from "./propsXml.js";
|
|
17
21
|
import {
|
|
18
|
-
openTag,
|
|
19
22
|
preservedXml,
|
|
23
|
+
rawAttrsOf,
|
|
20
24
|
serializeParagraph
|
|
21
25
|
} from "./serializeParagraph.js";
|
|
22
26
|
function propsOf(xml, tag) {
|
|
@@ -27,15 +31,24 @@ function propsOf(xml, tag) {
|
|
|
27
31
|
}
|
|
28
32
|
return parsed;
|
|
29
33
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
34
|
+
function modelled(name, attrs, replacing) {
|
|
35
|
+
const inner = replacing === void 0 ? "" : innerXml(replacing);
|
|
36
|
+
return elementXml(wName(name), attrs, inner === "" ? [] : [inner]);
|
|
37
|
+
}
|
|
38
|
+
function widthXml(name, width, replacing) {
|
|
39
|
+
return modelled(
|
|
40
|
+
name,
|
|
41
|
+
[
|
|
42
|
+
[wName("w"), `${widthNumber(width) ?? 0}`],
|
|
43
|
+
[wName("type"), width.type]
|
|
44
|
+
],
|
|
45
|
+
replacing
|
|
46
|
+
);
|
|
32
47
|
}
|
|
33
48
|
function withWidth(props, name, width, order) {
|
|
34
49
|
if (!width) return props;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
children: setPropsChild(props.children, name, widthXml(name, width), order)
|
|
38
|
-
};
|
|
50
|
+
const replacing = propsChild(props.children, name)?.xml;
|
|
51
|
+
return setPropsChild(props, name, widthXml(name, width, replacing), order);
|
|
39
52
|
}
|
|
40
53
|
function tablePropsXml(table) {
|
|
41
54
|
const props = propsOf(table.attrs.tblPr, "w:tblPr");
|
|
@@ -45,26 +58,27 @@ function tablePropsXml(table) {
|
|
|
45
58
|
function tableGridXml(table) {
|
|
46
59
|
const gridCols = toGridCols(table.attrs.gridCols);
|
|
47
60
|
if (gridCols.length === 0) return "";
|
|
48
|
-
const cols = gridCols.map((w) =>
|
|
49
|
-
return
|
|
61
|
+
const cols = gridCols.map((w) => elementXml(wName("gridCol"), [[wName("w"), `${w}`]])).join("");
|
|
62
|
+
return elementXml(wName("tblGrid"), [], [cols]);
|
|
50
63
|
}
|
|
51
64
|
function cellPropsXml(cell, role) {
|
|
52
65
|
const props = propsOf(cell.attrs.tcPr, "w:tcPr");
|
|
53
66
|
const colspan = spanCount(cell.attrs.colspan);
|
|
54
67
|
const rowspan = spanCount(cell.attrs.rowspan);
|
|
55
|
-
const gridSpan = colspan > 1 ?
|
|
56
|
-
const vMerge = role === "continue" ? "<w:vMerge/>" : rowspan > 1 ? '<w:vMerge w:val="restart"/>' : null;
|
|
57
|
-
let children = setPropsChild(
|
|
58
|
-
props.children,
|
|
68
|
+
const gridSpan = colspan > 1 ? modelled(
|
|
59
69
|
"gridSpan",
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
|
|
70
|
+
[[wName("val"), `${colspan}`]],
|
|
71
|
+
propsChild(props.children, "gridSpan")?.xml
|
|
72
|
+
) : null;
|
|
73
|
+
const vMerge = role === "continue" ? elementXml(wName("vMerge"), []) : rowspan > 1 ? modelled(
|
|
74
|
+
"vMerge",
|
|
75
|
+
[[wName("val"), "restart"]],
|
|
76
|
+
propsChild(props.children, "vMerge")?.xml
|
|
77
|
+
) : null;
|
|
78
|
+
const spanned = setPropsChild(props, "gridSpan", gridSpan, TC_PR_ORDER);
|
|
79
|
+
const merged = setPropsChild(spanned, "vMerge", vMerge, TC_PR_ORDER);
|
|
64
80
|
const width = toTableWidth(cell.attrs.tcW);
|
|
65
|
-
return renderProps(
|
|
66
|
-
withWidth({ ...props, children }, "tcW", width, TC_PR_ORDER)
|
|
67
|
-
);
|
|
81
|
+
return renderProps(withWidth(merged, "tcW", width, TC_PR_ORDER));
|
|
68
82
|
}
|
|
69
83
|
function cellBlockXml(block, refs) {
|
|
70
84
|
if (block.type.name === "paragraph") return serializeParagraph(block, refs);
|
|
@@ -81,8 +95,8 @@ function wrapInSdt(xml, cell, role) {
|
|
|
81
95
|
return `${prefix}<w:sdtContent>${xml}</w:sdtContent></w:sdt>`;
|
|
82
96
|
}
|
|
83
97
|
function cellXml(cell, role, refs) {
|
|
84
|
-
const body = role === "continue" ? "
|
|
85
|
-
const xml =
|
|
98
|
+
const body = role === "continue" ? elementXml(wName("p"), []) : cell.children.map((block) => cellBlockXml(block, refs)).join("");
|
|
99
|
+
const xml = openTagXml(wName("tc"), rawAttrsOf(cell.attrs.tcAttrs)) + cellPropsXml(cell, role) + body + "</w:tc>";
|
|
86
100
|
return wrapInSdt(xml, cell, role);
|
|
87
101
|
}
|
|
88
102
|
function placeRow(row, covering) {
|
|
@@ -116,7 +130,7 @@ function rowXml(row, covering, refs) {
|
|
|
116
130
|
const cells = placeRow(row, covering).map((placed) => cellXml(placed.cell, placed.role, refs)).join("");
|
|
117
131
|
const tblPrEx = row.attrs.tblPrEx;
|
|
118
132
|
const trPr = row.attrs.trPr;
|
|
119
|
-
return
|
|
133
|
+
return openTagXml(wName("tr"), rawAttrsOf(row.attrs.trAttrs)) + (typeof tblPrEx === "string" ? tblPrEx : "") + (typeof trPr === "string" ? trPr : "") + cells + "</w:tr>";
|
|
120
134
|
}
|
|
121
135
|
function serializeTable(table, refs = NO_EXPORT_REFS) {
|
|
122
136
|
const covering = [];
|
|
@@ -127,7 +141,7 @@ function serializeTable(table, refs = NO_EXPORT_REFS) {
|
|
|
127
141
|
"a vertical merge in the table reaches past the last row"
|
|
128
142
|
);
|
|
129
143
|
}
|
|
130
|
-
return
|
|
144
|
+
return openTagXml(wName("tbl"), rawAttrsOf(table.attrs.tblAttrs)) + tablePropsXml(table) + tableGridXml(table) + rows + "</w:tbl>";
|
|
131
145
|
}
|
|
132
146
|
export {
|
|
133
147
|
serializeTable
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document story as the parts of the package see it: which blocks the editor models and
|
|
3
|
+
* writes back itself, and the text those blocks go back out as.
|
|
4
|
+
*
|
|
5
|
+
* Two files saying the same thing can spell it differently. A block carries the XML it arrived
|
|
6
|
+
* as on its attrs, so a table Word wrote (`w:w` before `w:type` inside a `w:tcW`) and the same
|
|
7
|
+
* table this editor wrote (the other way round) compare unequal attr for attr, and a comparison
|
|
8
|
+
* over the model would call that a body edit. Writing both stories out through the one writer
|
|
9
|
+
* takes the wording out of the comparison and leaves what the story says, because the writer
|
|
10
|
+
* reaches a fixed point on its own output: opening what it wrote and writing it again gives the
|
|
11
|
+
* same text (the test beside this file holds that over every fixture).
|
|
12
|
+
*
|
|
13
|
+
* What falls out of the comparison is of two kinds, and the test beside this file keeps them
|
|
14
|
+
* apart. One is two spellings of a single thing, which the writer settles on one of; forgiving
|
|
15
|
+
* those is the point, and the set of them is open. The other is content the writer builds afresh
|
|
16
|
+
* instead of reading, a table's grid and a drawing's extent among it, which the comparison cannot
|
|
17
|
+
* report because it can no longer see it. That second set is enumerated and meant to shrink: a
|
|
18
|
+
* case joining it is a preservation defect in the writer, not a detail of the comparison.
|
|
19
|
+
*/
|
|
20
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
21
|
+
import type { SessionStore } from "./session";
|
|
22
|
+
/** A document and the package it was opened from, which is what writing it back out takes */
|
|
23
|
+
export interface Story {
|
|
24
|
+
readonly doc: PMNode;
|
|
25
|
+
readonly session: SessionStore;
|
|
26
|
+
}
|
|
27
|
+
/** Whether this is a block the editor takes apart and writes back itself, rather than one it keeps as it came */
|
|
28
|
+
export declare function isModelledBlock(node: PMNode): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Every top-level block as the writer puts it out, with the markup that is allowed to have
|
|
31
|
+
* changed taken away first.
|
|
32
|
+
*
|
|
33
|
+
* null where a block cannot be written back at all, which is a story no comparison can be held
|
|
34
|
+
* over: a caller judging two files answers such a story the way it answers a changed one.
|
|
35
|
+
*/
|
|
36
|
+
export declare function comparableStory(story: Story, strip: (node: PMNode) => PMNode): readonly string[] | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/docx/storyProjection.ts
|
|
2
|
+
import { DocxExportError } from "../ooxml/errors.js";
|
|
3
|
+
import { NO_EXPORT_REFS } from "./exportRefs.js";
|
|
4
|
+
import { serializeBlock } from "./serializeBlock.js";
|
|
5
|
+
function isModelledBlock(node) {
|
|
6
|
+
return node.type.isInGroup("modelled");
|
|
7
|
+
}
|
|
8
|
+
function comparableStory(story, strip) {
|
|
9
|
+
const blocks = [];
|
|
10
|
+
try {
|
|
11
|
+
story.doc.forEach((block) => {
|
|
12
|
+
blocks.push(serializeBlock(strip(block), story.session, NO_EXPORT_REFS));
|
|
13
|
+
});
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error instanceof DocxExportError) return null;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
return blocks;
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
comparableStory,
|
|
22
|
+
isModelledBlock
|
|
23
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// src/docx/tableFormatting/editing.ts
|
|
2
|
+
import { attrPairs, elementXml } from "../../ooxml/element.js";
|
|
3
|
+
import { wName } from "../../ooxml/names.js";
|
|
4
|
+
import { setAttr } from "../../ooxml/precedence.js";
|
|
2
5
|
import { normalizeHex, wAttr } from "../../ooxml/units.js";
|
|
3
|
-
import { childByLocalName
|
|
6
|
+
import { childByLocalName } from "../../ooxml/xml.js";
|
|
4
7
|
import {
|
|
5
8
|
parseProps,
|
|
6
9
|
parsePropsXml,
|
|
@@ -41,55 +44,34 @@ var SIDE_NAMES = {
|
|
|
41
44
|
right: ["right", "end"]
|
|
42
45
|
};
|
|
43
46
|
var PRESET_BORDER_EIGHTHS = 4;
|
|
44
|
-
var THEME_COLOR_ATTRS = ["themeColor", "themeTint", "themeShade"];
|
|
45
|
-
var THEME_FILL_ATTRS = ["themeFill", "themeFillTint", "themeFillShade"];
|
|
46
47
|
var EMPTY_TC_PR = { tag: "w:tcPr", attrs: null, children: [] };
|
|
47
|
-
function attrsOf(el) {
|
|
48
|
-
return el ? Array.from(el.attributes).map((attr) => [attr.name, attr.value]) : [];
|
|
49
|
-
}
|
|
50
|
-
function withAttr(attrs, name, value, overridden = []) {
|
|
51
|
-
const kept = attrs.filter(([attr]) => !overridden.includes(localPart(attr)));
|
|
52
|
-
const at = kept.findIndex(([attr]) => localPart(attr) === name);
|
|
53
|
-
if (at === -1) return [...kept, [`w:${name}`, value]];
|
|
54
|
-
return kept.map(
|
|
55
|
-
(attr, index) => index === at ? [attr[0], value] : attr
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
function elementXml(tag, attrs) {
|
|
59
|
-
const text = attrs.map(([name, value]) => `${name}="${escapeXml(value)}"`).join(" ");
|
|
60
|
-
return `<${tag} ${text}/>`;
|
|
61
|
-
}
|
|
62
48
|
function drawsLine(side) {
|
|
63
49
|
if (!side) return false;
|
|
64
50
|
const val = wAttr(side, "val");
|
|
65
51
|
return val !== null && val !== "nil" && val !== "none";
|
|
66
52
|
}
|
|
67
|
-
function sideAttrs(current, edit, fallback = null) {
|
|
53
|
+
function sideAttrs(name, current, edit, fallback = null) {
|
|
68
54
|
if (edit.kind === "color") {
|
|
69
55
|
if (!drawsLine(current)) {
|
|
70
56
|
if (current || !fallback || fallback === "none") return null;
|
|
71
57
|
return inheritedBorderAttrs(fallback, edit.hex);
|
|
72
58
|
}
|
|
73
|
-
return
|
|
74
|
-
attrsOf(current),
|
|
75
|
-
"color",
|
|
76
|
-
edit.hex ?? "auto",
|
|
77
|
-
THEME_COLOR_ATTRS
|
|
78
|
-
);
|
|
59
|
+
return setAttr(attrPairs(current), name, "color", edit.hex ?? "auto");
|
|
79
60
|
}
|
|
80
61
|
if (edit.line === "none") {
|
|
81
|
-
return current ?
|
|
62
|
+
return current ? setAttr(attrPairs(current), name, "val", "none") : [[wName("val"), "none"]];
|
|
82
63
|
}
|
|
83
64
|
if (!current) {
|
|
84
65
|
return [
|
|
85
|
-
["
|
|
86
|
-
["
|
|
87
|
-
["
|
|
88
|
-
["
|
|
66
|
+
[wName("val"), "single"],
|
|
67
|
+
[wName("sz"), `${PRESET_BORDER_EIGHTHS}`],
|
|
68
|
+
[wName("space"), "0"],
|
|
69
|
+
[wName("color"), "auto"]
|
|
89
70
|
];
|
|
90
71
|
}
|
|
91
|
-
return
|
|
92
|
-
|
|
72
|
+
return setAttr(
|
|
73
|
+
setAttr(attrPairs(current), name, "val", "single"),
|
|
74
|
+
name,
|
|
93
75
|
"sz",
|
|
94
76
|
`${PRESET_BORDER_EIGHTHS}`
|
|
95
77
|
);
|
|
@@ -110,10 +92,10 @@ function inheritedBorderAttrs(border, hex) {
|
|
|
110
92
|
const eighths = Math.round(Number.parseFloat(widthText) * 8);
|
|
111
93
|
if (!Number.isSafeInteger(eighths) || eighths <= 0) return null;
|
|
112
94
|
return [
|
|
113
|
-
["
|
|
114
|
-
["
|
|
115
|
-
["
|
|
116
|
-
["
|
|
95
|
+
[wName("val"), BORDER_VAL_BY_CSS_STYLE[style.toLowerCase()]],
|
|
96
|
+
[wName("sz"), `${eighths}`],
|
|
97
|
+
[wName("space"), "0"],
|
|
98
|
+
[wName("color"), hex ?? "auto"]
|
|
117
99
|
];
|
|
118
100
|
}
|
|
119
101
|
function sideNames(borders, side) {
|
|
@@ -127,12 +109,12 @@ function editedBorders(current, edit, defaults = NO_BORDER_DEFAULTS) {
|
|
|
127
109
|
const borders = current === null ? null : parsePropsXml(current);
|
|
128
110
|
if (!props || current !== null && !borders) return null;
|
|
129
111
|
const sides = edit.kind === "line" ? edit.sides : ALL_CELL_SIDES;
|
|
130
|
-
const
|
|
112
|
+
const edited = sides.reduce((kept, side) => {
|
|
131
113
|
const { write, drop } = sideNames(borders, side);
|
|
132
114
|
const currentSide = borders ? childByLocalName(borders, write) : null;
|
|
133
|
-
const attrs = sideAttrs(currentSide, edit, defaults[side]);
|
|
115
|
+
const attrs = sideAttrs(write, currentSide, edit, defaults[side]);
|
|
134
116
|
if (!attrs) return kept;
|
|
135
|
-
const tag = currentSide?.nodeName ??
|
|
117
|
+
const tag = currentSide?.nodeName ?? wName(write);
|
|
136
118
|
const written = setPropsChild(
|
|
137
119
|
kept,
|
|
138
120
|
write,
|
|
@@ -140,8 +122,8 @@ function editedBorders(current, edit, defaults = NO_BORDER_DEFAULTS) {
|
|
|
140
122
|
TC_BORDERS_ORDER
|
|
141
123
|
);
|
|
142
124
|
return drop === null ? written : setPropsChild(written, drop, null, TC_BORDERS_ORDER);
|
|
143
|
-
}, props
|
|
144
|
-
const xml = renderProps(
|
|
125
|
+
}, props);
|
|
126
|
+
const xml = renderProps(edited);
|
|
145
127
|
return { xml: xml === "" ? null : xml };
|
|
146
128
|
}
|
|
147
129
|
function hasPattern(shd) {
|
|
@@ -149,23 +131,20 @@ function hasPattern(shd) {
|
|
|
149
131
|
return val !== null && val !== "clear" && val !== "nil";
|
|
150
132
|
}
|
|
151
133
|
function editedShading(current, fill) {
|
|
152
|
-
const tag = current?.nodeName ?? "
|
|
134
|
+
const tag = current?.nodeName ?? wName("shd");
|
|
153
135
|
if (fill === null) {
|
|
154
136
|
if (!hasPattern(current)) return null;
|
|
155
|
-
return elementXml(
|
|
156
|
-
tag,
|
|
157
|
-
withAttr(attrsOf(current), "fill", "auto", THEME_FILL_ATTRS)
|
|
158
|
-
);
|
|
137
|
+
return elementXml(tag, setAttr(attrPairs(current), "shd", "fill", "auto"));
|
|
159
138
|
}
|
|
160
139
|
if (!current) {
|
|
161
140
|
return elementXml(tag, [
|
|
162
|
-
["
|
|
163
|
-
["
|
|
164
|
-
["
|
|
141
|
+
[wName("val"), "clear"],
|
|
142
|
+
[wName("color"), "auto"],
|
|
143
|
+
[wName("fill"), fill]
|
|
165
144
|
]);
|
|
166
145
|
}
|
|
167
|
-
const painting = hasPattern(current) ?
|
|
168
|
-
return elementXml(tag,
|
|
146
|
+
const painting = hasPattern(current) ? attrPairs(current) : setAttr(attrPairs(current), "shd", "val", "clear");
|
|
147
|
+
return elementXml(tag, setAttr(painting, "shd", "fill", fill));
|
|
169
148
|
}
|
|
170
149
|
function bordersChange(props, edit, defaults) {
|
|
171
150
|
const current = propsChild(props.children, "tcBorders")?.xml ?? null;
|
|
@@ -191,7 +170,7 @@ function paddingChange(props, values) {
|
|
|
191
170
|
const margins = current === null ? null : parsePropsXml(current);
|
|
192
171
|
const parsed = current === null ? { tag: "w:tcMar", attrs: null, children: [] } : parseProps(current);
|
|
193
172
|
if (!parsed || current !== null && !margins) return null;
|
|
194
|
-
let
|
|
173
|
+
let edited = parsed;
|
|
195
174
|
let wrote = false;
|
|
196
175
|
for (const side of ALL_CELL_SIDES) {
|
|
197
176
|
const points = values[side];
|
|
@@ -202,21 +181,22 @@ function paddingChange(props, values) {
|
|
|
202
181
|
}
|
|
203
182
|
const existing = margins ? MARGIN_SIDE_NAMES[side].map((name2) => childByLocalName(margins, name2)).find((element) => element !== null) ?? null : null;
|
|
204
183
|
const name = existing?.localName ?? side;
|
|
205
|
-
const attrs =
|
|
206
|
-
|
|
184
|
+
const attrs = setAttr(
|
|
185
|
+
setAttr(attrPairs(existing), name, "w", `${twips}`),
|
|
186
|
+
name,
|
|
207
187
|
"type",
|
|
208
188
|
"dxa"
|
|
209
189
|
);
|
|
210
|
-
|
|
211
|
-
|
|
190
|
+
edited = setPropsChild(
|
|
191
|
+
edited,
|
|
212
192
|
name,
|
|
213
|
-
elementXml(existing?.nodeName ??
|
|
193
|
+
elementXml(existing?.nodeName ?? wName(side), attrs),
|
|
214
194
|
TC_MAR_ORDER
|
|
215
195
|
);
|
|
216
196
|
wrote = true;
|
|
217
197
|
}
|
|
218
198
|
if (!wrote) return null;
|
|
219
|
-
return { name: "tcMar", xml: renderProps(
|
|
199
|
+
return { name: "tcMar", xml: renderProps(edited) };
|
|
220
200
|
}
|
|
221
201
|
function childChange(edit, props, defaults) {
|
|
222
202
|
switch (edit.kind) {
|
|
@@ -250,8 +230,8 @@ function childChange(edit, props, defaults) {
|
|
|
250
230
|
return {
|
|
251
231
|
name: "vAlign",
|
|
252
232
|
xml: elementXml(
|
|
253
|
-
element?.nodeName ?? "
|
|
254
|
-
|
|
233
|
+
element?.nodeName ?? wName("vAlign"),
|
|
234
|
+
setAttr(attrPairs(element), "vAlign", "val", edit.align)
|
|
255
235
|
)
|
|
256
236
|
};
|
|
257
237
|
}
|
|
@@ -264,13 +244,9 @@ function editCellProps(tcPr, edit, defaults = NO_BORDER_DEFAULTS, margins = NO_C
|
|
|
264
244
|
if (!props) return null;
|
|
265
245
|
const change = childChange(edit, props, defaults);
|
|
266
246
|
if (!change) return null;
|
|
267
|
-
const
|
|
268
|
-
props.
|
|
269
|
-
change.name,
|
|
270
|
-
change.xml,
|
|
271
|
-
TC_PR_ORDER
|
|
247
|
+
const rendered = renderProps(
|
|
248
|
+
setPropsChild(props, change.name, change.xml, TC_PR_ORDER)
|
|
272
249
|
);
|
|
273
|
-
const rendered = renderProps({ ...props, children });
|
|
274
250
|
const next = rendered === "" ? null : rendered;
|
|
275
251
|
if (next === tcPr) return null;
|
|
276
252
|
return { tcPr: next, format: readCellProps(next, defaults, margins) };
|
|
@@ -294,17 +270,17 @@ function editRowHeight(trPr, heightPt) {
|
|
|
294
270
|
const element = current === null ? null : parsePropsXml(current);
|
|
295
271
|
if (current !== null && !element) return null;
|
|
296
272
|
const writtenRule = element ? wAttr(element, "hRule") : null;
|
|
297
|
-
const attrs =
|
|
298
|
-
const nextAttrs =
|
|
273
|
+
const attrs = setAttr(attrPairs(element), "trHeight", "val", `${twips}`);
|
|
274
|
+
const nextAttrs = setAttr(
|
|
299
275
|
attrs,
|
|
276
|
+
"trHeight",
|
|
300
277
|
"hRule",
|
|
301
278
|
writtenRule === "exact" ? "exact" : "atLeast"
|
|
302
279
|
);
|
|
303
|
-
const child = elementXml(element?.nodeName ?? "
|
|
304
|
-
const rendered = renderProps(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
});
|
|
280
|
+
const child = elementXml(element?.nodeName ?? wName("trHeight"), nextAttrs);
|
|
281
|
+
const rendered = renderProps(
|
|
282
|
+
setPropsChild(props, "trHeight", child, TR_PR_ORDER)
|
|
283
|
+
);
|
|
308
284
|
if (rendered === trPr) return null;
|
|
309
285
|
const parsed = parsePropsXml(rendered);
|
|
310
286
|
const format = parsed ? readRowFormat(parsed) : null;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// src/docx/tableTemplate.ts
|
|
2
|
+
import { elementXml } from "../ooxml/element.js";
|
|
3
|
+
import { wName } from "../ooxml/names.js";
|
|
2
4
|
import { docxSchema } from "../schema/index.js";
|
|
3
5
|
import { A4_PORTRAIT, bodyWidth } from "./pageGeometry.js";
|
|
4
6
|
import { parsePropsXml } from "./propsXml.js";
|
|
@@ -25,14 +27,33 @@ var BORDER_SIDES = [
|
|
|
25
27
|
"insideV"
|
|
26
28
|
];
|
|
27
29
|
function borderXml(side) {
|
|
28
|
-
return
|
|
30
|
+
return elementXml(wName(side), [
|
|
31
|
+
[wName("val"), "single"],
|
|
32
|
+
[wName("sz"), `${BORDER_EIGHTHS}`],
|
|
33
|
+
[wName("space"), "0"],
|
|
34
|
+
[wName("color"), "000000"]
|
|
35
|
+
]);
|
|
29
36
|
}
|
|
30
37
|
function tablePropsXml(width) {
|
|
31
38
|
const borders = BORDER_SIDES.map(borderXml).join("");
|
|
32
|
-
return
|
|
39
|
+
return elementXml(
|
|
40
|
+
wName("tblPr"),
|
|
41
|
+
[],
|
|
42
|
+
[
|
|
43
|
+
widthXml("tblW", width),
|
|
44
|
+
elementXml(wName("tblBorders"), [], [borders]),
|
|
45
|
+
elementXml(wName("tblLayout"), [[wName("type"), "fixed"]])
|
|
46
|
+
]
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function widthXml(name, width) {
|
|
50
|
+
return elementXml(wName(name), [
|
|
51
|
+
[wName("w"), `${width}`],
|
|
52
|
+
[wName("type"), "dxa"]
|
|
53
|
+
]);
|
|
33
54
|
}
|
|
34
55
|
function cellPropsXml(width) {
|
|
35
|
-
return
|
|
56
|
+
return elementXml(wName("tcPr"), [], [widthXml("tcW", width)]);
|
|
36
57
|
}
|
|
37
58
|
function evenGridCols(cols, total) {
|
|
38
59
|
const base = Math.floor(total / cols);
|