@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.
Files changed (217) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/assets/editor.png +0 -0
  3. package/dist/DocxEditor.d.ts +1 -1
  4. package/dist/DocxEditor.js +45 -25
  5. package/dist/core.d.ts +1 -1
  6. package/dist/docx/cloning.js +4 -9
  7. package/dist/docx/commentOnlyChange.d.ts +5 -3
  8. package/dist/docx/comments/grammar.d.ts +27 -10
  9. package/dist/docx/comments/grammar.js +18 -67
  10. package/dist/docx/comments/model.d.ts +5 -6
  11. package/dist/docx/comments/model.js +0 -9
  12. package/dist/docx/comments/parts.d.ts +16 -10
  13. package/dist/docx/comments/parts.js +65 -13
  14. package/dist/docx/comments/people.d.ts +11 -3
  15. package/dist/docx/comments/people.js +20 -6
  16. package/dist/docx/comments/policy.js +7 -1
  17. package/dist/docx/comments/reading.d.ts +4 -2
  18. package/dist/docx/comments/reading.js +1 -14
  19. package/dist/docx/comments/writing.js +78 -35
  20. package/dist/docx/exportDocx.js +30 -18
  21. package/dist/docx/exportRefs.d.ts +8 -3
  22. package/dist/docx/exportRefs.js +2 -1
  23. package/dist/docx/fidelity.js +24 -10
  24. package/dist/docx/fields.d.ts +42 -0
  25. package/dist/docx/fields.js +76 -0
  26. package/dist/docx/formatting/attrs.d.ts +20 -2
  27. package/dist/docx/formatting/attrs.js +57 -2
  28. package/dist/docx/formatting/context.d.ts +10 -2
  29. package/dist/docx/formatting/context.js +23 -5
  30. package/dist/docx/formatting/resolve.d.ts +10 -4
  31. package/dist/docx/formatting/resolve.js +31 -11
  32. package/dist/docx/formatting/styles.d.ts +25 -2
  33. package/dist/docx/formatting/styles.js +84 -3
  34. package/dist/docx/headersFooters.d.ts +89 -20
  35. package/dist/docx/headersFooters.js +163 -172
  36. package/dist/docx/hyperlink.d.ts +14 -4
  37. package/dist/docx/hyperlink.js +4 -1
  38. package/dist/docx/importDocx.d.ts +1 -1
  39. package/dist/docx/importDocx.js +135 -74
  40. package/dist/docx/importParagraph.d.ts +16 -4
  41. package/dist/docx/importParagraph.js +87 -93
  42. package/dist/docx/importPolicy.d.ts +70 -0
  43. package/dist/docx/importPolicy.js +190 -0
  44. package/dist/docx/importPreserved.d.ts +30 -0
  45. package/dist/docx/importPreserved.js +54 -0
  46. package/dist/docx/importTable.d.ts +2 -2
  47. package/dist/docx/importTable.js +107 -66
  48. package/dist/docx/invariants.js +42 -31
  49. package/dist/docx/newLists.d.ts +30 -0
  50. package/dist/docx/newLists.js +24 -1
  51. package/dist/docx/notes.d.ts +6 -1
  52. package/dist/docx/notes.js +6 -16
  53. package/dist/docx/numberingPlanner.d.ts +6 -3
  54. package/dist/docx/numberingPlanner.js +38 -7
  55. package/dist/docx/pageGeometry.d.ts +5 -14
  56. package/dist/docx/pageGeometry.js +0 -12
  57. package/dist/docx/protectionPolicy.d.ts +13 -5
  58. package/dist/docx/protectionPolicy.js +32 -22
  59. package/dist/docx/scan.d.ts +18 -8
  60. package/dist/docx/scan.js +17 -11
  61. package/dist/docx/sections.d.ts +126 -0
  62. package/dist/docx/sections.js +207 -0
  63. package/dist/docx/serializeBlock.d.ts +13 -4
  64. package/dist/docx/serializeBlock.js +16 -18
  65. package/dist/docx/serializeParagraph.d.ts +4 -0
  66. package/dist/docx/serializeParagraph.js +1 -0
  67. package/dist/docx/serializePreserved.d.ts +14 -0
  68. package/dist/docx/serializePreserved.js +24 -0
  69. package/dist/docx/serializeStory.d.ts +17 -0
  70. package/dist/docx/serializeStory.js +17 -0
  71. package/dist/docx/serializeTable.js +10 -13
  72. package/dist/docx/session.d.ts +24 -7
  73. package/dist/docx/session.js +37 -6
  74. package/dist/docx/story.d.ts +140 -0
  75. package/dist/docx/story.js +237 -0
  76. package/dist/docx/storyProjection.js +6 -1
  77. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  78. package/dist/docx/tableFormatting/conditions.js +196 -0
  79. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  80. package/dist/docx/tableFormatting/editing.js +4 -5
  81. package/dist/docx/tableFormatting/reading.d.ts +49 -19
  82. package/dist/docx/tableFormatting/reading.js +109 -35
  83. package/dist/docx/tableFormatting.d.ts +1 -0
  84. package/dist/docx/tableFormatting.js +1 -0
  85. package/dist/docx/tableTemplate.js +12 -12
  86. package/dist/editor/clipboard/htmlReader.d.ts +30 -0
  87. package/dist/editor/clipboard/htmlReader.js +302 -0
  88. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  89. package/dist/editor/clipboard/internalChannel.js +58 -0
  90. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  91. package/dist/editor/clipboard/normalizers.js +199 -0
  92. package/dist/editor/clipboard/parser.d.ts +34 -0
  93. package/dist/editor/clipboard/parser.js +58 -0
  94. package/dist/editor/clipboard/plugin.d.ts +21 -0
  95. package/dist/editor/clipboard/plugin.js +320 -0
  96. package/dist/editor/clipboard/readContext.d.ts +24 -0
  97. package/dist/editor/clipboard/readContext.js +19 -0
  98. package/dist/editor/clipboard/readers.d.ts +33 -0
  99. package/dist/editor/clipboard/readers.js +37 -0
  100. package/dist/editor/commands/comments/editing.d.ts +26 -6
  101. package/dist/editor/commands/comments/editing.js +175 -105
  102. package/dist/editor/commands/comments/model.d.ts +34 -17
  103. package/dist/editor/commands/comments/model.js +11 -0
  104. package/dist/editor/commands/comments/reading.d.ts +5 -3
  105. package/dist/editor/commands/comments/reading.js +8 -58
  106. package/dist/editor/commands/formatting/propertyCommands.js +6 -1
  107. package/dist/editor/commands/index.d.ts +2 -2
  108. package/dist/editor/commands/index.js +2 -0
  109. package/dist/editor/commands/listCommands.d.ts +12 -3
  110. package/dist/editor/commands/listCommands.js +76 -30
  111. package/dist/editor/commands/noteQueries.d.ts +22 -5
  112. package/dist/editor/commands/noteQueries.js +34 -6
  113. package/dist/editor/commands/paragraphCommands.js +6 -1
  114. package/dist/editor/createEditor.js +14 -4
  115. package/dist/editor/documentStyles.d.ts +26 -4
  116. package/dist/editor/documentStyles.js +9 -4
  117. package/dist/editor/editorDocument.d.ts +7 -6
  118. package/dist/editor/editorDocument.js +18 -6
  119. package/dist/editor/imageFiles.d.ts +2 -2
  120. package/dist/editor/imageFiles.js +2 -0
  121. package/dist/editor/insertTable.d.ts +1 -1
  122. package/dist/editor/insertTable.js +2 -2
  123. package/dist/editor/paragraphEdits.d.ts +16 -1
  124. package/dist/editor/paragraphEdits.js +13 -5
  125. package/dist/editor/paragraphPlacement.d.ts +11 -0
  126. package/dist/editor/paragraphPlacement.js +18 -0
  127. package/dist/editor/plainText.d.ts +12 -0
  128. package/dist/editor/plainText.js +2 -1
  129. package/dist/editor/plugins/columnResize.js +6 -3
  130. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  131. package/dist/editor/plugins/commentComposer.js +46 -0
  132. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  133. package/dist/editor/plugins/commentDecorations.js +89 -15
  134. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  135. package/dist/editor/plugins/documentProjection.js +22 -0
  136. package/dist/editor/plugins/imagePaste.js +20 -10
  137. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  138. package/dist/editor/plugins/linkPanel.js +13 -27
  139. package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
  140. package/dist/editor/plugins/numberingDecorations.js +43 -2
  141. package/dist/editor/plugins/panelState.d.ts +67 -0
  142. package/dist/editor/plugins/panelState.js +54 -0
  143. package/dist/editor/plugins/paragraphDisplay.js +9 -3
  144. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  145. package/dist/editor/plugins/tableContextMenu.js +30 -47
  146. package/dist/editor/plugins/tableDisplay.js +19 -5
  147. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  148. package/dist/editor/plugins/textContextMenu.js +30 -47
  149. package/dist/model/format.d.ts +50 -0
  150. package/dist/model/format.js +65 -0
  151. package/dist/numbering/listRegistry.d.ts +37 -0
  152. package/dist/numbering/listRegistry.js +112 -0
  153. package/dist/numbering/listTemplate.d.ts +26 -17
  154. package/dist/numbering/listTemplate.js +66 -20
  155. package/dist/numbering/markers.d.ts +15 -6
  156. package/dist/numbering/markers.js +26 -59
  157. package/dist/numbering/parseNumbering.d.ts +70 -5
  158. package/dist/numbering/parseNumbering.js +100 -26
  159. package/dist/numbering/spellers.d.ts +15 -0
  160. package/dist/numbering/spellers.js +96 -0
  161. package/dist/numbering/writeNumbering.d.ts +5 -3
  162. package/dist/numbering/writeNumbering.js +15 -15
  163. package/dist/ooxml/conformance.d.ts +28 -0
  164. package/dist/ooxml/conformance.js +20 -0
  165. package/dist/ooxml/errors.d.ts +5 -5
  166. package/dist/ooxml/fragment.d.ts +11 -0
  167. package/dist/ooxml/fragment.js +14 -4
  168. package/dist/ooxml/image.d.ts +3 -4
  169. package/dist/ooxml/image.js +50 -5
  170. package/dist/ooxml/names.d.ts +15 -0
  171. package/dist/ooxml/names.js +10 -1
  172. package/dist/ooxml/partSplice.d.ts +1 -1
  173. package/dist/ooxml/partSplice.js +10 -3
  174. package/dist/ooxml/props.d.ts +8 -0
  175. package/dist/ooxml/props.js +14 -4
  176. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  177. package/dist/ooxml/rangeMarkers.js +24 -0
  178. package/dist/ooxml/xml.d.ts +8 -9
  179. package/dist/ooxml/xml.js +6 -6
  180. package/dist/page/PageGuides.d.ts +8 -4
  181. package/dist/page/PageGuides.js +13 -37
  182. package/dist/page/pageLayout.d.ts +62 -13
  183. package/dist/page/pageLayout.js +91 -37
  184. package/dist/page/usePageLayout.d.ts +14 -11
  185. package/dist/page/usePageLayout.js +21 -29
  186. package/dist/schema/attrRoles.js +44 -19
  187. package/dist/schema/docxSchema.d.ts +9 -1
  188. package/dist/schema/docxSchema.js +220 -139
  189. package/dist/schema/editGuard.d.ts +1 -1
  190. package/dist/schema/guards.js +2 -2
  191. package/dist/schema/index.d.ts +1 -0
  192. package/dist/schema/preservedFragments.d.ts +12 -0
  193. package/dist/schema/preservedFragments.js +38 -0
  194. package/dist/schema/preservedGuards.d.ts +9 -7
  195. package/dist/schema/preservedGuards.js +112 -11
  196. package/dist/schema/protection.d.ts +34 -5
  197. package/dist/schema/protection.js +42 -14
  198. package/dist/schema/rendering.js +3 -1
  199. package/dist/schema/stories.d.ts +49 -0
  200. package/dist/schema/stories.js +78 -0
  201. package/dist/styles/classNames.d.ts +16 -7
  202. package/dist/styles/classNames.js +16 -7
  203. package/dist/styles.css +90 -30
  204. package/dist/table/cellFormatting.js +1 -2
  205. package/dist/table/gridBorders.d.ts +11 -15
  206. package/dist/table/gridBorders.js +32 -18
  207. package/dist/table/resize.d.ts +5 -3
  208. package/dist/ui/CommentsPanel.d.ts +1 -3
  209. package/dist/ui/CommentsPanel.js +24 -15
  210. package/dist/ui/NotesPanel.js +2 -2
  211. package/dist/ui/TextMenu.d.ts +1 -3
  212. package/dist/ui/TextMenu.js +10 -6
  213. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  214. package/dist/ui/comments/CommentComposer.js +26 -3
  215. package/package.json +1 -1
  216. package/dist/editor/externalClipboard.d.ts +0 -11
  217. package/dist/editor/externalClipboard.js +0 -456
