@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/docx/comments/writing.ts
|
|
2
|
-
import {
|
|
2
|
+
import { attrsText } from "../../ooxml/element.js";
|
|
3
3
|
import { NAMESPACES, wName, xmlnsDecl } from "../../ooxml/names.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureRootDeclarations,
|
|
@@ -7,10 +7,18 @@ import {
|
|
|
7
7
|
splicePart
|
|
8
8
|
} from "../../ooxml/partSplice.js";
|
|
9
9
|
import { encodeUtf8 } from "../../ooxml/xml.js";
|
|
10
|
+
import { sameSource } from "../../schema/sourceEquality.js";
|
|
11
|
+
import { NO_EXPORT_REFS } from "../exportRefs.js";
|
|
10
12
|
import { directoryOf } from "../relationships.js";
|
|
13
|
+
import { serializeStory } from "../serializeStory.js";
|
|
14
|
+
import {
|
|
15
|
+
storyFromText,
|
|
16
|
+
storyKey,
|
|
17
|
+
storyOf,
|
|
18
|
+
withThreadKeyOn
|
|
19
|
+
} from "../story.js";
|
|
11
20
|
import {
|
|
12
21
|
arrivedEntries,
|
|
13
|
-
renderCommentBody,
|
|
14
22
|
renderCommentExtension,
|
|
15
23
|
withThreadKey
|
|
16
24
|
} from "./grammar.js";
|
|
@@ -20,7 +28,26 @@ import {
|
|
|
20
28
|
import { commentsExtendedPart, commentsPart, peoplePart } from "./parts.js";
|
|
21
29
|
import { planPeoplePart } from "./people.js";
|
|
22
30
|
function needsThreadKey(id, comment, session) {
|
|
23
|
-
return
|
|
31
|
+
return session.comments.byId.has(id) && carriesThreadMetadata(comment) && (session.comments.byId.get(id)?.paraId ?? null) === null;
|
|
32
|
+
}
|
|
33
|
+
function bodyStory(doc, id) {
|
|
34
|
+
return storyOf(doc, storyKey("comment", id));
|
|
35
|
+
}
|
|
36
|
+
function importedBody(session, id) {
|
|
37
|
+
return session.stories.get(storyKey("comment", id)) ?? null;
|
|
38
|
+
}
|
|
39
|
+
function importedEntry(comment, session) {
|
|
40
|
+
const arrived = session.comments.byId.get(comment.id);
|
|
41
|
+
if (arrived === void 0 || arrived.author !== comment.author || arrived.date !== comment.date || arrived.initials !== comment.initials) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return importedBody(session, comment.id);
|
|
45
|
+
}
|
|
46
|
+
function bodyKept(doc, comment, session) {
|
|
47
|
+
const imported = importedEntry(comment, session);
|
|
48
|
+
if (imported === null) return null;
|
|
49
|
+
const current = bodyStory(doc, comment.id);
|
|
50
|
+
return current === null || sameSource(current, imported.doc) ? imported : null;
|
|
24
51
|
}
|
|
25
52
|
function commentsChanged(doc, session) {
|
|
26
53
|
const current = commentReferencesIn(doc);
|
|
@@ -34,12 +61,8 @@ function commentsChanged(doc, session) {
|
|
|
34
61
|
for (const id of originalBodies) {
|
|
35
62
|
if (!currentBodies.has(id)) return true;
|
|
36
63
|
}
|
|
37
|
-
for (const
|
|
38
|
-
|
|
39
|
-
if (!comment?.imported) return true;
|
|
40
|
-
}
|
|
41
|
-
for (const comment of current.values()) {
|
|
42
|
-
if (comment.replies.some((reply) => !reply.imported)) return true;
|
|
64
|
+
for (const comment of currentBodies.values()) {
|
|
65
|
+
if (bodyKept(doc, comment, session) === null) return true;
|
|
43
66
|
}
|
|
44
67
|
for (const [id, comment] of current) {
|
|
45
68
|
if (needsThreadKey(id, comment, session)) return true;
|
|
@@ -111,27 +134,34 @@ function carriesThreadMetadata(comment) {
|
|
|
111
134
|
function keyedEntry(comment, arrivedKeyed) {
|
|
112
135
|
return carriesThreadMetadata(comment) || arrivedKeyed.has(comment.id);
|
|
113
136
|
}
|
|
114
|
-
function
|
|
115
|
-
if (comment.imported && comment.commentXml !== null) {
|
|
116
|
-
return carriesThreadMetadata(comment) ? withThreadKey(
|
|
117
|
-
comment.commentXml,
|
|
118
|
-
comment.paraId,
|
|
119
|
-
arrived.get(comment.id) ?? null
|
|
120
|
-
) : comment.commentXml;
|
|
121
|
-
}
|
|
137
|
+
function writtenContainer(comment) {
|
|
122
138
|
const attrs = [
|
|
123
139
|
[wName("id"), comment.id],
|
|
124
140
|
comment.author === null ? null : [wName("author"), comment.author],
|
|
125
141
|
comment.date === null ? null : [wName("date"), comment.date],
|
|
126
142
|
comment.initials === null ? null : [wName("initials"), comment.initials]
|
|
127
143
|
];
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
return elementXml(
|
|
131
|
-
wName("comment"),
|
|
132
|
-
attrs.filter((attr) => attr !== null),
|
|
133
|
-
[body]
|
|
144
|
+
const text = attrsText(
|
|
145
|
+
attrs.filter((attr) => attr !== null)
|
|
134
146
|
);
|
|
147
|
+
return {
|
|
148
|
+
open: text === "" ? `<${wName("comment")}>` : `<${wName("comment")} ${text}>`,
|
|
149
|
+
close: `</${wName("comment")}>`
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function renderedComment(comment, arrivedKeyed, arrived, doc, session) {
|
|
153
|
+
const kept = bodyKept(doc, comment, session);
|
|
154
|
+
if (kept !== null) {
|
|
155
|
+
return carriesThreadMetadata(comment) ? withThreadKey(kept.xml, comment.paraId, arrived.get(comment.id) ?? null) : kept.xml;
|
|
156
|
+
}
|
|
157
|
+
const paraId = keyedEntry(comment, arrivedKeyed) ? comment.paraId : null;
|
|
158
|
+
const entry = importedEntry(comment, session);
|
|
159
|
+
const said = bodyStory(doc, comment.id) ?? storyFromText("");
|
|
160
|
+
const body = paraId === null ? said : withThreadKeyOn(said, paraId);
|
|
161
|
+
return serializeStory(body, null, entry ?? writtenContainer(comment), {
|
|
162
|
+
...NO_EXPORT_REFS,
|
|
163
|
+
session
|
|
164
|
+
});
|
|
135
165
|
}
|
|
136
166
|
var COMMENT_MARKUP = { namespaces: { w: NAMESPACES.w } };
|
|
137
167
|
var THREAD_MARKUP = {
|
|
@@ -160,7 +190,8 @@ function originalThreadIds(comments, rootIds) {
|
|
|
160
190
|
}
|
|
161
191
|
return ids;
|
|
162
192
|
}
|
|
163
|
-
function commentsXml(
|
|
193
|
+
function commentsXml(doc, session, references, originallyReferenced) {
|
|
194
|
+
const comments = session.comments;
|
|
164
195
|
const currentBodies = currentCommentBodies(references);
|
|
165
196
|
const arrivedKeyed = new Set(
|
|
166
197
|
Array.from(comments.byId.values()).flatMap(
|
|
@@ -175,20 +206,26 @@ function commentsXml(references, comments, originallyReferenced) {
|
|
|
175
206
|
const pieces = [];
|
|
176
207
|
const written = /* @__PURE__ */ new Set();
|
|
177
208
|
for (const original of comments.ordered) {
|
|
209
|
+
if (written.has(original.id)) continue;
|
|
178
210
|
const current = currentBodies.get(original.id);
|
|
179
211
|
if (current) {
|
|
180
|
-
pieces.push(
|
|
212
|
+
pieces.push(
|
|
213
|
+
renderedComment(current, arrivedKeyed, arrived, doc, session)
|
|
214
|
+
);
|
|
181
215
|
written.add(original.id);
|
|
182
216
|
continue;
|
|
183
217
|
}
|
|
184
218
|
if (!originalThreads.has(original.id)) {
|
|
185
|
-
|
|
219
|
+
const story = importedBody(session, original.id);
|
|
220
|
+
if (story !== null) pieces.push(story.xml);
|
|
186
221
|
written.add(original.id);
|
|
187
222
|
}
|
|
188
223
|
}
|
|
189
224
|
for (const [id, comment] of currentBodies) {
|
|
190
225
|
if (!written.has(id)) {
|
|
191
|
-
pieces.push(
|
|
226
|
+
pieces.push(
|
|
227
|
+
renderedComment(comment, arrivedKeyed, arrived, doc, session)
|
|
228
|
+
);
|
|
192
229
|
}
|
|
193
230
|
}
|
|
194
231
|
if (comments.xml === null) {
|
|
@@ -221,7 +258,7 @@ function planCommentParts(doc, session, context) {
|
|
|
221
258
|
parts.set(
|
|
222
259
|
partPath,
|
|
223
260
|
encodeUtf8(
|
|
224
|
-
commentsXml(
|
|
261
|
+
commentsXml(doc, session, references, session.commentReferenceIds),
|
|
225
262
|
session.comments.hadBom
|
|
226
263
|
)
|
|
227
264
|
);
|
package/dist/docx/exportDocx.js
CHANGED
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
W_NS,
|
|
11
11
|
withXmlParser
|
|
12
12
|
} from "../ooxml/xml.js";
|
|
13
|
-
import { sameSource } from "../schema/sourceEquality.js";
|
|
14
13
|
import { commentsPlanner } from "./comments.js";
|
|
15
14
|
import { repackParts } from "./container.js";
|
|
16
15
|
import {
|
|
17
16
|
fidelityNotesOf
|
|
18
17
|
} from "./fidelity.js";
|
|
18
|
+
import { headerFooterPlanner } from "./headersFooters.js";
|
|
19
19
|
import { hyperlinkRefs } from "./hyperlink.js";
|
|
20
20
|
import { withUniqueIdentities } from "./identities.js";
|
|
21
21
|
import { problemsOf } from "./invariants.js";
|
|
@@ -31,21 +31,15 @@ import {
|
|
|
31
31
|
relationshipWriter,
|
|
32
32
|
relsPathOf
|
|
33
33
|
} from "./relationships.js";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
originalBlock,
|
|
37
|
-
sessionOf
|
|
38
|
-
} from "./session.js";
|
|
39
|
-
function blockXml(node, session, refs) {
|
|
40
|
-
const imported = originalBlock(node, session);
|
|
41
|
-
if (imported && sameSource(node, imported.node)) return imported.xml;
|
|
42
|
-
return serializeBlock(node, session, refs);
|
|
43
|
-
}
|
|
34
|
+
import { blockXml } from "./serializeBlock.js";
|
|
35
|
+
import { sessionOf } from "./session.js";
|
|
44
36
|
function buildDocumentXml(doc, session, refs) {
|
|
45
37
|
const pieces = [session.documentPrefix];
|
|
46
38
|
withUniqueIdentities(doc).forEach((child) => {
|
|
47
|
-
pieces.push(blockXml(child,
|
|
39
|
+
pieces.push(blockXml(child, refs, doc.childCount === 1));
|
|
48
40
|
});
|
|
41
|
+
const sectPr = doc.attrs.sectPr;
|
|
42
|
+
if (typeof sectPr === "string") pieces.push(sectPr);
|
|
49
43
|
pieces.push(session.documentSuffix);
|
|
50
44
|
return pieces.join("");
|
|
51
45
|
}
|
|
@@ -113,7 +107,8 @@ function assertMainPart(documentXml, wroteLinks) {
|
|
|
113
107
|
var LINK_MARKUP = { namespaces: { r: R_NS } };
|
|
114
108
|
var PART_PLANNERS = [
|
|
115
109
|
numberingPlanner,
|
|
116
|
-
commentsPlanner
|
|
110
|
+
commentsPlanner,
|
|
111
|
+
headerFooterPlanner
|
|
117
112
|
];
|
|
118
113
|
function exportDocx(doc, session, options) {
|
|
119
114
|
return exportDocxReport(doc, session, options).bytes;
|
|
@@ -153,7 +148,8 @@ function writeDocx(doc, store, notes, planners) {
|
|
|
153
148
|
const body = buildDocumentXml(doc, store, {
|
|
154
149
|
images: media?.refs ?? NO_IMAGE_REFS,
|
|
155
150
|
links,
|
|
156
|
-
notes
|
|
151
|
+
notes,
|
|
152
|
+
session: store
|
|
157
153
|
});
|
|
158
154
|
const documentXml = links.addedRelId() ? ensureRootDeclarations(body, LINK_MARKUP) : body;
|
|
159
155
|
assertMainPart(documentXml, links.addedRelId());
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What the serializers need from the rest of the package while a body is being written: which
|
|
3
3
|
* relationship an inserted image ended up on (`docx/media`), which one each link's address lives
|
|
4
|
-
* on (`docx/hyperlink`),
|
|
5
|
-
* (`docx/
|
|
4
|
+
* on (`docx/hyperlink`), where to record an approximation a writer had to make (`docx/fidelity`),
|
|
5
|
+
* and the fragments the document was opened from (`docx/session`).
|
|
6
6
|
*
|
|
7
7
|
* They travel as one value so that a serializer taking a body apart passes on everything the
|
|
8
|
-
* pieces of it may need, whatever a later kind of content turns out to require.
|
|
8
|
+
* pieces of it may need, whatever a later kind of content turns out to require. The session is
|
|
9
|
+
* among them because a preserved block names the fragment it stands for wherever it ends up: an
|
|
10
|
+
* edit may move one into a table cell, and the cell writer has no other way to reach the original.
|
|
9
11
|
*/
|
|
10
12
|
import { type FidelityCollector } from "./fidelity";
|
|
11
13
|
import { type LinkRefs } from "./hyperlink";
|
|
12
14
|
import { type ImageRefs } from "./media";
|
|
15
|
+
import type { SessionStore } from "./session";
|
|
13
16
|
export interface ExportRefs {
|
|
14
17
|
images: ImageRefs;
|
|
15
18
|
links: LinkRefs;
|
|
16
19
|
notes: FidelityCollector;
|
|
20
|
+
/** null for a serializer called on a document nobody opened */
|
|
21
|
+
session: SessionStore | null;
|
|
17
22
|
}
|
|
18
23
|
/** What a serializer called on its own, outside an export, has to work with */
|
|
19
24
|
export declare const NO_EXPORT_REFS: ExportRefs;
|
package/dist/docx/exportRefs.js
CHANGED
package/dist/docx/fidelity.js
CHANGED
|
@@ -13,31 +13,45 @@ function elementOf(node) {
|
|
|
13
13
|
return stringAttr(node.attrs.name) ?? elementNameOf(stringAttr(node.attrs.xml) ?? "");
|
|
14
14
|
}
|
|
15
15
|
function demotedBlock(element) {
|
|
16
|
-
const name =
|
|
16
|
+
const name = localNameOf(element);
|
|
17
17
|
if (name === "tbl") return { severity: "placeholder", code: "table-demoted" };
|
|
18
18
|
if (name === "p") {
|
|
19
19
|
return { severity: "placeholder", code: "paragraph-demoted" };
|
|
20
20
|
}
|
|
21
21
|
return { severity: "placeholder", code: "preserved-block" };
|
|
22
22
|
}
|
|
23
|
+
function severityOf(display) {
|
|
24
|
+
return display === "chip" ? "placeholder" : "hidden";
|
|
25
|
+
}
|
|
23
26
|
function preservedBy(node, element) {
|
|
24
27
|
switch (node.type.name) {
|
|
25
|
-
case "docxRaw":
|
|
26
|
-
return demotedBlock(element);
|
|
27
28
|
case "rawBlock":
|
|
28
|
-
return
|
|
29
|
-
case "
|
|
30
|
-
return {
|
|
29
|
+
return preservedBlockNote(node, element);
|
|
30
|
+
case "rawRunContent":
|
|
31
|
+
return {
|
|
32
|
+
severity: severityOf(node.attrs.display),
|
|
33
|
+
code: "preserved-run-content"
|
|
34
|
+
};
|
|
31
35
|
case "rawInline":
|
|
32
|
-
return preservedInline(element);
|
|
36
|
+
return preservedInline(node, element);
|
|
33
37
|
default:
|
|
34
38
|
return null;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
41
|
+
function preservedBlockNote(node, element) {
|
|
42
|
+
if (severityOf(node.attrs.display) === "placeholder") {
|
|
43
|
+
return demotedBlock(element);
|
|
44
|
+
}
|
|
45
|
+
return node.attrs.guarded === true ? { severity: "hidden", code: "range-marker" } : { severity: "hidden", code: "preserved-block" };
|
|
46
|
+
}
|
|
47
|
+
function preservedInline(node, element) {
|
|
48
|
+
const name = stringAttr(node.attrs.element) ?? localNameOf(element);
|
|
39
49
|
if (name === "r") return null;
|
|
40
|
-
|
|
50
|
+
const severity = severityOf(node.attrs.display);
|
|
51
|
+
return severity === "hidden" && node.attrs.guarded === true ? { severity, code: "range-marker" } : { severity, code: "preserved-inline" };
|
|
52
|
+
}
|
|
53
|
+
function localNameOf(element) {
|
|
54
|
+
return element === null ? null : localPart(element);
|
|
41
55
|
}
|
|
42
56
|
function blockNumberOf(node) {
|
|
43
57
|
const srcId = stringAttr(node.attrs.srcId);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the fields of a paragraph stand, read back off the fragments the editor keeps as they came.
|
|
3
|
+
*
|
|
4
|
+
* A complex field is not one element. It is a begin character, the instruction text, an optional
|
|
5
|
+
* separate character, the result its producer cached, and an end character, all standing loose
|
|
6
|
+
* among the runs of the paragraph in that order (§17.16.18). Nothing in the model pairs them up, so
|
|
7
|
+
* anything that wants to know what a field was told to print has to read the characters back. A
|
|
8
|
+
* simple field says the same in one element and carries its instruction as an attribute (§17.16.19).
|
|
9
|
+
*
|
|
10
|
+
* What a field means, and how to work it out again, belongs to the fields feature. This answers the
|
|
11
|
+
* one question a projection has: where each field stands and what its instruction says.
|
|
12
|
+
*/
|
|
13
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
14
|
+
export interface FieldSpan {
|
|
15
|
+
/** The instruction text between begin and separate (or end), whitespace-trimmed */
|
|
16
|
+
instr: string;
|
|
17
|
+
/** Where the begin character stands, and the simple field itself for one written as a single element */
|
|
18
|
+
begin: number;
|
|
19
|
+
/** Where the separate character stands, and null for a field that cached no result */
|
|
20
|
+
separate: number | null;
|
|
21
|
+
/** Where the end character stands, which is `begin` again for a simple field */
|
|
22
|
+
end: number;
|
|
23
|
+
/** The half-open range of positions the cached result covers, and null for a field carrying none */
|
|
24
|
+
result: [number, number] | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether the node is one of the pieces a complex field is written out of rather than something
|
|
28
|
+
* the field prints. A projection drops these whether or not they pair up into a field.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isFieldCharacter(node: PMNode): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The fields this paragraph holds, in the order they begin, each position offset by `offset`.
|
|
33
|
+
*
|
|
34
|
+
* Positions are counted inside the paragraph's content, so `offset` is where that content starts:
|
|
35
|
+
* a caller reading a paragraph standing on its own passes 0, and one reading a paragraph of a
|
|
36
|
+
* document passes the position just inside it.
|
|
37
|
+
*
|
|
38
|
+
* A begin character with no end is no field: the file says a field starts there and never says
|
|
39
|
+
* what it prints, so there is nothing to pair it with and it is passed over. `documentFidelity`
|
|
40
|
+
* still reports the characters themselves as content kept rather than modelled.
|
|
41
|
+
*/
|
|
42
|
+
export declare function fieldSpans(paragraph: PMNode, offset: number): FieldSpan[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/docx/fields.ts
|
|
2
|
+
import { parsePropsXml } from "../ooxml/props.js";
|
|
3
|
+
import { attributeByLocalName } from "../ooxml/xml.js";
|
|
4
|
+
var FIELD_CHARACTER = "fldChar";
|
|
5
|
+
var INSTRUCTION = "instrText";
|
|
6
|
+
var SIMPLE_FIELD = "fldSimple";
|
|
7
|
+
function attributeOf(node, name) {
|
|
8
|
+
const xml = node.attrs.xml;
|
|
9
|
+
if (typeof xml !== "string") return null;
|
|
10
|
+
const el = parsePropsXml(xml);
|
|
11
|
+
return el === null ? null : attributeByLocalName(el, name);
|
|
12
|
+
}
|
|
13
|
+
function chipText(node) {
|
|
14
|
+
const text = node.attrs.text;
|
|
15
|
+
return typeof text === "string" ? text : "";
|
|
16
|
+
}
|
|
17
|
+
var DELETED_INSTRUCTION = "delInstrText";
|
|
18
|
+
var FIELD_PLUMBING = /* @__PURE__ */ new Set([
|
|
19
|
+
FIELD_CHARACTER,
|
|
20
|
+
INSTRUCTION,
|
|
21
|
+
DELETED_INSTRUCTION
|
|
22
|
+
]);
|
|
23
|
+
function isFieldCharacter(node) {
|
|
24
|
+
const element = node.attrs.element;
|
|
25
|
+
return node.type.name === "rawRunContent" && typeof element === "string" && FIELD_PLUMBING.has(element);
|
|
26
|
+
}
|
|
27
|
+
function fieldSpans(paragraph, offset) {
|
|
28
|
+
const spans = [];
|
|
29
|
+
const open = [];
|
|
30
|
+
paragraph.forEach((child, at) => {
|
|
31
|
+
const pos = offset + at;
|
|
32
|
+
const element = child.attrs.element;
|
|
33
|
+
if (child.type.name === "rawInline" && element === SIMPLE_FIELD) {
|
|
34
|
+
spans.push({
|
|
35
|
+
instr: (attributeOf(child, "instr") ?? "").trim(),
|
|
36
|
+
begin: pos,
|
|
37
|
+
separate: null,
|
|
38
|
+
end: pos,
|
|
39
|
+
result: null
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (child.type.name !== "rawRunContent") return;
|
|
44
|
+
if (element === INSTRUCTION) {
|
|
45
|
+
const active2 = open.at(-1);
|
|
46
|
+
if (active2 && active2.separate === null) active2.instr += chipText(child);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (element !== FIELD_CHARACTER) return;
|
|
50
|
+
const kind = attributeOf(child, "fldCharType");
|
|
51
|
+
if (kind === "begin") {
|
|
52
|
+
open.push({ instr: "", begin: pos, separate: null });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const active = open.at(-1);
|
|
56
|
+
if (active === void 0) return;
|
|
57
|
+
if (kind === "separate") {
|
|
58
|
+
if (active.separate === null) active.separate = pos;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (kind !== "end") return;
|
|
62
|
+
open.pop();
|
|
63
|
+
spans.push({
|
|
64
|
+
instr: active.instr.trim(),
|
|
65
|
+
begin: active.begin,
|
|
66
|
+
separate: active.separate,
|
|
67
|
+
end: pos,
|
|
68
|
+
result: active.separate === null ? null : [active.separate + 1, pos]
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return spans.sort((a, b) => a.begin - b.begin);
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
fieldSpans,
|
|
75
|
+
isFieldCharacter
|
|
76
|
+
};
|
|
@@ -75,8 +75,6 @@ export interface ParagraphStyleOption {
|
|
|
75
75
|
* `defaultParagraphStyleIdOf` reads them by.
|
|
76
76
|
*/
|
|
77
77
|
export declare function readParagraphStyles(styles: Document): ParagraphStyleOption[];
|
|
78
|
-
/** The style name the paragraph formatting XML points at. null if it points at none */
|
|
79
|
-
export declare function styleIdOf(pPr: unknown): string | null;
|
|
80
78
|
/**
|
|
81
79
|
* The values the style a paragraph wears lays down.
|
|
82
80
|
*
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
toRunFormat,
|
|
6
6
|
toTableFormat
|
|
7
7
|
} from "../../model/format.js";
|
|
8
|
-
import {
|
|
8
|
+
import { styleIdOf } from "../../ooxml/props.js";
|
|
9
9
|
import { ST_OnOff } from "../../ooxml/simpleTypes.js";
|
|
10
10
|
import { childValue, isOn, wAttr } from "../../ooxml/units.js";
|
|
11
11
|
import { childByLocalName, elementChildren } from "../../ooxml/xml.js";
|
|
@@ -201,18 +201,6 @@ function readParagraphStyles(styles) {
|
|
|
201
201
|
}
|
|
202
202
|
return options;
|
|
203
203
|
}
|
|
204
|
-
var styleIdsByPPr = /* @__PURE__ */ new Map();
|
|
205
|
-
var STYLE_ID_CACHE_LIMIT = 2e3;
|
|
206
|
-
function styleIdOf(pPr) {
|
|
207
|
-
if (typeof pPr !== "string" || !pPr.includes("pStyle")) return null;
|
|
208
|
-
const known = styleIdsByPPr.get(pPr);
|
|
209
|
-
if (known !== void 0) return known;
|
|
210
|
-
const el = parsePropsXml(pPr);
|
|
211
|
-
const id = el ? childValue(el, "pStyle") : null;
|
|
212
|
-
if (styleIdsByPPr.size >= STYLE_ID_CACHE_LIMIT) styleIdsByPPr.clear();
|
|
213
|
-
styleIdsByPPr.set(pPr, id);
|
|
214
|
-
return id;
|
|
215
|
-
}
|
|
216
204
|
function paragraphStyleFormat(pPr, styles, defaultStyleId = null) {
|
|
217
205
|
const id = styleIdOf(pPr) ?? defaultStyleId;
|
|
218
206
|
return id === null ? void 0 : styles.get(id);
|
|
@@ -241,6 +229,5 @@ export {
|
|
|
241
229
|
numberingStyleLinks,
|
|
242
230
|
paragraphStyleFormat,
|
|
243
231
|
readParagraphStyles,
|
|
244
|
-
readStyles
|
|
245
|
-
styleIdOf
|
|
232
|
+
readStyles
|
|
246
233
|
};
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The header and footer stories of a document: where each one is written, which section shows it,
|
|
3
|
+
* what it reads as on a given page, and where an edited one goes back out.
|
|
4
|
+
*
|
|
5
|
+
* A header part is a story of the same schema as the body (`./story`): its blocks are read by the
|
|
6
|
+
* same readers and sliced verbatim by the same scanner, so an edit to one rides a transaction and
|
|
7
|
+
* the part it stands in goes back out as the bytes it arrived as until somebody changes it. What
|
|
8
|
+
* the page preview draws is a projection of that story rather than a reading of its own.
|
|
9
|
+
*
|
|
10
|
+
* Which story a section shows is the section's question (`./sections`): every section names its own
|
|
11
|
+
* variants, so the second section of a document draws its own header and not the first section's.
|
|
12
|
+
*/
|
|
13
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
14
|
+
import { type ParagraphAlign } from "../model/format";
|
|
15
|
+
import { type StoryKey } from "../schema/stories";
|
|
16
|
+
import type { PartPlanner } from "./partPlan";
|
|
17
|
+
import { type DocumentSection } from "./sections";
|
|
18
|
+
import { type ImportedStory, type StoryDeps } from "./story";
|
|
19
|
+
/** One story a section may show, as the document currently says it */
|
|
13
20
|
export interface HeaderFooterContent {
|
|
14
|
-
|
|
21
|
+
story: PMNode;
|
|
22
|
+
/** What the first paragraph of the story is aligned to, which is what the preview is drawn with */
|
|
15
23
|
align: ParagraphAlign | null;
|
|
16
24
|
}
|
|
17
25
|
export interface HeaderFooterVariants {
|
|
@@ -26,11 +34,72 @@ export interface HeadersFooters {
|
|
|
26
34
|
evenAndOdd: boolean;
|
|
27
35
|
pageNumberStart: number;
|
|
28
36
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
/**
|
|
38
|
+
* What picking one section's stories takes beyond the section itself.
|
|
39
|
+
*
|
|
40
|
+
* A section names a relationship, a relationship names a part, and a part is where a story stands,
|
|
41
|
+
* so the two ends are joined once when the document is opened. `w:evenAndOddHeaders` is a
|
|
42
|
+
* document-wide setting rather than a section's own (§17.15.1.29), so it rides along here.
|
|
43
|
+
*/
|
|
44
|
+
export interface HeaderFooterStories {
|
|
45
|
+
/**
|
|
46
|
+
* The story each header or footer relationship of the main part names. The key says which of the
|
|
47
|
+
* two kinds it is, so a `w:headerReference` naming a footer relationship names no story.
|
|
48
|
+
*/
|
|
49
|
+
readonly keyByRelId: ReadonlyMap<string, StoryKey>;
|
|
50
|
+
readonly evenAndOdd: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Every header and footer part a section of the document names, read as a story apiece, and the
|
|
54
|
+
* relationship each one answers to.
|
|
55
|
+
*
|
|
56
|
+
* A part is read once however many relationships point at it, and a part whose root is not the
|
|
57
|
+
* element its relationship promises is passed over: the reference names nothing this can draw, and
|
|
58
|
+
* the bytes stay where they are. A part no section names is not read at all, so an orphan a
|
|
59
|
+
* producer left behind - which may hold anything, a document this one never draws included - is
|
|
60
|
+
* neither a story of this document nor a reason to refuse the file, and its bytes go back out as
|
|
61
|
+
* they came.
|
|
62
|
+
*/
|
|
63
|
+
export declare function readHeaderFooterStories(parts: Map<string, Uint8Array>, mainPartPath: string, referenced: ReadonlySet<string>, depsFor: (partPath: string) => StoryDeps): {
|
|
64
|
+
stories: readonly ImportedStory[];
|
|
65
|
+
refs: HeaderFooterStories;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The stories this section shows and the switches that pick between them.
|
|
69
|
+
*
|
|
70
|
+
* `storyOf` answers with what the document currently says rather than what the package arrived
|
|
71
|
+
* holding, so a header the editor rewrote is the one the preview draws.
|
|
72
|
+
*/
|
|
73
|
+
export declare function variantsFor(section: DocumentSection, refs: HeaderFooterStories, storyOf: (key: StoryKey) => PMNode | null): HeadersFooters;
|
|
74
|
+
/**
|
|
75
|
+
* The number this visual page shows, counted from the start its section declares.
|
|
76
|
+
*
|
|
77
|
+
* `page` is the page's place in the whole document: the count is not restarted at a section
|
|
78
|
+
* boundary, which is what the editor's documented page numbering says.
|
|
79
|
+
*/
|
|
32
80
|
export declare function displayPageNumber(headersFooters: HeadersFooters, page: number): number;
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
81
|
+
/**
|
|
82
|
+
* The story this page of a section shows, and null where the section declares none for it.
|
|
83
|
+
*
|
|
84
|
+
* `pageInSection` is the page's place within its own section rather than in the document:
|
|
85
|
+
* `w:titlePg` selects the `first` story for the first page of every section it is written in
|
|
86
|
+
* (§17.10.1), and the odd or even story follows the number that page would carry counting from
|
|
87
|
+
* the section's own start (§17.10.6), so a second section opening halfway down a document draws
|
|
88
|
+
* its own first page as a first page.
|
|
89
|
+
*/
|
|
90
|
+
export declare function headerFooterOn(variants: HeaderFooterVariants, headersFooters: HeadersFooters, pageInSection: number): HeaderFooterContent | null;
|
|
91
|
+
/**
|
|
92
|
+
* What the story reads as on one visual page: its paragraphs joined by newlines, with `PAGE` and
|
|
93
|
+
* `NUMPAGES` replaced by the numbers this page actually carries.
|
|
94
|
+
*
|
|
95
|
+
* `page` is the number the page shows rather than its place on the sheet, since that is what a
|
|
96
|
+
* `PAGE` field prints (§17.16.5.45).
|
|
97
|
+
*/
|
|
98
|
+
export declare function headerFooterText(story: PMNode, page: number, totalPages: number): string;
|
|
99
|
+
/**
|
|
100
|
+
* Writes back the header and footer parts an edit changed, and no others.
|
|
101
|
+
*
|
|
102
|
+
* A story nobody touched is not written at all, so a document opened and exported hands every
|
|
103
|
+
* header part back as the bytes it arrived as, and editing one header leaves the rest untouched.
|
|
104
|
+
*/
|
|
105
|
+
export declare const headerFooterPlanner: PartPlanner;
|