@portone/docx-editor 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +17 -5
- package/dist/DocxEditor.d.ts +34 -10
- package/dist/DocxEditor.js +69 -24
- package/dist/core.d.ts +2 -0
- package/dist/core.js +2 -0
- package/dist/docx/commentOnlyChange.d.ts +47 -0
- package/dist/docx/commentOnlyChange.js +162 -0
- package/dist/docx/comments/constants.d.ts +11 -3
- package/dist/docx/comments/constants.js +10 -3
- package/dist/docx/comments/contentTypes.d.ts +7 -0
- package/dist/docx/comments/contentTypes.js +38 -0
- package/dist/docx/comments/grammar.d.ts +80 -0
- package/dist/docx/comments/grammar.js +213 -0
- package/dist/docx/comments/model.d.ts +2 -0
- package/dist/docx/comments/model.js +3 -0
- package/dist/docx/comments/people.d.ts +39 -0
- package/dist/docx/comments/people.js +196 -0
- package/dist/docx/comments/reading.d.ts +18 -1
- package/dist/docx/comments/reading.js +33 -20
- package/dist/docx/comments/verifying.d.ts +44 -0
- package/dist/docx/comments/verifying.js +206 -0
- package/dist/docx/comments/writing.d.ts +4 -1
- package/dist/docx/comments/writing.js +84 -77
- package/dist/docx/exportDocx.js +2 -1
- package/dist/docx/formatting/direct.js +3 -6
- package/dist/docx/headersFooters.js +8 -10
- package/dist/docx/importParagraph.js +1 -0
- package/dist/docx/notes.js +3 -5
- package/dist/docx/paraProps.js +51 -46
- package/dist/docx/propsXml.d.ts +20 -2
- package/dist/docx/propsXml.js +72 -32
- package/dist/docx/relationships.js +8 -3
- package/dist/docx/runProps.js +34 -34
- package/dist/docx/sdt.js +16 -14
- package/dist/docx/serializeBlock.js +1 -2
- package/dist/docx/serializeParagraph.d.ts +2 -1
- package/dist/docx/serializeParagraph.js +25 -14
- package/dist/docx/serializeTable.js +38 -24
- package/dist/docx/storyProjection.d.ts +36 -0
- package/dist/docx/storyProjection.js +23 -0
- package/dist/docx/tableFormatting/editing.js +49 -73
- package/dist/docx/tableTemplate.js +24 -3
- package/dist/docx/theme.d.ts +0 -6
- package/dist/docx/theme.js +0 -8
- package/dist/editor/clipboard/images.js +17 -3
- package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
- package/dist/editor/clipboard/inlineFormatting.js +1 -0
- package/dist/editor/commands/breakCommands.js +6 -2
- package/dist/editor/commands/canRunCommand.d.ts +3 -2
- package/dist/editor/commands/canRunCommand.js +2 -2
- package/dist/editor/commands/comments/editing.js +9 -10
- package/dist/editor/commands/comments/model.d.ts +9 -0
- package/dist/editor/commands/comments/reading.d.ts +7 -0
- package/dist/editor/commands/comments/reading.js +14 -0
- package/dist/editor/commands/formatting/editing.js +6 -1
- package/dist/editor/commands/historyCommands.js +14 -5
- package/dist/editor/commands/index.d.ts +7 -1
- package/dist/editor/commands/index.js +4 -0
- package/dist/editor/commands/linkCommands.js +2 -0
- package/dist/editor/commands/lockCommands.js +12 -1
- package/dist/editor/commands/tabCommands.js +4 -2
- package/dist/editor/createEditor.d.ts +13 -3
- package/dist/editor/createEditor.js +23 -15
- package/dist/editor/externalClipboard.js +166 -4
- package/dist/editor/insertImage.js +6 -2
- package/dist/editor/insertTable.js +2 -1
- package/dist/editor/paragraphEdits.d.ts +2 -0
- package/dist/editor/paragraphEdits.js +2 -0
- package/dist/editor/plugins/documentProtection.d.ts +21 -0
- package/dist/editor/plugins/documentProtection.js +44 -0
- package/dist/editor/plugins/imagePaste.js +4 -1
- package/dist/editor/plugins/keymap.d.ts +11 -1
- package/dist/editor/plugins/keymap.js +24 -4
- package/dist/editor/plugins/lockedContent.d.ts +4 -2
- package/dist/editor/plugins/lockedContent.js +2 -2
- package/dist/editor/plugins/tableContextMenu.js +2 -1
- package/dist/editor/plugins/textContextMenu.js +6 -1
- package/dist/index.d.ts +2 -0
- package/dist/numbering/listTemplate.js +32 -10
- package/dist/ooxml/element.d.ts +52 -0
- package/dist/ooxml/element.js +49 -0
- package/dist/ooxml/fragment.d.ts +53 -0
- package/dist/ooxml/fragment.js +76 -0
- package/dist/ooxml/image.js +4 -3
- package/dist/ooxml/names.d.ts +27 -0
- package/dist/ooxml/names.js +29 -0
- package/dist/ooxml/precedence.d.ts +31 -0
- package/dist/ooxml/precedence.js +51 -0
- package/dist/ooxml/xml.d.ts +31 -0
- package/dist/ooxml/xml.js +27 -0
- package/dist/page/blockKinds.d.ts +42 -0
- package/dist/page/blockKinds.js +0 -0
- package/dist/page/measureBlocks.d.ts +14 -1
- package/dist/page/measureBlocks.js +31 -11
- package/dist/page/pageDecorations.d.ts +16 -0
- package/dist/page/pageDecorations.js +1 -0
- package/dist/page/pageLayout.d.ts +8 -43
- package/dist/page/pageLayout.js +28 -51
- package/dist/page/tableMeasurements.d.ts +10 -2
- package/dist/page/tableMeasurements.js +20 -16
- package/dist/page/usePageLayout.d.ts +15 -0
- package/dist/page/usePageLayout.js +30 -2
- package/dist/schema/attrRoles.d.ts +35 -0
- package/dist/schema/attrRoles.js +106 -0
- package/dist/schema/docxSchema.d.ts +6 -0
- package/dist/schema/docxSchema.js +216 -100
- package/dist/schema/editGuard.d.ts +99 -0
- package/dist/schema/editGuard.js +43 -0
- package/dist/schema/guards.d.ts +68 -0
- package/dist/schema/guards.js +82 -0
- package/dist/schema/locks.d.ts +9 -8
- package/dist/schema/locks.js +32 -16
- package/dist/schema/preservedGuards.d.ts +30 -0
- package/dist/schema/preservedGuards.js +50 -0
- package/dist/schema/protection.d.ts +84 -0
- package/dist/schema/protection.js +171 -0
- package/dist/schema/protectionState.d.ts +20 -0
- package/dist/schema/protectionState.js +37 -0
- package/dist/schema/sourceEquality.d.ts +27 -0
- package/dist/schema/sourceEquality.js +65 -0
- package/dist/table/cellFormatting.js +5 -2
- package/dist/table/commands.d.ts +2 -2
- package/dist/table/commands.js +2 -2
- package/dist/table/index.d.ts +2 -2
- package/dist/table/merge.d.ts +2 -2
- package/dist/table/merge.js +3 -3
- package/dist/ui/CommentsPanel.d.ts +3 -3
- package/dist/ui/CommentsPanel.js +26 -18
- package/dist/ui/LinkCard.d.ts +1 -2
- package/dist/ui/LinkCard.js +3 -4
- package/dist/ui/TextMenu.js +15 -10
- package/package.json +5 -2
- package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
- package/dist/editor/plugins/bookmarkProtection.js +0 -28
- package/dist/editor/plugins/noteProtection.d.ts +0 -3
- package/dist/editor/plugins/noteProtection.js +0 -25
|
@@ -13,6 +13,12 @@ import {
|
|
|
13
13
|
toTableFormat,
|
|
14
14
|
toTableWidth
|
|
15
15
|
} from "../model/format.js";
|
|
16
|
+
import {
|
|
17
|
+
ANY_ELEMENT,
|
|
18
|
+
ATTRIBUTES,
|
|
19
|
+
acceptRawXml,
|
|
20
|
+
ELEMENT
|
|
21
|
+
} from "../ooxml/fragment.js";
|
|
16
22
|
import { toImageExtent, toImageSrc } from "../ooxml/image.js";
|
|
17
23
|
import { editorAttributes, editorClassNames } from "../styles/classNames.js";
|
|
18
24
|
import { DEFAULT_FONT_FALLBACKS } from "../styles/fontStack.js";
|
|
@@ -25,6 +31,9 @@ import {
|
|
|
25
31
|
} from "../styles/inlineStyle.js";
|
|
26
32
|
import { imageNodeSpec, runMarkSpec } from "./rendering.js";
|
|
27
33
|
import { imageNodeSpec as imageNodeSpec2, runMarkSpec as runMarkSpec2 } from "./rendering.js";
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return typeof value === "object" && value !== null;
|
|
36
|
+
}
|
|
28
37
|
function text(value) {
|
|
29
38
|
return typeof value === "string" ? value : void 0;
|
|
30
39
|
}
|
|
@@ -64,6 +73,29 @@ function srcIdOf(dom) {
|
|
|
64
73
|
const parsed = Number.parseInt(raw, 10);
|
|
65
74
|
return Number.isNaN(parsed) ? null : parsed;
|
|
66
75
|
}
|
|
76
|
+
function repliesHoldTheirXml(value) {
|
|
77
|
+
if (!Array.isArray(value)) return true;
|
|
78
|
+
const replies = value;
|
|
79
|
+
return replies.every((reply) => {
|
|
80
|
+
if (!isRecord(reply)) return true;
|
|
81
|
+
return acceptRawXml(ELEMENT("comment"), text(reply.commentXml) ?? null) !== false && acceptRawXml(ANY_ELEMENT, text(reply.extensionXml) ?? null) !== false;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
var SDT_PREFIX = {
|
|
85
|
+
kind: "openTag",
|
|
86
|
+
name: "sdt",
|
|
87
|
+
closedBy: "<w:sdtContent/></w:sdt>",
|
|
88
|
+
head: ["sdtPr", "sdtEndPr"]
|
|
89
|
+
};
|
|
90
|
+
var LINK_PREFIX = {
|
|
91
|
+
kind: "openTag",
|
|
92
|
+
name: "hyperlink",
|
|
93
|
+
closedBy: "</w:hyperlink>",
|
|
94
|
+
head: []
|
|
95
|
+
};
|
|
96
|
+
function rawXml(dom, attribute, shape) {
|
|
97
|
+
return acceptRawXml(shape, dom.getAttribute(attribute));
|
|
98
|
+
}
|
|
67
99
|
var TABLE_PLACEHOLDER = "Table (unsupported layout, original is preserved)";
|
|
68
100
|
var UNKNOWN_PLACEHOLDER = "Unsupported content (not editable, original is preserved)";
|
|
69
101
|
var TABLE_NAMES = ["w:tbl", "tbl"];
|
|
@@ -84,7 +116,7 @@ var docxSchema = new Schema({
|
|
|
84
116
|
nodes: {
|
|
85
117
|
doc: { content: "block+" },
|
|
86
118
|
paragraph: {
|
|
87
|
-
group: "block",
|
|
119
|
+
group: "block modelled",
|
|
88
120
|
content: "inline*",
|
|
89
121
|
// Leading and trailing whitespace carries meaning in docx text, so it is not stripped when the DOM is read back
|
|
90
122
|
whitespace: "pre",
|
|
@@ -125,15 +157,22 @@ var docxSchema = new Schema({
|
|
|
125
157
|
parseDOM: [
|
|
126
158
|
{
|
|
127
159
|
tag: `p.${editorClassNames.paragraph}`,
|
|
128
|
-
getAttrs: (dom) =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
pPr
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
getAttrs: (dom) => {
|
|
161
|
+
const pAttrs = rawXml(dom, "data-pattrs", ATTRIBUTES);
|
|
162
|
+
const pPr = rawXml(dom, "data-ppr", ELEMENT("pPr"));
|
|
163
|
+
if (pAttrs === false || pPr === false) return false;
|
|
164
|
+
return {
|
|
165
|
+
srcId: srcIdOf(dom),
|
|
166
|
+
pAttrs,
|
|
167
|
+
pPr,
|
|
168
|
+
format: toParagraphFormat(
|
|
169
|
+
parseJson(dom.getAttribute("data-fmt"))
|
|
170
|
+
),
|
|
171
|
+
styleRun: toRunFormat(
|
|
172
|
+
parseJson(dom.getAttribute("data-style-run"))
|
|
173
|
+
)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
137
176
|
}
|
|
138
177
|
]
|
|
139
178
|
},
|
|
@@ -142,7 +181,7 @@ var docxSchema = new Schema({
|
|
|
142
181
|
* edited table is written back the new XML is built from those two.
|
|
143
182
|
*/
|
|
144
183
|
table: {
|
|
145
|
-
group: "block",
|
|
184
|
+
group: "block modelled",
|
|
146
185
|
content: "tableRow+",
|
|
147
186
|
tableRole: "table",
|
|
148
187
|
isolating: true,
|
|
@@ -195,20 +234,25 @@ var docxSchema = new Schema({
|
|
|
195
234
|
{ tag: "colgroup", ignore: true },
|
|
196
235
|
{
|
|
197
236
|
tag: `table.${editorClassNames.table}`,
|
|
198
|
-
getAttrs: (dom) =>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
tblPr
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
parseJson(dom.getAttribute("data-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
237
|
+
getAttrs: (dom) => {
|
|
238
|
+
const tblAttrs = rawXml(dom, "data-tblattrs", ATTRIBUTES);
|
|
239
|
+
const tblPr = rawXml(dom, "data-tblpr", ELEMENT("tblPr"));
|
|
240
|
+
if (tblAttrs === false || tblPr === false) return false;
|
|
241
|
+
return {
|
|
242
|
+
srcId: srcIdOf(dom),
|
|
243
|
+
tblAttrs,
|
|
244
|
+
tblPr,
|
|
245
|
+
tblW: toTableWidth(parseJson(dom.getAttribute("data-tblw"))),
|
|
246
|
+
gridCols: parseNumberList(dom.getAttribute("data-cols")),
|
|
247
|
+
format: toTableFormat(parseJson(dom.getAttribute("data-fmt"))),
|
|
248
|
+
styleInside: toInsideBorders(
|
|
249
|
+
parseJson(dom.getAttribute("data-style-inside"))
|
|
250
|
+
),
|
|
251
|
+
styleCellMargins: toCellMargins(
|
|
252
|
+
parseJson(dom.getAttribute("data-style-margins"))
|
|
253
|
+
)
|
|
254
|
+
};
|
|
255
|
+
}
|
|
212
256
|
}
|
|
213
257
|
]
|
|
214
258
|
},
|
|
@@ -242,12 +286,20 @@ var docxSchema = new Schema({
|
|
|
242
286
|
parseDOM: [
|
|
243
287
|
{
|
|
244
288
|
tag: "tr",
|
|
245
|
-
getAttrs: (dom) =>
|
|
246
|
-
trAttrs
|
|
247
|
-
tblPrEx
|
|
248
|
-
trPr
|
|
249
|
-
|
|
250
|
-
|
|
289
|
+
getAttrs: (dom) => {
|
|
290
|
+
const trAttrs = rawXml(dom, "data-trattrs", ATTRIBUTES);
|
|
291
|
+
const tblPrEx = rawXml(dom, "data-tblprex", ELEMENT("tblPrEx"));
|
|
292
|
+
const trPr = rawXml(dom, "data-trpr", ELEMENT("trPr"));
|
|
293
|
+
if (trAttrs === false || tblPrEx === false || trPr === false) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
trAttrs,
|
|
298
|
+
tblPrEx,
|
|
299
|
+
trPr,
|
|
300
|
+
format: toRowFormat(parseJson(dom.getAttribute("data-fmt")))
|
|
301
|
+
};
|
|
302
|
+
}
|
|
251
303
|
}
|
|
252
304
|
]
|
|
253
305
|
},
|
|
@@ -309,20 +361,28 @@ var docxSchema = new Schema({
|
|
|
309
361
|
parseDOM: [
|
|
310
362
|
{
|
|
311
363
|
tag: "td",
|
|
312
|
-
getAttrs: (dom) =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
364
|
+
getAttrs: (dom) => {
|
|
365
|
+
const tcAttrs = rawXml(dom, "data-tcattrs", ATTRIBUTES);
|
|
366
|
+
const tcPr = rawXml(dom, "data-tcpr", ELEMENT("tcPr"));
|
|
367
|
+
const sdtPrefix = rawXml(dom, "data-sdt-prefix", SDT_PREFIX);
|
|
368
|
+
if (tcAttrs === false || tcPr === false || sdtPrefix === false) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
colspan: parseInt10(dom.getAttribute("colspan"), 1),
|
|
373
|
+
rowspan: parseInt10(dom.getAttribute("rowspan"), 1),
|
|
374
|
+
colwidth: toColWidth(
|
|
375
|
+
parseNumberList(dom.getAttribute("data-colwidth"))
|
|
376
|
+
),
|
|
377
|
+
tcAttrs,
|
|
378
|
+
tcPr,
|
|
379
|
+
tcW: toTableWidth(parseJson(dom.getAttribute("data-tcw"))),
|
|
380
|
+
format: toCellFormat(parseJson(dom.getAttribute("data-fmt"))),
|
|
381
|
+
sdtPrefix,
|
|
382
|
+
sdtContentsLocked: dom.getAttribute("data-sdt-contents-locked") === "1",
|
|
383
|
+
sdtDeletionLocked: dom.getAttribute("data-sdt-deletion-locked") === "1"
|
|
384
|
+
};
|
|
385
|
+
}
|
|
326
386
|
}
|
|
327
387
|
]
|
|
328
388
|
},
|
|
@@ -333,7 +393,7 @@ var docxSchema = new Schema({
|
|
|
333
393
|
* XML around with it.
|
|
334
394
|
*/
|
|
335
395
|
rawBlock: {
|
|
336
|
-
group: "block",
|
|
396
|
+
group: "block preserved",
|
|
337
397
|
atom: true,
|
|
338
398
|
selectable: false,
|
|
339
399
|
attrs: {
|
|
@@ -355,16 +415,17 @@ var docxSchema = new Schema({
|
|
|
355
415
|
parseDOM: [
|
|
356
416
|
{
|
|
357
417
|
tag: `div.${editorClassNames.rawXmlBlock}`,
|
|
358
|
-
getAttrs: (dom) =>
|
|
359
|
-
xml
|
|
360
|
-
|
|
361
|
-
|
|
418
|
+
getAttrs: (dom) => {
|
|
419
|
+
const xml = rawXml(dom, "data-xml", ANY_ELEMENT);
|
|
420
|
+
if (xml === false) return false;
|
|
421
|
+
return { xml, name: dom.getAttribute("data-name") };
|
|
422
|
+
}
|
|
362
423
|
}
|
|
363
424
|
]
|
|
364
425
|
},
|
|
365
426
|
/** The node that carries a non-paragraph body block (a table we could not model, sectPr, an unknown element) exactly as it came */
|
|
366
427
|
docxRaw: {
|
|
367
|
-
group: "block",
|
|
428
|
+
group: "block preserved",
|
|
368
429
|
atom: true,
|
|
369
430
|
selectable: false,
|
|
370
431
|
attrs: {
|
|
@@ -396,7 +457,7 @@ var docxSchema = new Schema({
|
|
|
396
457
|
},
|
|
397
458
|
/** A bookmark range marker that occurs directly under w:body rather than inside a paragraph */
|
|
398
459
|
bookmarkBlock: {
|
|
399
|
-
group: "block",
|
|
460
|
+
group: "block preserved",
|
|
400
461
|
atom: true,
|
|
401
462
|
isolating: true,
|
|
402
463
|
selectable: false,
|
|
@@ -442,7 +503,10 @@ var docxSchema = new Schema({
|
|
|
442
503
|
parseDOM: [
|
|
443
504
|
{
|
|
444
505
|
tag: "br",
|
|
445
|
-
getAttrs: (dom) =>
|
|
506
|
+
getAttrs: (dom) => {
|
|
507
|
+
const brAttrs = rawXml(dom, "data-battrs", ATTRIBUTES);
|
|
508
|
+
return brAttrs === false ? false : { brAttrs };
|
|
509
|
+
}
|
|
446
510
|
}
|
|
447
511
|
]
|
|
448
512
|
},
|
|
@@ -475,12 +539,16 @@ var docxSchema = new Schema({
|
|
|
475
539
|
parseDOM: [
|
|
476
540
|
{
|
|
477
541
|
tag: `img.${editorClassNames.image}`,
|
|
478
|
-
getAttrs: (dom) =>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
542
|
+
getAttrs: (dom) => {
|
|
543
|
+
const xml = rawXml(dom, "data-xml", ELEMENT("drawing"));
|
|
544
|
+
if (xml === false) return false;
|
|
545
|
+
return {
|
|
546
|
+
src: toImageSrc(dom.getAttribute("src")),
|
|
547
|
+
extent: toImageExtent(parseJson(dom.getAttribute("data-extent"))),
|
|
548
|
+
alt: dom.getAttribute("alt") || null,
|
|
549
|
+
xml
|
|
550
|
+
};
|
|
551
|
+
}
|
|
484
552
|
}
|
|
485
553
|
]
|
|
486
554
|
},
|
|
@@ -508,10 +576,11 @@ var docxSchema = new Schema({
|
|
|
508
576
|
parseDOM: [
|
|
509
577
|
{
|
|
510
578
|
tag: `span.${editorClassNames.commentMarker}[data-comment-marker="start"]`,
|
|
511
|
-
getAttrs: (dom) =>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
579
|
+
getAttrs: (dom) => {
|
|
580
|
+
const xml = rawXml(dom, "data-xml", ELEMENT("commentRangeStart"));
|
|
581
|
+
if (xml === false) return false;
|
|
582
|
+
return { id: dom.getAttribute("data-comment-id"), xml };
|
|
583
|
+
}
|
|
515
584
|
}
|
|
516
585
|
]
|
|
517
586
|
},
|
|
@@ -539,10 +608,11 @@ var docxSchema = new Schema({
|
|
|
539
608
|
parseDOM: [
|
|
540
609
|
{
|
|
541
610
|
tag: `span.${editorClassNames.commentMarker}[data-comment-marker="end"]`,
|
|
542
|
-
getAttrs: (dom) =>
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
611
|
+
getAttrs: (dom) => {
|
|
612
|
+
const xml = rawXml(dom, "data-xml", ELEMENT("commentRangeEnd"));
|
|
613
|
+
if (xml === false) return false;
|
|
614
|
+
return { id: dom.getAttribute("data-comment-id"), xml };
|
|
615
|
+
}
|
|
546
616
|
}
|
|
547
617
|
]
|
|
548
618
|
},
|
|
@@ -556,6 +626,9 @@ var docxSchema = new Schema({
|
|
|
556
626
|
id: { default: null },
|
|
557
627
|
referenceXml: { default: null },
|
|
558
628
|
author: { default: null },
|
|
629
|
+
// The identity behind the display name, read from and written to the people part. Null for
|
|
630
|
+
// a comment nobody's identity is recorded for, which every comment made in Word is here
|
|
631
|
+
authorId: { default: null },
|
|
559
632
|
initials: { default: null },
|
|
560
633
|
date: { default: null },
|
|
561
634
|
text: { default: "" },
|
|
@@ -576,6 +649,7 @@ var docxSchema = new Schema({
|
|
|
576
649
|
"data-comment-id": text(node.attrs.id),
|
|
577
650
|
"data-reference-xml": text(node.attrs.referenceXml),
|
|
578
651
|
"data-comment-author": text(node.attrs.author),
|
|
652
|
+
"data-comment-author-id": text(node.attrs.authorId),
|
|
579
653
|
"data-comment-initials": text(node.attrs.initials),
|
|
580
654
|
"data-comment-date": text(node.attrs.date),
|
|
581
655
|
"data-comment-text": text(node.attrs.text),
|
|
@@ -592,21 +666,43 @@ var docxSchema = new Schema({
|
|
|
592
666
|
parseDOM: [
|
|
593
667
|
{
|
|
594
668
|
tag: `span.${editorClassNames.commentMarker}[data-comment-marker="reference"]`,
|
|
595
|
-
getAttrs: (dom) =>
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
extensionXml
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
669
|
+
getAttrs: (dom) => {
|
|
670
|
+
const referenceXml = rawXml(
|
|
671
|
+
dom,
|
|
672
|
+
"data-reference-xml",
|
|
673
|
+
ELEMENT("commentReference")
|
|
674
|
+
);
|
|
675
|
+
const commentXml = rawXml(
|
|
676
|
+
dom,
|
|
677
|
+
"data-comment-xml",
|
|
678
|
+
ELEMENT("comment")
|
|
679
|
+
);
|
|
680
|
+
const extensionXml = rawXml(
|
|
681
|
+
dom,
|
|
682
|
+
"data-comment-extension-xml",
|
|
683
|
+
ANY_ELEMENT
|
|
684
|
+
);
|
|
685
|
+
const replies = parseJson(dom.getAttribute("data-comment-replies")) ?? [];
|
|
686
|
+
if (referenceXml === false || commentXml === false || extensionXml === false || !repliesHoldTheirXml(replies)) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
id: dom.getAttribute("data-comment-id"),
|
|
691
|
+
referenceXml,
|
|
692
|
+
author: dom.getAttribute("data-comment-author"),
|
|
693
|
+
authorId: dom.getAttribute("data-comment-author-id"),
|
|
694
|
+
initials: dom.getAttribute("data-comment-initials"),
|
|
695
|
+
date: dom.getAttribute("data-comment-date"),
|
|
696
|
+
text: dom.getAttribute("data-comment-text") ?? "",
|
|
697
|
+
commentXml,
|
|
698
|
+
imported: dom.getAttribute("data-comment-imported") === "1",
|
|
699
|
+
paraId: dom.getAttribute("data-comment-para-id"),
|
|
700
|
+
resolved: dom.getAttribute("data-comment-resolved") === "1",
|
|
701
|
+
extensionXml,
|
|
702
|
+
threadImported: dom.getAttribute("data-comment-thread-imported") === "1",
|
|
703
|
+
replies
|
|
704
|
+
};
|
|
705
|
+
}
|
|
610
706
|
}
|
|
611
707
|
]
|
|
612
708
|
},
|
|
@@ -647,14 +743,22 @@ var docxSchema = new Schema({
|
|
|
647
743
|
parseDOM: [
|
|
648
744
|
{
|
|
649
745
|
tag: `sup.${editorClassNames.noteReference}`,
|
|
650
|
-
getAttrs: (dom) =>
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
referenceXml
|
|
657
|
-
|
|
746
|
+
getAttrs: (dom) => {
|
|
747
|
+
const referenceXml = rawXml(
|
|
748
|
+
dom,
|
|
749
|
+
"data-reference-xml",
|
|
750
|
+
ELEMENT("footnoteReference", "endnoteReference")
|
|
751
|
+
);
|
|
752
|
+
if (referenceXml === false) return false;
|
|
753
|
+
return {
|
|
754
|
+
kind: dom.getAttribute("data-note-kind") === "endnote" ? "endnote" : "footnote",
|
|
755
|
+
id: dom.getAttribute("data-note-id"),
|
|
756
|
+
label: dom.getAttribute("data-note-label") ?? "?",
|
|
757
|
+
text: dom.getAttribute("data-note-text") ?? "",
|
|
758
|
+
customMarkFollows: dom.getAttribute("data-custom-mark-follows") === "1",
|
|
759
|
+
referenceXml
|
|
760
|
+
};
|
|
761
|
+
}
|
|
658
762
|
}
|
|
659
763
|
]
|
|
660
764
|
},
|
|
@@ -677,7 +781,10 @@ var docxSchema = new Schema({
|
|
|
677
781
|
parseDOM: [
|
|
678
782
|
{
|
|
679
783
|
tag: `span.${editorClassNames.rawInline}`,
|
|
680
|
-
getAttrs: (dom) =>
|
|
784
|
+
getAttrs: (dom) => {
|
|
785
|
+
const xml = rawXml(dom, "data-xml", ANY_ELEMENT);
|
|
786
|
+
return xml === false ? false : { xml };
|
|
787
|
+
}
|
|
681
788
|
}
|
|
682
789
|
]
|
|
683
790
|
},
|
|
@@ -732,8 +839,8 @@ var docxSchema = new Schema({
|
|
|
732
839
|
{
|
|
733
840
|
tag: `span.${editorClassNames.sdt}`,
|
|
734
841
|
getAttrs: (dom) => {
|
|
735
|
-
const prefix = dom
|
|
736
|
-
if (prefix === null) return false;
|
|
842
|
+
const prefix = rawXml(dom, "data-sdt-prefix", SDT_PREFIX);
|
|
843
|
+
if (prefix === null || prefix === false) return false;
|
|
737
844
|
return {
|
|
738
845
|
sdtPrefix: prefix,
|
|
739
846
|
sdtKey: parseInt10(dom.getAttribute("data-sdt-key"), 0),
|
|
@@ -795,8 +902,9 @@ var docxSchema = new Schema({
|
|
|
795
902
|
{
|
|
796
903
|
tag: `span.${editorClassNames.link}`,
|
|
797
904
|
getAttrs: (dom) => {
|
|
798
|
-
const prefix = dom
|
|
905
|
+
const prefix = rawXml(dom, "data-link-prefix", LINK_PREFIX);
|
|
799
906
|
const href = dom.getAttribute("data-href");
|
|
907
|
+
if (prefix === false) return false;
|
|
800
908
|
if (prefix === null && href === null) return false;
|
|
801
909
|
return {
|
|
802
910
|
linkPrefix: prefix,
|
|
@@ -824,11 +932,16 @@ var docxSchema = new Schema({
|
|
|
824
932
|
parseDOM: [
|
|
825
933
|
{
|
|
826
934
|
tag: `span.${editorClassNames.run}`,
|
|
827
|
-
getAttrs: (dom) =>
|
|
828
|
-
rAttrs
|
|
829
|
-
rPr
|
|
830
|
-
|
|
831
|
-
|
|
935
|
+
getAttrs: (dom) => {
|
|
936
|
+
const rAttrs = rawXml(dom, "data-rattrs", ATTRIBUTES);
|
|
937
|
+
const rPr = rawXml(dom, "data-rpr", ELEMENT("rPr"));
|
|
938
|
+
if (rAttrs === false || rPr === false) return false;
|
|
939
|
+
return {
|
|
940
|
+
rAttrs,
|
|
941
|
+
rPr,
|
|
942
|
+
format: toRunFormat(parseJson(dom.getAttribute("data-fmt")))
|
|
943
|
+
};
|
|
944
|
+
}
|
|
832
945
|
}
|
|
833
946
|
]
|
|
834
947
|
},
|
|
@@ -849,7 +962,10 @@ var docxSchema = new Schema({
|
|
|
849
962
|
parseDOM: [
|
|
850
963
|
{
|
|
851
964
|
tag: `span.${editorClassNames.tab}`,
|
|
852
|
-
getAttrs: (dom) =>
|
|
965
|
+
getAttrs: (dom) => {
|
|
966
|
+
const tabAttrs = rawXml(dom, "data-tattrs", ATTRIBUTES);
|
|
967
|
+
return tabAttrs === false ? false : { tabAttrs };
|
|
968
|
+
}
|
|
853
969
|
}
|
|
854
970
|
]
|
|
855
971
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a guard is, and the reach primitives one is written with.
|
|
3
|
+
*
|
|
4
|
+
* This module names no guard and imports none, so a module that writes a guard (`./locks`,
|
|
5
|
+
* `./preservedGuards`) and the list that registers them (`./guards`) can both read it without
|
|
6
|
+
* pointing at each other. A guard reading its shape off `./guards` instead would put a cycle
|
|
7
|
+
* there: `EDIT_GUARDS` is built while `./guards` is evaluated, so whenever the guard's own module
|
|
8
|
+
* happened to be evaluated first the list would be built out of bindings that had not been
|
|
9
|
+
* assigned yet, and every edit would then be judged against a list holding holes.
|
|
10
|
+
*/
|
|
11
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
12
|
+
import type { EditorState, PluginKey, Transaction } from "prosemirror-state";
|
|
13
|
+
import { type Step } from "prosemirror-transform";
|
|
14
|
+
/**
|
|
15
|
+
* What a command means to do, in as much of it as a guard can answer for before anything is built.
|
|
16
|
+
*
|
|
17
|
+
* The positions are the ones the command would work over, counted in the document as it stands.
|
|
18
|
+
*/
|
|
19
|
+
export type EditIntent =
|
|
20
|
+
/** Something goes in at this spot, and nothing there goes away */
|
|
21
|
+
{
|
|
22
|
+
kind: "insert";
|
|
23
|
+
at: number;
|
|
24
|
+
}
|
|
25
|
+
/** What stands in this stretch is marked where it stands */
|
|
26
|
+
| {
|
|
27
|
+
kind: "mark";
|
|
28
|
+
from: number;
|
|
29
|
+
to: number;
|
|
30
|
+
}
|
|
31
|
+
/** What stands in this stretch goes away, whatever takes its place */
|
|
32
|
+
| {
|
|
33
|
+
kind: "replace";
|
|
34
|
+
from: number;
|
|
35
|
+
to: number;
|
|
36
|
+
}
|
|
37
|
+
/** The block at this spot is rewritten around its content, an alignment or an indent */
|
|
38
|
+
| {
|
|
39
|
+
kind: "block";
|
|
40
|
+
at: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* How a guard is named in the honesty test and in a refusal read by a developer.
|
|
44
|
+
*
|
|
45
|
+
* The names are written out because the registry is a closed list: `lockHonesty` checks each one
|
|
46
|
+
* against the places it puts that guard to the test, and a place naming a guard that no longer
|
|
47
|
+
* stands has to be a mistake the compiler catches rather than an annotation answering for nothing.
|
|
48
|
+
*/
|
|
49
|
+
export type EditGuardName = "protection" | "lock" | "bookmark" | "note";
|
|
50
|
+
/** What every guard answers, whichever of the two judgements it is written as */
|
|
51
|
+
interface GuardCommon {
|
|
52
|
+
readonly name: EditGuardName;
|
|
53
|
+
/** Whether the intent is shut where it stands, which is what a command asks before it builds */
|
|
54
|
+
shuts(intent: EditIntent, state: EditorState): boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A guard that judges one step at a time, over the document that step was built against.
|
|
58
|
+
*
|
|
59
|
+
* Only this form takes a pass. A pass lifts one guard's reading of a step, and a rule that has to
|
|
60
|
+
* see both documents of the change at once has no single step for it to let through.
|
|
61
|
+
*/
|
|
62
|
+
export interface StepGuard extends GuardCommon {
|
|
63
|
+
step(step: Step, before: PMNode, after: PMNode, state: EditorState): boolean;
|
|
64
|
+
/** The passes that lift this guard's step judgement. Omitted: none does */
|
|
65
|
+
liftedBy?: readonly PluginKey<boolean>[];
|
|
66
|
+
change?: never;
|
|
67
|
+
}
|
|
68
|
+
/** A guard that needs both documents of the whole change at once */
|
|
69
|
+
export interface ChangeGuard extends GuardCommon {
|
|
70
|
+
change(tr: Transaction, state: EditorState): boolean;
|
|
71
|
+
step?: never;
|
|
72
|
+
liftedBy?: never;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One rule an edit is judged by: whether it would leave the document in a state the file cannot be
|
|
76
|
+
* written back from.
|
|
77
|
+
*
|
|
78
|
+
* A guard is one judgement or the other, and answers `shuts` either way, so there is no writing
|
|
79
|
+
* one that judges nothing and no hanging a pass on a judgement no pass can lift.
|
|
80
|
+
*/
|
|
81
|
+
export type EditGuard = StepGuard | ChangeGuard;
|
|
82
|
+
/** Whether this stretch of the document holds a node the question answers for */
|
|
83
|
+
export declare function rangeHolds(doc: PMNode, from: number, to: number, holds: (node: PMNode) => boolean): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the step reaches a node the question answers for: puts one in, takes one out, or
|
|
86
|
+
* rewrites the one where it stands.
|
|
87
|
+
*
|
|
88
|
+
* A rule about such nodes cannot have been broken by a change that reaches none of them, which is
|
|
89
|
+
* what lets a guard settle the common transaction - typing, and nothing more - over the stretches
|
|
90
|
+
* its own steps rewrote rather than over the whole document.
|
|
91
|
+
*
|
|
92
|
+
* A step of a kind this does not know - one a consumer brought - is answered as reaching one,
|
|
93
|
+
* since what it rewrote is not known either. The whole-document judgement then has the say, and
|
|
94
|
+
* an unknown step costs a comparison rather than a hole in the guard.
|
|
95
|
+
*/
|
|
96
|
+
export declare function stepReaches(step: Step, before: PMNode, after: PMNode, holds: (node: PMNode) => boolean): boolean;
|
|
97
|
+
/** Whether any step of the transaction reaches a node the question answers for */
|
|
98
|
+
export declare function transactionReaches(tr: Transaction, holds: (node: PMNode) => boolean): boolean;
|
|
99
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/schema/editGuard.ts
|
|
2
|
+
import {
|
|
3
|
+
AddMarkStep,
|
|
4
|
+
AddNodeMarkStep,
|
|
5
|
+
AttrStep,
|
|
6
|
+
RemoveMarkStep,
|
|
7
|
+
RemoveNodeMarkStep,
|
|
8
|
+
ReplaceAroundStep,
|
|
9
|
+
ReplaceStep
|
|
10
|
+
} from "prosemirror-transform";
|
|
11
|
+
function rangeHolds(doc, from, to, holds) {
|
|
12
|
+
let found = false;
|
|
13
|
+
doc.nodesBetween(from, to, (node) => {
|
|
14
|
+
if (found) return false;
|
|
15
|
+
if (holds(node)) found = true;
|
|
16
|
+
return !found;
|
|
17
|
+
});
|
|
18
|
+
return found;
|
|
19
|
+
}
|
|
20
|
+
function stepReaches(step, before, after, holds) {
|
|
21
|
+
if (step instanceof AttrStep || step instanceof AddNodeMarkStep || step instanceof RemoveNodeMarkStep) {
|
|
22
|
+
const node = before.nodeAt(step.pos);
|
|
23
|
+
return node !== null && holds(node);
|
|
24
|
+
}
|
|
25
|
+
if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep || step instanceof AddMarkStep || step instanceof RemoveMarkStep)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
let reached = false;
|
|
29
|
+
step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
|
|
30
|
+
reached ||= rangeHolds(before, oldStart, oldEnd, holds) || rangeHolds(after, newStart, newEnd, holds);
|
|
31
|
+
});
|
|
32
|
+
return reached;
|
|
33
|
+
}
|
|
34
|
+
function transactionReaches(tr, holds) {
|
|
35
|
+
return tr.steps.some(
|
|
36
|
+
(step, index) => stepReaches(step, tr.docs[index], tr.docs[index + 1] ?? tr.doc, holds)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
rangeHolds,
|
|
41
|
+
stepReaches,
|
|
42
|
+
transactionReaches
|
|
43
|
+
};
|