@portone/docx-editor 0.6.1 → 0.6.2
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 +16 -0
- package/dist/core.d.ts +4 -3
- package/dist/docx/exportDocx.d.ts +1 -1
- package/dist/docx/exportDocx.js +3 -1
- package/dist/docx/identities.d.ts +2 -0
- package/dist/docx/identities.js +1 -0
- package/dist/docx/invariants.d.ts +13 -10
- package/dist/docx/invariants.js +269 -126
- package/dist/docx/newLists.js +12 -3
- package/dist/docx/notes/numbering.js +3 -10
- package/dist/docx/notes/references.d.ts +33 -0
- package/dist/docx/notes/references.js +37 -0
- package/dist/docx/notes/writing.js +5 -6
- package/dist/docx/serializePreserved.d.ts +5 -1
- package/dist/docx/serializePreserved.js +24 -7
- package/dist/docx/storyParts.d.ts +29 -8
- package/dist/docx/storyParts.js +40 -15
- package/dist/editor/commands/exportQueries.d.ts +4 -3
- package/dist/editor/commands/index.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/ooxml/errors.d.ts +135 -2
- package/dist/ooxml/errors.js +6 -0
- package/dist/schema/stories.d.ts +11 -3
- package/dist/schema/stories.js +9 -3
- package/package.json +1 -1
package/dist/docx/invariants.js
CHANGED
|
@@ -8,7 +8,12 @@ import {
|
|
|
8
8
|
} from "../ooxml/xml.js";
|
|
9
9
|
import { visitPreservedFragments } from "../schema/preservedFragments.js";
|
|
10
10
|
import { unattributedCommentAuthors } from "../schema/protection.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
HEADER_FOOTER_KINDS,
|
|
13
|
+
NOTE_KINDS,
|
|
14
|
+
splitStoryKey,
|
|
15
|
+
storyKey
|
|
16
|
+
} from "../schema/stories.js";
|
|
12
17
|
import {
|
|
13
18
|
commentReferencesIn,
|
|
14
19
|
commentsChanged,
|
|
@@ -23,12 +28,10 @@ import {
|
|
|
23
28
|
} from "./comments/parts.js";
|
|
24
29
|
import { unrecordedAuthors } from "./comments/people.js";
|
|
25
30
|
import { currentCommentBodies } from "./comments/writing.js";
|
|
26
|
-
import {
|
|
27
|
-
identityProblems,
|
|
28
|
-
identityProblemsInStories
|
|
29
|
-
} from "./identities.js";
|
|
31
|
+
import { identityProblems, identityProblemsInStories } from "./identities.js";
|
|
30
32
|
import { insertedImageSrcs } from "./media.js";
|
|
31
33
|
import { canDefineNewList, newNumIds, startedLists } from "./newLists.js";
|
|
34
|
+
import { firstNoteReferences } from "./notes/references.js";
|
|
32
35
|
import { CONTENT_TYPES_PATH } from "./packageParts.js";
|
|
33
36
|
import { STORY_ENTRIES_PARTS, STORY_WRITINGS } from "./partPlanners.js";
|
|
34
37
|
import { lostOriginal } from "./serializePreserved.js";
|
|
@@ -37,75 +40,110 @@ import {
|
|
|
37
40
|
sessionOf
|
|
38
41
|
} from "./session.js";
|
|
39
42
|
import {
|
|
43
|
+
rewrittenStories,
|
|
40
44
|
storyChangesOf,
|
|
41
45
|
storyEntriesOf,
|
|
42
46
|
storyEntriesProblems,
|
|
43
47
|
unwrittenStoryChanges
|
|
44
48
|
} from "./storyParts.js";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
function found(scope, pos, problem) {
|
|
50
|
+
return scope.story === null ? { ...problem, pos } : problem;
|
|
51
|
+
}
|
|
52
|
+
function overScopes(name, check) {
|
|
53
|
+
return {
|
|
54
|
+
name,
|
|
55
|
+
check: ({ scopes, session }) => scopes.flatMap((scope) => check(scope, session))
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
var bookmarkPairs = overScopes("bookmarkPairs", (scope, session) => {
|
|
59
|
+
const problems = [];
|
|
60
|
+
const open = /* @__PURE__ */ new Map();
|
|
61
|
+
const used = /* @__PURE__ */ new Set();
|
|
62
|
+
visitPreservedFragments(scope.doc, (node, pos, xml) => {
|
|
63
|
+
const source = xml ?? originalBlock(node, session)?.xml ?? null;
|
|
64
|
+
if (source === null) return;
|
|
65
|
+
if (!source.includes("bookmark")) return;
|
|
66
|
+
let root;
|
|
67
|
+
try {
|
|
68
|
+
root = parseXml(
|
|
69
|
+
session.documentPrefix + source + session.documentSuffix
|
|
70
|
+
).documentElement;
|
|
71
|
+
} catch {
|
|
72
|
+
problems.push(
|
|
73
|
+
found(scope, pos, {
|
|
62
74
|
code: "malformed-xml",
|
|
63
75
|
message: "preserved bookmark XML could not be parsed",
|
|
64
|
-
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
reason: { kind: "unreadable-preserved-xml", story: scope.story }
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const marker of Array.from(root.getElementsByTagName("*"))) {
|
|
82
|
+
if (marker.namespaceURI !== W_NS || marker.localName !== "bookmarkStart" && marker.localName !== "bookmarkEnd")
|
|
83
|
+
continue;
|
|
84
|
+
const kind = marker.localName.slice("bookmark".length);
|
|
85
|
+
const value = attributeByLocalName(marker, "id");
|
|
86
|
+
if (value === null || value === "") {
|
|
87
|
+
problems.push(
|
|
88
|
+
found(scope, pos, {
|
|
75
89
|
code: "malformed-xml",
|
|
76
90
|
message: `a bookmark${kind} has no id`,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
reason: {
|
|
92
|
+
kind: "unnamed-bookmark",
|
|
93
|
+
marker: kind === "Start" ? "start" : "end",
|
|
94
|
+
story: scope.story
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
} else if (kind === "Start") {
|
|
99
|
+
if (used.has(value)) {
|
|
100
|
+
problems.push(
|
|
101
|
+
found(scope, pos, {
|
|
82
102
|
code: "malformed-xml",
|
|
83
103
|
message: `bookmark ${value} has more than one start marker`,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
reason: {
|
|
105
|
+
kind: "repeated-bookmark-start",
|
|
106
|
+
id: value,
|
|
107
|
+
story: scope.story
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
open.set(value, pos);
|
|
113
|
+
used.add(value);
|
|
114
|
+
}
|
|
115
|
+
} else if (!open.delete(value)) {
|
|
116
|
+
problems.push(
|
|
117
|
+
found(scope, pos, {
|
|
92
118
|
code: "malformed-xml",
|
|
93
119
|
message: `bookmark ${value} ends without an earlier start marker`,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
reason: {
|
|
121
|
+
kind: "unmatched-bookmark",
|
|
122
|
+
id: value,
|
|
123
|
+
marker: "end",
|
|
124
|
+
story: scope.story
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
);
|
|
97
128
|
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
for (const [id, pos] of open) {
|
|
132
|
+
problems.push(
|
|
133
|
+
found(scope, pos, {
|
|
101
134
|
code: "malformed-xml",
|
|
102
135
|
message: `bookmark ${id} has no end marker`,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
136
|
+
reason: {
|
|
137
|
+
kind: "unmatched-bookmark",
|
|
138
|
+
id,
|
|
139
|
+
marker: "start",
|
|
140
|
+
story: scope.story
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
);
|
|
107
144
|
}
|
|
108
|
-
|
|
145
|
+
return problems;
|
|
146
|
+
});
|
|
109
147
|
function mergeReachesPastLastRow(table) {
|
|
110
148
|
let overlong = false;
|
|
111
149
|
table.forEach((row, _offset, at) => {
|
|
@@ -117,135 +155,190 @@ function mergeReachesPastLastRow(table) {
|
|
|
117
155
|
});
|
|
118
156
|
return overlong;
|
|
119
157
|
}
|
|
120
|
-
var tableGrids = {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
problems.push({
|
|
158
|
+
var tableGrids = overScopes("tableGrids", (scope) => {
|
|
159
|
+
const problems = [];
|
|
160
|
+
scope.doc.descendants((node, pos) => {
|
|
161
|
+
if (node.type.name !== "table") return true;
|
|
162
|
+
if (mergeReachesPastLastRow(node)) {
|
|
163
|
+
problems.push(
|
|
164
|
+
found(scope, pos, {
|
|
128
165
|
code: "invalid-table",
|
|
129
166
|
message: "a vertical merge in the table reaches past the last row",
|
|
130
|
-
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
167
|
+
reason: { kind: "vertical-merge-past-table", story: scope.story }
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
return problems;
|
|
174
|
+
});
|
|
138
175
|
var CARRIES_ITS_XML = /* @__PURE__ */ new Set([
|
|
139
176
|
"rawInline",
|
|
140
177
|
"rawRunContent"
|
|
141
178
|
]);
|
|
142
|
-
function lostOriginalOf(node, session) {
|
|
179
|
+
function lostOriginalOf(node, session, story) {
|
|
143
180
|
if (CARRIES_ITS_XML.has(node.type.name)) {
|
|
144
|
-
return typeof node.attrs.xml === "string" ? null :
|
|
181
|
+
return typeof node.attrs.xml === "string" ? null : {
|
|
182
|
+
message: "a preserved element has lost its original XML",
|
|
183
|
+
reason: {
|
|
184
|
+
kind: "lost-preserved-xml",
|
|
185
|
+
node: node.type.name,
|
|
186
|
+
story
|
|
187
|
+
}
|
|
188
|
+
};
|
|
145
189
|
}
|
|
146
190
|
if (node.type.isInGroup("preserved")) {
|
|
147
191
|
if (typeof node.attrs.xml === "string") return null;
|
|
148
|
-
return originalBlock(node, session) ? null : lostOriginal(node, session);
|
|
192
|
+
return originalBlock(node, session) ? null : lostOriginal(node, session, story);
|
|
149
193
|
}
|
|
150
194
|
return null;
|
|
151
195
|
}
|
|
152
|
-
var preservedOriginals =
|
|
153
|
-
|
|
154
|
-
|
|
196
|
+
var preservedOriginals = overScopes(
|
|
197
|
+
"preservedOriginals",
|
|
198
|
+
(scope, session) => {
|
|
155
199
|
const problems = [];
|
|
156
|
-
doc.descendants((node, pos) => {
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
problems.push({ code: "lost-original",
|
|
200
|
+
scope.doc.descendants((node, pos) => {
|
|
201
|
+
const lost = lostOriginalOf(node, session, scope.story);
|
|
202
|
+
if (lost !== null) {
|
|
203
|
+
problems.push(found(scope, pos, { code: "lost-original", ...lost }));
|
|
204
|
+
}
|
|
160
205
|
return true;
|
|
161
206
|
});
|
|
162
207
|
return problems;
|
|
163
208
|
}
|
|
164
|
-
|
|
209
|
+
);
|
|
165
210
|
var uniqueIdentities = {
|
|
166
211
|
name: "uniqueIdentities",
|
|
167
|
-
check(doc, session) {
|
|
212
|
+
check({ doc, session }) {
|
|
168
213
|
const parts = [
|
|
169
214
|
...HEADER_FOOTER_KINDS.flatMap(
|
|
170
|
-
(kind) => storyChangesOf(doc, session, kind)
|
|
171
|
-
|
|
172
|
-
|
|
215
|
+
(kind) => storyChangesOf(doc, session, kind).flatMap(
|
|
216
|
+
(change) => change.change === "edited" ? [
|
|
217
|
+
[
|
|
218
|
+
{
|
|
219
|
+
key: change.imported.key,
|
|
220
|
+
story: change.current,
|
|
221
|
+
frozen: false
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
] : []
|
|
225
|
+
)
|
|
173
226
|
),
|
|
174
227
|
...STORY_ENTRIES_PARTS.map((part) => storyEntriesOf(part, doc, session))
|
|
175
228
|
];
|
|
176
229
|
return [
|
|
177
|
-
...identityProblems(doc)
|
|
178
|
-
|
|
230
|
+
...identityProblems(doc).map(
|
|
231
|
+
({ code, message, node, pos }) => ({
|
|
232
|
+
code,
|
|
233
|
+
message,
|
|
234
|
+
reason: { kind: "duplicate-preserved-block", node, story: null },
|
|
235
|
+
pos
|
|
236
|
+
})
|
|
237
|
+
),
|
|
238
|
+
...parts.flatMap(
|
|
239
|
+
(entries) => identityProblemsInStories(entries).map(
|
|
240
|
+
({ code, message, node, story }) => {
|
|
241
|
+
const entry = entries[story];
|
|
242
|
+
return {
|
|
243
|
+
code,
|
|
244
|
+
message,
|
|
245
|
+
reason: {
|
|
246
|
+
kind: "duplicate-preserved-block",
|
|
247
|
+
node,
|
|
248
|
+
story: entry === void 0 ? null : splitStoryKey(entry.key)
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
)
|
|
179
254
|
];
|
|
180
255
|
}
|
|
181
256
|
};
|
|
182
257
|
var storiesHaveWriters = {
|
|
183
258
|
name: "storiesHaveWriters",
|
|
184
|
-
check(doc, session) {
|
|
259
|
+
check({ doc, session }) {
|
|
185
260
|
return unwrittenStoryChanges(STORY_WRITINGS, doc, session).map(
|
|
186
261
|
({ key, change }) => ({
|
|
187
262
|
code: "unsupported-content",
|
|
188
|
-
message: `the ${key} story was ${change}, and no part writer carries that into the file
|
|
263
|
+
message: `the ${key} story was ${change}, and no part writer carries that into the file`,
|
|
264
|
+
reason: {
|
|
265
|
+
kind: "unwritten-story-change",
|
|
266
|
+
story: splitStoryKey(key),
|
|
267
|
+
change
|
|
268
|
+
}
|
|
189
269
|
})
|
|
190
270
|
);
|
|
191
271
|
}
|
|
192
272
|
};
|
|
273
|
+
function undefinedListProblems(scope, undefinedIds) {
|
|
274
|
+
const problems = [];
|
|
275
|
+
scope.doc.descendants((node, pos) => {
|
|
276
|
+
if (node.type.name !== "paragraph") return true;
|
|
277
|
+
const numId = toParagraphFormat(node.attrs.format)?.numbering?.numId;
|
|
278
|
+
if (numId === void 0 || !undefinedIds.delete(numId)) return false;
|
|
279
|
+
problems.push(
|
|
280
|
+
found(scope, pos, {
|
|
281
|
+
code: "unsupported-content",
|
|
282
|
+
message: `the list numbered ${numId} has no definition to be written`,
|
|
283
|
+
reason: { kind: "undefined-list", numId, story: scope.story }
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
return false;
|
|
287
|
+
});
|
|
288
|
+
return problems;
|
|
289
|
+
}
|
|
193
290
|
var listDefinitions = {
|
|
194
291
|
name: "listDefinitions",
|
|
195
|
-
check(doc, session) {
|
|
292
|
+
check({ doc, session, scopes }) {
|
|
196
293
|
const undefinedIds = new Set(startedLists(doc, session).unregistered);
|
|
197
294
|
if (undefinedIds.size === 0) return [];
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const numId = toParagraphFormat(node.attrs.format)?.numbering?.numId;
|
|
202
|
-
if (numId === void 0 || !undefinedIds.delete(numId)) return false;
|
|
203
|
-
problems.push({
|
|
204
|
-
code: "unsupported-content",
|
|
205
|
-
message: `the list numbered ${numId} has no definition to be written`,
|
|
206
|
-
pos
|
|
207
|
-
});
|
|
208
|
-
return false;
|
|
209
|
-
});
|
|
210
|
-
return problems;
|
|
295
|
+
return scopes.flatMap(
|
|
296
|
+
(scope) => undefinedListProblems(scope, undefinedIds)
|
|
297
|
+
);
|
|
211
298
|
}
|
|
212
299
|
};
|
|
213
|
-
function
|
|
300
|
+
function addedCommentsPart(doc, session) {
|
|
214
301
|
const bodyChanged = commentsChanged(doc, session);
|
|
215
302
|
const threadChanged = extensionsChanged(doc, session);
|
|
216
|
-
if (!bodyChanged && !threadChanged) return
|
|
217
|
-
if (commentsPart.pathIn(session) === null || session.comments.xml === null)
|
|
218
|
-
return
|
|
303
|
+
if (!bodyChanged && !threadChanged) return null;
|
|
304
|
+
if (commentsPart.pathIn(session) === null || session.comments.xml === null) {
|
|
305
|
+
return "comments";
|
|
306
|
+
}
|
|
219
307
|
const references = commentReferencesIn(doc);
|
|
220
|
-
if (threadChanged && (references.size > 0 || session.comments.extendedPartPath !== null) && (commentsExtendedPart.pathIn(session) === null || session.comments.extendedXml === null))
|
|
221
|
-
return
|
|
308
|
+
if (threadChanged && (references.size > 0 || session.comments.extendedPartPath !== null) && (commentsExtendedPart.pathIn(session) === null || session.comments.extendedXml === null)) {
|
|
309
|
+
return "commentsExtended";
|
|
310
|
+
}
|
|
222
311
|
return bodyChanged && (peoplePart.pathIn(session) === null || session.comments.people.xml === null) && unrecordedAuthors(
|
|
223
312
|
currentCommentBodies(references).values(),
|
|
224
313
|
session.comments.people,
|
|
225
314
|
unattributedCommentAuthors(session.comments.ordered)
|
|
226
|
-
).size > 0;
|
|
315
|
+
).size > 0 ? "people" : null;
|
|
227
316
|
}
|
|
228
317
|
var mediaContentTypes = {
|
|
229
318
|
name: "mediaContentTypes",
|
|
230
|
-
check(doc, session) {
|
|
319
|
+
check({ doc, session }) {
|
|
231
320
|
if (session.parts.has(CONTENT_TYPES_PATH)) return [];
|
|
232
321
|
const problems = [];
|
|
233
322
|
if (insertedImageSrcs(doc).length > 0) {
|
|
234
323
|
problems.push({
|
|
235
324
|
code: "missing-content-types",
|
|
236
|
-
message: `cannot add an image to a package that has no ${CONTENT_TYPES_PATH}
|
|
325
|
+
message: `cannot add an image to a package that has no ${CONTENT_TYPES_PATH}`,
|
|
326
|
+
reason: { kind: "missing-content-types", part: "media" }
|
|
237
327
|
});
|
|
238
328
|
}
|
|
239
329
|
if (!canDefineNewList(session) && newNumIds(doc, session).length > 0) {
|
|
240
330
|
problems.push({
|
|
241
331
|
code: "missing-content-types",
|
|
242
|
-
message: `cannot add a part to a package that has no ${CONTENT_TYPES_PATH}
|
|
332
|
+
message: `cannot add a part to a package that has no ${CONTENT_TYPES_PATH}`,
|
|
333
|
+
reason: { kind: "missing-content-types", part: "numbering" }
|
|
243
334
|
});
|
|
244
335
|
}
|
|
245
|
-
|
|
336
|
+
const comments = addedCommentsPart(doc, session);
|
|
337
|
+
if (comments !== null) {
|
|
246
338
|
problems.push({
|
|
247
339
|
code: "missing-content-types",
|
|
248
|
-
message: `cannot add a part to a package that has no ${CONTENT_TYPES_PATH}
|
|
340
|
+
message: `cannot add a part to a package that has no ${CONTENT_TYPES_PATH}`,
|
|
341
|
+
reason: { kind: "missing-content-types", part: comments }
|
|
249
342
|
});
|
|
250
343
|
}
|
|
251
344
|
return problems;
|
|
@@ -253,28 +346,63 @@ var mediaContentTypes = {
|
|
|
253
346
|
};
|
|
254
347
|
var commentPartRoots = {
|
|
255
348
|
name: "commentPartRoots",
|
|
256
|
-
check(doc, session) {
|
|
349
|
+
check({ doc, session }) {
|
|
257
350
|
const problems = [];
|
|
258
351
|
const { xml, extendedXml, extendedPartPath } = session.comments;
|
|
259
352
|
if (xml !== null && commentsChanged(doc, session)) {
|
|
260
353
|
const problem = commentsRootProblem(xml);
|
|
261
|
-
if (problem !== null)
|
|
262
|
-
problems.push({
|
|
354
|
+
if (problem !== null) {
|
|
355
|
+
problems.push({
|
|
356
|
+
code: "malformed-xml",
|
|
357
|
+
message: problem,
|
|
358
|
+
reason: { kind: "unwritable-part-root", part: "comments" }
|
|
359
|
+
});
|
|
360
|
+
}
|
|
263
361
|
}
|
|
264
362
|
if (extendedXml !== null && extensionsChanged(doc, session) && (commentReferencesIn(doc).size > 0 || extendedPartPath !== null)) {
|
|
265
363
|
const problem = extensionsRootProblem(extendedXml);
|
|
266
|
-
if (problem !== null)
|
|
267
|
-
problems.push({
|
|
364
|
+
if (problem !== null) {
|
|
365
|
+
problems.push({
|
|
366
|
+
code: "malformed-xml",
|
|
367
|
+
message: problem,
|
|
368
|
+
reason: { kind: "unwritable-part-root", part: "commentsExtended" }
|
|
369
|
+
});
|
|
370
|
+
}
|
|
268
371
|
}
|
|
269
372
|
return problems;
|
|
270
373
|
}
|
|
271
374
|
};
|
|
272
375
|
var notePartRoots = {
|
|
273
376
|
name: "notePartRoots",
|
|
274
|
-
check: (doc, session) => STORY_ENTRIES_PARTS.flatMap(
|
|
377
|
+
check: ({ doc, session }) => STORY_ENTRIES_PARTS.flatMap(
|
|
275
378
|
(part) => storyEntriesProblems(part, doc, session)
|
|
276
379
|
)
|
|
277
380
|
};
|
|
381
|
+
function noteKeyIn(reason) {
|
|
382
|
+
const story = "story" in reason ? reason.story : null;
|
|
383
|
+
if (story === null) return null;
|
|
384
|
+
const kind = NOTE_KINDS.find((candidate) => candidate === story.kind);
|
|
385
|
+
return kind === void 0 ? null : storyKey(kind, story.id);
|
|
386
|
+
}
|
|
387
|
+
function atNoteReferences(problems, doc) {
|
|
388
|
+
let references = null;
|
|
389
|
+
const placed = (key) => {
|
|
390
|
+
references ??= firstNoteReferences(
|
|
391
|
+
doc,
|
|
392
|
+
new Set(
|
|
393
|
+
problems.flatMap(
|
|
394
|
+
({ pos, reason }) => pos === void 0 ? noteKeyIn(reason) ?? [] : []
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
);
|
|
398
|
+
return references.get(key);
|
|
399
|
+
};
|
|
400
|
+
return problems.map((problem) => {
|
|
401
|
+
const key = problem.pos === void 0 ? noteKeyIn(problem.reason) : null;
|
|
402
|
+
const pos = key === null ? void 0 : placed(key);
|
|
403
|
+
return pos === void 0 ? problem : { ...problem, pos };
|
|
404
|
+
});
|
|
405
|
+
}
|
|
278
406
|
var EXPORT_INVARIANTS = [
|
|
279
407
|
bookmarkPairs,
|
|
280
408
|
tableGrids,
|
|
@@ -286,12 +414,27 @@ var EXPORT_INVARIANTS = [
|
|
|
286
414
|
commentPartRoots,
|
|
287
415
|
notePartRoots
|
|
288
416
|
];
|
|
417
|
+
function exportScopes(doc, session) {
|
|
418
|
+
return [
|
|
419
|
+
{ doc, story: null },
|
|
420
|
+
...rewrittenStories(STORY_WRITINGS, doc, session).map(({ key, story }) => ({
|
|
421
|
+
doc: story,
|
|
422
|
+
story: splitStoryKey(key)
|
|
423
|
+
}))
|
|
424
|
+
];
|
|
425
|
+
}
|
|
289
426
|
var answered = /* @__PURE__ */ new WeakMap();
|
|
290
427
|
function problemsOf(doc, session) {
|
|
291
428
|
const known = answered.get(doc);
|
|
292
429
|
if (known && known.session === session) return known.problems;
|
|
293
|
-
const
|
|
294
|
-
|
|
430
|
+
const subject = {
|
|
431
|
+
doc,
|
|
432
|
+
session,
|
|
433
|
+
scopes: exportScopes(doc, session)
|
|
434
|
+
};
|
|
435
|
+
const problems = atNoteReferences(
|
|
436
|
+
EXPORT_INVARIANTS.flatMap((invariant) => invariant.check(subject)),
|
|
437
|
+
doc
|
|
295
438
|
);
|
|
296
439
|
answered.set(doc, { session, problems });
|
|
297
440
|
return problems;
|
package/dist/docx/newLists.js
CHANGED
|
@@ -3,6 +3,7 @@ import { toParagraphFormat } from "../model/format.js";
|
|
|
3
3
|
import { NEW_LISTS_ATTR, newListsOf } from "../numbering/listRegistry.js";
|
|
4
4
|
import { parseNumbering } from "../numbering/parseNumbering.js";
|
|
5
5
|
import { R_NS } from "../ooxml/xml.js";
|
|
6
|
+
import { asStoryKey, storiesOf, storyNodeOf } from "../schema/stories.js";
|
|
6
7
|
import { CONTENT_TYPES_PATH } from "./packageParts.js";
|
|
7
8
|
var NUMBERING_REL_TYPE = `${R_NS}/numbering`;
|
|
8
9
|
var NUMBERING_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml";
|
|
@@ -15,20 +16,28 @@ function collectNumIds(node, into) {
|
|
|
15
16
|
};
|
|
16
17
|
if (visit(node)) node.descendants(visit);
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
+
function numIdsNow(doc) {
|
|
19
20
|
const used = /* @__PURE__ */ new Set();
|
|
20
|
-
collectNumIds(
|
|
21
|
+
collectNumIds(doc, used);
|
|
22
|
+
for (const key of Object.keys(storiesOf(doc))) {
|
|
23
|
+
const story = asStoryKey(key);
|
|
24
|
+
const node = story === null ? null : storyNodeOf(doc, story);
|
|
25
|
+
if (node !== null) collectNumIds(node, used);
|
|
26
|
+
}
|
|
21
27
|
return used;
|
|
22
28
|
}
|
|
23
29
|
function numIdsAtOpen(session) {
|
|
24
30
|
const used = /* @__PURE__ */ new Set();
|
|
25
31
|
for (const block of session.blocks) collectNumIds(block.node, used);
|
|
32
|
+
for (const story of session.stories.values()) {
|
|
33
|
+
for (const block of story.blocks) collectNumIds(block.node, used);
|
|
34
|
+
}
|
|
26
35
|
return used;
|
|
27
36
|
}
|
|
28
37
|
function newNumIds(doc, session) {
|
|
29
38
|
const defined = parseNumbering(session.numberingXml).lists;
|
|
30
39
|
const atOpen = numIdsAtOpen(session);
|
|
31
|
-
return Array.from(
|
|
40
|
+
return Array.from(numIdsNow(doc)).filter((numId) => !defined.has(numId) && !atOpen.has(numId)).sort((a, b) => a - b);
|
|
32
41
|
}
|
|
33
42
|
function startedLists(doc, session) {
|
|
34
43
|
const registered = newListsOf(doc.attrs[NEW_LISTS_ATTR]);
|
|
@@ -9,19 +9,12 @@ import { sectionsOf } from "../sections.js";
|
|
|
9
9
|
import {
|
|
10
10
|
withNoteProps
|
|
11
11
|
} from "./reading.js";
|
|
12
|
+
import { eachNoteReference } from "./references.js";
|
|
12
13
|
var MAX_LABEL_CHARS = 64;
|
|
13
14
|
function referencesBySection(doc) {
|
|
14
15
|
const found = [];
|
|
15
|
-
doc
|
|
16
|
-
|
|
17
|
-
if (node.type.name === "noteReference") {
|
|
18
|
-
found.push({
|
|
19
|
-
block: index,
|
|
20
|
-
reference: { pos: offset + 1 + pos, node }
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
return true;
|
|
24
|
-
});
|
|
16
|
+
eachNoteReference(doc, ({ node, pos, block }) => {
|
|
17
|
+
found.push({ block, reference: { pos, node } });
|
|
25
18
|
});
|
|
26
19
|
if (found.length === 0) return [];
|
|
27
20
|
const sections = sectionsOf(doc);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the body refers to its notes.
|
|
3
|
+
*
|
|
4
|
+
* Three readers ask: the notes writers collect the ids each part is referred to by, the numbering
|
|
5
|
+
* counts the references section by section (`./numbering`), and the export invariants look up the
|
|
6
|
+
* first reference to one note, which is where a problem about a note's own text is reported
|
|
7
|
+
* (`docx/invariants`). One walk here is what keeps the three reading the same nodes in the same
|
|
8
|
+
* order.
|
|
9
|
+
*/
|
|
10
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
11
|
+
import { type NoteKey } from "../../schema/stories";
|
|
12
|
+
/** One reference the body makes to a note */
|
|
13
|
+
export interface PlacedNoteReference {
|
|
14
|
+
readonly node: PMNode;
|
|
15
|
+
/** Where the reference stands in the document */
|
|
16
|
+
readonly pos: number;
|
|
17
|
+
/** The top-level block it stands in, by index, which is what a section covers a run of */
|
|
18
|
+
readonly block: number;
|
|
19
|
+
/** The note it names, and null where it names no kind of note or carries no id */
|
|
20
|
+
readonly key: NoteKey | null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Every `noteReference` of the body in document order, until `visit` answers false.
|
|
24
|
+
*
|
|
25
|
+
* Only the document node is walked: WordprocessingML puts a reference in the body and a note's own
|
|
26
|
+
* text in a part of its own, so a side story holds none.
|
|
27
|
+
*/
|
|
28
|
+
export declare function eachNoteReference(doc: PMNode, visit: (reference: PlacedNoteReference) => boolean | void): void;
|
|
29
|
+
/**
|
|
30
|
+
* Where the body first refers to each of these notes, and nothing for one it refers to nowhere.
|
|
31
|
+
* The walk stops as soon as every note asked about has been placed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function firstNoteReferences(doc: PMNode, wanted: ReadonlySet<NoteKey>): ReadonlyMap<NoteKey, number>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/docx/notes/references.ts
|
|
2
|
+
import { NOTE_KINDS, storyKey } from "../../schema/stories.js";
|
|
3
|
+
function keyOf(node) {
|
|
4
|
+
const kind = NOTE_KINDS.find((candidate) => candidate === node.attrs.kind);
|
|
5
|
+
const id = node.attrs.id;
|
|
6
|
+
return kind === void 0 || typeof id !== "string" ? null : storyKey(kind, id);
|
|
7
|
+
}
|
|
8
|
+
function eachNoteReference(doc, visit) {
|
|
9
|
+
let running = true;
|
|
10
|
+
doc.forEach((block, offset, index) => {
|
|
11
|
+
if (!running) return;
|
|
12
|
+
block.descendants((node, pos) => {
|
|
13
|
+
if (!running) return false;
|
|
14
|
+
if (node.type.name !== "noteReference") return true;
|
|
15
|
+
running = visit({
|
|
16
|
+
node,
|
|
17
|
+
pos: offset + 1 + pos,
|
|
18
|
+
block: index,
|
|
19
|
+
key: keyOf(node)
|
|
20
|
+
}) !== false;
|
|
21
|
+
return false;
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function firstNoteReferences(doc, wanted) {
|
|
26
|
+
const first = /* @__PURE__ */ new Map();
|
|
27
|
+
if (wanted.size === 0) return first;
|
|
28
|
+
eachNoteReference(doc, ({ key, pos }) => {
|
|
29
|
+
if (key !== null && wanted.has(key) && !first.has(key)) first.set(key, pos);
|
|
30
|
+
return first.size < wanted.size;
|
|
31
|
+
});
|
|
32
|
+
return first;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
eachNoteReference,
|
|
36
|
+
firstNoteReferences
|
|
37
|
+
};
|