@portone/docx-editor 0.2.0 → 0.3.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.
Files changed (256) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/CONTRIBUTING.md +9 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +12 -29
  5. package/dist/core.d.ts +23 -8
  6. package/dist/core.js +9 -4
  7. package/dist/docx/cloning.d.ts +38 -0
  8. package/dist/docx/cloning.js +74 -0
  9. package/dist/docx/commentOnlyChange.d.ts +14 -7
  10. package/dist/docx/commentOnlyChange.js +9 -139
  11. package/dist/docx/comments/constants.d.ts +3 -4
  12. package/dist/docx/comments/constants.js +4 -5
  13. package/dist/docx/comments/grammar.d.ts +86 -0
  14. package/dist/docx/comments/grammar.js +223 -0
  15. package/dist/docx/comments/parts.d.ts +52 -0
  16. package/dist/docx/comments/parts.js +279 -0
  17. package/dist/docx/comments/people.d.ts +11 -2
  18. package/dist/docx/comments/people.js +28 -106
  19. package/dist/docx/comments/policy.d.ts +7 -0
  20. package/dist/docx/comments/policy.js +43 -0
  21. package/dist/docx/comments/reading.d.ts +5 -3
  22. package/dist/docx/comments/reading.js +30 -29
  23. package/dist/docx/comments/writing.d.ts +19 -7
  24. package/dist/docx/comments/writing.js +105 -137
  25. package/dist/docx/documentSettings.d.ts +7 -0
  26. package/dist/docx/documentSettings.js +10 -2
  27. package/dist/docx/exportDocx.d.ts +40 -5
  28. package/dist/docx/exportDocx.js +71 -72
  29. package/dist/docx/exportRefs.d.ts +5 -2
  30. package/dist/docx/exportRefs.js +3 -1
  31. package/dist/docx/fidelity.d.ts +43 -0
  32. package/dist/docx/fidelity.js +64 -0
  33. package/dist/docx/formatting/attrs.d.ts +27 -0
  34. package/dist/docx/formatting/attrs.js +31 -0
  35. package/dist/docx/formatting/context.d.ts +31 -0
  36. package/dist/docx/formatting/context.js +42 -0
  37. package/dist/docx/formatting/direct.d.ts +6 -5
  38. package/dist/docx/formatting/direct.js +52 -81
  39. package/dist/docx/formatting/resolve.d.ts +45 -0
  40. package/dist/docx/formatting/resolve.js +140 -0
  41. package/dist/docx/formatting/runProperties.d.ts +93 -0
  42. package/dist/docx/formatting/runProperties.js +316 -0
  43. package/dist/docx/formatting/styles.js +3 -3
  44. package/dist/docx/formatting/tabStops.js +7 -22
  45. package/dist/docx/formatting.d.ts +4 -1
  46. package/dist/docx/formatting.js +4 -1
  47. package/dist/docx/headersFooters.js +13 -22
  48. package/dist/docx/identities.d.ts +67 -0
  49. package/dist/docx/identities.js +174 -0
  50. package/dist/docx/importDocx.d.ts +12 -1
  51. package/dist/docx/importDocx.js +72 -79
  52. package/dist/docx/importParagraph.d.ts +1 -1
  53. package/dist/docx/importParagraph.js +4 -3
  54. package/dist/docx/importTable.d.ts +1 -1
  55. package/dist/docx/importTable.js +17 -1
  56. package/dist/docx/invariants.d.ts +33 -0
  57. package/dist/docx/invariants.js +256 -0
  58. package/dist/docx/media.d.ts +6 -4
  59. package/dist/docx/media.js +6 -37
  60. package/dist/docx/newLists.d.ts +20 -0
  61. package/dist/docx/newLists.js +36 -0
  62. package/dist/docx/notes.js +6 -12
  63. package/dist/docx/numberingPlanner.d.ts +8 -0
  64. package/dist/docx/numberingPlanner.js +19 -0
  65. package/dist/docx/packageParts.d.ts +42 -0
  66. package/dist/docx/packageParts.js +135 -0
  67. package/dist/docx/pageGeometry.d.ts +2 -0
  68. package/dist/docx/pageGeometry.js +18 -9
  69. package/dist/docx/paraProps.d.ts +10 -22
  70. package/dist/docx/paraProps.js +75 -99
  71. package/dist/docx/partPlan.d.ts +36 -0
  72. package/dist/docx/partPlan.js +59 -0
  73. package/dist/docx/protectionPolicy.d.ts +127 -0
  74. package/dist/docx/protectionPolicy.js +169 -0
  75. package/dist/docx/relationships.d.ts +1 -1
  76. package/dist/docx/relationships.js +15 -15
  77. package/dist/docx/runProps.d.ts +9 -22
  78. package/dist/docx/runProps.js +15 -168
  79. package/dist/docx/scan.js +20 -52
  80. package/dist/docx/sdt.js +14 -37
  81. package/dist/docx/sdtProps.d.ts +8 -1
  82. package/dist/docx/sdtProps.js +10 -0
  83. package/dist/docx/serializeBlock.d.ts +2 -0
  84. package/dist/docx/serializeBlock.js +9 -7
  85. package/dist/docx/serializeParagraph.d.ts +2 -1
  86. package/dist/docx/serializeParagraph.js +25 -14
  87. package/dist/docx/serializeTable.js +63 -32
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +43 -17
  90. package/dist/docx/storyProjection.d.ts +36 -0
  91. package/dist/docx/storyProjection.js +23 -0
  92. package/dist/docx/tableFormatting/editing.js +130 -185
  93. package/dist/docx/tableFormatting/reading.d.ts +16 -6
  94. package/dist/docx/tableFormatting/reading.js +40 -20
  95. package/dist/docx/tableTemplate.js +37 -5
  96. package/dist/docx/theme.d.ts +0 -6
  97. package/dist/docx/theme.js +0 -8
  98. package/dist/download.d.ts +8 -5
  99. package/dist/download.js +2 -0
  100. package/dist/editor/clipboard/images.js +17 -3
  101. package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
  102. package/dist/editor/clipboard/inlineFormatting.js +20 -30
  103. package/dist/editor/commands/breakCommands.js +4 -3
  104. package/dist/editor/commands/canRunCommand.d.ts +2 -2
  105. package/dist/editor/commands/canRunCommand.js +1 -1
  106. package/dist/editor/commands/comments/editing.d.ts +6 -2
  107. package/dist/editor/commands/comments/editing.js +21 -36
  108. package/dist/editor/commands/exportQueries.d.ts +15 -0
  109. package/dist/editor/commands/exportQueries.js +14 -0
  110. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  111. package/dist/editor/commands/fidelityQueries.js +8 -0
  112. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  113. package/dist/editor/commands/formatting/editing.js +26 -108
  114. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  115. package/dist/editor/commands/formatting/propertyCommands.js +103 -0
  116. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  117. package/dist/editor/commands/formatting/shared.js +5 -2
  118. package/dist/editor/commands/historyCommands.js +2 -1
  119. package/dist/editor/commands/indentCommands.js +5 -4
  120. package/dist/editor/commands/index.d.ts +11 -0
  121. package/dist/editor/commands/index.js +5 -0
  122. package/dist/editor/commands/linkCommands.js +5 -6
  123. package/dist/editor/commands/listCommands.js +8 -10
  124. package/dist/editor/commands/lockCommands.d.ts +7 -1
  125. package/dist/editor/commands/lockCommands.js +35 -37
  126. package/dist/editor/commands/paragraphCommands.js +28 -31
  127. package/dist/editor/commands/spacingCommands.js +1 -1
  128. package/dist/editor/commands/tabCommands.js +4 -3
  129. package/dist/editor/createEditor.d.ts +17 -29
  130. package/dist/editor/createEditor.js +61 -67
  131. package/dist/editor/documentStyles.d.ts +11 -35
  132. package/dist/editor/documentStyles.js +9 -52
  133. package/dist/editor/editorDocument.d.ts +56 -0
  134. package/dist/editor/editorDocument.js +74 -0
  135. package/dist/editor/externalClipboard.js +166 -33
  136. package/dist/editor/insertImage.js +4 -3
  137. package/dist/editor/insertTable.js +4 -3
  138. package/dist/editor/paragraphEdits.d.ts +11 -18
  139. package/dist/editor/paragraphEdits.js +7 -16
  140. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  141. package/dist/editor/plugins/displayDerivation.js +78 -0
  142. package/dist/editor/plugins/documentProtection.d.ts +3 -3
  143. package/dist/editor/plugins/imagePaste.js +4 -3
  144. package/dist/editor/plugins/keymap.js +18 -3
  145. package/dist/editor/plugins/lockedContent.d.ts +4 -4
  146. package/dist/editor/plugins/lockedContent.js +1 -1
  147. package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
  148. package/dist/editor/plugins/numberingDecorations.js +7 -17
  149. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  150. package/dist/editor/plugins/paragraphDisplay.js +51 -0
  151. package/dist/editor/plugins/tabLayout.js +1 -1
  152. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  153. package/dist/editor/plugins/tableDisplay.js +16 -0
  154. package/dist/index.d.ts +2 -0
  155. package/dist/model/format.d.ts +30 -8
  156. package/dist/model/format.js +35 -22
  157. package/dist/model/tabStops.d.ts +9 -0
  158. package/dist/model/tabStops.js +18 -0
  159. package/dist/numbering/listTemplate.js +49 -10
  160. package/dist/numbering/parseNumbering.d.ts +14 -1
  161. package/dist/numbering/parseNumbering.js +35 -22
  162. package/dist/numbering/writeNumbering.d.ts +3 -4
  163. package/dist/numbering/writeNumbering.js +14 -25
  164. package/dist/ooxml/childOrder.d.ts +34 -0
  165. package/dist/ooxml/childOrder.js +496 -0
  166. package/dist/ooxml/element.d.ts +65 -0
  167. package/dist/ooxml/element.js +68 -0
  168. package/dist/ooxml/errors.d.ts +3 -2
  169. package/dist/ooxml/fragment.d.ts +53 -0
  170. package/dist/ooxml/fragment.js +76 -0
  171. package/dist/ooxml/image.d.ts +4 -0
  172. package/dist/ooxml/image.js +3 -5
  173. package/dist/ooxml/names.d.ts +27 -0
  174. package/dist/ooxml/names.js +29 -0
  175. package/dist/ooxml/partSplice.d.ts +67 -0
  176. package/dist/ooxml/partSplice.js +169 -0
  177. package/dist/ooxml/precedence.d.ts +31 -0
  178. package/dist/ooxml/precedence.js +51 -0
  179. package/dist/ooxml/props.d.ts +112 -0
  180. package/dist/ooxml/props.js +214 -0
  181. package/dist/ooxml/simpleTypes.d.ts +103 -0
  182. package/dist/ooxml/simpleTypes.js +182 -0
  183. package/dist/ooxml/tabStops.js +8 -27
  184. package/dist/ooxml/tagScan.d.ts +34 -0
  185. package/dist/ooxml/tagScan.js +108 -0
  186. package/dist/ooxml/units.d.ts +25 -13
  187. package/dist/ooxml/units.js +65 -31
  188. package/dist/ooxml/xml.d.ts +52 -0
  189. package/dist/ooxml/xml.js +64 -5
  190. package/dist/page/blockKinds.d.ts +103 -0
  191. package/dist/page/blockKinds.js +11 -0
  192. package/dist/page/kinds/index.d.ts +6 -0
  193. package/dist/page/kinds/index.js +10 -0
  194. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  195. package/dist/page/kinds/paragraphKind.js +71 -0
  196. package/dist/page/kinds/tableKind.d.ts +10 -0
  197. package/dist/page/kinds/tableKind.js +178 -0
  198. package/dist/page/measureBlocks.d.ts +3 -1
  199. package/dist/page/measureBlocks.js +24 -25
  200. package/dist/page/pageDecorations.d.ts +27 -28
  201. package/dist/page/pageDecorations.js +70 -151
  202. package/dist/page/pageLayout.d.ts +10 -62
  203. package/dist/page/pageLayout.js +60 -53
  204. package/dist/page/usePageLayout.d.ts +2 -2
  205. package/dist/page/usePageLayout.js +3 -11
  206. package/dist/schema/attrRoles.d.ts +42 -0
  207. package/dist/schema/attrRoles.js +141 -0
  208. package/dist/schema/displayDerivation.d.ts +82 -0
  209. package/dist/schema/displayDerivation.js +130 -0
  210. package/dist/schema/docxSchema.d.ts +9 -0
  211. package/dist/schema/docxSchema.js +237 -114
  212. package/dist/schema/editGuard.d.ts +99 -0
  213. package/dist/schema/editGuard.js +43 -0
  214. package/dist/schema/guards.d.ts +91 -0
  215. package/dist/schema/guards.js +108 -0
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +17 -77
  219. package/dist/schema/locks.js +26 -70
  220. package/dist/schema/preservedGuards.d.ts +50 -0
  221. package/dist/schema/preservedGuards.js +80 -0
  222. package/dist/schema/protection.d.ts +17 -4
  223. package/dist/schema/protection.js +4 -2
  224. package/dist/schema/sourceEquality.d.ts +19 -0
  225. package/dist/schema/sourceEquality.js +39 -0
  226. package/dist/styles/inlineStyle.js +15 -6
  227. package/dist/table/cellFormatting.d.ts +8 -1
  228. package/dist/table/cellFormatting.js +10 -17
  229. package/dist/table/commands.d.ts +2 -2
  230. package/dist/table/commands.js +20 -18
  231. package/dist/table/format.d.ts +3 -0
  232. package/dist/table/format.js +3 -9
  233. package/dist/table/gridBorders.d.ts +14 -16
  234. package/dist/table/gridBorders.js +3 -41
  235. package/dist/table/index.d.ts +2 -2
  236. package/dist/table/merge.d.ts +4 -8
  237. package/dist/table/merge.js +5 -5
  238. package/package.json +10 -3
  239. package/dist/docx/comments/contentTypes.d.ts +0 -7
  240. package/dist/docx/comments/contentTypes.js +0 -38
  241. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  242. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  243. package/dist/docx/propsXml.d.ts +0 -49
  244. package/dist/docx/propsXml.js +0 -302
  245. package/dist/docx/uniqueControls.d.ts +0 -14
  246. package/dist/docx/uniqueControls.js +0 -62
  247. package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
  248. package/dist/editor/plugins/bookmarkProtection.js +0 -28
  249. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  250. package/dist/editor/plugins/commentReservations.js +0 -26
  251. package/dist/editor/plugins/noteProtection.d.ts +0 -3
  252. package/dist/editor/plugins/noteProtection.js +0 -25
  253. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  254. package/dist/editor/plugins/styledParagraphs.js +0 -65
  255. package/dist/page/tableMeasurements.d.ts +0 -10
  256. package/dist/page/tableMeasurements.js +0 -115
