@portone/docx-editor 0.4.0 → 0.5.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 +201 -0
- package/CONTRIBUTING.md +2 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +1 -1
- package/dist/DocxEditor.js +44 -25
- package/dist/docx/cloning.js +4 -9
- package/dist/docx/commentOnlyChange.d.ts +5 -3
- package/dist/docx/comments/grammar.d.ts +27 -10
- package/dist/docx/comments/grammar.js +18 -67
- package/dist/docx/comments/model.d.ts +5 -6
- package/dist/docx/comments/model.js +0 -9
- package/dist/docx/comments/parts.d.ts +16 -10
- package/dist/docx/comments/parts.js +65 -13
- package/dist/docx/comments/people.d.ts +11 -3
- package/dist/docx/comments/people.js +20 -6
- package/dist/docx/comments/policy.js +7 -1
- package/dist/docx/comments/reading.d.ts +4 -2
- package/dist/docx/comments/reading.js +1 -14
- package/dist/docx/comments/writing.js +65 -28
- package/dist/docx/exportDocx.js +10 -14
- package/dist/docx/exportRefs.d.ts +8 -3
- package/dist/docx/exportRefs.js +2 -1
- package/dist/docx/fidelity.js +24 -10
- package/dist/docx/fields.d.ts +42 -0
- package/dist/docx/fields.js +76 -0
- package/dist/docx/formatting/styles.d.ts +0 -2
- package/dist/docx/formatting/styles.js +2 -15
- package/dist/docx/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/identities.js +17 -26
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +100 -48
- package/dist/docx/importParagraph.d.ts +18 -5
- package/dist/docx/importParagraph.js +75 -128
- package/dist/docx/importPolicy.d.ts +70 -0
- package/dist/docx/importPolicy.js +195 -0
- package/dist/docx/importPreserved.d.ts +30 -0
- package/dist/docx/importPreserved.js +54 -0
- package/dist/docx/importTable.js +56 -12
- package/dist/docx/invariants.js +16 -18
- package/dist/docx/notes.d.ts +6 -1
- package/dist/docx/notes.js +6 -16
- package/dist/docx/pageGeometry.d.ts +5 -14
- package/dist/docx/pageGeometry.js +0 -12
- package/dist/docx/protectionPolicy.d.ts +13 -5
- package/dist/docx/protectionPolicy.js +32 -22
- package/dist/docx/scan.d.ts +18 -8
- package/dist/docx/scan.js +17 -11
- package/dist/docx/sections.d.ts +126 -0
- package/dist/docx/sections.js +207 -0
- package/dist/docx/serializeBlock.d.ts +13 -4
- package/dist/docx/serializeBlock.js +16 -18
- package/dist/docx/serializeParagraph.d.ts +9 -4
- package/dist/docx/serializeParagraph.js +23 -58
- package/dist/docx/serializePreserved.d.ts +14 -0
- package/dist/docx/serializePreserved.js +24 -0
- package/dist/docx/serializeStory.d.ts +17 -0
- package/dist/docx/serializeStory.js +17 -0
- package/dist/docx/serializeTable.js +10 -13
- package/dist/docx/session.d.ts +23 -6
- package/dist/docx/session.js +26 -5
- package/dist/docx/story.d.ts +140 -0
- package/dist/docx/story.js +237 -0
- package/dist/docx/storyProjection.js +6 -1
- package/dist/docx/tableTemplate.d.ts +25 -0
- package/dist/docx/tableTemplate.js +30 -16
- package/dist/docx/wrappers.d.ts +67 -0
- package/dist/docx/wrappers.js +117 -0
- package/dist/editor/clipboard/blockReaders.d.ts +74 -0
- package/dist/editor/clipboard/blockReaders.js +193 -0
- package/dist/editor/clipboard/htmlReader.d.ts +26 -0
- package/dist/editor/clipboard/htmlReader.js +376 -0
- package/dist/editor/clipboard/inlineFormatting.d.ts +0 -7
- package/dist/editor/clipboard/inlineFormatting.js +1 -7
- package/dist/editor/clipboard/internalChannel.d.ts +57 -0
- package/dist/editor/clipboard/internalChannel.js +58 -0
- package/dist/editor/clipboard/normalizers.d.ts +99 -0
- package/dist/editor/clipboard/normalizers.js +199 -0
- package/dist/editor/clipboard/parser.d.ts +34 -0
- package/dist/editor/clipboard/parser.js +58 -0
- package/dist/editor/clipboard/plugin.d.ts +21 -0
- package/dist/editor/clipboard/plugin.js +188 -0
- package/dist/editor/clipboard/readContext.d.ts +34 -0
- package/dist/editor/clipboard/readContext.js +25 -0
- package/dist/editor/clipboard/readers.d.ts +33 -0
- package/dist/editor/clipboard/readers.js +37 -0
- package/dist/editor/clipboard/source.d.ts +12 -0
- package/dist/editor/clipboard/source.js +21 -0
- package/dist/editor/commands/comments/editing.d.ts +26 -6
- package/dist/editor/commands/comments/editing.js +177 -106
- package/dist/editor/commands/comments/model.d.ts +34 -17
- package/dist/editor/commands/comments/model.js +11 -0
- package/dist/editor/commands/comments/reading.d.ts +5 -3
- package/dist/editor/commands/comments/reading.js +8 -58
- package/dist/editor/commands/index.d.ts +2 -2
- package/dist/editor/commands/index.js +2 -0
- package/dist/editor/commands/linkCommands.js +20 -5
- package/dist/editor/commands/listCommands.d.ts +6 -0
- package/dist/editor/commands/listCommands.js +3 -2
- package/dist/editor/commands/lockCommands.js +59 -16
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- package/dist/editor/commands/paragraphCommands.js +2 -2
- package/dist/editor/createEditor.js +13 -3
- package/dist/editor/documentStyles.d.ts +26 -4
- package/dist/editor/documentStyles.js +9 -4
- package/dist/editor/imageFiles.d.ts +2 -2
- package/dist/editor/imageFiles.js +2 -0
- package/dist/editor/insertTable.d.ts +1 -1
- package/dist/editor/insertTable.js +2 -2
- package/dist/editor/plainText.d.ts +12 -0
- package/dist/editor/plainText.js +2 -1
- package/dist/editor/plugins/columnResize.js +6 -3
- package/dist/editor/plugins/commentComposer.d.ts +28 -0
- package/dist/editor/plugins/commentComposer.js +46 -0
- package/dist/editor/plugins/commentDecorations.d.ts +29 -2
- package/dist/editor/plugins/commentDecorations.js +89 -15
- package/dist/editor/plugins/documentProjection.d.ts +31 -0
- package/dist/editor/plugins/documentProjection.js +22 -0
- package/dist/editor/plugins/imagePaste.js +15 -8
- package/dist/editor/plugins/linkPanel.d.ts +7 -3
- package/dist/editor/plugins/linkPanel.js +13 -27
- package/dist/editor/plugins/panelState.d.ts +67 -0
- package/dist/editor/plugins/panelState.js +54 -0
- package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
- package/dist/editor/plugins/tableContextMenu.js +30 -47
- package/dist/editor/plugins/textContextMenu.d.ts +1 -1
- package/dist/editor/plugins/textContextMenu.js +30 -47
- package/dist/ooxml/fragment.d.ts +11 -0
- package/dist/ooxml/fragment.js +14 -4
- package/dist/ooxml/image.d.ts +3 -4
- package/dist/ooxml/image.js +50 -5
- package/dist/ooxml/names.d.ts +6 -0
- package/dist/ooxml/names.js +2 -0
- package/dist/ooxml/props.d.ts +10 -0
- package/dist/ooxml/props.js +28 -4
- package/dist/ooxml/rangeMarkers.d.ts +5 -0
- package/dist/ooxml/rangeMarkers.js +24 -0
- package/dist/page/PageGuides.d.ts +7 -3
- package/dist/page/PageGuides.js +13 -24
- package/dist/page/pageLayout.d.ts +64 -5
- package/dist/page/pageLayout.js +90 -29
- package/dist/page/usePageLayout.d.ts +14 -4
- package/dist/page/usePageLayout.js +19 -20
- package/dist/schema/attrRoles.js +46 -23
- package/dist/schema/clipboard.d.ts +72 -0
- package/dist/schema/clipboard.js +288 -0
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +217 -165
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +2 -2
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/locks.d.ts +3 -0
- package/dist/schema/locks.js +29 -12
- package/dist/schema/preservedFragments.d.ts +12 -0
- package/dist/schema/preservedFragments.js +38 -0
- package/dist/schema/preservedGuards.d.ts +9 -7
- package/dist/schema/preservedGuards.js +112 -11
- package/dist/schema/protection.d.ts +34 -5
- package/dist/schema/protection.js +42 -14
- package/dist/schema/rendering.d.ts +14 -0
- package/dist/schema/rendering.js +27 -12
- package/dist/schema/stories.d.ts +49 -0
- package/dist/schema/stories.js +78 -0
- package/dist/schema/wrappers.d.ts +58 -0
- package/dist/schema/wrappers.js +77 -0
- package/dist/styles/classNames.d.ts +3 -5
- package/dist/styles/classNames.js +3 -5
- package/dist/styles.css +74 -12
- package/dist/table/resize.d.ts +5 -3
- package/dist/ui/CommentsPanel.d.ts +1 -3
- package/dist/ui/CommentsPanel.js +24 -15
- package/dist/ui/NotesPanel.js +2 -2
- package/dist/ui/TextMenu.d.ts +1 -3
- package/dist/ui/TextMenu.js +10 -6
- package/dist/ui/comments/CommentComposer.d.ts +6 -1
- package/dist/ui/comments/CommentComposer.js +26 -3
- package/package.json +5 -4
- package/dist/editor/externalClipboard.d.ts +0 -22
- package/dist/editor/externalClipboard.js +0 -486
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// src/docx/story.ts
|
|
2
|
+
import { Fragment } from "prosemirror-model";
|
|
3
|
+
import { xmlnsAttr } from "../ooxml/element.js";
|
|
4
|
+
import { DocxImportError } from "../ooxml/errors.js";
|
|
5
|
+
import { NAMESPACES, qualify } from "../ooxml/names.js";
|
|
6
|
+
import { attributeByLocalName, elementChildren, parseXml } from "../ooxml/xml.js";
|
|
7
|
+
import { docxSchema } from "../schema/index.js";
|
|
8
|
+
import { isPreservedNode } from "../schema/preservedFragments.js";
|
|
9
|
+
import {
|
|
10
|
+
STORIES_ATTR,
|
|
11
|
+
storiesOf,
|
|
12
|
+
storyKey,
|
|
13
|
+
storyNodeOf
|
|
14
|
+
} from "../schema/stories.js";
|
|
15
|
+
import { styledParagraph } from "./formatting.js";
|
|
16
|
+
import { buildParagraph } from "./importParagraph.js";
|
|
17
|
+
import { buildPreservedBlock } from "./importPreserved.js";
|
|
18
|
+
import { buildTable } from "./importTable.js";
|
|
19
|
+
import { scanBlocksIn } from "./scan.js";
|
|
20
|
+
import { blockKey } from "./session.js";
|
|
21
|
+
import {
|
|
22
|
+
asStoryKey,
|
|
23
|
+
sameStory,
|
|
24
|
+
storiesOf as storiesOf2,
|
|
25
|
+
storyKey as storyKey2,
|
|
26
|
+
withoutCommentStories
|
|
27
|
+
} from "../schema/stories.js";
|
|
28
|
+
var storyOf = storyNodeOf;
|
|
29
|
+
function buildBlock(el, srcId, sources, context) {
|
|
30
|
+
if (el.localName === "p") return buildParagraph(el, srcId, sources);
|
|
31
|
+
if (el.localName === "tbl") {
|
|
32
|
+
const table = buildTable(el, srcId, sources, context);
|
|
33
|
+
if (table) return table;
|
|
34
|
+
}
|
|
35
|
+
return buildPreservedBlock(el, srcId, "body");
|
|
36
|
+
}
|
|
37
|
+
function withStyleFormats(node, context) {
|
|
38
|
+
if (node.type === docxSchema.nodes.paragraph) {
|
|
39
|
+
return styledParagraph(node, context);
|
|
40
|
+
}
|
|
41
|
+
if (node.childCount === 0 || node.type === docxSchema.nodes.table) {
|
|
42
|
+
return node;
|
|
43
|
+
}
|
|
44
|
+
const children = node.children.map(
|
|
45
|
+
(child) => withStyleFormats(child, context)
|
|
46
|
+
);
|
|
47
|
+
if (children.every((child, i) => child === node.child(i))) return node;
|
|
48
|
+
return node.copy(Fragment.fromArray(children));
|
|
49
|
+
}
|
|
50
|
+
function malformed(what) {
|
|
51
|
+
throw new DocxImportError("malformed-xml", what);
|
|
52
|
+
}
|
|
53
|
+
var SELF_CLOSING = /\/>\s*$/;
|
|
54
|
+
function scanElement(xml, el) {
|
|
55
|
+
const scan = scanBlocksIn(xml, (_tag, depth) => depth === 0);
|
|
56
|
+
if (scan !== null) return scan;
|
|
57
|
+
if (!SELF_CLOSING.test(xml) || el.children.length > 0) {
|
|
58
|
+
malformed(`no ${el.nodeName} element to read from`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
prefix: xml.replace(SELF_CLOSING, ">"),
|
|
62
|
+
blocks: [],
|
|
63
|
+
suffix: `</${el.nodeName}>`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function scanContainer(xml, el) {
|
|
67
|
+
const scan = scanElement(xml, el);
|
|
68
|
+
const children = elementChildren(el);
|
|
69
|
+
if (children.length !== scan.blocks.length) {
|
|
70
|
+
malformed(
|
|
71
|
+
`the block count of ${el.nodeName} differs between the scan and the DOM`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
children.forEach((child, at) => {
|
|
75
|
+
if (child.nodeName !== scan.blocks[at].name) {
|
|
76
|
+
malformed(
|
|
77
|
+
`story block names disagree: ${child.nodeName} vs ${scan.blocks[at].name}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return { scan, children };
|
|
82
|
+
}
|
|
83
|
+
function readStory(place, container, deps) {
|
|
84
|
+
const key = storyKey(place.kind, place.id);
|
|
85
|
+
const { scan, children } = scanContainer(container.xml, container.el);
|
|
86
|
+
const nodes = children.map(
|
|
87
|
+
(child, at) => withStyleFormats(
|
|
88
|
+
buildBlock(
|
|
89
|
+
child,
|
|
90
|
+
blockKey(deps.session, key, at),
|
|
91
|
+
deps.sources,
|
|
92
|
+
deps.formatting
|
|
93
|
+
),
|
|
94
|
+
deps.formatting
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
const blocks = nodes.map((node, at) => ({
|
|
98
|
+
xml: scan.blocks[at].xml,
|
|
99
|
+
node
|
|
100
|
+
}));
|
|
101
|
+
if (nodes.length === 0) {
|
|
102
|
+
const placeholder = withStyleFormats(
|
|
103
|
+
docxSchema.nodes.paragraph.create({
|
|
104
|
+
srcId: blockKey(deps.session, key, 0)
|
|
105
|
+
}),
|
|
106
|
+
deps.formatting
|
|
107
|
+
);
|
|
108
|
+
nodes.push(placeholder);
|
|
109
|
+
blocks.push({ xml: "", node: placeholder });
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
key,
|
|
113
|
+
kind: place.kind,
|
|
114
|
+
id: place.id,
|
|
115
|
+
partPath: place.partPath,
|
|
116
|
+
xml: container.xml,
|
|
117
|
+
open: scan.prefix,
|
|
118
|
+
close: scan.suffix,
|
|
119
|
+
blocks,
|
|
120
|
+
doc: docxSchema.nodes.doc.create(null, nodes)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function readStories(part, deps) {
|
|
124
|
+
const root = parseXml(part.xml).documentElement;
|
|
125
|
+
const scan = scanElement(part.xml, root);
|
|
126
|
+
const children = elementChildren(root);
|
|
127
|
+
if (children.length !== scan.blocks.length) {
|
|
128
|
+
malformed(
|
|
129
|
+
`the entry count of ${part.partPath} differs between the scan and the DOM`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const stories = [];
|
|
133
|
+
const seen = /* @__PURE__ */ new Set();
|
|
134
|
+
children.forEach((el, at) => {
|
|
135
|
+
if (el.localName !== part.entryName) return;
|
|
136
|
+
const id = attributeByLocalName(el, part.idAttr);
|
|
137
|
+
if (id === null || seen.has(id)) return;
|
|
138
|
+
seen.add(id);
|
|
139
|
+
const xml = scan.blocks[at].xml;
|
|
140
|
+
stories.push(
|
|
141
|
+
readStory(
|
|
142
|
+
{ kind: part.kind, id, partPath: part.partPath },
|
|
143
|
+
{ el, xml: xml.slice(xml.indexOf("<")) },
|
|
144
|
+
deps
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
return stories;
|
|
149
|
+
}
|
|
150
|
+
function storiesByKey(stories) {
|
|
151
|
+
return new Map(Array.from(stories, (story) => [story.key, story]));
|
|
152
|
+
}
|
|
153
|
+
function setStory(tr, key, story) {
|
|
154
|
+
const json = story.toJSON();
|
|
155
|
+
return tr.setDocAttribute(STORIES_ATTR, {
|
|
156
|
+
...storiesOf(tr.doc),
|
|
157
|
+
[key]: json
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function withoutStories(tr, keys) {
|
|
161
|
+
const dropped = new Set(keys);
|
|
162
|
+
const stories = storiesOf(tr.doc);
|
|
163
|
+
const kept = Object.fromEntries(
|
|
164
|
+
Object.entries(stories).filter(([held]) => !dropped.has(held))
|
|
165
|
+
);
|
|
166
|
+
if (Object.keys(kept).length === Object.keys(stories).length) return tr;
|
|
167
|
+
return tr.setDocAttribute(STORIES_ATTR, kept);
|
|
168
|
+
}
|
|
169
|
+
function storyFromText(text) {
|
|
170
|
+
const pieces = [];
|
|
171
|
+
text.split("\n").forEach((line, at) => {
|
|
172
|
+
if (at > 0) pieces.push(docxSchema.nodes.hardBreak.create());
|
|
173
|
+
if (line.length > 0) pieces.push(docxSchema.text(line));
|
|
174
|
+
});
|
|
175
|
+
return docxSchema.nodes.doc.create(null, [
|
|
176
|
+
docxSchema.nodes.paragraph.create(null, pieces)
|
|
177
|
+
]);
|
|
178
|
+
}
|
|
179
|
+
var EMBEDDED = /* @__PURE__ */ new Set([
|
|
180
|
+
"drawing",
|
|
181
|
+
"pict",
|
|
182
|
+
"object",
|
|
183
|
+
"AlternateContent"
|
|
184
|
+
]);
|
|
185
|
+
function storyLeafText(leaf) {
|
|
186
|
+
if (leaf.isText) return leaf.text ?? "";
|
|
187
|
+
if (leaf.type === docxSchema.nodes.hardBreak) return "\n";
|
|
188
|
+
if (!isPreservedNode(leaf)) return "";
|
|
189
|
+
const element = leaf.attrs.element;
|
|
190
|
+
if (typeof element === "string" && EMBEDDED.has(element)) return "";
|
|
191
|
+
if (leaf.attrs.display === "break") return "\n";
|
|
192
|
+
return typeof leaf.attrs.text === "string" ? leaf.attrs.text : "";
|
|
193
|
+
}
|
|
194
|
+
function storyText(story) {
|
|
195
|
+
if (story === null) return "";
|
|
196
|
+
return story.textBetween(0, story.content.size, "\n", storyLeafText);
|
|
197
|
+
}
|
|
198
|
+
function withThreadKeyOn(story, paraId) {
|
|
199
|
+
let at = -1;
|
|
200
|
+
story.forEach((block, _offset, index) => {
|
|
201
|
+
if (block.type === docxSchema.nodes.paragraph) at = index;
|
|
202
|
+
});
|
|
203
|
+
if (at === -1) return story;
|
|
204
|
+
const paragraph = story.child(at);
|
|
205
|
+
const written = paragraph.attrs.pAttrs;
|
|
206
|
+
const pAttrs = typeof written === "string" ? written : "";
|
|
207
|
+
if (/\bw14:paraId\s*=/.test(pAttrs)) return story;
|
|
208
|
+
const declared = /\bxmlns:w14\s*=/.test(pAttrs) ? "" : `${xmlnsAttr("w14")[0]}="${NAMESPACES.w14}" `;
|
|
209
|
+
const keyed = paragraph.type.create(
|
|
210
|
+
{
|
|
211
|
+
...paragraph.attrs,
|
|
212
|
+
pAttrs: `${pAttrs === "" ? "" : `${pAttrs} `}${declared}${qualify("w14", "paraId")}="${paraId}"`
|
|
213
|
+
},
|
|
214
|
+
paragraph.content,
|
|
215
|
+
paragraph.marks
|
|
216
|
+
);
|
|
217
|
+
return story.copy(story.content.replaceChild(at, keyed));
|
|
218
|
+
}
|
|
219
|
+
export {
|
|
220
|
+
asStoryKey,
|
|
221
|
+
buildBlock,
|
|
222
|
+
readStories,
|
|
223
|
+
readStory,
|
|
224
|
+
sameStory,
|
|
225
|
+
setStory,
|
|
226
|
+
storiesByKey,
|
|
227
|
+
storiesOf2 as storiesOf,
|
|
228
|
+
storyFromText,
|
|
229
|
+
storyKey2 as storyKey,
|
|
230
|
+
storyLeafText,
|
|
231
|
+
storyOf,
|
|
232
|
+
storyText,
|
|
233
|
+
withStyleFormats,
|
|
234
|
+
withThreadKeyOn,
|
|
235
|
+
withoutCommentStories,
|
|
236
|
+
withoutStories
|
|
237
|
+
};
|
|
@@ -9,7 +9,12 @@ function comparableStory(story, strip) {
|
|
|
9
9
|
const blocks = [];
|
|
10
10
|
try {
|
|
11
11
|
story.doc.forEach((block) => {
|
|
12
|
-
blocks.push(
|
|
12
|
+
blocks.push(
|
|
13
|
+
serializeBlock(strip(block), {
|
|
14
|
+
...NO_EXPORT_REFS,
|
|
15
|
+
session: story.session
|
|
16
|
+
})
|
|
17
|
+
);
|
|
13
18
|
});
|
|
14
19
|
} catch (error) {
|
|
15
20
|
if (error instanceof DocxExportError) return null;
|
|
@@ -8,8 +8,24 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Node as PMNode } from "prosemirror-model";
|
|
10
10
|
import { type PageGeometry } from "./pageGeometry";
|
|
11
|
+
import { type GridRect } from "./tableFormatting";
|
|
12
|
+
/**
|
|
13
|
+
* The limit on how many rows and cells one table can hold.
|
|
14
|
+
* The toolbar grid lets you pick from something smaller (6x6); this only blocks absurd values.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_TABLE_SIDE = 50;
|
|
11
17
|
/** Whether the value can be used as a row count or a cell count */
|
|
12
18
|
export declare function isTableSide(value: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* One cell of a table being built: the block of the grid it covers, and the blocks it holds.
|
|
21
|
+
*
|
|
22
|
+
* A cell reaching across columns or down rows exists once, as the cell it starts at, which is how
|
|
23
|
+
* the model holds a merge and what `serializeTable` writes `w:gridSpan` and `w:vMerge` from.
|
|
24
|
+
*/
|
|
25
|
+
export interface TableCellPlan {
|
|
26
|
+
rect: GridRect;
|
|
27
|
+
content?: readonly PMNode[];
|
|
28
|
+
}
|
|
13
29
|
/**
|
|
14
30
|
* A single table made up of nothing but empty cells.
|
|
15
31
|
*
|
|
@@ -20,3 +36,12 @@ export declare function isTableSide(value: number): boolean;
|
|
|
20
36
|
* The row and cell counts must be values that have passed through `isTableSide`.
|
|
21
37
|
*/
|
|
22
38
|
export declare function createTableNode(rows: number, cols: number, geometry?: PageGeometry): PMNode;
|
|
39
|
+
/**
|
|
40
|
+
* The same table, laid out and filled by the caller: the shape a table read off the clipboard
|
|
41
|
+
* takes, where the cells are neither all one wide nor all empty.
|
|
42
|
+
*
|
|
43
|
+
* Everything the table itself wears is the same as a newly inserted one's, so a pasted table is
|
|
44
|
+
* drawn and written exactly like a table the toolbar put there. The grid the cells are measured
|
|
45
|
+
* and dressed against is the one they cover between them, row by row.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createTableNodeFrom(plan: readonly (readonly TableCellPlan[])[], geometry?: PageGeometry): PMNode;
|
|
@@ -83,27 +83,43 @@ function cellTemplate(width, defaults) {
|
|
|
83
83
|
format: readCellFormat(el, defaults)
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
-
function cellNode(props) {
|
|
86
|
+
function cellNode(props, plan) {
|
|
87
|
+
const content = plan.content ?? [];
|
|
87
88
|
return docxSchema.nodes.tableCell.create(
|
|
88
89
|
{
|
|
89
|
-
colspan:
|
|
90
|
-
rowspan:
|
|
90
|
+
colspan: plan.rect.right - plan.rect.left,
|
|
91
|
+
rowspan: plan.rect.bottom - plan.rect.top,
|
|
91
92
|
colwidth: null,
|
|
92
93
|
tcAttrs: null,
|
|
93
94
|
tcPr: props.tcPr,
|
|
94
95
|
tcW: props.tcW,
|
|
95
96
|
format: props.format
|
|
96
97
|
},
|
|
97
|
-
|
|
98
|
+
// A cell holds blocks, so one holding nothing still holds an empty paragraph
|
|
99
|
+
content.length === 0 ? docxSchema.nodes.paragraph.create() : [...content]
|
|
98
100
|
);
|
|
99
101
|
}
|
|
100
102
|
function rowNode(cells) {
|
|
101
103
|
return docxSchema.nodes.tableRow.create(
|
|
102
104
|
{ trAttrs: null, trPr: null, format: null },
|
|
103
|
-
cells
|
|
105
|
+
[...cells]
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
function evenCells(rows, cols) {
|
|
109
|
+
return Array.from(
|
|
110
|
+
{ length: rows },
|
|
111
|
+
(_, row) => Array.from({ length: cols }, (_2, col) => ({
|
|
112
|
+
rect: { top: row, bottom: row + 1, left: col, right: col + 1 }
|
|
113
|
+
}))
|
|
104
114
|
);
|
|
105
115
|
}
|
|
106
116
|
function createTableNode(rows, cols, geometry = A4_PORTRAIT) {
|
|
117
|
+
return createTableNodeFrom(evenCells(rows, cols), geometry);
|
|
118
|
+
}
|
|
119
|
+
function createTableNodeFrom(plan, geometry = A4_PORTRAIT) {
|
|
120
|
+
const placed = plan.flat();
|
|
121
|
+
const rows = Math.max(plan.length, ...placed.map((cell) => cell.rect.bottom));
|
|
122
|
+
const cols = Math.max(1, ...placed.map((cell) => cell.rect.right));
|
|
107
123
|
const total = bodyWidth(geometry).twips;
|
|
108
124
|
const gridCols = evenGridCols(cols, total);
|
|
109
125
|
const tblPr = tablePropsXml(total);
|
|
@@ -114,13 +130,12 @@ function createTableNode(rows, cols, geometry = A4_PORTRAIT) {
|
|
|
114
130
|
outer,
|
|
115
131
|
inside: readInsideBorders(tblPrEl)
|
|
116
132
|
};
|
|
117
|
-
const cellAt = (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
133
|
+
const cellAt = (cell) => cellNode(
|
|
134
|
+
cellTemplate(
|
|
135
|
+
gridCols.slice(cell.rect.left, cell.rect.right).reduce((width, column) => width + column, 0),
|
|
136
|
+
cellDefaultsFor(cell.rect, { rows, cols }, sources)
|
|
137
|
+
),
|
|
138
|
+
cell
|
|
124
139
|
);
|
|
125
140
|
return docxSchema.nodes.table.create(
|
|
126
141
|
{
|
|
@@ -131,13 +146,12 @@ function createTableNode(rows, cols, geometry = A4_PORTRAIT) {
|
|
|
131
146
|
gridCols,
|
|
132
147
|
format: outer
|
|
133
148
|
},
|
|
134
|
-
|
|
135
|
-
{ length: rows },
|
|
136
|
-
(_, row) => rowNode(gridCols.map((_width, col) => cellAt(row, col)))
|
|
137
|
-
)
|
|
149
|
+
plan.map((row) => rowNode(row.map(cellAt)))
|
|
138
150
|
);
|
|
139
151
|
}
|
|
140
152
|
export {
|
|
153
|
+
MAX_TABLE_SIDE,
|
|
141
154
|
createTableNode,
|
|
155
|
+
createTableNodeFrom,
|
|
142
156
|
isTableSide
|
|
143
157
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inline wrappers this editor takes apart and puts back together, one entry each.
|
|
3
|
+
*
|
|
4
|
+
* A wrapper is an element holding inline content that goes back out around the same content: a
|
|
5
|
+
* content control (`w:sdt`, §17.5.2.17), a hyperlink (`w:hyperlink`, §17.16.22). `EG_PContent`
|
|
6
|
+
* lets them nest in any order and any depth, so import reads the nesting as the file wrote it and
|
|
7
|
+
* export rebuilds it from the marks (`schema/wrappers` holds the order, `docx/importParagraph`
|
|
8
|
+
* reads, `docx/serializeParagraph` writes).
|
|
9
|
+
*
|
|
10
|
+
* A kind knows three things and nothing about either walk: what a mark reads out of one element,
|
|
11
|
+
* what opens it again, and what closes it. Tracked changes (`w:ins`, `w:del`) and simple fields
|
|
12
|
+
* (`w:fldSimple`) are wrappers of the same shape. Registering one is an entry here and a mark spec
|
|
13
|
+
* in `schema/docxSchema` carrying `WRAPPER_ATTRS`; its attrs also want a row in
|
|
14
|
+
* `schema/attrRoles`, and two test lists name the kinds outright (`WRAPPERS_READS` in
|
|
15
|
+
* `schema/attrClasses.test.ts`, the kind list in `schema/wrappers.test.ts`). Neither paragraph walk
|
|
16
|
+
* and neither policy table has to be touched.
|
|
17
|
+
*
|
|
18
|
+
* The opening tag is the verbatim string the file wrote wherever there is one, so a wrapper nobody
|
|
19
|
+
* edited goes back out byte for byte; only the closing tag is written from scratch.
|
|
20
|
+
*/
|
|
21
|
+
import type { Mark } from "prosemirror-model";
|
|
22
|
+
import type { ExportRefs } from "./exportRefs";
|
|
23
|
+
import type { ImportSources } from "./importParagraph";
|
|
24
|
+
/** A wrapper read off the file: the mark its content wears, and the element that content stands in */
|
|
25
|
+
export interface WrapperReading {
|
|
26
|
+
mark: Mark;
|
|
27
|
+
content: Element;
|
|
28
|
+
}
|
|
29
|
+
/** One kind of inline wrapper, as import reads it and export writes it */
|
|
30
|
+
export interface WrapperKind {
|
|
31
|
+
/** The mark name in the schema */
|
|
32
|
+
readonly mark: string;
|
|
33
|
+
/** The local name of the element it comes from */
|
|
34
|
+
readonly element: string;
|
|
35
|
+
/**
|
|
36
|
+
* The mark this element goes on and the content to read on inside it. null for a shape we do
|
|
37
|
+
* not write back ourselves, which leaves the element preserved whole where it stood.
|
|
38
|
+
*/
|
|
39
|
+
read(el: Element, depth: number, sources: ImportSources): WrapperReading | null;
|
|
40
|
+
/** The opening XML this mark goes back out as */
|
|
41
|
+
open(mark: Mark, refs: ExportRefs): string;
|
|
42
|
+
/** What closes it again */
|
|
43
|
+
close(mark: Mark): string;
|
|
44
|
+
/**
|
|
45
|
+
* The same wrapper under a name of its own, for a second stretch claiming one name
|
|
46
|
+
* (`docx/identities`). Absent for a kind that names nothing a document must keep unique.
|
|
47
|
+
*/
|
|
48
|
+
copy?(mark: Mark): Mark;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Every wrapper the editor models, in the order the schema declares their marks: what stands
|
|
52
|
+
* first here stands outside the others where two of them are written at one depth.
|
|
53
|
+
*/
|
|
54
|
+
export declare const WRAPPER_KINDS: readonly WrapperKind[];
|
|
55
|
+
/** The kind this element is one of, undefined for an element no kind reads */
|
|
56
|
+
export declare function wrapperKindFor(el: Element): WrapperKind | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Whether a wrapper of this kind may be read inside the wrappers already around it.
|
|
59
|
+
*
|
|
60
|
+
* A kind whose mark excludes its own type records one of itself at a time, so the inner of two is
|
|
61
|
+
* left whole where it stood rather than pushing the outer one off the content it wraps: that is a
|
|
62
|
+
* `w:hyperlink` inside a `w:hyperlink`, which OOXML admits and Word does not write. A kind that
|
|
63
|
+
* excludes nothing nests as deeply as the file does (`w:sdt`, §17.5.2.17).
|
|
64
|
+
*/
|
|
65
|
+
export declare function wrapperFits(kind: WrapperKind, wrappers: readonly Mark[]): boolean;
|
|
66
|
+
/** The kind this mark stands for. A wrapper mark nobody registered has no way back out */
|
|
67
|
+
export declare function wrapperKindOf(mark: Mark): WrapperKind;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/docx/wrappers.ts
|
|
2
|
+
import { DocxExportError } from "../ooxml/errors.js";
|
|
3
|
+
import { docxSchema } from "../schema/index.js";
|
|
4
|
+
import { readHyperlinkWrapper, relIdIn, withRelId } from "./hyperlink.js";
|
|
5
|
+
import { copiedControlPrefix, newControlId, readSdtWrapper } from "./sdt.js";
|
|
6
|
+
var counts = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function nextKey(kind, el) {
|
|
8
|
+
const perDocument = counts.get(el.ownerDocument) ?? /* @__PURE__ */ new Map();
|
|
9
|
+
counts.set(el.ownerDocument, perDocument);
|
|
10
|
+
const key = perDocument.get(kind.mark) ?? 0;
|
|
11
|
+
perDocument.set(kind.mark, key + 1);
|
|
12
|
+
return key;
|
|
13
|
+
}
|
|
14
|
+
var SDT = {
|
|
15
|
+
mark: "sdt",
|
|
16
|
+
element: "sdt",
|
|
17
|
+
read(el, depth) {
|
|
18
|
+
const wrapper = readSdtWrapper(el);
|
|
19
|
+
if (!wrapper) return null;
|
|
20
|
+
return {
|
|
21
|
+
mark: docxSchema.marks.sdt.create({
|
|
22
|
+
sdtPrefix: wrapper.prefix,
|
|
23
|
+
depth,
|
|
24
|
+
key: nextKey(SDT, el),
|
|
25
|
+
contentsLocked: wrapper.contentsLocked,
|
|
26
|
+
deletionLocked: wrapper.deletionLocked
|
|
27
|
+
}),
|
|
28
|
+
content: wrapper.content
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
open(mark) {
|
|
32
|
+
const prefix = mark.attrs.sdtPrefix;
|
|
33
|
+
if (typeof prefix !== "string") {
|
|
34
|
+
throw new DocxExportError(
|
|
35
|
+
"lost-original",
|
|
36
|
+
"a content control has lost the opening XML it goes back out as"
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return `${prefix}<w:sdtContent>`;
|
|
40
|
+
},
|
|
41
|
+
close: () => "</w:sdtContent></w:sdt>",
|
|
42
|
+
copy(mark) {
|
|
43
|
+
const prefix = mark.attrs.sdtPrefix;
|
|
44
|
+
if (typeof prefix !== "string") return mark;
|
|
45
|
+
return mark.type.create({
|
|
46
|
+
...mark.attrs,
|
|
47
|
+
sdtPrefix: copiedControlPrefix(prefix, newControlId())
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var LINK = {
|
|
52
|
+
mark: "link",
|
|
53
|
+
element: "hyperlink",
|
|
54
|
+
read(el, depth, sources) {
|
|
55
|
+
const wrapper = readHyperlinkWrapper(el);
|
|
56
|
+
if (!wrapper) return null;
|
|
57
|
+
return {
|
|
58
|
+
mark: docxSchema.marks.link.create({
|
|
59
|
+
linkPrefix: wrapper.prefix,
|
|
60
|
+
href: wrapper.relId === null ? null : sources.links.get(wrapper.relId) ?? null,
|
|
61
|
+
depth,
|
|
62
|
+
key: nextKey(LINK, el)
|
|
63
|
+
}),
|
|
64
|
+
content: el
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
open(mark, refs) {
|
|
68
|
+
const prefix = mark.attrs.linkPrefix;
|
|
69
|
+
const original = typeof prefix === "string" ? prefix : null;
|
|
70
|
+
const href = mark.attrs.href;
|
|
71
|
+
if (typeof href !== "string") {
|
|
72
|
+
if (original !== null) return original;
|
|
73
|
+
throw new DocxExportError(
|
|
74
|
+
"lost-original",
|
|
75
|
+
"a hyperlink carries neither an address nor the opening XML it goes back out as"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const relId = refs.links.relIdOf(
|
|
79
|
+
href,
|
|
80
|
+
original === null ? null : relIdIn(original)
|
|
81
|
+
);
|
|
82
|
+
if (relId === void 0) {
|
|
83
|
+
if (original !== null) return original;
|
|
84
|
+
throw new DocxExportError(
|
|
85
|
+
"unsupported-content",
|
|
86
|
+
"an inserted hyperlink has no relationship to point at; export it through exportDocx"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return withRelId(original ?? "<w:hyperlink>", relId);
|
|
90
|
+
},
|
|
91
|
+
close: () => "</w:hyperlink>"
|
|
92
|
+
};
|
|
93
|
+
var WRAPPER_KINDS = [SDT, LINK];
|
|
94
|
+
function wrapperKindFor(el) {
|
|
95
|
+
return WRAPPER_KINDS.find((kind) => kind.element === el.localName);
|
|
96
|
+
}
|
|
97
|
+
function wrapperFits(kind, wrappers) {
|
|
98
|
+
const type = docxSchema.marks[kind.mark];
|
|
99
|
+
if (!type.excludes(type)) return true;
|
|
100
|
+
return !wrappers.some((mark) => mark.type === type);
|
|
101
|
+
}
|
|
102
|
+
function wrapperKindOf(mark) {
|
|
103
|
+
const kind = WRAPPER_KINDS.find((entry) => entry.mark === mark.type.name);
|
|
104
|
+
if (!kind) {
|
|
105
|
+
throw new DocxExportError(
|
|
106
|
+
"unsupported-content",
|
|
107
|
+
`inline wrapper we cannot serialize: ${mark.type.name}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return kind;
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
WRAPPER_KINDS,
|
|
114
|
+
wrapperFits,
|
|
115
|
+
wrapperKindFor,
|
|
116
|
+
wrapperKindOf
|
|
117
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The elements a reading takes as blocks of their own.
|
|
3
|
+
*
|
|
4
|
+
* Everything else in a piece of clipboard HTML is read by the shape of the markup alone: an
|
|
5
|
+
* element either names a block or it is text in the paragraph around it. A block reader is how an
|
|
6
|
+
* element that means more than its shape says - a table, a Word paragraph that is really a list
|
|
7
|
+
* item - is read as what it means, without the reading itself learning about the applications
|
|
8
|
+
* one by one.
|
|
9
|
+
*/
|
|
10
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
11
|
+
import { type ListKind } from "../../numbering/listTemplate";
|
|
12
|
+
import { type InlineContext } from "./inlineFormatting";
|
|
13
|
+
import type { HtmlReadContext } from "./readContext";
|
|
14
|
+
/** Where one paragraph being read stands in a list */
|
|
15
|
+
export interface ListPlacement {
|
|
16
|
+
kind: ListKind;
|
|
17
|
+
/** The number the list wears here, or null where this document may start no list */
|
|
18
|
+
numId: number | null;
|
|
19
|
+
/** How deep the item sits, counted from zero */
|
|
20
|
+
level: number;
|
|
21
|
+
}
|
|
22
|
+
/** What a block reader may ask of the reading it is part of */
|
|
23
|
+
export interface HtmlBlockHost {
|
|
24
|
+
/** What the markup is being read against, including the application that wrote it */
|
|
25
|
+
readonly context: HtmlReadContext;
|
|
26
|
+
/** Whether the reading already stands inside a cell of a table it is reading */
|
|
27
|
+
readonly inTable: boolean;
|
|
28
|
+
/** The blocks the content of one cell reads as. None where the cell holds nothing */
|
|
29
|
+
readCell(cell: ParentNode, inline: InlineContext): readonly PMNode[];
|
|
30
|
+
/** The inline nodes these nodes read as */
|
|
31
|
+
readInline(nodes: Iterable<Node>, inline: InlineContext): readonly PMNode[];
|
|
32
|
+
/** One paragraph of the reading, as an item of the list it belongs to when it is one */
|
|
33
|
+
paragraph(content: readonly PMNode[], list?: ListPlacement | null): PMNode;
|
|
34
|
+
/**
|
|
35
|
+
* The number the pasted list this key names takes here. The same key answers the same number,
|
|
36
|
+
* so the paragraphs of one list join it rather than each starting a list of their own.
|
|
37
|
+
*/
|
|
38
|
+
listNumber(key: string, kind: ListKind): number | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One kind of element read as blocks of its own rather than as text where it stands.
|
|
42
|
+
*
|
|
43
|
+
* A reader answers null for an element it has nothing to say about, so that deciding whether an
|
|
44
|
+
* element is its own and reading it are one pass rather than two.
|
|
45
|
+
*/
|
|
46
|
+
export interface HtmlBlockReader {
|
|
47
|
+
read(element: HTMLElement, inline: InlineContext, host: HtmlBlockHost): readonly PMNode[] | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* An HTML table, read as a table of this document.
|
|
51
|
+
*
|
|
52
|
+
* The cells hold what their own markup reads as, one paragraph per block they hold, and the table
|
|
53
|
+
* is as wide as a table inserted here would be: a pasted table is drawn and written out exactly
|
|
54
|
+
* like one the toolbar put there, since nothing of the width a foreign document measured its own
|
|
55
|
+
* paper against would mean the same here.
|
|
56
|
+
*
|
|
57
|
+
* A table inside a cell is not read as a table, since the model only makes the outer one editable,
|
|
58
|
+
* and neither is one larger than the model holds. Both are read as their rows instead, so that a
|
|
59
|
+
* cell of the inner table still stands apart from the next one. A caption stands as the paragraph
|
|
60
|
+
* above whichever of the two the table is read as.
|
|
61
|
+
*/
|
|
62
|
+
export declare const tableBlockReader: HtmlBlockReader;
|
|
63
|
+
/**
|
|
64
|
+
* A Word paragraph that is an item of a list, read as an item of a list here.
|
|
65
|
+
*
|
|
66
|
+
* Word puts no `<ul>` or `<ol>` on the clipboard: every item is a paragraph saying which list it
|
|
67
|
+
* belongs to, so the paragraphs of one list are joined by that name rather than by standing inside
|
|
68
|
+
* one element. Whether the list counts its items is only visible in the marker Word drew, which
|
|
69
|
+
* is taken out of the paragraph wherever it is: marked as a marker, or standing at the front of
|
|
70
|
+
* the content as anything else would.
|
|
71
|
+
*/
|
|
72
|
+
export declare const wordListReader: HtmlBlockReader;
|
|
73
|
+
/** The readers every reading consults, in the order they are tried */
|
|
74
|
+
export declare const DEFAULT_BLOCK_READERS: readonly HtmlBlockReader[];
|