@portone/docx-editor 0.2.1 → 0.4.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 (253) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/CONTRIBUTING.md +5 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +13 -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 +12 -7
  10. package/dist/docx/commentOnlyChange.js +9 -156
  11. package/dist/docx/comments/constants.d.ts +0 -1
  12. package/dist/docx/comments/constants.js +0 -2
  13. package/dist/docx/comments/grammar.d.ts +10 -4
  14. package/dist/docx/comments/grammar.js +12 -2
  15. package/dist/docx/comments/{verifying.d.ts → parts.d.ts} +26 -18
  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 +16 -92
  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.js +8 -10
  22. package/dist/docx/comments/writing.d.ts +19 -7
  23. package/dist/docx/comments/writing.js +59 -112
  24. package/dist/docx/documentSettings.d.ts +7 -0
  25. package/dist/docx/documentSettings.js +10 -2
  26. package/dist/docx/exportDocx.d.ts +40 -5
  27. package/dist/docx/exportDocx.js +88 -74
  28. package/dist/docx/exportRefs.d.ts +5 -2
  29. package/dist/docx/exportRefs.js +3 -1
  30. package/dist/docx/fidelity.d.ts +43 -0
  31. package/dist/docx/fidelity.js +64 -0
  32. package/dist/docx/formatting/attrs.d.ts +45 -0
  33. package/dist/docx/formatting/attrs.js +86 -0
  34. package/dist/docx/formatting/context.d.ts +39 -0
  35. package/dist/docx/formatting/context.js +60 -0
  36. package/dist/docx/formatting/direct.d.ts +6 -5
  37. package/dist/docx/formatting/direct.js +52 -78
  38. package/dist/docx/formatting/resolve.d.ts +51 -0
  39. package/dist/docx/formatting/resolve.js +160 -0
  40. package/dist/docx/formatting/runProperties.d.ts +93 -0
  41. package/dist/docx/formatting/runProperties.js +316 -0
  42. package/dist/docx/formatting/styles.d.ts +25 -2
  43. package/dist/docx/formatting/styles.js +87 -6
  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 +6 -13
  48. package/dist/docx/hyperlink.d.ts +14 -4
  49. package/dist/docx/hyperlink.js +4 -1
  50. package/dist/docx/identities.d.ts +67 -0
  51. package/dist/docx/identities.js +174 -0
  52. package/dist/docx/importDocx.d.ts +12 -1
  53. package/dist/docx/importDocx.js +90 -88
  54. package/dist/docx/importParagraph.d.ts +1 -1
  55. package/dist/docx/importParagraph.js +4 -3
  56. package/dist/docx/importTable.d.ts +2 -2
  57. package/dist/docx/importTable.js +69 -56
  58. package/dist/docx/invariants.d.ts +33 -0
  59. package/dist/docx/invariants.js +269 -0
  60. package/dist/docx/media.d.ts +6 -4
  61. package/dist/docx/media.js +6 -37
  62. package/dist/docx/newLists.d.ts +50 -0
  63. package/dist/docx/newLists.js +59 -0
  64. package/dist/docx/notes.js +3 -7
  65. package/dist/docx/numberingPlanner.d.ts +11 -0
  66. package/dist/docx/numberingPlanner.js +50 -0
  67. package/dist/docx/packageParts.d.ts +42 -0
  68. package/dist/docx/packageParts.js +135 -0
  69. package/dist/docx/pageGeometry.d.ts +2 -0
  70. package/dist/docx/pageGeometry.js +18 -9
  71. package/dist/docx/paraProps.d.ts +10 -22
  72. package/dist/docx/paraProps.js +47 -76
  73. package/dist/docx/partPlan.d.ts +36 -0
  74. package/dist/docx/partPlan.js +59 -0
  75. package/dist/docx/protectionPolicy.d.ts +127 -0
  76. package/dist/docx/protectionPolicy.js +169 -0
  77. package/dist/docx/relationships.d.ts +1 -1
  78. package/dist/docx/relationships.js +8 -13
  79. package/dist/docx/runProps.d.ts +9 -22
  80. package/dist/docx/runProps.js +15 -168
  81. package/dist/docx/scan.js +20 -52
  82. package/dist/docx/sdt.js +9 -34
  83. package/dist/docx/sdtProps.d.ts +8 -1
  84. package/dist/docx/sdtProps.js +10 -0
  85. package/dist/docx/serializeBlock.d.ts +2 -0
  86. package/dist/docx/serializeBlock.js +8 -5
  87. package/dist/docx/serializeTable.js +32 -15
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +53 -17
  90. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  91. package/dist/docx/tableFormatting/conditions.js +196 -0
  92. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  93. package/dist/docx/tableFormatting/editing.js +111 -143
  94. package/dist/docx/tableFormatting/reading.d.ts +63 -23
  95. package/dist/docx/tableFormatting/reading.js +132 -38
  96. package/dist/docx/tableFormatting.d.ts +1 -0
  97. package/dist/docx/tableFormatting.js +1 -0
  98. package/dist/docx/tableTemplate.js +30 -19
  99. package/dist/download.d.ts +8 -5
  100. package/dist/download.js +2 -0
  101. package/dist/editor/clipboard/inlineFormatting.js +19 -30
  102. package/dist/editor/commands/comments/editing.d.ts +6 -2
  103. package/dist/editor/commands/comments/editing.js +19 -31
  104. package/dist/editor/commands/exportQueries.d.ts +15 -0
  105. package/dist/editor/commands/exportQueries.js +14 -0
  106. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  107. package/dist/editor/commands/fidelityQueries.js +8 -0
  108. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  109. package/dist/editor/commands/formatting/editing.js +26 -108
  110. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  111. package/dist/editor/commands/formatting/propertyCommands.js +108 -0
  112. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  113. package/dist/editor/commands/formatting/shared.js +5 -2
  114. package/dist/editor/commands/indentCommands.js +5 -4
  115. package/dist/editor/commands/index.d.ts +11 -0
  116. package/dist/editor/commands/index.js +5 -0
  117. package/dist/editor/commands/linkCommands.js +5 -6
  118. package/dist/editor/commands/listCommands.d.ts +6 -3
  119. package/dist/editor/commands/listCommands.js +73 -30
  120. package/dist/editor/commands/lockCommands.d.ts +7 -1
  121. package/dist/editor/commands/lockCommands.js +35 -46
  122. package/dist/editor/commands/paragraphCommands.js +33 -31
  123. package/dist/editor/commands/spacingCommands.js +1 -1
  124. package/dist/editor/createEditor.d.ts +17 -29
  125. package/dist/editor/createEditor.js +58 -58
  126. package/dist/editor/documentStyles.d.ts +11 -35
  127. package/dist/editor/documentStyles.js +9 -52
  128. package/dist/editor/editorDocument.d.ts +57 -0
  129. package/dist/editor/editorDocument.js +86 -0
  130. package/dist/editor/externalClipboard.d.ts +13 -2
  131. package/dist/editor/externalClipboard.js +76 -75
  132. package/dist/editor/insertTable.js +4 -3
  133. package/dist/editor/paragraphEdits.d.ts +27 -19
  134. package/dist/editor/paragraphEdits.js +18 -19
  135. package/dist/editor/paragraphPlacement.d.ts +11 -0
  136. package/dist/editor/paragraphPlacement.js +18 -0
  137. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  138. package/dist/editor/plugins/displayDerivation.js +78 -0
  139. package/dist/editor/plugins/imagePaste.js +11 -7
  140. package/dist/editor/plugins/keymap.js +18 -3
  141. package/dist/editor/plugins/numberingDecorations.d.ts +14 -14
  142. package/dist/editor/plugins/numberingDecorations.js +50 -19
  143. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  144. package/dist/editor/plugins/paragraphDisplay.js +57 -0
  145. package/dist/editor/plugins/tabLayout.js +1 -1
  146. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  147. package/dist/editor/plugins/tableDisplay.js +30 -0
  148. package/dist/index.d.ts +2 -0
  149. package/dist/model/format.d.ts +80 -8
  150. package/dist/model/format.js +100 -22
  151. package/dist/model/tabStops.d.ts +9 -0
  152. package/dist/model/tabStops.js +18 -0
  153. package/dist/numbering/listRegistry.d.ts +37 -0
  154. package/dist/numbering/listRegistry.js +112 -0
  155. package/dist/numbering/listTemplate.d.ts +26 -17
  156. package/dist/numbering/listTemplate.js +88 -25
  157. package/dist/numbering/markers.d.ts +15 -6
  158. package/dist/numbering/markers.js +26 -59
  159. package/dist/numbering/parseNumbering.d.ts +84 -6
  160. package/dist/numbering/parseNumbering.js +131 -44
  161. package/dist/numbering/spellers.d.ts +15 -0
  162. package/dist/numbering/spellers.js +96 -0
  163. package/dist/numbering/writeNumbering.d.ts +8 -7
  164. package/dist/numbering/writeNumbering.js +28 -39
  165. package/dist/ooxml/childOrder.d.ts +34 -0
  166. package/dist/ooxml/childOrder.js +496 -0
  167. package/dist/ooxml/conformance.d.ts +28 -0
  168. package/dist/ooxml/conformance.js +20 -0
  169. package/dist/ooxml/element.d.ts +24 -11
  170. package/dist/ooxml/element.js +31 -12
  171. package/dist/ooxml/errors.d.ts +6 -5
  172. package/dist/ooxml/image.d.ts +4 -0
  173. package/dist/ooxml/image.js +2 -5
  174. package/dist/ooxml/names.d.ts +9 -0
  175. package/dist/ooxml/names.js +8 -1
  176. package/dist/ooxml/partSplice.d.ts +67 -0
  177. package/dist/ooxml/partSplice.js +176 -0
  178. package/dist/ooxml/props.d.ts +112 -0
  179. package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
  180. package/dist/ooxml/simpleTypes.d.ts +103 -0
  181. package/dist/ooxml/simpleTypes.js +182 -0
  182. package/dist/ooxml/tabStops.js +8 -27
  183. package/dist/ooxml/tagScan.d.ts +34 -0
  184. package/dist/ooxml/tagScan.js +108 -0
  185. package/dist/ooxml/units.d.ts +25 -13
  186. package/dist/ooxml/units.js +65 -31
  187. package/dist/ooxml/xml.d.ts +35 -15
  188. package/dist/ooxml/xml.js +43 -11
  189. package/dist/page/PageGuides.d.ts +1 -1
  190. package/dist/page/PageGuides.js +0 -13
  191. package/dist/page/blockKinds.d.ts +61 -0
  192. package/dist/page/blockKinds.js +11 -0
  193. package/dist/page/kinds/index.d.ts +6 -0
  194. package/dist/page/kinds/index.js +10 -0
  195. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  196. package/dist/page/kinds/paragraphKind.js +71 -0
  197. package/dist/page/kinds/tableKind.d.ts +10 -0
  198. package/dist/page/kinds/tableKind.js +178 -0
  199. package/dist/page/measureBlocks.d.ts +3 -14
  200. package/dist/page/measureBlocks.js +24 -45
  201. package/dist/page/pageDecorations.d.ts +27 -44
  202. package/dist/page/pageDecorations.js +70 -152
  203. package/dist/page/pageLayout.d.ts +2 -29
  204. package/dist/page/pageLayout.js +39 -16
  205. package/dist/page/usePageLayout.d.ts +2 -24
  206. package/dist/page/usePageLayout.js +5 -48
  207. package/dist/schema/attrRoles.d.ts +25 -18
  208. package/dist/schema/attrRoles.js +110 -68
  209. package/dist/schema/displayDerivation.d.ts +82 -0
  210. package/dist/schema/displayDerivation.js +130 -0
  211. package/dist/schema/docxSchema.d.ts +3 -0
  212. package/dist/schema/docxSchema.js +58 -17
  213. package/dist/schema/editGuard.d.ts +1 -1
  214. package/dist/schema/guards.d.ts +37 -14
  215. package/dist/schema/guards.js +29 -3
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +11 -44
  219. package/dist/schema/locks.js +0 -9
  220. package/dist/schema/preservedGuards.d.ts +26 -6
  221. package/dist/schema/preservedGuards.js +31 -1
  222. package/dist/schema/protection.d.ts +7 -1
  223. package/dist/schema/protection.js +2 -1
  224. package/dist/schema/sourceEquality.d.ts +1 -9
  225. package/dist/schema/sourceEquality.js +1 -27
  226. package/dist/styles/classNames.d.ts +13 -2
  227. package/dist/styles/classNames.js +13 -2
  228. package/dist/styles/inlineStyle.js +15 -6
  229. package/dist/styles.css +16 -18
  230. package/dist/table/cellFormatting.d.ts +8 -1
  231. package/dist/table/cellFormatting.js +11 -20
  232. package/dist/table/commands.js +20 -18
  233. package/dist/table/format.d.ts +3 -0
  234. package/dist/table/format.js +3 -9
  235. package/dist/table/gridBorders.d.ts +24 -30
  236. package/dist/table/gridBorders.js +35 -59
  237. package/dist/table/merge.d.ts +2 -6
  238. package/dist/table/merge.js +5 -5
  239. package/package.json +7 -3
  240. package/dist/docx/comments/contentTypes.d.ts +0 -7
  241. package/dist/docx/comments/contentTypes.js +0 -38
  242. package/dist/docx/comments/verifying.js +0 -206
  243. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  244. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  245. package/dist/docx/propsXml.d.ts +0 -67
  246. package/dist/docx/uniqueControls.d.ts +0 -14
  247. package/dist/docx/uniqueControls.js +0 -62
  248. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  249. package/dist/editor/plugins/commentReservations.js +0 -26
  250. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  251. package/dist/editor/plugins/styledParagraphs.js +0 -65
  252. package/dist/page/tableMeasurements.d.ts +0 -18
  253. package/dist/page/tableMeasurements.js +0 -119
