@portone/docx-editor 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +159 -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/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +100 -48
- package/dist/docx/importParagraph.d.ts +16 -4
- package/dist/docx/importParagraph.js +87 -93
- package/dist/docx/importPolicy.d.ts +70 -0
- package/dist/docx/importPolicy.js +190 -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 +4 -0
- package/dist/docx/serializeParagraph.js +1 -0
- 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/editor/clipboard/htmlReader.d.ts +30 -0
- package/dist/editor/{externalClipboard.js → clipboard/htmlReader.js} +39 -223
- 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 +320 -0
- package/dist/editor/clipboard/readContext.d.ts +24 -0
- package/dist/editor/clipboard/readContext.js +19 -0
- package/dist/editor/clipboard/readers.d.ts +33 -0
- package/dist/editor/clipboard/readers.js +37 -0
- package/dist/editor/commands/comments/editing.d.ts +26 -6
- package/dist/editor/commands/comments/editing.js +175 -105
- 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/listCommands.d.ts +6 -0
- package/dist/editor/commands/listCommands.js +3 -2
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- 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 +8 -0
- package/dist/ooxml/props.js +14 -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 +38 -20
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +191 -139
- 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/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.js +3 -1
- package/dist/schema/stories.d.ts +49 -0
- package/dist/schema/stories.js +78 -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 +1 -1
- package/dist/editor/externalClipboard.d.ts +0 -22
|
@@ -26,9 +26,6 @@ function importedCommentReplies(comments, rootId) {
|
|
|
26
26
|
authorId: reply.authorId,
|
|
27
27
|
initials: reply.initials,
|
|
28
28
|
date: reply.date,
|
|
29
|
-
text: reply.text,
|
|
30
|
-
commentXml: reply.xml,
|
|
31
|
-
imported: true,
|
|
32
29
|
paraId,
|
|
33
30
|
parentParaId,
|
|
34
31
|
extensionXml: reply.extensionXml
|
|
@@ -59,9 +56,6 @@ function replyData(value) {
|
|
|
59
56
|
authorId: nullableString(entry.authorId),
|
|
60
57
|
initials: nullableString(entry.initials),
|
|
61
58
|
date: nullableString(entry.date),
|
|
62
|
-
text: nullableString(entry.text) ?? "",
|
|
63
|
-
commentXml: nullableString(entry.commentXml),
|
|
64
|
-
imported: entry.imported === true,
|
|
65
59
|
paraId,
|
|
66
60
|
parentParaId,
|
|
67
61
|
extensionXml: nullableString(entry.extensionXml)
|
|
@@ -79,9 +73,6 @@ function referenceData(node) {
|
|
|
79
73
|
authorId: nullableString(node.attrs.authorId),
|
|
80
74
|
initials: nullableString(node.attrs.initials),
|
|
81
75
|
date: nullableString(node.attrs.date),
|
|
82
|
-
text: nullableString(node.attrs.text) ?? "",
|
|
83
|
-
commentXml: nullableString(node.attrs.commentXml),
|
|
84
|
-
imported: node.attrs.imported === true,
|
|
85
76
|
paraId: nullableString(node.attrs.paraId) ?? "00000001",
|
|
86
77
|
resolved: node.attrs.resolved === true,
|
|
87
78
|
extensionXml: nullableString(node.attrs.extensionXml),
|
|
@@ -11,16 +11,17 @@
|
|
|
11
11
|
* have made the change. Asking them as one would refuse a moderator rewriting somebody else's
|
|
12
12
|
* comment under `editableComments: "all"`, which is a thing the editor lets them do.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type EditableComments } from "../../schema/protection";
|
|
15
15
|
import type { StoryPartKind } from "../protectionPolicy";
|
|
16
16
|
import { type ImportedPeople } from "./people";
|
|
17
17
|
/**
|
|
18
18
|
* Whether this editor's writer could have put the entry out, whoever it belongs to.
|
|
19
19
|
*
|
|
20
|
-
* Shape alone: which attributes it carries and what stands inside it
|
|
21
|
-
*
|
|
20
|
+
* Shape alone: which attributes it carries and what stands inside it, judged against the entry the
|
|
21
|
+
* original file held under the same id. Who may have written it is `entryAllowed`'s question.
|
|
22
|
+
* Each kind is read where it is written (`./grammar`).
|
|
22
23
|
*/
|
|
23
|
-
export declare function wellFormedEntry(entry: Element): boolean;
|
|
24
|
+
export declare function wellFormedEntry(entry: Element, original: Element | null): boolean;
|
|
24
25
|
/**
|
|
25
26
|
* Whether the only thing between the two is that a thread gained its key.
|
|
26
27
|
*
|
|
@@ -34,13 +35,18 @@ export declare function threadKeyAlone(entry: Element, original: Element): boole
|
|
|
34
35
|
* Whether `authorId` could have written or rewritten this entry.
|
|
35
36
|
*
|
|
36
37
|
* Permission alone: whether the shape is one this editor writes is `wellFormedEntry`'s question.
|
|
37
|
-
* An entry that appeared has to be this author
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* An entry that appeared has to be one this author could have written, which
|
|
39
|
+
* `commentAdditionAllowed` decides here as it does over the story. One that arrived keeps the
|
|
40
|
+
* identity it arrived with; settling its thread or replying to it belong to everyone and leave
|
|
41
|
+
* what it says alone, while rewriting what it says is its author's, a moderator's, or anyone's
|
|
42
|
+
* where no identity was recorded for it. Both are the rules the editor holds to
|
|
43
|
+
* (`schema/protection`), and the two have to answer alike or a file the editor wrote would be
|
|
44
|
+
* turned down here.
|
|
45
|
+
*
|
|
46
|
+
* `unattributed` is read from the file that arrived, since it says what the submission is judged
|
|
47
|
+
* against; `people` is the submission's, since it says who the submission names.
|
|
42
48
|
*/
|
|
43
|
-
export declare function entryAllowed(entry: Element, original: Element | null, authorId: string, editableComments: EditableComments, people: ImportedPeople): boolean;
|
|
49
|
+
export declare function entryAllowed(entry: Element, original: Element | null, authorId: string, editableComments: EditableComments, people: ImportedPeople, unattributed: ReadonlySet<string>): boolean;
|
|
44
50
|
export declare const commentsPart: StoryPartKind;
|
|
45
51
|
export declare const commentsExtendedPart: StoryPartKind;
|
|
46
52
|
export declare const peoplePart: StoryPartKind;
|
|
@@ -7,7 +7,16 @@ import {
|
|
|
7
7
|
serializeXml,
|
|
8
8
|
W_NS
|
|
9
9
|
} from "../../ooxml/xml.js";
|
|
10
|
+
import { isPreservedNode } from "../../schema/preservedFragments.js";
|
|
11
|
+
import {
|
|
12
|
+
commentAdditionAllowed
|
|
13
|
+
} from "../../schema/protection.js";
|
|
14
|
+
import { NO_FORMATTING } from "../formatting.js";
|
|
15
|
+
import { NO_IMPORT_SOURCES } from "../importParagraph.js";
|
|
10
16
|
import { availablePartPath } from "../packageParts.js";
|
|
17
|
+
import { serializeBlock } from "../serializeBlock.js";
|
|
18
|
+
import { buildBlock } from "../story.js";
|
|
19
|
+
import { isModelledBlock } from "../storyProjection.js";
|
|
11
20
|
import {
|
|
12
21
|
COMMENTS_CONTENT_TYPE,
|
|
13
22
|
COMMENTS_EXTENDED_CONTENT_TYPE,
|
|
@@ -21,9 +30,11 @@ import {
|
|
|
21
30
|
import {
|
|
22
31
|
attributesWithin,
|
|
23
32
|
COMMENT_ATTRIBUTES,
|
|
33
|
+
declarationsWritten,
|
|
24
34
|
declarationWritten,
|
|
35
|
+
holdsElementsAndLayout,
|
|
36
|
+
isLayoutText,
|
|
25
37
|
lastBodyParagraph,
|
|
26
|
-
readStrictCommentBody,
|
|
27
38
|
recordedIdentity,
|
|
28
39
|
wellFormedCommentExtension,
|
|
29
40
|
wellFormedPerson
|
|
@@ -42,9 +53,46 @@ function sameAttributes(entry, original, names) {
|
|
|
42
53
|
(name) => attributeByLocalName(entry, name) === attributeByLocalName(original, name)
|
|
43
54
|
);
|
|
44
55
|
}
|
|
45
|
-
function
|
|
56
|
+
function holdsPreserved(node) {
|
|
57
|
+
if (isPreservedNode(node)) return true;
|
|
58
|
+
let found = false;
|
|
59
|
+
node.descendants((child) => {
|
|
60
|
+
if (isPreservedNode(child)) found = true;
|
|
61
|
+
return !found;
|
|
62
|
+
});
|
|
63
|
+
return found;
|
|
64
|
+
}
|
|
65
|
+
var BLOCK_DECLARATIONS = Object.entries(NAMESPACES).map(([prefix, uri]) => `xmlns:${prefix}="${uri}"`).join(" ");
|
|
66
|
+
function normalizedBlock(xml) {
|
|
67
|
+
try {
|
|
68
|
+
const root = parseXml(
|
|
69
|
+
`<b ${BLOCK_DECLARATIONS}>${xml}</b>`
|
|
70
|
+
).documentElement;
|
|
71
|
+
const [block] = Array.from(root.children);
|
|
72
|
+
return block === void 0 || root.children.length !== 1 ? null : serializeXml(block);
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function writtenBlock(el) {
|
|
78
|
+
if (!declarationsWritten(el)) return false;
|
|
79
|
+
const node = buildBlock(el, "", NO_IMPORT_SOURCES, NO_FORMATTING);
|
|
80
|
+
if (!isModelledBlock(node) || holdsPreserved(node)) return false;
|
|
81
|
+
const written = normalizedBlock(serializeBlock(node));
|
|
82
|
+
return written !== null && written === normalizedBlock(serializeXml(el));
|
|
83
|
+
}
|
|
84
|
+
function wellFormedStoryBody(entry, original) {
|
|
85
|
+
if (!holdsElementsAndLayout(entry)) return false;
|
|
86
|
+
const kept = new Set(
|
|
87
|
+
original === null ? [] : Array.from(original.children, serializeXml)
|
|
88
|
+
);
|
|
89
|
+
return Array.from(entry.children).every(
|
|
90
|
+
(block) => kept.has(serializeXml(block)) || writtenBlock(block)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
function wellFormedEntry(entry, original) {
|
|
46
94
|
if (entry.namespaceURI === W_NS && entry.localName === "comment") {
|
|
47
|
-
return attributesWithin(entry, COMMENT_ATTRIBUTES) &&
|
|
95
|
+
return attributesWithin(entry, COMMENT_ATTRIBUTES) && wellFormedStoryBody(entry, original);
|
|
48
96
|
}
|
|
49
97
|
if (entry.namespaceURI === W15_NS && entry.localName === "commentEx") {
|
|
50
98
|
return wellFormedCommentExtension(entry);
|
|
@@ -67,11 +115,17 @@ function threadKeyAlone(entry, original) {
|
|
|
67
115
|
const kept = before === null || after === before;
|
|
68
116
|
return kept && withoutThreadKey(entry) === withoutThreadKey(original);
|
|
69
117
|
}
|
|
70
|
-
function entryAllowed(entry, original, authorId, editableComments, people) {
|
|
118
|
+
function entryAllowed(entry, original, authorId, editableComments, people, unattributed) {
|
|
71
119
|
if (entry.namespaceURI === W_NS && entry.localName === "comment") {
|
|
72
120
|
const author = attributeByLocalName(entry, "author");
|
|
73
121
|
const recorded = author === null ? null : commentAuthorId(people, author);
|
|
74
|
-
if (original === null)
|
|
122
|
+
if (original === null) {
|
|
123
|
+
return commentAdditionAllowed(
|
|
124
|
+
{ author, authorId: recorded },
|
|
125
|
+
authorId,
|
|
126
|
+
unattributed
|
|
127
|
+
);
|
|
128
|
+
}
|
|
75
129
|
const paraId = lastParagraphId(original);
|
|
76
130
|
if (!sameAttributes(entry, original, COMMENT_IDENTITY) || paraId !== null && lastParagraphId(entry) !== paraId) {
|
|
77
131
|
return false;
|
|
@@ -83,23 +137,20 @@ function entryAllowed(entry, original, authorId, editableComments, people) {
|
|
|
83
137
|
return sameAttributes(entry, original, ["paraId", "paraIdParent"]);
|
|
84
138
|
}
|
|
85
139
|
if (entry.namespaceURI === W15_NS && entry.localName === "person") {
|
|
86
|
-
|
|
140
|
+
const author = attributeByLocalName(entry, "author");
|
|
141
|
+
return original === null && recordedIdentity(entry) === authorId && author !== null && !unattributed.has(author);
|
|
87
142
|
}
|
|
88
143
|
return false;
|
|
89
144
|
}
|
|
90
|
-
var TEXT_NODE = 3;
|
|
91
145
|
var COMPATIBILITY = /* @__PURE__ */ new Map([
|
|
92
146
|
["xmlns:w14", NAMESPACES.w14],
|
|
93
147
|
["xmlns:mc", NAMESPACES.mc]
|
|
94
148
|
]);
|
|
95
149
|
var IGNORABLE = "mc:Ignorable";
|
|
96
150
|
var IGNORED = "w14";
|
|
97
|
-
function isLayout(node) {
|
|
98
|
-
return node.nodeType === TEXT_NODE && /^[ \t\r\n]*$/.test(node.nodeValue ?? "");
|
|
99
|
-
}
|
|
100
151
|
function surroundingNodes(parent) {
|
|
101
152
|
return parent === null ? [] : Array.from(parent.childNodes).filter(
|
|
102
|
-
(node) => !isElement(node) && !
|
|
153
|
+
(node) => !isElement(node) && !isLayoutText(node)
|
|
103
154
|
);
|
|
104
155
|
}
|
|
105
156
|
function sameNodes(now, was) {
|
|
@@ -192,12 +243,13 @@ function storyPart(shape) {
|
|
|
192
243
|
referents: shape.referents,
|
|
193
244
|
wellFormed: wellFormedEntry,
|
|
194
245
|
anyonesChange: threadKeyAlone,
|
|
195
|
-
allowed: (entry, original, authorId, options, session) => entryAllowed(
|
|
246
|
+
allowed: (entry, original, authorId, options, session, unattributed) => entryAllowed(
|
|
196
247
|
entry,
|
|
197
248
|
original,
|
|
198
249
|
authorId,
|
|
199
250
|
options.editableComments,
|
|
200
|
-
session.comments.people
|
|
251
|
+
session.comments.people,
|
|
252
|
+
unattributed
|
|
201
253
|
)
|
|
202
254
|
};
|
|
203
255
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* part by, so a name stands for one identity per file: a name the part already records is read as
|
|
9
9
|
* it stands and never appended to. Appending a second person for it would leave a file naming two
|
|
10
10
|
* identities for one name, where a reader keying by name hands one author's comments to the other.
|
|
11
|
+
* A name the file's comments already write under carries the same weight, recorded or not: a
|
|
12
|
+
* person appended for it would claim those comments too.
|
|
11
13
|
*/
|
|
12
14
|
import type { PartPlanContext } from "../partPlan";
|
|
13
15
|
import type { StoryPartKind } from "../protectionPolicy";
|
|
@@ -33,10 +35,16 @@ export declare function readPeople(parts: Map<string, Uint8Array>, mainPartPath:
|
|
|
33
35
|
*/
|
|
34
36
|
export declare function commentAuthorId(people: ImportedPeople, author: string): string | null;
|
|
35
37
|
/**
|
|
36
|
-
* The authors
|
|
37
|
-
*
|
|
38
|
+
* The authors to record a person for, each under the name they write as: the ones the comments
|
|
39
|
+
* name one identity for and the part does not record already. A name the part records is left as
|
|
40
|
+
* it stands, whichever provider recorded it and whatever it resolves to. So is a name the comments
|
|
41
|
+
* carry under more than one identity, one identity and none among them: recording it would hand
|
|
42
|
+
* the comments carrying no identity to whoever the name then resolved to, and a document saved by
|
|
43
|
+
* a Word that wrote no people part carries every one of its comments that way.
|
|
44
|
+
* `unattributed` also reserves names from the original Comments part, including preserved entries
|
|
45
|
+
* the current editable bodies do not carry. The planner and export invariants use this same set.
|
|
38
46
|
*/
|
|
39
|
-
export declare function unrecordedAuthors(bodies: Iterable<CommentReferenceData | CommentReplyData>, people: ImportedPeople): Map<string, string>;
|
|
47
|
+
export declare function unrecordedAuthors(bodies: Iterable<CommentReferenceData | CommentReplyData>, people: ImportedPeople, unattributed: ReadonlySet<string>): Map<string, string>;
|
|
40
48
|
/**
|
|
41
49
|
* Plans the people part, its relationship and its content type for every identity the current
|
|
42
50
|
* comments carry that the document has not recorded. Null when it has recorded them all, which
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
encodeUtf8,
|
|
11
11
|
parseXml
|
|
12
12
|
} from "../../ooxml/xml.js";
|
|
13
|
+
import { unattributedCommentAuthors } from "../../schema/protection.js";
|
|
13
14
|
import { relatedPartPath } from "../packageParts.js";
|
|
14
15
|
import { directoryOf } from "../relationships.js";
|
|
15
16
|
import { COMMENT_AUTHOR_PROVIDER, PEOPLE_REL_TYPE, W15_NS } from "./constants.js";
|
|
@@ -51,12 +52,21 @@ function readPeople(parts, mainPartPath) {
|
|
|
51
52
|
function commentAuthorId(people, author) {
|
|
52
53
|
return people.byAuthor.get(author) ?? null;
|
|
53
54
|
}
|
|
54
|
-
function unrecordedAuthors(bodies, people) {
|
|
55
|
-
const
|
|
55
|
+
function unrecordedAuthors(bodies, people, unattributed) {
|
|
56
|
+
const identities = /* @__PURE__ */ new Map();
|
|
56
57
|
for (const body of bodies) {
|
|
57
|
-
if (body.author === null
|
|
58
|
-
if (people.byAuthor.has(body.author))
|
|
59
|
-
|
|
58
|
+
if (body.author === null) continue;
|
|
59
|
+
if (people.byAuthor.has(body.author) || unattributed.has(body.author))
|
|
60
|
+
continue;
|
|
61
|
+
const ids = identities.get(body.author) ?? /* @__PURE__ */ new Set();
|
|
62
|
+
ids.add(body.authorId);
|
|
63
|
+
identities.set(body.author, ids);
|
|
64
|
+
}
|
|
65
|
+
const unrecorded = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const [author, ids] of identities) {
|
|
67
|
+
if (ids.size !== 1) continue;
|
|
68
|
+
const only = Array.from(ids)[0] ?? null;
|
|
69
|
+
if (only !== null) unrecorded.set(author, only);
|
|
60
70
|
}
|
|
61
71
|
return unrecorded;
|
|
62
72
|
}
|
|
@@ -94,7 +104,11 @@ function peopleXml(people, added) {
|
|
|
94
104
|
}
|
|
95
105
|
function planPeoplePart(part, bodies, session, context) {
|
|
96
106
|
const people = session.comments.people;
|
|
97
|
-
const added = unrecordedAuthors(
|
|
107
|
+
const added = unrecordedAuthors(
|
|
108
|
+
bodies,
|
|
109
|
+
people,
|
|
110
|
+
unattributedCommentAuthors(session.comments.ordered)
|
|
111
|
+
);
|
|
98
112
|
if (added.size === 0) return null;
|
|
99
113
|
const parts = /* @__PURE__ */ new Map();
|
|
100
114
|
const addingPart = part.pathIn(session) === null;
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
commentAdditionsBy,
|
|
4
4
|
commentEditsOwned,
|
|
5
5
|
commentIdentitiesKept,
|
|
6
|
+
unattributedCommentAuthors,
|
|
6
7
|
withoutComments
|
|
7
8
|
} from "../../schema/protection.js";
|
|
8
9
|
import {
|
|
@@ -18,7 +19,12 @@ function sameBody(before, after) {
|
|
|
18
19
|
}
|
|
19
20
|
function commentsStoryKept(before, after, authorId, { editableComments }) {
|
|
20
21
|
if (!sameBody(before, after)) return refused("body-changed");
|
|
21
|
-
if (!commentIdentitiesKept(before.doc, after.doc) || !commentAdditionsBy(
|
|
22
|
+
if (!commentIdentitiesKept(before.doc, after.doc) || !commentAdditionsBy(
|
|
23
|
+
before.doc,
|
|
24
|
+
after.doc,
|
|
25
|
+
authorId,
|
|
26
|
+
unattributedCommentAuthors(before.session.comments.ordered)
|
|
27
|
+
)) {
|
|
22
28
|
return refused("comment-author-forged");
|
|
23
29
|
}
|
|
24
30
|
if (!commentEditsOwned(before.doc, after.doc, {
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Reads comment and comment-extension package parts.
|
|
3
3
|
*/
|
|
4
4
|
import { type ImportedPeople } from "./people";
|
|
5
|
+
/**
|
|
6
|
+
* One entry of the Comments part as it arrived, everything about it but what it says: that is a
|
|
7
|
+
* story of its own, sliced and read the way a body block is (`docx/story`).
|
|
8
|
+
*/
|
|
5
9
|
export interface ImportedComment {
|
|
6
10
|
id: string;
|
|
7
11
|
author: string | null;
|
|
@@ -13,8 +17,6 @@ export interface ImportedComment {
|
|
|
13
17
|
authorId: string | null;
|
|
14
18
|
initials: string | null;
|
|
15
19
|
date: string | null;
|
|
16
|
-
text: string;
|
|
17
|
-
xml: string;
|
|
18
20
|
paraId: string | null;
|
|
19
21
|
parentParaId: string | null;
|
|
20
22
|
resolved: boolean;
|
|
@@ -4,8 +4,7 @@ import {
|
|
|
4
4
|
decodeUtf8,
|
|
5
5
|
elementChildren,
|
|
6
6
|
parseXml,
|
|
7
|
-
serializeXml
|
|
8
|
-
W_NS
|
|
7
|
+
serializeXml
|
|
9
8
|
} from "../../ooxml/xml.js";
|
|
10
9
|
import { relatedPartPath } from "../packageParts.js";
|
|
11
10
|
import { COMMENTS_EXTENDED_REL_TYPE, COMMENTS_REL_TYPE } from "./constants.js";
|
|
@@ -15,16 +14,6 @@ import {
|
|
|
15
14
|
NO_PEOPLE,
|
|
16
15
|
readPeople
|
|
17
16
|
} from "./people.js";
|
|
18
|
-
function inlineCommentText(node) {
|
|
19
|
-
if (node.localName === "t") return node.textContent ?? "";
|
|
20
|
-
if (node.localName === "tab") return " ";
|
|
21
|
-
if (node.localName === "br" || node.localName === "cr") return "\n";
|
|
22
|
-
return elementChildren(node).map(inlineCommentText).join("");
|
|
23
|
-
}
|
|
24
|
-
function commentText(el) {
|
|
25
|
-
const paragraphs = Array.from(el.getElementsByTagNameNS(W_NS, "p"));
|
|
26
|
-
return paragraphs.map(inlineCommentText).join("\n");
|
|
27
|
-
}
|
|
28
17
|
var NO_COMMENTS = {
|
|
29
18
|
partPath: null,
|
|
30
19
|
xml: null,
|
|
@@ -117,8 +106,6 @@ function readComments(parts, mainPartPath) {
|
|
|
117
106
|
authorId: author === null ? null : commentAuthorId(people, author),
|
|
118
107
|
initials: attributeByLocalName(el, "initials"),
|
|
119
108
|
date: attributeByLocalName(el, "date"),
|
|
120
|
-
text: commentText(el),
|
|
121
|
-
xml: serializeXml(el),
|
|
122
109
|
paraId,
|
|
123
110
|
parentParaId: extension?.parentParaId ?? null,
|
|
124
111
|
resolved: extension?.resolved ?? false,
|
|
@@ -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