@portone/docx-editor 0.3.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 +205 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +1 -1
- package/dist/DocxEditor.js +45 -25
- package/dist/core.d.ts +1 -1
- 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 +78 -35
- package/dist/docx/exportDocx.js +30 -18
- package/dist/docx/exportRefs.d.ts +8 -3
- package/dist/docx/exportRefs.js +2 -1
- package/dist/docx/fidelity.js +24 -10
- package/dist/docx/fields.d.ts +42 -0
- package/dist/docx/fields.js +76 -0
- package/dist/docx/formatting/attrs.d.ts +20 -2
- package/dist/docx/formatting/attrs.js +57 -2
- package/dist/docx/formatting/context.d.ts +10 -2
- package/dist/docx/formatting/context.js +23 -5
- package/dist/docx/formatting/resolve.d.ts +10 -4
- package/dist/docx/formatting/resolve.js +31 -11
- package/dist/docx/formatting/styles.d.ts +25 -2
- package/dist/docx/formatting/styles.js +84 -3
- package/dist/docx/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/hyperlink.d.ts +14 -4
- package/dist/docx/hyperlink.js +4 -1
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +135 -74
- 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.d.ts +2 -2
- package/dist/docx/importTable.js +107 -66
- package/dist/docx/invariants.js +42 -31
- package/dist/docx/newLists.d.ts +30 -0
- package/dist/docx/newLists.js +24 -1
- package/dist/docx/notes.d.ts +6 -1
- package/dist/docx/notes.js +6 -16
- package/dist/docx/numberingPlanner.d.ts +6 -3
- package/dist/docx/numberingPlanner.js +38 -7
- 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 +24 -7
- package/dist/docx/session.js +37 -6
- package/dist/docx/story.d.ts +140 -0
- package/dist/docx/story.js +237 -0
- package/dist/docx/storyProjection.js +6 -1
- package/dist/docx/tableFormatting/conditions.d.ts +76 -0
- package/dist/docx/tableFormatting/conditions.js +196 -0
- package/dist/docx/tableFormatting/editing.d.ts +7 -3
- package/dist/docx/tableFormatting/editing.js +4 -5
- package/dist/docx/tableFormatting/reading.d.ts +49 -19
- package/dist/docx/tableFormatting/reading.js +109 -35
- package/dist/docx/tableFormatting.d.ts +1 -0
- package/dist/docx/tableFormatting.js +1 -0
- package/dist/docx/tableTemplate.js +12 -12
- package/dist/editor/clipboard/htmlReader.d.ts +30 -0
- package/dist/editor/clipboard/htmlReader.js +302 -0
- 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/formatting/propertyCommands.js +6 -1
- package/dist/editor/commands/index.d.ts +2 -2
- package/dist/editor/commands/index.js +2 -0
- package/dist/editor/commands/listCommands.d.ts +12 -3
- package/dist/editor/commands/listCommands.js +76 -30
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- package/dist/editor/commands/paragraphCommands.js +6 -1
- package/dist/editor/createEditor.js +14 -4
- package/dist/editor/documentStyles.d.ts +26 -4
- package/dist/editor/documentStyles.js +9 -4
- package/dist/editor/editorDocument.d.ts +7 -6
- package/dist/editor/editorDocument.js +18 -6
- 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/paragraphEdits.d.ts +16 -1
- package/dist/editor/paragraphEdits.js +13 -5
- package/dist/editor/paragraphPlacement.d.ts +11 -0
- package/dist/editor/paragraphPlacement.js +18 -0
- 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 +20 -10
- package/dist/editor/plugins/linkPanel.d.ts +7 -3
- package/dist/editor/plugins/linkPanel.js +13 -27
- package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
- package/dist/editor/plugins/numberingDecorations.js +43 -2
- package/dist/editor/plugins/panelState.d.ts +67 -0
- package/dist/editor/plugins/panelState.js +54 -0
- package/dist/editor/plugins/paragraphDisplay.js +9 -3
- package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
- package/dist/editor/plugins/tableContextMenu.js +30 -47
- package/dist/editor/plugins/tableDisplay.js +19 -5
- package/dist/editor/plugins/textContextMenu.d.ts +1 -1
- package/dist/editor/plugins/textContextMenu.js +30 -47
- package/dist/model/format.d.ts +50 -0
- package/dist/model/format.js +65 -0
- package/dist/numbering/listRegistry.d.ts +37 -0
- package/dist/numbering/listRegistry.js +112 -0
- package/dist/numbering/listTemplate.d.ts +26 -17
- package/dist/numbering/listTemplate.js +66 -20
- package/dist/numbering/markers.d.ts +15 -6
- package/dist/numbering/markers.js +26 -59
- package/dist/numbering/parseNumbering.d.ts +70 -5
- package/dist/numbering/parseNumbering.js +100 -26
- package/dist/numbering/spellers.d.ts +15 -0
- package/dist/numbering/spellers.js +96 -0
- package/dist/numbering/writeNumbering.d.ts +5 -3
- package/dist/numbering/writeNumbering.js +15 -15
- package/dist/ooxml/conformance.d.ts +28 -0
- package/dist/ooxml/conformance.js +20 -0
- package/dist/ooxml/errors.d.ts +5 -5
- 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 +15 -0
- package/dist/ooxml/names.js +10 -1
- package/dist/ooxml/partSplice.d.ts +1 -1
- package/dist/ooxml/partSplice.js +10 -3
- 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/ooxml/xml.d.ts +8 -9
- package/dist/ooxml/xml.js +6 -6
- package/dist/page/PageGuides.d.ts +8 -4
- package/dist/page/PageGuides.js +13 -37
- package/dist/page/pageLayout.d.ts +62 -13
- package/dist/page/pageLayout.js +91 -37
- package/dist/page/usePageLayout.d.ts +14 -11
- package/dist/page/usePageLayout.js +21 -29
- package/dist/schema/attrRoles.js +44 -19
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +220 -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 +16 -7
- package/dist/styles/classNames.js +16 -7
- package/dist/styles.css +90 -30
- package/dist/table/cellFormatting.js +1 -2
- package/dist/table/gridBorders.d.ts +11 -15
- package/dist/table/gridBorders.js +32 -18
- 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 -11
- package/dist/editor/externalClipboard.js +0 -456
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The guards over what a document is opened with and the editor only preserves: the
|
|
3
|
-
*
|
|
4
|
-
* paragraph that ends a section.
|
|
2
|
+
* The guards over what a document is opened with and the editor only preserves: the fragments a
|
|
3
|
+
* file falls apart without, the reference standing where a footnote or an endnote is called, and
|
|
4
|
+
* the paragraph that ends a section.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The first of those is the two ends of a bookmark range, the two ends of a permission or move
|
|
7
|
+
* range, and the pieces of a field, which say what they say only in the order they stand in. None
|
|
8
|
+
* of it is content the editor writes, so nothing in it can put such a thing back once it is gone,
|
|
9
|
+
* and a document that lost one of a bookmark's two ends cannot be written back as a file at all
|
|
10
|
+
* (`docx/exportDocx` refuses it). What the first two guards hold is therefore the whole list of
|
|
9
11
|
* the nodes they answer for, in the order the document carries them; a section break, which an
|
|
10
12
|
* edit may legitimately move from one paragraph to another, is held by its number instead.
|
|
11
13
|
*/
|
|
@@ -27,7 +29,7 @@ type Signature = (node: PMNode) => string;
|
|
|
27
29
|
* was.
|
|
28
30
|
*/
|
|
29
31
|
export declare function preservedNodeGuard(name: EditGuardName, holds: (node: PMNode) => boolean, signature: Signature): ChangeGuard;
|
|
30
|
-
export declare const
|
|
32
|
+
export declare const preservedGuard: ChangeGuard;
|
|
31
33
|
export declare const noteGuard: ChangeGuard;
|
|
32
34
|
/**
|
|
33
35
|
* A guard that lets a change through only while it leaves at least as many section breaks standing
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
// src/schema/preservedGuards.ts
|
|
2
|
+
import {
|
|
3
|
+
AddMarkStep,
|
|
4
|
+
AddNodeMarkStep,
|
|
5
|
+
AttrStep,
|
|
6
|
+
RemoveMarkStep,
|
|
7
|
+
RemoveNodeMarkStep,
|
|
8
|
+
ReplaceAroundStep,
|
|
9
|
+
ReplaceStep
|
|
10
|
+
} from "prosemirror-transform";
|
|
11
|
+
import { W_NS } from "../ooxml/names.js";
|
|
2
12
|
import { parseProps, propsChild } from "../ooxml/props.js";
|
|
13
|
+
import {
|
|
14
|
+
COMMENT_RANGE_MARKERS,
|
|
15
|
+
PERMISSION_MARKERS,
|
|
16
|
+
RANGE_MARKERS
|
|
17
|
+
} from "../ooxml/rangeMarkers.js";
|
|
18
|
+
import { parseAttrs } from "../ooxml/tagScan.js";
|
|
3
19
|
import {
|
|
4
20
|
rangeHolds,
|
|
5
21
|
transactionReaches
|
|
6
22
|
} from "./editGuard.js";
|
|
23
|
+
import { visitPreservedFragments } from "./preservedFragments.js";
|
|
7
24
|
function signatures(doc, holds, signature) {
|
|
8
25
|
const found = [];
|
|
9
26
|
doc.descendants((node) => {
|
|
@@ -23,19 +40,103 @@ function preservedNodeGuard(name, holds, signature) {
|
|
|
23
40
|
shuts: (intent, state) => intent.kind === "replace" && rangeHolds(state.doc, intent.from, intent.to, holds)
|
|
24
41
|
};
|
|
25
42
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (node.type.name === "bookmarkBlock") return true;
|
|
29
|
-
return node.type.name === "rawInline" && typeof node.attrs.xml === "string" && BOOKMARK_XML.test(node.attrs.xml);
|
|
43
|
+
function isGuardedFragment(node) {
|
|
44
|
+
return node.attrs.guarded === true;
|
|
30
45
|
}
|
|
31
|
-
function
|
|
32
|
-
return node.type.name
|
|
46
|
+
function preservedSignature(node) {
|
|
47
|
+
return `${node.type.name}:${node.attrs.name}:${node.attrs.srcId}:${node.attrs.xml}`;
|
|
33
48
|
}
|
|
34
|
-
var
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
var guardedNodes = preservedNodeGuard(
|
|
50
|
+
"preserved",
|
|
51
|
+
isGuardedFragment,
|
|
52
|
+
preservedSignature
|
|
38
53
|
);
|
|
54
|
+
var rangeMarkers = /* @__PURE__ */ new Set([
|
|
55
|
+
...RANGE_MARKERS,
|
|
56
|
+
...PERMISSION_MARKERS,
|
|
57
|
+
...COMMENT_RANGE_MARKERS
|
|
58
|
+
]);
|
|
59
|
+
function markerFragments(xml) {
|
|
60
|
+
const children = parseProps(`<markers>${xml}</markers>`)?.children ?? [];
|
|
61
|
+
return children.flatMap((child) => {
|
|
62
|
+
if (!rangeMarkers.has(child.name)) return [];
|
|
63
|
+
const props = parseProps(child.xml);
|
|
64
|
+
if (!props) return [];
|
|
65
|
+
const prefixEnd = props.tag.indexOf(":");
|
|
66
|
+
const binding = prefixEnd < 0 ? "xmlns" : `xmlns:${props.tag.slice(0, prefixEnd)}`;
|
|
67
|
+
const namespace = parseAttrs(props.attrs ?? "")?.find(
|
|
68
|
+
([name]) => name === binding
|
|
69
|
+
)?.[1];
|
|
70
|
+
if (namespace !== void 0 && namespace !== W_NS) return [];
|
|
71
|
+
return [child.xml];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function hasMarkerAttrs(node) {
|
|
75
|
+
return (node.type.name === "table" || node.type.name === "tableRow") && typeof node.attrs.leadingXml === "string" || (node.type.name === "tableCell" || node.type.name === "tableRow") && typeof node.attrs.trailingXml === "string";
|
|
76
|
+
}
|
|
77
|
+
function carrierBoundaryIn(doc, from, to) {
|
|
78
|
+
let found = false;
|
|
79
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
80
|
+
if (hasMarkerAttrs(node) && (from <= pos || to >= pos + node.nodeSize)) {
|
|
81
|
+
found = true;
|
|
82
|
+
}
|
|
83
|
+
return !found;
|
|
84
|
+
});
|
|
85
|
+
return found;
|
|
86
|
+
}
|
|
87
|
+
function markerAttrsReached(tr) {
|
|
88
|
+
return tr.steps.some((step, index) => {
|
|
89
|
+
const before = tr.docs[index];
|
|
90
|
+
const after = tr.docs[index + 1] ?? tr.doc;
|
|
91
|
+
if (step instanceof AttrStep || step instanceof AddNodeMarkStep || step instanceof RemoveNodeMarkStep) {
|
|
92
|
+
const old = before.nodeAt(step.pos);
|
|
93
|
+
const next = after.nodeAt(step.pos);
|
|
94
|
+
return old !== null && hasMarkerAttrs(old) || next !== null && hasMarkerAttrs(next);
|
|
95
|
+
}
|
|
96
|
+
if (step instanceof AddMarkStep || step instanceof RemoveMarkStep)
|
|
97
|
+
return false;
|
|
98
|
+
if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep))
|
|
99
|
+
return true;
|
|
100
|
+
let reached = false;
|
|
101
|
+
step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
|
|
102
|
+
reached ||= carrierBoundaryIn(before, oldStart, oldEnd) || carrierBoundaryIn(after, newStart, newEnd);
|
|
103
|
+
});
|
|
104
|
+
return reached;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function preservedFragments(doc) {
|
|
108
|
+
const found = [];
|
|
109
|
+
visitPreservedFragments(doc, (node, _pos, xml, attr) => {
|
|
110
|
+
if (attr !== void 0 && xml !== null) {
|
|
111
|
+
found.push(
|
|
112
|
+
...markerFragments(xml).map((fragment) => `marker:${fragment}`)
|
|
113
|
+
);
|
|
114
|
+
} else if (isGuardedFragment(node)) {
|
|
115
|
+
found.push(preservedSignature(node));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return found;
|
|
119
|
+
}
|
|
120
|
+
var preservedGuard = {
|
|
121
|
+
name: "preserved",
|
|
122
|
+
change: (tr) => !(transactionReaches(tr, isGuardedFragment) || markerAttrsReached(tr)) || same(preservedFragments(tr.before), preservedFragments(tr.doc)),
|
|
123
|
+
shuts: (intent, state) => {
|
|
124
|
+
if (guardedNodes.shuts(intent, state)) return true;
|
|
125
|
+
if (intent.kind !== "replace") return false;
|
|
126
|
+
let shut = false;
|
|
127
|
+
state.doc.nodesBetween(intent.from, intent.to, (node, pos) => {
|
|
128
|
+
if (intent.from > pos && intent.to < pos + node.nodeSize) return true;
|
|
129
|
+
if (!hasMarkerAttrs(node)) return true;
|
|
130
|
+
for (const attr of ["leadingXml", "trailingXml"]) {
|
|
131
|
+
const xml = node.attrs[attr];
|
|
132
|
+
if (typeof xml === "string" && markerFragments(xml).length > 0)
|
|
133
|
+
shut = true;
|
|
134
|
+
}
|
|
135
|
+
return !shut;
|
|
136
|
+
});
|
|
137
|
+
return shut;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
39
140
|
function isNoteReference(node) {
|
|
40
141
|
return node.type.name === "noteReference";
|
|
41
142
|
}
|
|
@@ -73,8 +174,8 @@ var sectionGuard = {
|
|
|
73
174
|
}
|
|
74
175
|
};
|
|
75
176
|
export {
|
|
76
|
-
bookmarkGuard,
|
|
77
177
|
noteGuard,
|
|
178
|
+
preservedGuard,
|
|
78
179
|
preservedNodeGuard,
|
|
79
180
|
sectionGuard
|
|
80
181
|
};
|
|
@@ -44,6 +44,11 @@ export declare function withoutComments(node: PMNode): PMNode;
|
|
|
44
44
|
* The comparison is by source (`./sourceEquality`): a display value worked out again beside a
|
|
45
45
|
* comment is no content change, so it neither turns a comment edit into a body edit nor takes the
|
|
46
46
|
* ownership question off it.
|
|
47
|
+
*
|
|
48
|
+
* What a comment says is a story on the document node (`./stories`), so the comment stories come
|
|
49
|
+
* off before the markers do; without that, writing one of them would move the document node and
|
|
50
|
+
* every comment edit would read as a body edit. The footnote and header stories stay: rewriting
|
|
51
|
+
* one of those is a change to what the document says, and a comment protection turns it down.
|
|
47
52
|
*/
|
|
48
53
|
export declare function changesOnlyComments(before: PMNode, after: PMNode): boolean;
|
|
49
54
|
/**
|
|
@@ -82,9 +87,33 @@ export declare function commentEditsOwned(before: PMNode, after: PMNode, rules:
|
|
|
82
87
|
*/
|
|
83
88
|
export declare function protectionAllows(before: PMNode, after: PMNode, rules: ProtectionState): boolean;
|
|
84
89
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
90
|
+
* The display names these comment bodies write under while recording nobody for them.
|
|
91
|
+
*
|
|
92
|
+
* A name here is one no comment in the file can be attributed through, so a comment that appears
|
|
93
|
+
* under it names no more of a person than the ones already there do.
|
|
94
|
+
*/
|
|
95
|
+
export declare function unattributedCommentAuthors(bodies: Iterable<{
|
|
96
|
+
author: string | null;
|
|
97
|
+
authorId: string | null;
|
|
98
|
+
}>): ReadonlySet<string>;
|
|
99
|
+
/**
|
|
100
|
+
* Whether a comment or reply that appeared is one this author was in a position to write.
|
|
101
|
+
*
|
|
102
|
+
* One carrying this identity is, since a file can claim any author and only the requester's own
|
|
103
|
+
* identity vouches for itself. So is one carrying no identity at all under a name the original
|
|
104
|
+
* already holds unattributed: the writer records no person for such a name, precisely so that the
|
|
105
|
+
* comments already written under it are not handed to whoever comments next (`docx/comments/people`),
|
|
106
|
+
* and a submission the writer produced has to be one the verifier takes. Such a comment claims
|
|
107
|
+
* nobody and stays everyone's to edit, and the name it shows was already in the file.
|
|
108
|
+
*/
|
|
109
|
+
export declare function commentAdditionAllowed(added: {
|
|
110
|
+
author: string | null;
|
|
111
|
+
authorId: string | null;
|
|
112
|
+
}, authorId: string, unattributed: ReadonlySet<string>): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Whether every comment and reply that appeared between the two documents was one this identity
|
|
115
|
+
* could have written, and every one that was already there kept the identity it was written under.
|
|
116
|
+
* The editor takes an addition from anyone, since it writes the author itself; a server taking a
|
|
117
|
+
* file back does not, since the file could claim any author.
|
|
89
118
|
*/
|
|
90
|
-
export declare function commentAdditionsBy(before: PMNode, after: PMNode, authorId: string): boolean;
|
|
119
|
+
export declare function commentAdditionsBy(before: PMNode, after: PMNode, authorId: string, unattributed?: ReadonlySet<string>): boolean;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// src/schema/protection.ts
|
|
2
2
|
import { Fragment } from "prosemirror-model";
|
|
3
3
|
import { sameSource } from "./sourceEquality.js";
|
|
4
|
+
import {
|
|
5
|
+
sameStory,
|
|
6
|
+
storyKey,
|
|
7
|
+
storyNodeOf,
|
|
8
|
+
withoutCommentStories
|
|
9
|
+
} from "./stories.js";
|
|
4
10
|
var COMMENT_NODES = /* @__PURE__ */ new Set([
|
|
5
11
|
"commentStart",
|
|
6
12
|
"commentEnd",
|
|
@@ -18,21 +24,25 @@ function withoutComments(node) {
|
|
|
18
24
|
return node.copy(Fragment.fromArray(kept));
|
|
19
25
|
}
|
|
20
26
|
function changesOnlyComments(before, after) {
|
|
21
|
-
return sameSource(
|
|
27
|
+
return sameSource(
|
|
28
|
+
withoutComments(withoutCommentStories(before)),
|
|
29
|
+
withoutComments(withoutCommentStories(after))
|
|
30
|
+
);
|
|
22
31
|
}
|
|
23
32
|
function stringOrNull(value) {
|
|
24
33
|
return typeof value === "string" ? value : null;
|
|
25
34
|
}
|
|
26
|
-
function bodyOf(attrs) {
|
|
35
|
+
function bodyOf(attrs, doc) {
|
|
36
|
+
const id = stringOrNull(attrs.id);
|
|
27
37
|
return {
|
|
28
38
|
authorId: stringOrNull(attrs.authorId),
|
|
29
39
|
author: stringOrNull(attrs.author),
|
|
30
40
|
initials: stringOrNull(attrs.initials),
|
|
31
41
|
date: stringOrNull(attrs.date),
|
|
32
|
-
|
|
42
|
+
body: id === null ? null : storyNodeOf(doc, storyKey("comment", id))
|
|
33
43
|
};
|
|
34
44
|
}
|
|
35
|
-
function replyBodies(value) {
|
|
45
|
+
function replyBodies(value, doc) {
|
|
36
46
|
const replies = /* @__PURE__ */ new Map();
|
|
37
47
|
if (!Array.isArray(value)) return replies;
|
|
38
48
|
for (const entry of value) {
|
|
@@ -40,7 +50,7 @@ function replyBodies(value) {
|
|
|
40
50
|
const reply = entry;
|
|
41
51
|
const id = stringOrNull(reply.id);
|
|
42
52
|
if (id === null) continue;
|
|
43
|
-
replies.set(id, bodyOf(reply));
|
|
53
|
+
replies.set(id, bodyOf(reply, doc));
|
|
44
54
|
}
|
|
45
55
|
return replies;
|
|
46
56
|
}
|
|
@@ -51,8 +61,8 @@ function threadsIn(doc) {
|
|
|
51
61
|
const id = stringOrNull(node.attrs.id);
|
|
52
62
|
if (id === null || threads.has(id)) return true;
|
|
53
63
|
threads.set(id, {
|
|
54
|
-
...bodyOf(node.attrs),
|
|
55
|
-
replies: replyBodies(node.attrs.replies)
|
|
64
|
+
...bodyOf(node.attrs, doc),
|
|
65
|
+
replies: replyBodies(node.attrs.replies, doc)
|
|
56
66
|
});
|
|
57
67
|
return true;
|
|
58
68
|
});
|
|
@@ -90,7 +100,7 @@ function sameIdentity(was, now) {
|
|
|
90
100
|
return was.authorId === now.authorId && was.author === now.author;
|
|
91
101
|
}
|
|
92
102
|
function sameWords(was, now) {
|
|
93
|
-
return was.
|
|
103
|
+
return sameStory(was.body, now.body) && was.initials === now.initials && was.date === now.date;
|
|
94
104
|
}
|
|
95
105
|
function commentIdentitiesKept(before, after) {
|
|
96
106
|
const now = threadsIn(after);
|
|
@@ -140,33 +150,51 @@ function protectionAllows(before, after, rules) {
|
|
|
140
150
|
}
|
|
141
151
|
}
|
|
142
152
|
}
|
|
143
|
-
function
|
|
153
|
+
function unattributedCommentAuthors(bodies) {
|
|
154
|
+
const names = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const body of bodies) {
|
|
156
|
+
if (body.authorId === null && body.author !== null) names.add(body.author);
|
|
157
|
+
}
|
|
158
|
+
return names;
|
|
159
|
+
}
|
|
160
|
+
function commentAdditionAllowed(added, authorId, unattributed) {
|
|
161
|
+
if (added.authorId !== null) return added.authorId === authorId;
|
|
162
|
+
return added.author !== null && unattributed.has(added.author);
|
|
163
|
+
}
|
|
164
|
+
function commentAdditionsBy(before, after, authorId, unattributed) {
|
|
144
165
|
if (!commentIdentitiesKept(before, after)) return false;
|
|
145
166
|
const was = threadsIn(before);
|
|
167
|
+
const names = unattributed ?? unattributedCommentAuthors(
|
|
168
|
+
Array.from(was.values()).flatMap((thread) => [
|
|
169
|
+
thread,
|
|
170
|
+
...thread.replies.values()
|
|
171
|
+
])
|
|
172
|
+
);
|
|
173
|
+
const allowed = (added) => commentAdditionAllowed(added, authorId, names);
|
|
146
174
|
for (const [id, thread] of threadsIn(after)) {
|
|
147
175
|
const earlier = was.get(id);
|
|
148
176
|
if (earlier === void 0) {
|
|
149
|
-
if (thread
|
|
177
|
+
if (!allowed(thread)) return false;
|
|
150
178
|
for (const reply of thread.replies.values()) {
|
|
151
|
-
if (reply
|
|
179
|
+
if (!allowed(reply)) return false;
|
|
152
180
|
}
|
|
153
181
|
continue;
|
|
154
182
|
}
|
|
155
183
|
for (const [replyId, reply] of thread.replies) {
|
|
156
|
-
if (!earlier.replies.has(replyId) && reply
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
184
|
+
if (!earlier.replies.has(replyId) && !allowed(reply)) return false;
|
|
159
185
|
}
|
|
160
186
|
}
|
|
161
187
|
return true;
|
|
162
188
|
}
|
|
163
189
|
export {
|
|
164
190
|
changesOnlyComments,
|
|
191
|
+
commentAdditionAllowed,
|
|
165
192
|
commentAdditionsBy,
|
|
166
193
|
commentEditsOwned,
|
|
167
194
|
commentIdentitiesKept,
|
|
168
195
|
commentOwned,
|
|
169
196
|
isCommentNode,
|
|
170
197
|
protectionAllows,
|
|
198
|
+
unattributedCommentAuthors,
|
|
171
199
|
withoutComments
|
|
172
200
|
};
|
package/dist/schema/rendering.js
CHANGED
|
@@ -20,7 +20,9 @@ function runMarkSpec(attrs, fontFallbacks) {
|
|
|
20
20
|
lang: format?.lang,
|
|
21
21
|
"data-rattrs": text(attrs.rAttrs),
|
|
22
22
|
"data-rpr": text(attrs.rPr),
|
|
23
|
-
"data-fmt": formatJson(format)
|
|
23
|
+
"data-fmt": formatJson(format),
|
|
24
|
+
// Read by `styles/editor.css` to stand a link's own line down
|
|
25
|
+
"data-underline": format?.underline && format.underline !== "none" ? "" : void 0
|
|
24
26
|
},
|
|
25
27
|
0
|
|
26
28
|
];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The side stories a document holds beside its body: a comment's text, a footnote's, a header's.
|
|
3
|
+
*
|
|
4
|
+
* Each is a document of this same schema, held on the document node under the key that names it,
|
|
5
|
+
* so an edit to one rides a transaction and lands in the history the way a body edit does. What
|
|
6
|
+
* turns one into XML and back is `docx/story`; here is only what the schema layer needs, which is
|
|
7
|
+
* the key, the value on the node, and the two comparisons the protection rules are written over.
|
|
8
|
+
*/
|
|
9
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
10
|
+
export type StoryKind = "comment" | "footnote" | "endnote" | "header" | "footer";
|
|
11
|
+
/** `comment:4`, `footnote:2`, `header:word/header1.xml` */
|
|
12
|
+
export type StoryKey = `${StoryKind}:${string}`;
|
|
13
|
+
/** One story as the document node holds it, which is `Node.toJSON` of a document of this schema */
|
|
14
|
+
export interface StoryJson {
|
|
15
|
+
readonly type: string;
|
|
16
|
+
readonly attrs?: Readonly<Record<string, unknown>>;
|
|
17
|
+
readonly content?: readonly unknown[];
|
|
18
|
+
}
|
|
19
|
+
/** The attr the stories stand on, named here so the schema and the readers cannot spell it apart */
|
|
20
|
+
export declare const STORIES_ATTR = "stories";
|
|
21
|
+
export declare function storyKey(kind: StoryKind, id: string): StoryKey;
|
|
22
|
+
/**
|
|
23
|
+
* The key this text spells, or null for one that names no kind of story.
|
|
24
|
+
*
|
|
25
|
+
* An id carries colons of its own - a header is named by its part path - so the kind is read off
|
|
26
|
+
* the front and everything after the first colon is the id.
|
|
27
|
+
*/
|
|
28
|
+
export declare function asStoryKey(text: string): StoryKey | null;
|
|
29
|
+
/** The stories this document holds, under the key naming each */
|
|
30
|
+
export declare function storiesOf(doc: PMNode): Readonly<Record<string, StoryJson>>;
|
|
31
|
+
/** The story this key names as a document node, and null where the document holds none */
|
|
32
|
+
export declare function storyNodeOf(doc: PMNode, key: StoryKey): PMNode | null;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the two stories say the same thing.
|
|
35
|
+
*
|
|
36
|
+
* The block keys are left out of it, because they name the document each story was read from and
|
|
37
|
+
* two files opened side by side never share one. Everything else a block would be written back as
|
|
38
|
+
* is compared (`./sourceEquality`), so a body that gained a bold run reads as changed and one
|
|
39
|
+
* whose display values were worked out again does not.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sameStory(a: PMNode | null, b: PMNode | null): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The document with its comment stories taken out of it, replies included.
|
|
44
|
+
*
|
|
45
|
+
* A comment edit now moves the document node, and a comparison that could not see past that would
|
|
46
|
+
* call every comment edit a body edit. The footnote and header stories stay: changing one of them
|
|
47
|
+
* is a change to what the document says, and a comment protection is meant to turn it down.
|
|
48
|
+
*/
|
|
49
|
+
export declare function withoutCommentStories(doc: PMNode): PMNode;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/schema/stories.ts
|
|
2
|
+
import { sameSource } from "./sourceEquality.js";
|
|
3
|
+
var STORIES_ATTR = "stories";
|
|
4
|
+
var STORY_KINDS = [
|
|
5
|
+
"comment",
|
|
6
|
+
"footnote",
|
|
7
|
+
"endnote",
|
|
8
|
+
"header",
|
|
9
|
+
"footer"
|
|
10
|
+
];
|
|
11
|
+
function storyKey(kind, id) {
|
|
12
|
+
return `${kind}:${id}`;
|
|
13
|
+
}
|
|
14
|
+
function asStoryKey(text) {
|
|
15
|
+
const at = text.indexOf(":");
|
|
16
|
+
if (at < 1) return null;
|
|
17
|
+
const named = text.slice(0, at);
|
|
18
|
+
const kind = STORY_KINDS.find((candidate) => candidate === named);
|
|
19
|
+
return kind === void 0 ? null : storyKey(kind, text.slice(at + 1));
|
|
20
|
+
}
|
|
21
|
+
function isStoryJson(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && typeof Reflect.get(value, "type") === "string";
|
|
23
|
+
}
|
|
24
|
+
function storiesOf(doc) {
|
|
25
|
+
const held = doc.attrs[STORIES_ATTR];
|
|
26
|
+
if (typeof held !== "object" || held === null) return {};
|
|
27
|
+
const stories = {};
|
|
28
|
+
for (const [key, value] of Object.entries(held)) {
|
|
29
|
+
if (asStoryKey(key) !== null && isStoryJson(value)) stories[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return stories;
|
|
32
|
+
}
|
|
33
|
+
var built = /* @__PURE__ */ new WeakMap();
|
|
34
|
+
function storyNodeOf(doc, key) {
|
|
35
|
+
const json = storiesOf(doc)[key];
|
|
36
|
+
if (json === void 0) return null;
|
|
37
|
+
const cached = built.get(json);
|
|
38
|
+
if (cached) return cached;
|
|
39
|
+
const node = doc.type.schema.nodeFromJSON(json);
|
|
40
|
+
built.set(json, node);
|
|
41
|
+
return node;
|
|
42
|
+
}
|
|
43
|
+
var unkeyed = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
function withoutBlockKeys(node) {
|
|
45
|
+
if (node.isText) return node;
|
|
46
|
+
const cached = unkeyed.get(node);
|
|
47
|
+
if (cached) return cached;
|
|
48
|
+
const children = node.children.map(withoutBlockKeys);
|
|
49
|
+
const attrs = node.attrs.srcId === null || node.attrs.srcId === void 0 ? node.attrs : { ...node.attrs, srcId: null };
|
|
50
|
+
const stripped = attrs === node.attrs && children.every((child, at) => child === node.child(at)) ? node : node.type.create(attrs, children, node.marks);
|
|
51
|
+
unkeyed.set(node, stripped);
|
|
52
|
+
return stripped;
|
|
53
|
+
}
|
|
54
|
+
function sameStory(a, b) {
|
|
55
|
+
if (a === null || b === null) return a === b;
|
|
56
|
+
return sameSource(withoutBlockKeys(a), withoutBlockKeys(b));
|
|
57
|
+
}
|
|
58
|
+
function withoutCommentStories(doc) {
|
|
59
|
+
const stories = storiesOf(doc);
|
|
60
|
+
const kept = Object.fromEntries(
|
|
61
|
+
Object.entries(stories).filter(([key]) => !key.startsWith("comment:"))
|
|
62
|
+
);
|
|
63
|
+
if (Object.keys(kept).length === Object.keys(stories).length) return doc;
|
|
64
|
+
return doc.type.create(
|
|
65
|
+
{ ...doc.attrs, [STORIES_ATTR]: kept },
|
|
66
|
+
doc.content,
|
|
67
|
+
doc.marks
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
STORIES_ATTR,
|
|
72
|
+
asStoryKey,
|
|
73
|
+
sameStory,
|
|
74
|
+
storiesOf,
|
|
75
|
+
storyKey,
|
|
76
|
+
storyNodeOf,
|
|
77
|
+
withoutCommentStories
|
|
78
|
+
};
|
|
@@ -46,13 +46,11 @@ export declare const editorClassNames: {
|
|
|
46
46
|
readonly commentMarker: "docx-editor-comment-marker";
|
|
47
47
|
/** A superscript footnote or endnote reference */
|
|
48
48
|
readonly noteReference: "docx-editor-note-reference";
|
|
49
|
-
/** An invisible bookmark marker that occurs between body blocks */
|
|
50
|
-
readonly bookmarkBlock: "docx-editor-bookmark-block";
|
|
51
49
|
readonly rawInline: "docx-editor-raw-inline";
|
|
50
|
+
/** A preserved child of a run, drawn by the `display` its policy gave it */
|
|
51
|
+
readonly rawRunContent: "docx-editor-raw-run";
|
|
52
|
+
/** A preserved block, drawn by the `display` its policy gave it */
|
|
52
53
|
readonly rawBlock: "docx-editor-raw-block";
|
|
53
|
-
/** A preserved block that carries the original XML directly (inside a table cell, and so on) */
|
|
54
|
-
readonly rawXmlBlock: "docx-editor-raw-xml";
|
|
55
|
-
readonly tablePlaceholder: "docx-editor-table";
|
|
56
54
|
/** The box holding both the paper and the marks overlaid on it */
|
|
57
55
|
readonly pageLayer: "docx-editor-page-layer";
|
|
58
56
|
/** All of the page marks drawn over the paper */
|
|
@@ -61,8 +59,6 @@ export declare const editorClassNames: {
|
|
|
61
59
|
readonly pageSplit: "docx-editor-page-split";
|
|
62
60
|
/** The line drawn where a block that cannot be pushed down crosses a page */
|
|
63
61
|
readonly pageCrossed: "docx-editor-page-crossed";
|
|
64
|
-
/** The page number laid on the top corner of a page */
|
|
65
|
-
readonly pageBadge: "docx-editor-page-badge";
|
|
66
62
|
/** A first-section header story projected into a visual page margin */
|
|
67
63
|
readonly pageHeader: "docx-editor-page-header";
|
|
68
64
|
/** A first-section footer story projected into a visual page margin */
|
|
@@ -165,6 +161,19 @@ export declare const editorCssVariables: {
|
|
|
165
161
|
readonly lineHeight: "--docx-editor-line-height";
|
|
166
162
|
/** The hanging indent width the list marker sits in */
|
|
167
163
|
readonly markerWidth: "--docx-editor-marker-width";
|
|
164
|
+
/** The room kept after the list marker, which a level asking for no tab closes up (`w:suff`) */
|
|
165
|
+
readonly markerGap: "--docx-editor-marker-gap";
|
|
166
|
+
/** Where the marker sits inside the width kept for it (`w:lvlJc`) */
|
|
167
|
+
readonly markerAlign: "--docx-editor-marker-align";
|
|
168
|
+
/**
|
|
169
|
+
* The formatting the numbering level puts on its marker (`lvl/rPr`). It dresses the marker
|
|
170
|
+
* alone, so it reaches the pseudo-element that draws it and nothing the paragraph holds
|
|
171
|
+
*/
|
|
172
|
+
readonly markerFontWeight: "--docx-editor-marker-font-weight";
|
|
173
|
+
readonly markerFontStyle: "--docx-editor-marker-font-style";
|
|
174
|
+
readonly markerColor: "--docx-editor-marker-color";
|
|
175
|
+
readonly markerFontSize: "--docx-editor-marker-font-size";
|
|
176
|
+
readonly markerFontFamily: "--docx-editor-marker-font-family";
|
|
168
177
|
/** The width calculated for a tab using custom paragraph stops. */
|
|
169
178
|
readonly tabWidth: "--docx-editor-tab-width";
|
|
170
179
|
/** The paper height, grown to match the page count */
|
|
@@ -41,13 +41,11 @@ var editorClassNames = {
|
|
|
41
41
|
commentMarker: `${PREFIX}-comment-marker`,
|
|
42
42
|
/** A superscript footnote or endnote reference */
|
|
43
43
|
noteReference: `${PREFIX}-note-reference`,
|
|
44
|
-
/** An invisible bookmark marker that occurs between body blocks */
|
|
45
|
-
bookmarkBlock: `${PREFIX}-bookmark-block`,
|
|
46
44
|
rawInline: `${PREFIX}-raw-inline`,
|
|
45
|
+
/** A preserved child of a run, drawn by the `display` its policy gave it */
|
|
46
|
+
rawRunContent: `${PREFIX}-raw-run`,
|
|
47
|
+
/** A preserved block, drawn by the `display` its policy gave it */
|
|
47
48
|
rawBlock: `${PREFIX}-raw-block`,
|
|
48
|
-
/** A preserved block that carries the original XML directly (inside a table cell, and so on) */
|
|
49
|
-
rawXmlBlock: `${PREFIX}-raw-xml`,
|
|
50
|
-
tablePlaceholder: `${PREFIX}-table`,
|
|
51
49
|
/** The box holding both the paper and the marks overlaid on it */
|
|
52
50
|
pageLayer: `${PREFIX}-page-layer`,
|
|
53
51
|
/** All of the page marks drawn over the paper */
|
|
@@ -56,8 +54,6 @@ var editorClassNames = {
|
|
|
56
54
|
pageSplit: `${PREFIX}-page-split`,
|
|
57
55
|
/** The line drawn where a block that cannot be pushed down crosses a page */
|
|
58
56
|
pageCrossed: `${PREFIX}-page-crossed`,
|
|
59
|
-
/** The page number laid on the top corner of a page */
|
|
60
|
-
pageBadge: `${PREFIX}-page-badge`,
|
|
61
57
|
/** A first-section header story projected into a visual page margin */
|
|
62
58
|
pageHeader: `${PREFIX}-page-header`,
|
|
63
59
|
/** A first-section footer story projected into a visual page margin */
|
|
@@ -161,6 +157,19 @@ var editorCssVariables = {
|
|
|
161
157
|
lineHeight: `--${PREFIX}-line-height`,
|
|
162
158
|
/** The hanging indent width the list marker sits in */
|
|
163
159
|
markerWidth: `--${PREFIX}-marker-width`,
|
|
160
|
+
/** The room kept after the list marker, which a level asking for no tab closes up (`w:suff`) */
|
|
161
|
+
markerGap: `--${PREFIX}-marker-gap`,
|
|
162
|
+
/** Where the marker sits inside the width kept for it (`w:lvlJc`) */
|
|
163
|
+
markerAlign: `--${PREFIX}-marker-align`,
|
|
164
|
+
/**
|
|
165
|
+
* The formatting the numbering level puts on its marker (`lvl/rPr`). It dresses the marker
|
|
166
|
+
* alone, so it reaches the pseudo-element that draws it and nothing the paragraph holds
|
|
167
|
+
*/
|
|
168
|
+
markerFontWeight: `--${PREFIX}-marker-font-weight`,
|
|
169
|
+
markerFontStyle: `--${PREFIX}-marker-font-style`,
|
|
170
|
+
markerColor: `--${PREFIX}-marker-color`,
|
|
171
|
+
markerFontSize: `--${PREFIX}-marker-font-size`,
|
|
172
|
+
markerFontFamily: `--${PREFIX}-marker-font-family`,
|
|
164
173
|
/** The width calculated for a tab using custom paragraph stops. */
|
|
165
174
|
tabWidth: `--${PREFIX}-tab-width`,
|
|
166
175
|
/** The paper height, grown to match the page count */
|