@@ -12,14 +12,16 @@
12
12
  * `./editGuard` so that a module writing a guard need not read this one, and is handed on from
13
13
  * here so that a caller has one door to the whole seam.
14
14
  *
15
- * `editShut` is not yet what every command asks. Only `editor/commands/breakCommands`,
16
- * `editor/commands/tabCommands` and `editor/insertImage` ask it today; the rest still compose
17
- * `editsShut` with a lock predicate of their own (`./locks`), and each decides for itself whether
18
- * a stretch a guard shuts is trimmed out of the edit or refuses the whole of it. Until they move
19
- * over, a guard added to the list below reaches those commands through `transactionAllowed` alone,
20
- * which refuses the transaction they built rather than telling them not to build it.
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.
21
23
  */
22
- import type { EditorState, Selection, Transaction } from "prosemirror-state";
24
+ import type { Command, EditorState, Selection, Transaction } from "prosemirror-state";
23
25
  import { type EditGuard, type EditIntent } from "./editGuard";
24
26
  import { type ProtectionState } from "./protection";
25
27
  export type { EditGuard, EditGuardName, EditIntent } from "./editGuard";
@@ -47,9 +49,10 @@ export declare const EDIT_GUARDS: readonly EditGuard[];
47
49
  * decision stands apart from it and every caller building an edit asks this rather than handing