@@ -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
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The one list an edit is judged against.
3
+ *
4
+ * A guard answers, for one rule, whether an edit would leave the document in a state the file
5
+ * cannot be written back from. `transactionAllowed` runs the list over a built transaction, both
6
+ * where an edit is dispatched (`editor/plugins/lockedContent`) and where a command is dry-run
7
+ * (`editor/commands/canRunCommand`), and `editShut` runs it over the intent a command has before
8
+ * it builds anything. Both ask the same list, which is what keeps a disabled control and a refused
9
+ * edit saying the same thing.
10
+ *
11
+ * What a guard is written against - `EditGuard`, `EditIntent` and the reach primitives - stands in
12
+ * `./editGuard` so that a module writing a guard need not read this one, and is handed on from
13
+ * here so that a caller has one door to the whole seam.
14
+ *
15
+ * A command decides nothing of its own about any of this. It takes one of the two shapes an edit
16
+ * comes in - `guardedCommand`, which builds the whole edit and is refused whole, and
17
+ * `openStretches`, which leaves the shut stretches out and applies to the rest - and both ask the
18
+ * list below. A guard registered here therefore reaches every command by being registered, rather
19
+ * than by each command being taught about it.
20
+ *
21
+ * `editsShut` (`./protectionState`) stays what it was, the view-level question of whether the body
22
+ * is open at all, which is a question about the editor rather than about an edit.
23
+ */
24
+ import type { Command, EditorState, Selection, Transaction } from "prosemirror-state";
25
+ import { type EditGuard, type EditIntent } from "./editGuard";
26
+ import { type ProtectionState } from "./protection";
27
+ export type { EditGuard, EditGuardName, EditIntent } from "./editGuard";
28
+ export { stepReaches } from "./editGuard";
29
+ /**
30
+ * Whether the transaction reaches a comment node, which is how a body, a reply and a resolution
31
+ * change. Every comment lives in its three nodes, so a change that reaches none of them cannot
32
+ * have changed a comment.
33
+ */
34
+ export declare function transactionTouchesComments(tr: Transaction): boolean;
35
+ /**
36
+ * Whether the protection lets this transaction through (`./protection`).
37
+ *
38
+ * The whole-document judgement is reached for only when a comment is touched at all
39
+ * (`transactionTouchesComments`). A change that touches none is a body edit: through under `none`,
40
+ * refused under `comments`, and nothing about ownership to ask.
41
+ */
42
+ export declare function protectionAllowsTransaction(tr: Transaction, rules: ProtectionState): boolean;
43
+ export declare const EDIT_GUARDS: readonly EditGuard[];
44
+ /**
45
+ * Whether every guard would let this transaction through, decided and nothing else.
46
+ *
47
+ * The refusal a guard itself answers with carries a side effect - the composition it ends
48
+ * (`editor/plugins/lockedContent`) - which a query about a button's state may not set off, so the
49
+ * decision stands apart from it and every caller building an edit asks this rather than handing
50
+ * the transaction to a state.
51
+ *
52
+ * A re-derivation of display values is no edit and is let through before any guard is asked. The
53
+ * whole-change judgements then come first and take no pass, since a pass lifts one guard's
54
+ * reading of a step rather than another guard's reading of the change. What is left is judged
55
+ * step by step, each step over the document it was built against.
56
+ */
57
+ export declare function transactionAllowed(tr: Transaction, state: EditorState): boolean;
58
+ /**
59
+ * Whether any guard shuts this intent where it stands.
60
+ *
61
+ * This is what a command asks before it reports that it applies: a command reporting true and then
62
+ * being refused by the guard draws a live control that swallows the click.
63
+ */
64
+ export declare function editShut(state: EditorState, intent: EditIntent): boolean;
65
+ /** What a command doing this to whatever is selected means to do, one intent per selected stretch */
66
+ export declare function selectionIntents(selection: Selection, kind: "mark" | "replace"): EditIntent[];
67
+ /**
68
+ * Build, guard, dispatch: the one shape of a command that is refused whole.
69
+ *
70
+ * A structural edit has no smaller piece to fall back on - half a row cannot be deleted, and half a
71
+ * block of cells cannot be merged into one - so the whole transaction is built, handed to the whole
72
+ * guard list, and dispatched only if it comes back allowed. A character or a paragraph edit does
73
+ * the opposite and leaves the shut stretches out (`openStretches`).
74
+ *
75
+ * The answer is the same whether or not `dispatch` was passed. The transaction is built before
76
+ * either way, so asking the guard costs nothing more, and a command reporting one thing to a button
77
+ * and doing another would be worse than the button being wrong.
78
+ */
79
+ export declare function guardedCommand(build: (state: EditorState) => Transaction | null): Command;
80
+ /**
81
+ * The stretches the guards leave open, which is the one shape of a command that is trimmed.
82
+ *
83
+ * A shut stretch is left out rather than the whole edit refused: a guard turns down the whole
84
+ * transaction, so asking for the shut stretch as well would leave the rest of the selection
85
+ * unedited too. A selection the guards leave nothing of edits nothing, and the command reports that
86
+ * of its own accord, which is the disabled state of the control that runs it.
87
+ */
88
+ export declare function openStretches<S extends {
89
+ from: number;
90
+ to: number;
91
+ }>(state: EditorState, stretches: readonly S[], kind: "mark" | "replace"): S[];
@@ -0,0 +1,108 @@
1
+ // src/schema/guards.ts
2
+ import { changesOnlyDisplayAttrs, displayOnly } from "./displayDerivation.js";
3
+ import {
4
+ transactionReaches
5
+ } from "./editGuard.js";
6
+ import { lockGuard } from "./locks.js";
7
+ import { bookmarkGuard, noteGuard, sectionGuard } from "./preservedGuards.js";
8
+ import {
9
+ isCommentNode,
10
+ protectionAllows
11
+ } from "./protection.js";
12
+ import { editsShut, protectionOf } from "./protectionState.js";
13
+ import { stepReaches } from "./editGuard.js";
14
+ function transactionTouchesComments(tr) {
15
+ return transactionReaches(tr, isCommentNode);
16
+ }
17
+ function protectionAllowsTransaction(tr, rules) {
18
+ switch (rules.protection) {
19
+ case "readOnly":
20
+ return false;
21
+ case "none":
22
+ return !transactionTouchesComments(tr) || protectionAllows(tr.before, tr.doc, rules);
23
+ case "comments":
24
+ return transactionTouchesComments(tr) && protectionAllows(tr.before, tr.doc, rules);
25
+ default: {
26
+ const unmodelled = rules.protection;
27
+ return unmodelled;
28
+ }
29
+ }
30
+ }
31
+ var protectionGuard = {
32
+ name: "protection",
33
+ change: (tr, state) => protectionAllowsTransaction(tr, protectionOf(state)),
34
+ shuts: (_intent, state) => editsShut(state)
35
+ };
36
+ var EDIT_GUARDS = [
37
+ protectionGuard,
38
+ lockGuard,
39
+ bookmarkGuard,
40
+ noteGuard,
41
+ sectionGuard
42
+ ];
43
+ function judgesSteps(guard) {
44
+ return guard.step !== void 0;
45
+ }
46
+ function lifted(guard, tr) {
47
+ return guard.liftedBy?.some((pass) => tr.getMeta(pass) === true) === true;
48
+ }
49
+ function displayOnlyTransaction(tr, state) {
50
+ return tr.getMeta(displayOnly) === true && tr.steps.every(
51
+ (step, index) => changesOnlyDisplayAttrs(step, tr.docs[index] ?? state.doc)
52
+ );
53
+ }
54
+ function transactionAllowed(tr, state) {
55
+ if (!tr.docChanged) return true;
56
+ if (displayOnlyTransaction(tr, state)) return true;
57
+ if (EDIT_GUARDS.some((guard) => guard.change?.(tr, state) === false)) {
58
+ return false;
59
+ }
60
+ return EDIT_GUARDS.filter(judgesSteps).filter((guard) => !lifted(guard, tr)).every(
61
+ (guard) => (
62
+ // Each step counts positions in the document it was built against, which `docs` holds
63
+ tr.steps.every(
64
+ (step, index) => guard.step(
65
+ step,
66
+ tr.docs[index] ?? state.doc,
67
+ tr.docs[index + 1] ?? tr.doc,
68
+ state
69
+ )
70
+ )
71
+ )
72
+ );
73
+ }
74
+ function editShut(state, intent) {
75
+ return EDIT_GUARDS.some((guard) => guard.shuts(intent, state));
76
+ }
77
+ function intentOver(from, to, kind) {
78
+ return from === to ? { kind: "insert", at: from } : { kind, from, to };
79
+ }
80
+ function selectionIntents(selection, kind) {
81
+ return selection.ranges.map(
82
+ (range) => intentOver(range.$from.pos, range.$to.pos, kind)
83
+ );
84
+ }
85
+ function guardedCommand(build) {
86
+ return (state, dispatch) => {
87
+ const tr = build(state);
88
+ if (tr === null || !transactionAllowed(tr, state)) return false;
89
+ dispatch?.(tr);
90
+ return true;
91
+ };
92
+ }
93
+ function openStretches(state, stretches, kind) {
94
+ return stretches.filter(
95
+ (stretch) => !editShut(state, intentOver(stretch.from, stretch.to, kind))
96
+ );
97
+ }
98
+ export {
99
+ EDIT_GUARDS,
100
+ editShut,
101
+ guardedCommand,
102
+ openStretches,
103
+ protectionAllowsTransaction,
104
+ selectionIntents,
105
+ stepReaches,
106
+ transactionAllowed,
107
+ transactionTouchesComments
108
+ };
@@ -1,2 +1,4 @@
1
+ export type { AttrClass } from "./attrRoles";
2
+ export { attrsOfClass } from "./attrRoles";
1
3
  export { docxSchema, isPageBreak } from "./docxSchema";