package/dist/styles.css CHANGED
@@ -62,11 +62,6 @@
62
62
  overflow: hidden;
63
63
  }
64
64
 
65
- /* A body-level bookmark occupies a document-model position but draws no placeholder. */
66
- .docx-editor-bookmark-block {
67
- display: none;
68
- }
69
-
70
65
  .docx-editor-comment-range {
71
66
  background: #fff2a8;
72
67
  box-shadow: inset 0 -2px #e2b714;
@@ -588,10 +583,12 @@ button.docx-editor-comment-body {
588
583
  * is the same paper `A4_PORTRAIT` names in `docx/pageGeometry.ts`.
589
584
  *
590
585
  * The height is used only as a minimum, so that even short text feels like a full
591
- * sheet and longer text simply grows it.
592
- * The body left inside these margins is the width of a new table and the height
593
- * between page boundaries; `page/pageLayout.ts` works both out from the very same
594
- * geometry, so the divider on screen lands where the sheet actually ends.
586
+ * sheet and longer text simply grows it, and the editor supplies one grown to the
587
+ * last page's own bottom margin where a later section's is deeper than this one's.
588
+ * The body left inside these margins is what the sheet itself is drawn at. The width
589
+ * of a new table and the height between two page boundaries come from the paper of
590
+ * the section they stand in, which `page/pageLayout.ts` works out from the very same
591
+ * kind of geometry, so the divider on screen lands where that page actually ends.
595
592
  */
596
593
  --docx-editor-page-width: 21cm;
597
594
  --docx-editor-page-height: 29.7cm;
@@ -677,23 +674,10 @@ button.docx-editor-comment-body {
677
674
  border-top: 1px dotted rgb(26 79 208 / 40%);
678
675
  }
679
676
 
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
- /* Display-only projections of the first section's header and footer stories. */
677
+ /*
678
+ * Display-only projections of the header and footer stories of the section each page
679
+ * belongs to (`page/PageGuides.tsx`).
680
+ */
697
681
  .docx-editor-page-header,
698
682
  .docx-editor-page-footer {
699
683
  position: absolute;
@@ -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;
@@ -930,12 +928,21 @@ button.docx-editor-comment-body {
930
928
  * held as data rather than as an `href`, so a click here places the caret the way a click on any
931
929
  * other text does, and opening the address is the link panel's business.
932
930
  * A color the run itself carries paints over this one, the same as any other run formatting.
931
+ * The line is a border rather than an underline so that it runs on across a tab, which a text
932
+ * decoration is not painted over.
933
933
  */
934
934
  .docx-editor-link {
935
935
  color: #1a56db;
936
936
  border-bottom: 1px solid currentcolor;
937
937
  }
938
938
 
939
+ /* A word processor underlines a link through the `Hyperlink` style, so the line above would be a second one */
940
+ .docx-editor-link:has([data-underline]):not(
941
+ :has(.docx-editor-run:not([data-underline]))
942
+ ) {
943
+ border-bottom: none;
944
+ }
945
+
939
946
  /*
940
947
  * A content control that says its contents may not be edited (`editor/plugins/lockedContent` refuses
941
948
  * the edits). Yellow is what the contract templates already paint their settled parts with.
@@ -959,15 +966,63 @@ button.docx-editor-comment-body {
959
966
  cursor: not-allowed;
960
967
  }
961
968
 
969
+ /*
970
+ * A preserved fragment inside a paragraph or inside a run. What it draws is the `display` the
971
+ * preservation rule gave it (`docx/importPolicy`), so the rule is read here as an attribute
972
+ * rather than as a second list of element names.
973
+ */
974
+ .docx-editor-raw-inline,
975
+ .docx-editor-raw-run {
976
+ vertical-align: baseline;
977
+ }
978
+
962
979
  /* Elements that, like bookmarks, must not be visible */
963
- .docx-editor-raw-inline {
980
+ .docx-editor-raw-inline[data-display="hidden"],
981
+ .docx-editor-raw-run[data-display="hidden"] {
964
982
  display: inline-block;
965
983
  width: 0;
966
984
  overflow: hidden;
985
+ }
986
+
987
+ /* A character the file stands for and the editor cannot type, drawn as itself */
988
+ .docx-editor-raw-inline[data-display="text"],
989
+ .docx-editor-raw-run[data-display="text"] {
990
+ white-space: pre;
991
+ }
992
+
993
+ /*
994
+ * Content carried through whole that the editor has no model for: a field, a tracked change, a
995
+ * symbol. Small enough to read the line around it, outlined so it is plainly not ordinary text.
996
+ *
997
+ * Deliberately not `user-select: none`. A chip that carries its content whole can be selected and
998
+ * deleted whole, and taking it out of the selection would leave a reader dragging across it with
999
+ * no way to reach it.
1000
+ */
1001
+ .docx-editor-raw-inline[data-display="chip"],
1002
+ .docx-editor-raw-run[data-display="chip"] {
1003
+ display: inline-block;
1004
+ max-width: 16em;
1005
+ overflow: hidden;
1006
+ padding: 0 3px;
1007
+ border: 1px dashed #9a9a9a;
1008
+ border-radius: 3px;
1009
+ background: #f2f2f2;
1010
+ color: #595959;
1011
+ font-size: 0.85em;
1012
+ font-family: system-ui, sans-serif;
1013
+ line-height: 1.4;
1014
+ white-space: nowrap;
1015
+ text-overflow: ellipsis;
967
1016
  vertical-align: baseline;
968
1017
  }
969
1018
 
970
- .docx-editor-table,
1019
+ /* The chip while it is the selection, which is how a reader sees what a delete would take */
1020
+ .docx-editor-raw-inline[data-display="chip"].ProseMirror-selectednode,
1021
+ .docx-editor-raw-run[data-display="chip"].ProseMirror-selectednode {
1022
+ outline: 2px solid #1a73e8;
1023
+ outline-offset: 1px;
1024
+ }
1025
+
971
1026
  .docx-editor-raw-block {
972
1027
  margin: 8pt 0;
973
1028
  padding: 10pt 12pt;
@@ -980,6 +1035,11 @@ button.docx-editor-comment-body {
980
1035
  user-select: none;
981
1036
  }
982
1037
 
1038
+ /* A range marker holds a spot between blocks and stands for nothing on the page. */
1039
+ .docx-editor-raw-block[data-display="hidden"] {
1040
+ display: none;
1041
+ }
1042
+
983
1043
  /*
984
1044
  * The toolbar.
985
1045
  *
@@ -38,8 +38,7 @@ function planChanges(rect, edit) {
38
38
  const next = editCellProps(
39
39
  text(cell.attrs.tcPr),
40
40
  edit(rect, pos),
41
- cellDefaultsAt(rect.map, pos, sources),
42
- sources.margins
41
+ cellDefaultsAt(rect.map, pos, sources)
43
42
  );
44
43
  if (!next) continue;
45
44
  changes.push({
@@ -1,11 +1,12 @@
1
1
  /**
2
- * The lines a cell draws because of where it sits in the grid.
2
+ * The lines and the fill a cell draws because of where it sits in the grid.
3
3
  *
4
4
  * Every line of a table is drawn by its cells (see `docx/tableFormatting`), so which line a side
5
- * falls back on depends on where in the grid the cell sits. A new cell inherits its neighbour's
6
- * formatting, which is what a background has to do but not a line: the row appended under the last
7
- * one would draw the table's outer line against the row above it, and deleting the last row would
8
- * leave the table with no line along its bottom.
5
+ * falls back on depends on where in the grid the cell sits, and so does which parts of the table
6
+ * style dress it. A new cell inherits its neighbour's formatting, which is what a background has to
7
+ * do but not a line: the row appended under the last one would draw the table's outer line against
8
+ * the row above it, and deleting the last row would leave the table with no line along its bottom.
9
+ * A row added under the header row is likewise no header row.
9
10
  *
10
11
  * So the cells of a table whose grid moved derive their display values again, along the same path
11
12
  * the import takes. What a cell wrote down itself lives in its `w:tcPr` and is read straight back
@@ -14,21 +15,16 @@
14
15
  * deriver that runs them after an edit is `editor/plugins/tableDisplay`.
15
16
  */
16
17
  import type { Node as PMNode } from "prosemirror-model";
17
- import { type CellBorderDefaults } from "../docx/tableFormatting";
18
- import { type CellMargins, type InsideBorders, type TableFormat } from "../model/format";
18
+ import { type CellDefaults, type TableCellSources } from "../docx/tableFormatting";
19
19
  import type { NodeAttrs, TableGridMap } from "./format";
20
20
  /**
21
- * What a table lays down for its cells to draw.
21
+ * What this table lays down for its cells to draw.
22
22
  * The values a table style laid down are not in the `tblPr`, so the table carries them separately.
23
+ * A node never changes once it is built, so what is read out of one is read once.
23
24
  */
24
- export interface TableCellSources {
25
- outer: TableFormat | null;
26
- inside: InsideBorders;
27
- margins: CellMargins;
28
- }
29
25
  export declare function tableCellSources(table: PMNode): TableCellSources;
30
- /** The lines the cell at this position falls back on for the sides it draws no border of its own on */
31
- export declare function cellDefaultsAt(map: TableGridMap, pos: number, sources: TableCellSources): CellBorderDefaults;
26
+ /** What the cell at this position falls back on for everything it did not write down itself */
27
+ export declare function cellDefaultsAt(map: TableGridMap, pos: number, sources: TableCellSources): CellDefaults;
32
28
  export interface CellFix {
33
29
  /** The position of the cell within the table's content */
34
30
  pos: number;
@@ -1,30 +1,39 @@
1
1
  // src/table/gridBorders.ts
2
2
  import { TableMap } from "prosemirror-tables";
3
3
  import {
4
- cellBorderDefaults,
4
+ cellDefaultsFor,
5
5
  cellMarginsOf,
6
- gridEdgesOf,
7
6
  insideBordersOf,
8
7
  layerCellMargins,
9
8
  layerInsideBorders,
10
- NO_BORDER_DEFAULTS,
9
+ NO_BAND_SIZES,
10
+ NO_CELL_DEFAULTS,
11
11
  NO_CELL_MARGINS,
12
12
  NO_INSIDE_BORDERS,
13
- readCellProps
13
+ NO_TABLE_STYLE_CONDITIONS,
14
+ readCellProps,
15
+ readTableLook,
16
+ tblStyleIdOf
14
17
  } from "../docx/tableFormatting.js";
15
18
  import {
16
19
  spanCount,
20
+ toBandSizes,
17
21
  toCellFormat,
18
22
  toCellMargins,
19
23
  toInsideBorders,
20
- toTableFormat
24
+ toTableFormat,
25
+ toTableStyleConditions
21
26
  } from "../model/format.js";
27
+ import { parsePropsXml } from "../ooxml/props.js";
22
28
  function text(value) {
23
29
  return typeof value === "string" ? value : null;
24
30
  }
25
31
  function tableCellSources(table) {
32
+ const known = sourcesByTable.get(table);
33
+ if (known) return known;
26
34
  const tblPr = text(table.attrs.tblPr);
27
- return {
35
+ const props = tblPr === null ? null : parsePropsXml(tblPr);
36
+ const sources = {
28
37
  outer: toTableFormat(table.attrs.format),
29
38
  inside: layerInsideBorders(
30
39
  toInsideBorders(table.attrs.styleInside) ?? NO_INSIDE_BORDERS,
@@ -33,14 +42,23 @@ function tableCellSources(table) {
33
42
  margins: layerCellMargins(
34
43
  toCellMargins(table.attrs.styleCellMargins) ?? NO_CELL_MARGINS,
35
44
  cellMarginsOf(tblPr)
36
- )
45
+ ),
46
+ look: readTableLook(props),
47
+ bands: toBandSizes(table.attrs.styleBands) ?? NO_BAND_SIZES,
48
+ conditions: toTableStyleConditions(table.attrs.styleConditions) ?? NO_TABLE_STYLE_CONDITIONS,
49
+ // The style the paragraphs inside resolve against; what it lays down for the cells is already
50
+ // in `styleConditions`
51
+ styleId: tblStyleIdOf(props)
37
52
  };
53
+ sourcesByTable.set(table, sources);
54
+ return sources;
38
55
  }
56
+ var sourcesByTable = /* @__PURE__ */ new WeakMap();
39
57
  function cellDefaultsAt(map, pos, sources) {
40
- return cellBorderDefaults(
41
- gridEdgesOf(map.findCell(pos), { rows: map.height, cols: map.width }),
42
- sources.outer,
43
- sources.inside
58
+ return cellDefaultsFor(
59
+ map.findCell(pos),
60
+ { rows: map.height, cols: map.width },
61
+ sources
44
62
  );
45
63
  }
46
64
  var CELL_FORMAT_KEYS = [
@@ -106,16 +124,12 @@ function cellFixes(table) {
106
124
  const cell = table.nodeAt(pos);
107
125
  if (!cell) continue;
108
126
  const tcPr = text(cell.attrs.tcPr);
109
- const format = readCellProps(
110
- tcPr,
111
- cellDefaultsAt(map, pos, sources),
112
- sources.margins
113
- );
127
+ const format = readCellProps(tcPr, cellDefaultsAt(map, pos, sources));
114
128
  cells.set(pos, {
115
129
  pos,
116
130
  attrs: cell.attrs,
117
131
  format: { ...format },
118
- direct: readCellProps(tcPr, NO_BORDER_DEFAULTS)
132
+ direct: readCellProps(tcPr, NO_CELL_DEFAULTS)
119
133
  });
120
134
  }
121
135
  reconcileSharedBorders(map, cells);
@@ -144,7 +158,7 @@ function sameCellFormattingInputs(a, b) {
144
158
  );
145
159
  }
146
160
  function sameFormattingInputs(a, b) {
147
- return sameGrid(a, b) && a.attrs.tblPr === b.attrs.tblPr && a.attrs.format === b.attrs.format && a.attrs.styleInside === b.attrs.styleInside && a.attrs.styleCellMargins === b.attrs.styleCellMargins && sameCellFormattingInputs(a, b);
161
+ return sameGrid(a, b) && a.attrs.tblPr === b.attrs.tblPr && a.attrs.format === b.attrs.format && a.attrs.styleInside === b.attrs.styleInside && a.attrs.styleCellMargins === b.attrs.styleCellMargins && a.attrs.styleConditions === b.attrs.styleConditions && a.attrs.styleBands === b.attrs.styleBands && sameCellFormattingInputs(a, b);
148
162
  }
149
163
  export {
150
164
  cellDefaultsAt,
@@ -29,7 +29,8 @@ export interface ColumnEdge {
29
29
  /**
30
30
  * The limit a table can be widened to (dxa).
31
31
  *
32
- * The body width of the paper the document names, minus the table indent (`w:tblInd`).
32
+ * The body width of the paper handed in, which is the section's the table stands in, minus
33
+ * the table indent (`w:tblInd`).
33
34
  * For a table already stored wider than that, its current width is the limit, so
34
35
  * dragging can only narrow it.
35
36
  */
@@ -49,7 +50,8 @@ export declare function resizedGridCols(gridCols: number[], col: number, width:
49
50
  * Because it is one single transaction, a single undo returns everything to the state
50
51
  * before the drag.
51
52
  *
52
- * The geometry is the paper the open document names, which is what the table is kept
53
- * inside. The editor layer reads it off the state; this layer is handed it.
53
+ * The geometry is the paper of the section the table stands in, which is what the table is
54
+ * kept inside. The editor layer reads it off the state (`editor/documentStyles`); this layer
55
+ * is handed it.
54
56
  */
55
57
  export declare function buildResizeColumnTransaction(state: EditorState, edge: ColumnEdge, geometry: PageGeometry): Transaction | null;
@@ -5,11 +5,9 @@ import { type CommentAuthor } from "../editor/commands/commentCommands";
5
5
  export interface CommentsPanelProps {
6
6
  view: EditorView;
7
7
  state: EditorState;
8
- composerOpen: boolean;
9
8
  /** Whose comments the composers write. Null for a reader, who is offered none */
10
9
  author: CommentAuthor | null;
11
- closeComposer: () => void;
12
10
  scrollContainer: HTMLElement | null;
13
11
  allCommentsOpen: boolean;
14
12
  }
15
- export declare function CommentsPanel({ view, state, composerOpen, author, closeComposer, scrollContainer, allCommentsOpen, }: CommentsPanelProps): ReactElement;
13
+ export declare function CommentsPanel({ view, state, author, scrollContainer, allCommentsOpen, }: CommentsPanelProps): ReactElement;
@@ -4,7 +4,6 @@ import { useMemo, useRef, useState } from "react";
4
4
  import {
5
5
  addComment,
6
6
  addCommentReply,
7
- documentComments,
8
7
  removeComment,
9
8
  removeCommentReply,
10
9
  selectComment,
@@ -12,6 +11,11 @@ import {
12
11
  updateComment,
13
12
  updateCommentReply
14
13
  } from "../editor/commands/commentCommands.js";
14
+ import {
15
+ closeCommentComposer,
16
+ commentComposerRange
17
+ } from "../editor/plugins/commentComposer.js";
18
+ import { commentProjection } from "../editor/plugins/commentDecorations.js";
15
19
  import { commentOwned } from "../schema/protection.js";
16
20
  import { protectionOf } from "../schema/protectionState.js";
17
21
  import { editorClassNames } from "../styles/classNames.js";
@@ -37,19 +41,19 @@ function run(view, command) {
37
41
  if (applied) view.focus();
38
42
  return applied;
39
43
  }
44
+ var REFUSED_COMMENT = "This comment could not be added. The text it was written for may have been changed or locked since.";
45
+ var REFUSED_REPLY = "This reply could not be added. The comment it answers may have been changed or deleted since.";
40
46
  function sameTarget(left, right) {
41
47
  return left?.commentId === right.commentId && left.replyId === right.replyId;
42
48
  }
43
49
  function CommentsPanel({
44
50
  view,
45
51
  state,
46
- composerOpen,
47
52
  author,
48
- closeComposer,
49
53
  scrollContainer,
50
54
  allCommentsOpen
51
55
  }) {
52
- const comments = useMemo(() => documentComments(state), [state.doc]);
56
+ const comments = commentProjection.read(state).comments;
53
57
  const rules = protectionOf(state);
54
58
  const writer = author != null && rules.protection !== "readOnly" ? author : null;
55
59
  const owned = (authorId) => writer !== null && commentOwned(rules, authorId);
@@ -57,7 +61,9 @@ function CommentsPanel({
57
61
  const [editing, setEditing] = useState(null);
58
62
  const [draft, setDraft] = useState("");
59
63
  const [replying, setReplying] = useState(null);
60
- const composerSelectionPos = composerOpen ? state.selection.from : -1;
64
+ const composerRange = commentComposerRange(state);
65
+ const composerOpen = composerRange !== null;
66
+ const composerSelectionPos = composerRange?.from ?? -1;
61
67
  const visible = useMemo(
62
68
  () => allCommentsOpen ? [...comments].sort((left, right) => {
63
69
  const leftTime = Date.parse(left.date ?? "");
@@ -112,7 +118,7 @@ function CommentsPanel({
112
118
  className: editorClassNames.commentsCanvas,
113
119
  style: { height: allCommentsOpen ? void 0 : `${canvasHeight}px` },
114
120
  children: [
115
- composerOpen && writer !== null && /* @__PURE__ */ jsx(
121
+ composerRange !== null && writer !== null && /* @__PURE__ */ jsx(
116
122
  "div",
117
123
  {
118
124
  className: editorClassNames.commentPosition,
@@ -126,16 +132,19 @@ function CommentsPanel({
126
132
  author: writer,
127
133
  label: "Comment text",
128
134
  submitLabel: "Comment",
129
- onClose: closeComposer,
135
+ onClose: () => run(view, closeCommentComposer),
130
136
  onSubmit: (text) => run(
131
137
  view,
132
- addComment({
133
- text,
134
- author: writer.name,
135
- authorId: writer.id,
136
- initials: writer.initials
137
- })
138
- )
138
+ addComment(
139
+ {
140
+ text,
141
+ author: writer.name,
142
+ authorId: writer.id,
143
+ initials: writer.initials
144
+ },
145
+ composerRange
146
+ )
147
+ ) ? null : REFUSED_COMMENT
139
148
  }
140
149
  )
141
150
  }
@@ -335,7 +344,7 @@ function CommentsPanel({
335
344
  authorId: writer.id,
336
345
  initials: writer.initials
337
346
  })
338
- )
347
+ ) ? null : REFUSED_REPLY
339
348
  }
340
349
  )
341
350
  ] })
@@ -1,12 +1,12 @@
1
1
  // src/ui/NotesPanel.tsx
2
- import { documentNotes } from "../editor/commands/noteQueries.js";
2
+ import { noteProjection } from "../editor/commands/noteQueries.js";
3
3
  import { editorClassNames } from "../styles/classNames.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  function NotesPanel({
6
6
  state,
7
7
  pageWidth
8
8
  }) {
9
- const notes = documentNotes(state);
9
+ const { notes } = noteProjection.read(state);
10
10
  if (notes.length === 0) return null;
11
11
  return /* @__PURE__ */ jsxs(
12
12
  "section",
@@ -9,7 +9,5 @@ export interface TextMenuProps {
9
9
  anchor: TextMenuAnchor;
10
10
  /** Whether the entries that lock and unlock a stretch of text are offered */
11
11
  allowLocking?: boolean;
12
- /** Opens the built-in composer for the selected text. */
13
- onAddComment?: () => void;
14
12
  }
15
- export declare function TextMenu({ view, state, anchor, allowLocking, onAddComment, }: TextMenuProps): ReactElement;
13
+ export declare function TextMenu({ view, state, anchor, allowLocking, }: TextMenuProps): ReactElement;
@@ -17,7 +17,7 @@ import {
17
17
  selectionTouchesLocked,
18
18
  unlockSelection
19
19
  } from "../editor/commands/lockCommands.js";
20
- import { insertClipboardData } from "../editor/externalClipboard.js";
20
+ import { openCommentComposer } from "../editor/plugins/commentComposer.js";
21
21
  import {
22
22
  closeTextMenu
23
23
  } from "../editor/plugins/textContextMenu.js";
@@ -62,8 +62,13 @@ async function clipboardContent() {
62
62
  async function pasteFromClipboard(view) {
63
63
  const content = await clipboardContent();
64
64
  if (!content.html && !content.text) return;
65
+ const data = new DataTransfer();
66
+ if (content.html) data.setData("text/html", content.html);
67
+ if (content.text) data.setData("text/plain", content.text);
68
+ const event = new ClipboardEvent("paste", { clipboardData: data });
65
69
  view.focus();
66
- insertClipboardData(view, content);
70
+ if (content.html && view.pasteHTML(content.html, event)) return;
71
+ if (content.text) view.pasteText(content.text, event);
67
72
  }
68
73
  function lockItem(lock, run) {
69
74
  const locking = {
@@ -90,8 +95,7 @@ function TextMenu({
90
95
  view,
91
96
  state,
92
97
  anchor,
93
- allowLocking = false,
94
- onAddComment
98
+ allowLocking = false
95
99
  }) {
96
100
  const box = useRef(null);
97
101
  const placement = usePanelAtPoint(box, anchor);
@@ -143,8 +147,8 @@ function TextMenu({
143
147
  {
144
148
  label: "Add comment",
145
149
  icon: MessageSquarePlus,
146
- enabled: !!onAddComment && canAddComment(state),
147
- run: () => onAddComment?.()
150
+ enabled: canAddComment(state),
151
+ run: () => run(openCommentComposer)
148
152
  }
149
153
  ]);
150
154
  if (allowLocking && bodyOpen) {
@@ -10,7 +10,12 @@ export interface CommentComposerProps {
10
10
  author: CommentAuthor;
11
11
  label: string;
12
12
  submitLabel: string;
13
- onSubmit: (text: string) => boolean;
13
+ /**
14
+ * Writes the comment, and answers why it was not written where it was not. The text stays in
15
+ * the form then, together with the reason, rather than the form standing there as if nothing
16
+ * had been asked of it.
17
+ */
18
+ onSubmit: (text: string) => string | null;
14
19
  onClose: () => void;
15
20
  }
16
21
  export declare function CommentComposer({ author, label, submitLabel, onSubmit, onClose, }: CommentComposerProps): ReactElement;
@@ -1,5 +1,6 @@
1
1
  // src/ui/comments/CommentComposer.tsx
2
2
  import {
3
+ useEffect,
3
4
  useLayoutEffect,
4
5
  useRef,
5
6
  useState
@@ -12,7 +13,15 @@ function FocusedTextarea({
12
13
  onChange
13
14
  }) {
14
15
  const input = useRef(null);
15
- useLayoutEffect(() => input.current?.focus(), []);
16
+ useLayoutEffect(() => input.current?.focus({ preventScroll: true }), []);
17
+ useEffect(() => {
18
+ const field = input.current;
19
+ if (typeof field?.scrollIntoView !== "function") return;
20
+ const frame = requestAnimationFrame(() => {
21
+ field.scrollIntoView({ block: "nearest" });
22
+ });
23
+ return () => cancelAnimationFrame(frame);
24
+ }, []);
16
25
  return /* @__PURE__ */ jsx(
17
26
  "textarea",
18
27
  {
@@ -32,13 +41,27 @@ function CommentComposer({
32
41
  onClose
33
42
  }) {
34
43
  const [text, setText] = useState("");
44
+ const [refused, setRefused] = useState(null);
35
45
  const submit = (event) => {
36
46
  event.preventDefault();
37
- if (onSubmit(text)) onClose();
47
+ const reason = onSubmit(text);
48
+ setRefused(reason);
49
+ if (reason === null) onClose();
38
50
  };
39
51
  return /* @__PURE__ */ jsxs("form", { className: editorClassNames.commentComposer, onSubmit: submit, children: [
40
52
  /* @__PURE__ */ jsx("div", { className: editorClassNames.commentMeta, children: author.name }),
41
- /* @__PURE__ */ jsx(FocusedTextarea, { label, value: text, onChange: setText }),
53
+ /* @__PURE__ */ jsx(
54
+ FocusedTextarea,
55
+ {
56
+ label,
57
+ value: text,
58
+ onChange: (next) => {
59
+ setRefused(null);
60
+ setText(next);
61
+ }
62
+ }
63
+ ),
64
+ refused !== null && /* @__PURE__ */ jsx("p", { className: editorClassNames.commentMeta, role: "alert", children: refused }),
42
65
  /* @__PURE__ */ jsxs("div", { className: editorClassNames.commentActions, children: [
43
66
  /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, children: "Cancel" }),
44
67
  /* @__PURE__ */ jsx(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portone/docx-editor",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "A simple DOCX editor for React, built directly on OOXML.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,11 +0,0 @@
1
- import { Slice } from "prosemirror-model";
2
- import { type EditorState, Plugin } from "prosemirror-state";
3
- import type { EditorView } from "prosemirror-view";
4
- import type { ImageToInsert } from "./insertImage";
5
- export declare function richHtmlSlice(state: EditorState, document: Document, source: string, images?: ReadonlyMap<string, ImageToInsert>): Slice | null;
6
- export declare function insertRichHtml(view: EditorView, source: string): boolean;
7
- export declare function insertClipboardData(view: EditorView, data: {
8
- html?: string;
9
- text?: string;
10
- }): void;
11
- export declare function externalClipboard(): Plugin;