48
50
  * the transaction to a state.
49
51
  *
50
- * The whole-change judgements come first and take no pass, since a pass lifts one guard's reading
51
- * of a step rather than another guard's reading of the change. What is left is judged step by
52
- * step, each step over the document it was built against.
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.
53
56
  */
54
57
  export declare function transactionAllowed(tr: Transaction, state: EditorState): boolean;
55
58
  /**
@@ -59,10 +62,30 @@ export declare function transactionAllowed(tr: Transaction, state: EditorState):
59
62
  * being refused by the guard draws a live control that swallows the click.
60
63
  */
61
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[];
62
67
  /**
63
- * What a command doing this to whatever is selected means to do, one intent per selected stretch.
68
+ * Build, guard, dispatch: the one shape of a command that is refused whole.
64
69
  *
65
- * A stretch of no length holds nothing to mark or to put away, so whatever the command would do to
66
- * a stretch it is an insertion there.
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.
67
78
  */
68
- export declare function selectionIntents(selection: Selection, kind: "mark" | "replace"): EditIntent[];
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[];
@@ -1,9 +1,10 @@
1
1
  // src/schema/guards.ts
2
+ import { changesOnlyDisplayAttrs, displayOnly } from "./displayDerivation.js";
2
3
  import {
3
4
  transactionReaches
4
5
  } from "./editGuard.js";