2
4
  export { imageNodeSpec, runMarkSpec } from "./rendering";
@@ -1,7 +1,9 @@
1
1
  // src/schema/index.ts
2
+ import { attrsOfClass } from "./attrRoles.js";
2
3
  import { docxSchema, isPageBreak } from "./docxSchema.js";
3
4
  import { imageNodeSpec, runMarkSpec } from "./rendering.js";
4
5
  export {
6
+ attrsOfClass,
5
7
  docxSchema,
6
8
  imageNodeSpec,
7
9
  isPageBreak,
@@ -1,13 +1,14 @@
1
1
  /**
2
- * Evaluates OOXML content and deletion locks for inline controls and whole table cells. Commands
3
- * query these predicates before editing; `editor/plugins/lockedContent` enforces them at runtime.
2
+ * Evaluates OOXML content and deletion locks for inline controls and whole table cells.
4
3
  *
5
- * `transactionAllowed` is the one guard, and it answers for the protection the editor runs under
6
- * (`./protection`) as well as for the locks, so that a command asking it asks both.
4
+ * `lockGuard` is what `./guards` registers all of this as, and it is the only way in: a caller
5
+ * asking about a lock asks the one guard list, which asks the locks along with every other rule an
6
+ * edit is judged by. What a lock says about a whole document or a whole selection - a question
7
+ * about the document rather than about an edit - is exported beside it.
7
8
  */
8
9
  import type { Mark, Node as PMNode } from "prosemirror-model";
9
- import { type EditorState, PluginKey, type Selection, type Transaction } from "prosemirror-state";
10
- import { type ProtectionState } from "./protection";
10
+ import { PluginKey, type Selection } from "prosemirror-state";
11
+ import type { StepGuard } from "./editGuard";
11
12
  /**
12
13
  * The pass that lets a transaction through the guard, which is how a lock can be lifted at all.
13
14
  * A plugin key is used as the name so that it cannot collide with a consumer's own metadata.
@@ -58,83 +59,22 @@ export interface ControlSpan extends StepRange {
58
59
  */
59
60
  export declare function controlSpans(block: Textblock): ControlSpan[];
60
61
  /**
61
- * Whether what stands at this position sits inside a cell a control shuts.
62
+ * Whether a lock shuts editing where this selection stands, which is what a menu that would lift a
63
+ * lock is built on (`editor/commands/lockCommands`).
62
64
  *
63
- * This is the question a paragraph edit leaves its locked paragraphs out by
64
- * (`editor/paragraphEdits`), and it is deliberately not `rangeTouchesLocked` over the paragraph:
65
- * a paragraph merely holding a locked control keeps its own alignment and indent.
66
- */
67
- export declare function insideLockedCell(doc: PMNode, pos: number): boolean;
68
- /**
69
- * Whether this stretch reaches contents a lock shuts: text inside a control that shuts its
70
- * contents, or what a locked cell holds, the cell itself included.
71
- *
72
- * This is the question about editing what stands there rather than about taking it away, so every
73
- * control the stretch meets answers with its contents clause. The lock commands and the formatting
74
- * commands ask it of the stretch they are about to mark, so it is exported.
75
- */
76
- export declare function rangeTouchesLocked(doc: PMNode, from: number, to: number): boolean;
77
- /**
78
- * Whether something inserted at this spot would land inside a locked control or a locked cell.
79
- *
80
- * Only a spot with the very same control on both sides is inside one. At either edge the other
81
- * side belongs to a different control or to none, and since the mark is not inclusive what goes
82
- * in there falls outside the control.
83
- */
84
- export declare function insertionInsideLocked(doc: PMNode, pos: number): boolean;
85
- /**
86
- * Whether the guard shuts editing where this selection stands.
87
- *
88
- * This is the question a command that rewrites whatever is selected has to ask before it reports
89
- * that it applies (`editor/insertImage`), and the one a menu offering to lift a lock is built on
90
- * (`editor/commands/lockCommands`). A caret is shut by standing inside a control rather than
91
- * against its edge, which is the same rule an insertion follows.
65
+ * This is a reading of the selection rather than of an edit - the lock guard answers an edit
66
+ * (`./guards`) - and it names the locks alone, which is what the menu says. A caret is shut by
67
+ * standing inside a control rather than against its edge, which is the same rule an insertion
68
+ * follows.
92
69
  * A control locked against deletion alone shuts nothing here: its contents stand open, so editing
93
70
  * where it stands goes through, and only taking the control away is refused.
94
71
  */
95
72
  export declare function selectionShut(selection: Selection, doc: PMNode): boolean;
96
73
  /**
97
- * Whether the guard would refuse putting something in place of what this selection covers, which
98
- * is what an image insertion does (`editor/insertImage`).
99
- *
100
- * A different question from `selectionShut`: what the selection covers whole goes away rather than
101
- * being edited, so the deletion clause answers for it. A control that may be taken away whole may
102
- * therefore be replaced, and one locked against deletion alone may not, even though editing inside
103
- * it would have gone through.
104
- */
105
- export declare function replacementShut(selection: Selection, doc: PMNode): boolean;
106
- /**
107
- * Whether any step of the transaction reaches a comment node: puts one in, takes one out, or
108
- * rewrites the one where it stands, which is how a body, a reply and a resolution change.
109
- *
110
- * Every comment lives in its three nodes, so a change that reaches none of them cannot have
111
- * changed a comment. That is what lets the guard settle the common transaction - typing, and
112
- * nothing more - over the stretch it rewrote rather than over the whole document.
113
- *
114
- * A step of a kind this does not know - one a consumer brought - is answered as reaching one,
115
- * since what it rewrote is not known either. The whole-document judgement then has the say, and
116
- * an unknown step costs a comparison rather than a hole in the guard.
117
- */
118
- export declare function transactionTouchesComments(tr: Transaction): boolean;
119
- /**
120
- * Whether the protection lets this transaction through (`./protection`).
121
- *
122
- * The whole-document judgement is reached for only when a comment is touched at all
123
- * (`transactionTouchesComments`). A change that touches none is a body edit: through under `none`,
124
- * refused under `comments`, and nothing about ownership to ask.
125
- */
126
- export declare function protectionAllowsTransaction(tr: Transaction, rules: ProtectionState): boolean;
127
- /**
128
- * Whether the guard would let this transaction through, decided and nothing else.
129
- *
130
- * The refusal the guard itself answers with carries a side effect - the composition it ends
131
- * (`editor/plugins/lockedContent`) - which a query about a button's state may not set off, so the
132
- * decision stands apart from it and every caller building an edit asks this rather than handing
133
- * the transaction to a state.
74
+ * The locks the document carries, as `./guards` registers them.
134
75
  *
135
- * The protection is judged before the passes and without them: a replayed edit is still an edit,
136
- * and a pass that lifts a lock lifts no protection. Its judgement is over the whole change rather
137
- * than step by step, which is what lets it tell a comment from everything else.
76
+ * Both passes lift it: unlocking is the one edit that may reach into a lock, and every step the
77
+ * history replays is the reverse of a step that passed the guard when it was made.
138
78
  */
139
- export declare function transactionAllowed(tr: Transaction, state: EditorState): boolean;
79
+ export declare const lockGuard: StepGuard;
140
80
  export {};
@@ -1,7 +1,5 @@
1
1
  // src/schema/locks.ts
2
- import {
3
- PluginKey
4
- } from "prosemirror-state";
2
+ import { PluginKey } from "prosemirror-state";
5
3
  import {
6
4
  AddMarkStep,
7
5
  AddNodeMarkStep,
@@ -12,11 +10,6 @@ import {
12
10
  ReplaceStep
13
11
  } from "prosemirror-transform";
14
12
  import { docxSchema } from "./index.js";
15
- import {
16
- isCommentNode,
17
- protectionAllows
18
- } from "./protection.js";
19
- import { protectionOf } from "./protectionState.js";
20
13
  var unlockAllowed = new PluginKey("docxEditorUnlockAllowed");
21
14
  var historyReplay = new PluginKey("docxEditorHistoryReplay");
22
15
  var OPEN = { contents: false, deletion: false };
@@ -123,18 +116,17 @@ function insertionInsideLocked(doc, pos) {
123
116
  const after = lockedMarkOf($pos.nodeAfter);
124
117
  return before !== null && after !== null && before.eq(after);
125
118
  }
119
+ function markShut(doc, from, to) {
120
+ return from === to ? insertionInsideLocked(doc, from) : rangeTouchesLocked(doc, from, to);
121
+ }
122
+ function replaceShut(doc, from, to) {
123
+ return from === to ? insertionInsideLocked(doc, from) : rangeShut(doc, { from, to, takesAway: true });
124
+ }
126
125
  function selectionShut(selection, doc) {
127
126
  return selection.ranges.some(
128
- (range) => range.$from.pos === range.$to.pos ? insertionInsideLocked(doc, range.$from.pos) : rangeTouchesLocked(doc, range.$from.pos, range.$to.pos)
127
+ (range) => markShut(doc, range.$from.pos, range.$to.pos)
129
128
  );
130
129
  }
131
- function replacementShut(selection, doc) {
132
- return selection.ranges.some((range) => {
133
- const from = range.$from.pos;
134
- const to = range.$to.pos;
135
- return from === to ? insertionInsideLocked(doc, from) : rangeShut(doc, { from, to, takesAway: true });
136
- });
137
- }
138
130
  function editedRanges(step) {
139
131
  if (step instanceof ReplaceAroundStep) {
140
132
  return [
@@ -192,71 +184,35 @@ function stepAllowed(step, doc) {
192
184
  }
193
185
  return true;
194
186
  }
195
- function carriesPass(tr) {
196
- return tr.getMeta(unlockAllowed) === true || tr.getMeta(historyReplay) === true;
197
- }
198
- function rangeHoldsComment(doc, from, to) {
199
- let found = false;
200
- doc.nodesBetween(from, to, (node) => {
201
- if (found) return false;
202
- if (isCommentNode(node)) found = true;
203
- return !found;
204
- });
205
- return found;
206
- }
207
- function transactionTouchesComments(tr) {
208
- return tr.steps.some((step, index) => {
209
- const before = tr.docs[index];
210
- const after = tr.docs[index + 1] ?? tr.doc;
211
- if (step instanceof AttrStep || step instanceof AddNodeMarkStep || step instanceof RemoveNodeMarkStep) {
212
- const node = before.nodeAt(step.pos);
213
- return node !== null && isCommentNode(node);
214
- }
215
- if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep || step instanceof AddMarkStep || step instanceof RemoveMarkStep)) {
216
- return true;
217
- }
218
- let touched = false;
219
- step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
220
- touched ||= rangeHoldsComment(before, oldStart, oldEnd) || rangeHoldsComment(after, newStart, newEnd);
221
- });
222
- return touched;
223
- });
224
- }
225
- function protectionAllowsTransaction(tr, rules) {
226
- switch (rules.protection) {
227
- case "readOnly":
228
- return false;
229
- case "none":
230
- return !transactionTouchesComments(tr) || protectionAllows(tr.before, tr.doc, rules);
231
- case "comments":
232
- return transactionTouchesComments(tr) && protectionAllows(tr.before, tr.doc, rules);
187
+ function intentShut(doc, intent) {
188
+ switch (intent.kind) {
189
+ case "insert":
190
+ return insertionInsideLocked(doc, intent.at);
191
+ case "block":
192
+ return insideLockedCell(doc, intent.at);
193
+ case "mark":
194
+ return markShut(doc, intent.from, intent.to);
195
+ case "replace":
196
+ return replaceShut(doc, intent.from, intent.to);
233
197
  default: {
234
- const unmodelled = rules.protection;
198
+ const unmodelled = intent;
235
199
  return unmodelled;
236
200
  }
237
201
  }
238
202
  }
239
- function transactionAllowed(tr, state) {
240
- if (!tr.docChanged) return true;
241
- if (!protectionAllowsTransaction(tr, protectionOf(state))) return false;
242
- if (carriesPass(tr)) return true;
243
- return tr.steps.every(
244
- (step, index) => stepAllowed(step, tr.docs[index] ?? state.doc)
245
- );
246
- }
203
+ var lockGuard = {
204
+ name: "lock",
205
+ liftedBy: [unlockAllowed, historyReplay],
206
+ step: (step, before) => stepAllowed(step, before),
207
+ shuts: (intent, state) => intentShut(state.doc, intent)
208
+ };
247
209
  export {
248
210
  carriesLock,
249
211
  controlSpans,
250
212
  historyReplay,
251
- insertionInsideLocked,
252
- insideLockedCell,
253
213
  isLockedCell,
214
+ lockGuard,
254
215
  lockedMarkOf,
255
- protectionAllowsTransaction,
256
- rangeTouchesLocked,
257
- replacementShut,
258
216
  selectionShut,
259
- transactionAllowed,
260
- transactionTouchesComments,
261
217
  unlockAllowed
262
218
  };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The guards over what a document is opened with and the editor only preserves: the two ends of a
3
+ * bookmark range, the reference standing where a footnote or an endnote is called, and the
4
+ * paragraph that ends a section.
5
+ *
6
+ * None of it is content the editor writes, so nothing in it can put such a thing back once it is
7
+ * gone, and a document that lost one of a bookmark's two ends cannot be written back as a file at
8
+ * all (`docx/exportDocx` refuses it). What the first two guards hold is therefore the whole list of
9
+ * the nodes they answer for, in the order the document carries them; a section break, which an
10
+ * edit may legitimately move from one paragraph to another, is held by its number instead.
11
+ */
12
+ import type { Node as PMNode } from "prosemirror-model";
13
+ import { type ChangeGuard, type EditGuardName } from "./editGuard";
14
+ /** Everything about one preserved node that has to read the same after a change as before it */
15
+ type Signature = (node: PMNode) => string;
16
+ /**
17
+ * A guard that lets a change through only while it leaves the list of these nodes reading exactly
18
+ * as it did, which holds their number, their contents and their order at once.
19
+ *
20
+ * The comparison walks the whole document, so it is reached for only once a step of the change has
21
+ * touched such a node: ordinary typing costs the stretches its own steps rewrote and nothing more.
22
+ * It is judged over the change rather than step by step because a marker moved whole is two steps,
23
+ * one taking it out and one putting it back, and neither of them alone leaves the list as it was.
24
+ *
25
+ * Nothing lifts it. A pass is how an edit reaches past a lock the document put on itself, and
26
+ * there is no such thing to reach past here: the file simply has no way to say what a lost marker
27
+ * was.
28
+ */
29
+ export declare function preservedNodeGuard(name: EditGuardName, holds: (node: PMNode) => boolean, signature: Signature): ChangeGuard;
30
+ export declare const bookmarkGuard: ChangeGuard;
31
+ export declare const noteGuard: ChangeGuard;
32
+ /**
33
+ * A guard that lets a change through only while it leaves at least as many section breaks standing
34
+ * as it found.
35
+ *
36
+ * A paragraph-level `w:sectPr` is not content: it is where one section of the document ends, and
37
+ * the section carries the page size, the margins and the headers everything up to it is laid out
38
+ * under. Joining that paragraph into the one above it - a Backspace at its start, a selection run
39
+ * across its boundary - would take the whole section away with it, and nothing the editor writes
40
+ * puts a section back.
41
+ *
42
+ * It counts rather than compares, so splitting such a paragraph is still allowed: Enter leaves the
43
+ * break on the half that ends up last (`docx/cloning`), and one break stands where one stood. An
44
+ * edit that adds a break is no business of this guard's either.
45
+ *
46
+ * The count walks the whole document, so it is reached for only once a step has touched a
47
+ * paragraph that lays a break down.
48
+ */
49
+ export declare const sectionGuard: ChangeGuard;
50
+ export {};
@@ -0,0 +1,80 @@
1
+ // src/schema/preservedGuards.ts
2
+ import { parseProps, propsChild } from "../ooxml/props.js";
3
+ import {
4
+ rangeHolds,
5
+ transactionReaches
6
+ } from "./editGuard.js";
7
+ function signatures(doc, holds, signature) {
8
+ const found = [];
9
+ doc.descendants((node) => {
10
+ if (holds(node)) found.push(signature(node));
11
+ return true;
12
+ });
13
+ return found;
14
+ }
15
+ function same(a, b) {
16
+ return a.length === b.length && a.every((value, index) => value === b[index]);
17
+ }
18
+ function preservedNodeGuard(name, holds, signature) {
19
+ const listOf = (doc) => signatures(doc, holds, signature);
20
+ return {
21
+ name,
22
+ change: (tr) => !transactionReaches(tr, holds) || same(listOf(tr.before), listOf(tr.doc)),
23
+ shuts: (intent, state) => intent.kind === "replace" && rangeHolds(state.doc, intent.from, intent.to, holds)
24
+ };
25
+ }
26
+ var BOOKMARK_XML = /<(?:[\w.-]+:)?bookmark(?:Start|End)\b/;
27
+ function isBookmarkMarker(node) {
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);
30
+ }
31
+ function bookmarkSignature(node) {
32
+ return node.type.name === "bookmarkBlock" ? `block:${node.attrs.srcId}:${node.attrs.name}` : `inline:${node.attrs.xml}`;
33
+ }
34
+ var bookmarkGuard = preservedNodeGuard(
35
+ "bookmark",
36
+ isBookmarkMarker,
37
+ bookmarkSignature
38
+ );
39
+ function isNoteReference(node) {
40
+ return node.type.name === "noteReference";
41
+ }
42
+ var noteGuard = preservedNodeGuard(
43
+ "note",
44
+ isNoteReference,
45
+ (node) => JSON.stringify(node.attrs)
46
+ );
47
+ function endsASection(node) {
48
+ if (node.type.name !== "paragraph") return false;
49
+ const pPr = node.attrs.pPr;
50
+ if (typeof pPr !== "string") return false;
51
+ const props = parseProps(pPr);
52
+ return props !== null && propsChild(props.children, "sectPr") !== void 0;
53
+ }
54
+ function sectionBreaks(doc) {
55
+ let seen = 0;
56
+ doc.descendants((node) => {
57
+ if (endsASection(node)) seen += 1;
58
+ return true;
59
+ });
60
+ return seen;
61
+ }
62
+ var sectionGuard = {
63
+ name: "section",
64
+ change: (tr) => !transactionReaches(tr, endsASection) || sectionBreaks(tr.doc) >= sectionBreaks(tr.before),
65
+ shuts: (intent, state) => {
66
+ if (intent.kind !== "replace") return false;
67
+ const $from = state.doc.resolve(intent.from);
68
+ const $to = state.doc.resolve(intent.to);
69
+ if ($from.parent.type.name === "paragraph" && $from.sameParent($to)) {
70
+ return false;
71
+ }
72
+ return rangeHolds(state.doc, intent.from, intent.to, endsASection);
73
+ }
74
+ };
75
+ export {
76
+ bookmarkGuard,
77
+ noteGuard,
78
+ preservedNodeGuard,
79
+ sectionGuard
80
+ };