5
6
  import { lockGuard } from "./locks.js";
6
- import { bookmarkGuard, noteGuard } from "./preservedGuards.js";
7
+ import { bookmarkGuard, noteGuard, sectionGuard } from "./preservedGuards.js";
7
8
  import {
8
9
  isCommentNode,
9
10
  protectionAllows
@@ -36,7 +37,8 @@ var EDIT_GUARDS = [
36
37
  protectionGuard,
37
38
  lockGuard,
38
39
  bookmarkGuard,
39
- noteGuard
40
+ noteGuard,
41
+ sectionGuard
40
42
  ];
41
43
  function judgesSteps(guard) {
42
44
  return guard.step !== void 0;
@@ -44,8 +46,14 @@ function judgesSteps(guard) {
44
46
  function lifted(guard, tr) {
45
47
  return guard.liftedBy?.some((pass) => tr.getMeta(pass) === true) === true;
46
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
+ }
47
54
  function transactionAllowed(tr, state) {
48
55
  if (!tr.docChanged) return true;
56
+ if (displayOnlyTransaction(tr, state)) return true;
49
57
  if (EDIT_GUARDS.some((guard) => guard.change?.(tr, state) === false)) {
50
58
  return false;
51
59
  }
@@ -66,14 +74,32 @@ function transactionAllowed(tr, state) {
66
74
  function editShut(state, intent) {
67
75
  return EDIT_GUARDS.some((guard) => guard.shuts(intent, state));
68
76
  }
77
+ function intentOver(from, to, kind) {
78
+ return from === to ? { kind: "insert", at: from } : { kind, from, to };
79
+ }
69
80
  function selectionIntents(selection, kind) {
70
81
  return selection.ranges.map(
71
- (range) => range.$from.pos === range.$to.pos ? { kind: "insert", at: range.$from.pos } : { kind, from: range.$from.pos, to: range.$to.pos }
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))
72
96
  );
73
97
  }
74
98
  export {
75
99
  EDIT_GUARDS,
76
100
  editShut,
101
+ guardedCommand,
102
+ openStretches,
77
103
  protectionAllowsTransaction,
78
104
  selectionIntents,
79
105
  stepReaches,
@@ -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,9 +1,10 @@
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
- * `lockGuard` is what `./guards` registers all of this as, so that a caller asking the one guard
6
- * list asks the locks along with every other rule an edit is judged by.
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
10
  import { PluginKey, type Selection } from "prosemirror-state";
@@ -58,51 +59,17 @@ 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
- /**
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
73
  /**
107
74
  * The locks the document carries, as `./guards` registers them.
108
75
  *
@@ -127,11 +127,6 @@ function selectionShut(selection, doc) {
127
127
  (range) => markShut(doc, range.$from.pos, range.$to.pos)
128
128
  );
129
129
  }
130
- function replacementShut(selection, doc) {
131
- return selection.ranges.some(
132
- (range) => replaceShut(doc, range.$from.pos, range.$to.pos)
133
- );
134
- }
135
130
  function editedRanges(step) {
136
131
  if (step instanceof ReplaceAroundStep) {
137
132
  return [
@@ -215,13 +210,9 @@ export {
215
210
  carriesLock,
216
211
  controlSpans,
217
212
  historyReplay,
218
- insertionInsideLocked,
219
- insideLockedCell,
220
213
  isLockedCell,
221
214
  lockGuard,
222
215
  lockedMarkOf,
223
- rangeTouchesLocked,
224
- replacementShut,
225
216
  selectionShut,
226
217
  unlockAllowed
227
218
  };
@@ -1,11 +1,13 @@
1
1
  /**
2
- * The guards over the nodes a document is opened with and the editor only preserves: the two ends
3
- * of a bookmark range, and the reference standing where a footnote or an endnote is called.
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.
4
5
  *
5
- * Neither is content the editor writes, so nothing in it can put such a node back once it is gone,
6
- * and a document that lost one of a bookmark's two ends cannot be written back as a file at all
7
- * (`docx/exportDocx` refuses it). What each guard holds is therefore the whole list of the nodes it
8
- * answers for, in the order the document carries them.
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.
9
11
  */
10
12
  import type { Node as PMNode } from "prosemirror-model";
11
13
  import { type ChangeGuard, type EditGuardName } from "./editGuard";
@@ -27,4 +29,22 @@ type Signature = (node: PMNode) => string;
27
29
  export declare function preservedNodeGuard(name: EditGuardName, holds: (node: PMNode) => boolean, signature: Signature): ChangeGuard;
28
30
  export declare const bookmarkGuard: ChangeGuard;
29
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;
30
50
  export {};
@@ -1,4 +1,5 @@
1
1
  // src/schema/preservedGuards.ts
2
+ import { parseProps, propsChild } from "../ooxml/props.js";
2
3
  import {
3
4
  rangeHolds,
4
5
  transactionReaches
@@ -43,8 +44,37 @@ var noteGuard = preservedNodeGuard(
43
44
  isNoteReference,
44
45
  (node) => JSON.stringify(node.attrs)
45
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
+ };
46
75
  export {
47
76
  bookmarkGuard,
48
77
  noteGuard,
49
- preservedNodeGuard
78
+ preservedNodeGuard,
79
+ sectionGuard
50
80
  };
@@ -38,7 +38,13 @@ export declare function isCommentNode(node: PMNode): boolean;
38
38
  * reads the same as it did before the comment, and the two compare equal.
39
39
  */
40
40
  export declare function withoutComments(node: PMNode): PMNode;
41
- /** Whether the two documents differ in nothing but their comments */
41
+ /**
42
+ * Whether the two documents differ in nothing but their comments.
43
+ *
44
+ * The comparison is by source (`./sourceEquality`): a display value worked out again beside a
45
+ * comment is no content change, so it neither turns a comment edit into a body edit nor takes the
46
+ * ownership question off it.
47
+ */
42
48
  export declare function changesOnlyComments(before: PMNode, after: PMNode): boolean;
43
49
  /**
44
50
  * Whether a comment written under this identity may be edited or deleted.
@@ -1,5 +1,6 @@
1
1
  // src/schema/protection.ts
2
2
  import { Fragment } from "prosemirror-model";
3
+ import { sameSource } from "./sourceEquality.js";
3
4
  var COMMENT_NODES = /* @__PURE__ */ new Set([
4
5
  "commentStart",
5
6
  "commentEnd",
@@ -17,7 +18,7 @@ function withoutComments(node) {
17
18
  return node.copy(Fragment.fromArray(kept));
18
19
  }
19
20
  function changesOnlyComments(before, after) {
20
- return withoutComments(before).eq(withoutComments(after));
21
+ return sameSource(withoutComments(before), withoutComments(after));
21
22
  }
22
23
  function stringOrNull(value) {
23
24
  return typeof value === "string" ? value : null;
@@ -11,17 +11,9 @@
11
11
  * type, the text, the marks and the source and session attrs all have to agree, and the recursion
12
12
  * runs the same way, so a node that says the same thing in a different shape is still a change.
13
13
  */
14
- import { type Node as PMNode } from "prosemirror-model";
14
+ import type { Node as PMNode } from "prosemirror-model";
15
15
  /**
16
16
  * Whether the two would write the same XML: the same type, text, marks and source attrs, all the
17
17
  * way down. What the editor works out for the screen is not compared.
18
18
  */
19
19
  export declare function sameSource(a: PMNode, b: PMNode): boolean;
20
- /**
21
- * The node with every worked-out attr back at the value the schema starts it on, on itself and on
22
- * everything inside it.
23
- *
24
- * `sameSource` needs no such copy, and takes none. This is for a caller that has to hand a whole
25
- * document to something comparing the ordinary way.
26
- */
27
- export declare function withoutDisplayAttrs(node: PMNode): PMNode;
@@ -1,7 +1,4 @@
1
1
  // src/schema/sourceEquality.ts
2
- import {
3
- Fragment
4
- } from "prosemirror-model";
5
2
  import { displayAttrsOf } from "./attrRoles.js";
6
3
  function sameValue(a, b) {
7
4
  if (a === b) return true;
@@ -37,29 +34,6 @@ function sameSource(a, b) {
37
34
  }
38
35
  return true;
39
36
  }
40
- function withoutDisplay(type, attrs) {
41
- return Object.fromEntries(
42
- Object.entries(attrs).map(
43
- ([name, value]) => displayAttrsOf(type).includes(name) ? [name, type.spec.attrs?.[name]?.default ?? null] : [name, value]
44
- )
45
- );
46
- }
47
- function withoutDisplayMarkAttrs(mark) {
48
- return displayAttrsOf(mark.type).length === 0 ? mark : mark.type.create(withoutDisplay(mark.type, mark.attrs));
49
- }
50
- var childrenOf = (node) => Array.from({ length: node.childCount }, (_, at) => node.child(at));
51
- function withoutDisplayAttrs(node) {
52
- const marks = node.marks.map(withoutDisplayMarkAttrs);
53
- if (node.isText) return node.mark(marks);
54
- return node.type.create(
55
- withoutDisplay(node.type, node.attrs),
56
- Fragment.fromArray(
57
- childrenOf(node).map((child) => withoutDisplayAttrs(child))
58
- ),
59
- marks
60
- );
61
- }
62
37
  export {
63
- sameSource,
64
- withoutDisplayAttrs
38
+ sameSource
65
39
  };
@@ -61,8 +61,6 @@ export declare const editorClassNames: {
61
61
  readonly pageSplit: "docx-editor-page-split";
62
62
  /** The line drawn where a block that cannot be pushed down crosses a page */
63
63
  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
64
  /** A first-section header story projected into a visual page margin */
67
65
  readonly pageHeader: "docx-editor-page-header";
68
66
  /** A first-section footer story projected into a visual page margin */
@@ -165,6 +163,19 @@ export declare const editorCssVariables: {
165
163
  readonly lineHeight: "--docx-editor-line-height";
166
164
  /** The hanging indent width the list marker sits in */
167
165
  readonly markerWidth: "--docx-editor-marker-width";
166
+ /** The room kept after the list marker, which a level asking for no tab closes up (`w:suff`) */
167
+ readonly markerGap: "--docx-editor-marker-gap";
168
+ /** Where the marker sits inside the width kept for it (`w:lvlJc`) */
169
+ readonly markerAlign: "--docx-editor-marker-align";
170
+ /**
171
+ * The formatting the numbering level puts on its marker (`lvl/rPr`). It dresses the marker
172
+ * alone, so it reaches the pseudo-element that draws it and nothing the paragraph holds
173
+ */
174
+ readonly markerFontWeight: "--docx-editor-marker-font-weight";
175
+ readonly markerFontStyle: "--docx-editor-marker-font-style";
176
+ readonly markerColor: "--docx-editor-marker-color";
177
+ readonly markerFontSize: "--docx-editor-marker-font-size";
178
+ readonly markerFontFamily: "--docx-editor-marker-font-family";
168
179
  /** The width calculated for a tab using custom paragraph stops. */
169
180
  readonly tabWidth: "--docx-editor-tab-width";
170
181
  /** The paper height, grown to match the page count */
@@ -56,8 +56,6 @@ var editorClassNames = {
56
56
  pageSplit: `${PREFIX}-page-split`,
57
57
  /** The line drawn where a block that cannot be pushed down crosses a page */
58
58
  pageCrossed: `${PREFIX}-page-crossed`,
59
- /** The page number laid on the top corner of a page */
60
- pageBadge: `${PREFIX}-page-badge`,
61
59
  /** A first-section header story projected into a visual page margin */
62
60
  pageHeader: `${PREFIX}-page-header`,
63
61
  /** A first-section footer story projected into a visual page margin */
@@ -161,6 +159,19 @@ var editorCssVariables = {
161
159
  lineHeight: `--${PREFIX}-line-height`,
162
160
  /** The hanging indent width the list marker sits in */
163
161
  markerWidth: `--${PREFIX}-marker-width`,
162
+ /** The room kept after the list marker, which a level asking for no tab closes up (`w:suff`) */
163
+ markerGap: `--${PREFIX}-marker-gap`,
164
+ /** Where the marker sits inside the width kept for it (`w:lvlJc`) */
165
+ markerAlign: `--${PREFIX}-marker-align`,
166
+ /**
167
+ * The formatting the numbering level puts on its marker (`lvl/rPr`). It dresses the marker
168
+ * alone, so it reaches the pseudo-element that draws it and nothing the paragraph holds
169
+ */
170
+ markerFontWeight: `--${PREFIX}-marker-font-weight`,
171
+ markerFontStyle: `--${PREFIX}-marker-font-style`,
172
+ markerColor: `--${PREFIX}-marker-color`,
173
+ markerFontSize: `--${PREFIX}-marker-font-size`,
174
+ markerFontFamily: `--${PREFIX}-marker-font-family`,
164
175
  /** The width calculated for a tab using custom paragraph stops. */
165
176
  tabWidth: `--${PREFIX}-tab-width`,
166
177
  /** The paper height, grown to match the page count */
@@ -59,20 +59,29 @@ function lineHeightValue(spacing) {
59
59
  return `max(${pt(spacing.pt)},${SINGLE_LINE_RATIO}em)`;
60
60
  }
61
61
  function textDecoration(format) {
62
+ const underline = format.underline !== void 0 && format.underline !== "none" ? format.underline : void 0;
62
63
  const lines = [];
63
- if (format.underline) lines.push("underline");
64
+ if (underline) lines.push("underline");
64
65
  if (format.strike) lines.push("line-through");
65
- if (lines.length === 0) return [];
66
+ if (lines.length === 0) {
67
+ return format.underline === "none" || format.strike === false ? ["text-decoration-line:none"] : [];
68
+ }
66
69
  const css = [`text-decoration-line:${lines.join(" ")}`];
67
- const style = format.underline ? UNDERLINE_STYLES[format.underline] : void 0;
70
+ const style = underline ? UNDERLINE_STYLES[underline] : void 0;
68
71
  if (style) css.push(`text-decoration-style:${style}`);
69
72
  return css;
70
73
  }
71
74
  function inheritedRunCss(format, fontFallbacks) {
72
75
  const css = [];
73
- if (format.bold) css.push("font-weight:bold");
74
- if (format.italic) css.push("font-style:italic");
75
- if (format.smallCaps) css.push("font-variant:small-caps");
76
+ if (format.bold !== void 0) {
77
+ css.push(`font-weight:${format.bold ? "bold" : "normal"}`);
78
+ }
79
+ if (format.italic !== void 0) {
80
+ css.push(`font-style:${format.italic ? "italic" : "normal"}`);
81
+ }
82
+ if (format.smallCaps !== void 0) {
83
+ css.push(`font-variant:${format.smallCaps ? "small-caps" : "normal"}`);
84
+ }
76
85
  css.push(...textDecoration(format));
77
86
  if (format.fontSizePt !== void 0)
78
87
  css.push(`font-size:${pt(format.fontSizePt)}`);
package/dist/styles.css CHANGED
@@ -677,22 +677,6 @@ button.docx-editor-comment-body {
677
677
  border-top: 1px dotted rgb(26 79 208 / 40%);
678
678
  }
679
679
 
680
- /* The page number. Laid on the top right corner of every page */
681
- .docx-editor-page-badge {
682
- position: absolute;
683
- right: 10px;
684
- min-width: 18px;
685
- padding: 0 6px;
686
- border-radius: 9px;
687
- background: rgb(0 0 0 / 6%);
688
- color: #808080;
689
- font-family: system-ui, sans-serif;
690
- font-size: 10px;
691
- line-height: 18px;
692
- text-align: center;
693
- letter-spacing: 0.02em;
694
- }
695
-
696
680
  /* Display-only projections of the first section's header and footer stories. */
697
681
  .docx-editor-page-header,
698
682
  .docx-editor-page-footer {
@@ -720,14 +704,28 @@ button.docx-editor-comment-body {
720
704
  * the space.
721
705
  * Because box-sizing is border-box, as long as the number fits inside the space it
722
706
  * does not push where the body starts.
707
+ *
708
+ * A level that asks for a space or for nothing after its number (w:suff) keeps no
709
+ * space at all, so both variables are set to zero and the body starts right after
710
+ * the number. Where the number sits inside the space is the level's w:lvlJc.
711
+ *
712
+ * The level dresses its number and nothing else (lvl/rPr), so its character
713
+ * formatting arrives as variables read here rather than as anything the paragraph
714
+ * wears. Where the level writes none, the number is drawn like the text it stands
715
+ * in front of.
723
716
  */
724
717
  .docx-editor-p[data-marker]::before {
725
718
  content: attr(data-marker);
726
719
  display: inline-block;
727
720
  box-sizing: border-box;
728
721
  min-width: var(--docx-editor-marker-width, 18pt);
729
- padding-right: 4pt;
730
- text-align: left;
722
+ padding-right: var(--docx-editor-marker-gap, 4pt);
723
+ text-align: var(--docx-editor-marker-align, left);
724
+ font-weight: var(--docx-editor-marker-font-weight, inherit);
725
+ font-style: var(--docx-editor-marker-font-style, inherit);
726
+ font-size: var(--docx-editor-marker-font-size, inherit);
727
+ font-family: var(--docx-editor-marker-font-family, inherit);
728
+ color: var(--docx-editor-marker-color, inherit);
731
729
  /* Keeps the paragraph's hanging indent (text-indent) from being inherited and shifting the number again inside its own box */
732
730
  text-indent: 0;
733
731
  user-select: none;
@@ -41,7 +41,14 @@ export declare function setCellBorders(preset: CellBorderPreset): TableCommand;
41
41
  export declare function setCellBorderColor(hex: string | null): TableCommand;
42
42
  /** Fills the selected cells with `#RRGGBB`. Null takes the fill away */
43
43
  export declare function setCellBackground(hex: string | null): TableCommand;
44
- /** Whether direct cell formatting can act on the whole current cell selection. */
44
+ /**
45
+ * Whether direct cell formatting can act on the whole current cell selection.
46
+ *
47
+ * What a cell records about itself is rewritten around its content, which is the block intent the
48
+ * guards answer (`schema/guards`): a cell a control shuts is refused, and so is every cell while a
49
+ * protection shuts the body. The spot asked about stands inside the cell, since that is where the
50
+ * control wrapping it is an ancestor.
51
+ */
45
52
  export declare function canSetCellFormatting(state: EditorState): boolean;
46
53
  /** The fill shared by the selected cells. Null when they differ or none is filled */
47
54
  export declare function activeCellBackground(state: EditorState): string | null;