@portone/docx-editor 0.4.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 (155) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/assets/editor.png +0 -0
  3. package/dist/DocxEditor.d.ts +1 -1
  4. package/dist/DocxEditor.js +44 -25
  5. package/dist/docx/cloning.js +4 -9
  6. package/dist/docx/commentOnlyChange.d.ts +5 -3
  7. package/dist/docx/comments/grammar.d.ts +27 -10
  8. package/dist/docx/comments/grammar.js +18 -67
  9. package/dist/docx/comments/model.d.ts +5 -6
  10. package/dist/docx/comments/model.js +0 -9
  11. package/dist/docx/comments/parts.d.ts +16 -10
  12. package/dist/docx/comments/parts.js +65 -13
  13. package/dist/docx/comments/people.d.ts +11 -3
  14. package/dist/docx/comments/people.js +20 -6
  15. package/dist/docx/comments/policy.js +7 -1
  16. package/dist/docx/comments/reading.d.ts +4 -2
  17. package/dist/docx/comments/reading.js +1 -14
  18. package/dist/docx/comments/writing.js +65 -28
  19. package/dist/docx/exportDocx.js +10 -14
  20. package/dist/docx/exportRefs.d.ts +8 -3
  21. package/dist/docx/exportRefs.js +2 -1
  22. package/dist/docx/fidelity.js +24 -10
  23. package/dist/docx/fields.d.ts +42 -0
  24. package/dist/docx/fields.js +76 -0
  25. package/dist/docx/headersFooters.d.ts +89 -20
  26. package/dist/docx/headersFooters.js +163 -172
  27. package/dist/docx/importDocx.d.ts +1 -1
  28. package/dist/docx/importDocx.js +100 -48
  29. package/dist/docx/importParagraph.d.ts +16 -4
  30. package/dist/docx/importParagraph.js +87 -93
  31. package/dist/docx/importPolicy.d.ts +70 -0
  32. package/dist/docx/importPolicy.js +190 -0
  33. package/dist/docx/importPreserved.d.ts +30 -0
  34. package/dist/docx/importPreserved.js +54 -0
  35. package/dist/docx/importTable.js +56 -12
  36. package/dist/docx/invariants.js +16 -18
  37. package/dist/docx/notes.d.ts +6 -1
  38. package/dist/docx/notes.js +6 -16
  39. package/dist/docx/pageGeometry.d.ts +5 -14
  40. package/dist/docx/pageGeometry.js +0 -12
  41. package/dist/docx/protectionPolicy.d.ts +13 -5
  42. package/dist/docx/protectionPolicy.js +32 -22
  43. package/dist/docx/scan.d.ts +18 -8
  44. package/dist/docx/scan.js +17 -11
  45. package/dist/docx/sections.d.ts +126 -0
  46. package/dist/docx/sections.js +207 -0
  47. package/dist/docx/serializeBlock.d.ts +13 -4
  48. package/dist/docx/serializeBlock.js +16 -18
  49. package/dist/docx/serializeParagraph.d.ts +4 -0
  50. package/dist/docx/serializeParagraph.js +1 -0
  51. package/dist/docx/serializePreserved.d.ts +14 -0
  52. package/dist/docx/serializePreserved.js +24 -0
  53. package/dist/docx/serializeStory.d.ts +17 -0
  54. package/dist/docx/serializeStory.js +17 -0
  55. package/dist/docx/serializeTable.js +10 -13
  56. package/dist/docx/session.d.ts +23 -6
  57. package/dist/docx/session.js +26 -5
  58. package/dist/docx/story.d.ts +140 -0
  59. package/dist/docx/story.js +237 -0
  60. package/dist/docx/storyProjection.js +6 -1
  61. package/dist/editor/clipboard/htmlReader.d.ts +30 -0
  62. package/dist/editor/{externalClipboard.js → clipboard/htmlReader.js} +39 -223
  63. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  64. package/dist/editor/clipboard/internalChannel.js +58 -0
  65. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  66. package/dist/editor/clipboard/normalizers.js +199 -0
  67. package/dist/editor/clipboard/parser.d.ts +34 -0
  68. package/dist/editor/clipboard/parser.js +58 -0
  69. package/dist/editor/clipboard/plugin.d.ts +21 -0
  70. package/dist/editor/clipboard/plugin.js +320 -0
  71. package/dist/editor/clipboard/readContext.d.ts +24 -0
  72. package/dist/editor/clipboard/readContext.js +19 -0
  73. package/dist/editor/clipboard/readers.d.ts +33 -0
  74. package/dist/editor/clipboard/readers.js +37 -0
  75. package/dist/editor/commands/comments/editing.d.ts +26 -6
  76. package/dist/editor/commands/comments/editing.js +175 -105
  77. package/dist/editor/commands/comments/model.d.ts +34 -17
  78. package/dist/editor/commands/comments/model.js +11 -0
  79. package/dist/editor/commands/comments/reading.d.ts +5 -3
  80. package/dist/editor/commands/comments/reading.js +8 -58
  81. package/dist/editor/commands/index.d.ts +2 -2
  82. package/dist/editor/commands/index.js +2 -0
  83. package/dist/editor/commands/listCommands.d.ts +6 -0
  84. package/dist/editor/commands/listCommands.js +3 -2
  85. package/dist/editor/commands/noteQueries.d.ts +22 -5
  86. package/dist/editor/commands/noteQueries.js +34 -6
  87. package/dist/editor/createEditor.js +13 -3
  88. package/dist/editor/documentStyles.d.ts +26 -4
  89. package/dist/editor/documentStyles.js +9 -4
  90. package/dist/editor/imageFiles.d.ts +2 -2
  91. package/dist/editor/imageFiles.js +2 -0
  92. package/dist/editor/insertTable.d.ts +1 -1
  93. package/dist/editor/insertTable.js +2 -2
  94. package/dist/editor/plainText.d.ts +12 -0
  95. package/dist/editor/plainText.js +2 -1
  96. package/dist/editor/plugins/columnResize.js +6 -3
  97. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  98. package/dist/editor/plugins/commentComposer.js +46 -0
  99. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  100. package/dist/editor/plugins/commentDecorations.js +89 -15
  101. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  102. package/dist/editor/plugins/documentProjection.js +22 -0
  103. package/dist/editor/plugins/imagePaste.js +15 -8
  104. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  105. package/dist/editor/plugins/linkPanel.js +13 -27
  106. package/dist/editor/plugins/panelState.d.ts +67 -0
  107. package/dist/editor/plugins/panelState.js +54 -0
  108. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  109. package/dist/editor/plugins/tableContextMenu.js +30 -47
  110. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  111. package/dist/editor/plugins/textContextMenu.js +30 -47
  112. package/dist/ooxml/fragment.d.ts +11 -0
  113. package/dist/ooxml/fragment.js +14 -4
  114. package/dist/ooxml/image.d.ts +3 -4
  115. package/dist/ooxml/image.js +50 -5
  116. package/dist/ooxml/names.d.ts +6 -0
  117. package/dist/ooxml/names.js +2 -0
  118. package/dist/ooxml/props.d.ts +8 -0
  119. package/dist/ooxml/props.js +14 -4
  120. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  121. package/dist/ooxml/rangeMarkers.js +24 -0
  122. package/dist/page/PageGuides.d.ts +7 -3
  123. package/dist/page/PageGuides.js +13 -24
  124. package/dist/page/pageLayout.d.ts +64 -5
  125. package/dist/page/pageLayout.js +90 -29
  126. package/dist/page/usePageLayout.d.ts +14 -4
  127. package/dist/page/usePageLayout.js +19 -20
  128. package/dist/schema/attrRoles.js +38 -20
  129. package/dist/schema/docxSchema.d.ts +9 -1
  130. package/dist/schema/docxSchema.js +191 -139
  131. package/dist/schema/editGuard.d.ts +1 -1
  132. package/dist/schema/guards.js +2 -2
  133. package/dist/schema/index.d.ts +1 -0
  134. package/dist/schema/preservedFragments.d.ts +12 -0
  135. package/dist/schema/preservedFragments.js +38 -0
  136. package/dist/schema/preservedGuards.d.ts +9 -7
  137. package/dist/schema/preservedGuards.js +112 -11
  138. package/dist/schema/protection.d.ts +34 -5
  139. package/dist/schema/protection.js +42 -14
  140. package/dist/schema/rendering.js +3 -1
  141. package/dist/schema/stories.d.ts +49 -0
  142. package/dist/schema/stories.js +78 -0
  143. package/dist/styles/classNames.d.ts +3 -5
  144. package/dist/styles/classNames.js +3 -5
  145. package/dist/styles.css +74 -12
  146. package/dist/table/resize.d.ts +5 -3
  147. package/dist/ui/CommentsPanel.d.ts +1 -3
  148. package/dist/ui/CommentsPanel.js +24 -15
  149. package/dist/ui/NotesPanel.js +2 -2
  150. package/dist/ui/TextMenu.d.ts +1 -3
  151. package/dist/ui/TextMenu.js +10 -6
  152. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  153. package/dist/ui/comments/CommentComposer.js +26 -3
  154. package/package.json +1 -1
  155. package/dist/editor/externalClipboard.d.ts +0 -22
@@ -56,8 +56,8 @@ export { insertLineBreak, insertPageBreak } from "./breakCommands";
56
56
  * question takes are not exported anywhere.
57
57
  */
58
58
  export { canRunCommand } from "./canRunCommand";
59
- export type { CommentAuthor, DocumentComment, DocumentCommentReply, NewComment, } from "./commentCommands";
60
- export { addComment, addCommentReply, canAddComment, canEditComment, documentComments, removeComment, removeCommentReply, selectComment, setCommentResolved, updateComment, updateCommentReply, } from "./commentCommands";
59
+ export type { CommentAuthor, CommentRange, DocumentComment, DocumentCommentReply, NewComment, } from "./commentCommands";
60
+ export { addComment, addCommentReply, canAddComment, canEditComment, documentComments, removeComment, removeCommentReply, selectComment, setCommentBody, setCommentResolved, updateComment, updateCommentReply, } from "./commentCommands";
61
61
  /**
62
62
  * Whether the document as it stands can be written back, and every reason it could not, each
63
63
  * with the code `exportDocx` would throw it under. `canExport` is what an export control is
@@ -25,6 +25,7 @@ import {
25
25
  removeComment,
26
26
  removeCommentReply,
27
27
  selectComment,
28
+ setCommentBody,
28
29
  setCommentResolved,
29
30
  updateComment,
30
31
  updateCommentReply
@@ -158,6 +159,7 @@ export {
158
159
  selectComment,
159
160
  selectionLock,
160
161
  selectionTouchesLocked,
162
+ setCommentBody,
161
163
  setCommentResolved,
162
164
  setFontFamily,
163
165
  setFontSize,
@@ -33,6 +33,12 @@ export declare function numIdsIn(doc: PMNode): Set<number>;
33
33
  * of its own stays, putting it back where it was before it joined the list.
34
34
  */
35
35
  export declare const removeFromList: Command;
36
+ /**
37
+ * The kind of this list position, which the number format of its level decides.
38
+ * Null where nothing defines the list, which is a position whose kind cannot be told and so is
39
+ * neither of the two buttons.
40
+ */
41
+ export declare function listKindOf(numbering: Numbering, ref: NumberingRef): ListKind | null;
36
42
  /**
37
43
  * The kind of list the selected paragraphs belong to. Null when they are not a list, when nothing
38
44
  * defines the list they are in, or when the kinds are mixed.
@@ -122,7 +122,7 @@ var removeFromList = (state, dispatch) => changeParagraphs(
122
122
  dispatch,
123
123
  (node) => listRefOf(node) ? { numbering: null, indent: { kind: "clearHanging" } } : null
124
124
  );
125
- function kindOf(numbering, ref) {
125
+ function listKindOf(numbering, ref) {
126
126
  const level = listFor(numbering, ref.numId)?.levels.get(ref.ilvl);
127
127
  if (!level) return null;
128
128
  return level.format === "bullet" ? "bullet" : "numbered";
@@ -131,7 +131,7 @@ function activeListKind(state) {
131
131
  const numbering = documentNumbering(state);
132
132
  const kinds = editableParagraphs(state).map(({ node }) => {
133
133
  const ref = listRefOf(node);
134
- return ref === null ? null : kindOf(numbering, ref);
134
+ return ref === null ? null : listKindOf(numbering, ref);
135
135
  });
136
136
  const first = kinds[0] ?? null;
137
137
  return kinds.every((kind) => kind === first) ? first : null;
@@ -174,6 +174,7 @@ export {
174
174
  increaseListLevel,
175
175
  isInList,
176
176
  leaveEmptyListItem,
177
+ listKindOf,
177
178
  listLevelChange,
178
179
  listRefOf,
179
180
  numIdsIn,
@@ -1,12 +1,29 @@
1
1
  /** Document-level readers for imported footnotes and endnotes. */
2
2
  import type { EditorState } from "prosemirror-state";
3
+ import { DecorationSet } from "prosemirror-view";
3
4
  import type { NoteKind } from "../../docx/notes";
4
5
  export interface DocumentNote {
5
- kind: NoteKind;
6
- id: string;
7
- label: string;
8
- text: string;
9
- referencePos: number;
6
+ readonly kind: NoteKind;
7
+ readonly id: string;
8
+ readonly label: string;
9
+ readonly text: string;
10
+ readonly referencePos: number;
10
11
  }
12
+ interface NoteProjection {
13
+ notes: readonly DocumentNote[];
14
+ /**
15
+ * The body of each note on the marker pointing at it, which is the tooltip a reader gets without
16
+ * looking away from the line. It is drawn rather than written on the node, because what a note
17
+ * says is a story on the document and not an attr of the reference.
18
+ */
19
+ tooltips: DecorationSet;
20
+ }
21
+ /**
22
+ * Which notes the document refers to is the document's to decide, so the list is worked out once
23
+ * per edit (`editor/plugins/documentProjection`) rather than once per render of the panel under
24
+ * the page.
25
+ */
26
+ export declare const noteProjection: import("../plugins/documentProjection").DocumentProjection<NoteProjection>;
11
27
  /** The distinct notes referenced by the main document story, in first-reference order. */
12
28
  export declare function documentNotes(state: EditorState): readonly DocumentNote[];
29
+ export {};
@@ -1,15 +1,29 @@
1
1
  // src/editor/commands/noteQueries.ts
2
+ import { Decoration, DecorationSet } from "prosemirror-view";
3
+ import { storyKey, storyOf, storyText } from "../../docx/story.js";
4
+ import { documentProjection } from "../plugins/documentProjection.js";
2
5
  function stringAttr(value) {
3
6
  return typeof value === "string" ? value : null;
4
7
  }
5
- function documentNotes(state) {
8
+ function noteKindOf(node) {
9
+ return node.attrs.kind === "endnote" ? "endnote" : "footnote";
10
+ }
11
+ function noteText(doc, kind, id) {
12
+ return storyText(storyOf(doc, storyKey(kind, id)));
13
+ }
14
+ function deriveNotes(doc) {
6
15
  const notes = [];
16
+ const tooltips = [];
7
17
  const seen = /* @__PURE__ */ new Set();
8
- state.doc.descendants((node, pos) => {
18
+ doc.descendants((node, pos) => {
9
19
  if (node.type.name !== "noteReference") return true;
10
20
  const id = stringAttr(node.attrs.id);
11
21
  if (id === null) return true;
12
- const kind = node.attrs.kind === "endnote" ? "endnote" : "footnote";
22
+ const kind = noteKindOf(node);
23
+ const text = noteText(doc, kind, id);
24
+ if (text !== "") {
25
+ tooltips.push(Decoration.node(pos, pos + node.nodeSize, { title: text }));
26
+ }
13
27
  const key = `${kind}:${id}`;
14
28
  if (seen.has(key)) return true;
15
29
  seen.add(key);
@@ -17,13 +31,27 @@ function documentNotes(state) {
17
31
  kind,
18
32
  id,
19
33
  label: stringAttr(node.attrs.label) ?? "?",
20
- text: stringAttr(node.attrs.text) ?? "",
34
+ text,
21
35
  referencePos: pos
22
36
  });
23
37
  return true;
24
38
  });
25
- return notes;
39
+ return { notes, tooltips: DecorationSet.create(doc, tooltips) };
40
+ }
41
+ var noteProjection = documentProjection(
42
+ "docxEditorNotes",
43
+ deriveNotes,
44
+ {
45
+ // The set the last edit derived. Written as a method so that `this` is the plugin holding it
46
+ decorations(state) {
47
+ return this.getState(state)?.tooltips;
48
+ }
49
+ }
50
+ );
51
+ function documentNotes(state) {
52
+ return noteProjection.read(state).notes;
26
53
  }
27
54
  export {
28
- documentNotes
55
+ documentNotes,
56
+ noteProjection
29
57
  };
@@ -14,15 +14,17 @@ import {
14
14
  DEFAULT_FONT_FALLBACKS
15
15
  } from "../styles/fontStack.js";
16
16
  import { documentDefaultsStyle } from "../styles/inlineStyle.js";
17
+ import { docxClipboard } from "./clipboard/plugin.js";
18
+ import { noteProjection } from "./commands/noteQueries.js";
17
19
  import {
18
20
  documentOf,
19
21
  editorDocument,
20
22
  editorDocumentOf,
21
23
  NO_DOCUMENT
22
24
  } from "./editorDocument.js";
23
- import { externalClipboard } from "./externalClipboard.js";
24
25
  import { imageFiles } from "./imageFiles.js";
25
26
  import { columnResize } from "./plugins/columnResize.js";
27
+ import { commentComposer } from "./plugins/commentComposer.js";
26
28
  import { commentDecorations } from "./plugins/commentDecorations.js";
27
29
  import {
28
30
  displayDerivation,
@@ -80,17 +82,22 @@ function createEditorState(doc, options = {}) {
80
82
  // above, is what takes such a conversion back
81
83
  listInputRules(),
82
84
  dropCursor(),
83
- // Clipboard priority is files, resolvable HTML images, then regular HTML or plain text.
85
+ // Images get the first reading. The clipboard then handles only empty readings and text
86
+ // fallbacks; tableEditing owns insertion into a cell selection for every nonempty slice.
84
87
  imageFiles(),
85
88
  imagePaste(),
86
- externalClipboard(),
89
+ docxClipboard(),
87
90
  // A press that grabs a table edge must be intercepted before a cell-selection drag starts.
88
91
  // For DOM events the plugin registered first wins, so both resizers precede `tableEditing`.
89
92
  columnResize(),
90
93
  rowResize(),
91
94
  tableEditing(),
92
95
  // Comment anchors live in the model; this paints their ranges without changing document XML.
96
+ // The list beside the page, the lookup the comment commands ask, and these ranges are the
97
+ // one walk this plugin holds (`plugins/commentDecorations`)
93
98
  commentDecorations(),
99
+ // What the notes under the page are, worked out from the document the same way
100
+ noteProjection.plugin,
94
101
  // Adjacent text tabs still need separate DOM ranges for layout and pointer selection.
95
102
  tabDecorations(),
96
103
  tabPointer(),
@@ -103,6 +110,9 @@ function createEditorState(doc, options = {}) {
103
110
  // or fall together with the menus the editor draws. The text menu stands ahead of the table
104
111
  // menu, and hands a click with nothing selected inside a cell back to it
105
112
  ...contextMenus ? [textContextMenu(), tableContextMenu()] : [],
113
+ // Holds the stretch of text the built-in comment composer was opened over. It reads the
114
+ // protection above it, so it stands after the plugin that holds one
115
+ commentComposer(),
106
116
  numberingMarkers(),
107
117
  // Values only go in when page display is turned on
108
118
  pageDecorations()
@@ -19,10 +19,25 @@ export declare function documentDefaults(state: EditorState): DocumentDefaults;
19
19
  /** The paragraph styles this document defines. Empty when the editor does not know them */
20
20
  export declare function documentParagraphStyles(state: EditorState): ParagraphStyleOption[];
21
21
  /**
22
- * The paper the document is written on. A document the editor knows no paper for is drawn on
23
- * the same A4 every document was drawn on before the geometry was read
22
+ * The paper the sheet is drawn on: the first section's, since one sheet is drawn at one paper
23
+ * (`page/pageLayout`). A document the editor knows no paper for is drawn on the same A4 every
24
+ * document was drawn on before the geometry was read.
25
+ *
26
+ * Anything deciding a value for one spot in the document - the width of a table put there, the
27
+ * height a pasted image is fitted to - asks `sectionGeometryAt` instead, since a later section may
28
+ * be written on another paper.
24
29
  */
25
30
  export declare function documentGeometry(state: EditorState): PageGeometry;
31
+ /**
32
+ * The paper the block at this position is written on: the paper of the section that block belongs
33
+ * to (`docx/sections`), which is the width a table inserted or resized there is fitted to.
34
+ *
35
+ * The section table is read off the document rather than off the snapshot the state carries: a
36
+ * section is where the document says it is, and the snapshot holds the first section's paper
37
+ * alone. Reading it walks every block, so a caller drawing a whole document asks once and looks
38
+ * its positions up (`page/pageLayout`) rather than calling this per block.
39
+ */
40
+ export declare function sectionGeometryAt(state: EditorState, pos: number): PageGeometry;
26
41
  /** The document-wide interval between automatic tab stops, in points. */
27
42
  export declare function documentDefaultTabStopPt(state: EditorState): number;
28
43
  /**
@@ -32,5 +47,12 @@ export declare function documentDefaultTabStopPt(state: EditorState): number;
32
47
  * itself asks for it rather than taking the A4 width `fittedExtent` falls back to.
33
48
  */
34
49
  export declare function documentBodyWidthPx(state: EditorState): number;
35
- /** The height one page of body content occupies, in pixels. */
36
- export declare function documentBodyHeightPx(state: EditorState): number;
50
+ /**
51
+ * The height one page of body content occupies at this position, in pixels: the height the paper
52
+ * of the section that block sits in leaves.
53
+ *
54
+ * It is the height an image taller than the page is shrunk to when it is pasted there, so a tall
55
+ * image dropped into a section on shorter paper is fitted to that section rather than to the
56
+ * sheet's own.
57
+ */
58
+ export declare function sectionBodyHeightPx(state: EditorState, pos: number): number;
@@ -4,6 +4,7 @@ import {
4
4
  bodyWidth,
5
5
  twipsToPx
6
6
  } from "../docx/pageGeometry.js";
7
+ import { sectionAt } from "../docx/sections.js";
7
8
  import { documentOf } from "./editorDocument.js";
8
9
  function documentFormatting(state) {
9
10
  return documentOf(state).formatting;
@@ -17,21 +18,25 @@ function documentParagraphStyles(state) {
17
18
  function documentGeometry(state) {
18
19
  return documentOf(state).geometry;
19
20
  }
21
+ function sectionGeometryAt(state, pos) {
22
+ return sectionAt(state.doc, pos).props.geometry;
23
+ }
20
24
  function documentDefaultTabStopPt(state) {
21
25
  return documentOf(state).defaultTabStopPt;
22
26
  }
23
27
  function documentBodyWidthPx(state) {
24
28
  return bodyWidth(documentGeometry(state)).px;
25
29
  }
26
- function documentBodyHeightPx(state) {
27
- return twipsToPx(bodyHeightTwips(documentGeometry(state)));
30
+ function sectionBodyHeightPx(state, pos) {
31
+ return twipsToPx(bodyHeightTwips(sectionGeometryAt(state, pos)));
28
32
  }
29
33
  export {
30
- documentBodyHeightPx,
31
34
  documentBodyWidthPx,
32
35
  documentDefaultTabStopPt,
33
36
  documentDefaults,
34
37
  documentFormatting,
35
38
  documentGeometry,
36
- documentParagraphStyles
39
+ documentParagraphStyles,
40
+ sectionBodyHeightPx,
41
+ sectionGeometryAt
37
42
  };
@@ -65,8 +65,8 @@ export declare function insertImageFiles(view: EditorView, files: readonly File[
65
65
  /**
66
66
  * The plugin that takes image files pasted or dropped onto the paper.
67
67
  *
68
- * It has to sit ahead of the plain-text plugin, which answers for every paste and drop
69
- * whether or not it has anything to insert (see the plugin order in `createEditor.ts`).
68
+ * It runs before clipboard fallback and table insertion (see the order in `createEditor.ts`).
69
+ * A paste that chose plain text leaves any accompanying image file alone.
70
70
  * An event carrying no image file is passed on untouched, so text keeps coming in exactly
71
71
  * as it did.
72
72
  */
@@ -10,6 +10,7 @@ import {
10
10
  pxToEmu,
11
11
  toImageSrc
12
12
  } from "../ooxml/image.js";
13
+ import { plainTextPaste } from "./clipboard/parser.js";
13
14
  import { documentBodyWidthPx } from "./documentStyles.js";
14
15
  import { insertImage } from "./insertImage.js";
15
16
  import { moveCaretToDrop } from "./plugins/dropCaret.js";
@@ -140,6 +141,7 @@ function imageFiles() {
140
141
  return new Plugin({
141
142
  props: {
142
143
  handlePaste(view, event) {
144
+ if (plainTextPaste(view, event)) return false;
143
145
  const files = imageFilesIn(event.clipboardData);
144
146
  if (files.length === 0) return false;
145
147
  void insertImageFiles(view, files);
@@ -13,6 +13,6 @@ export interface TableSize {
13
13
  }
14
14
  /**
15
15
  * Inserts a table of empty cells and moves the caret into the first cell.
16
- * The table is as wide as one line of body text on the paper the open document names
16
+ * The table is as wide as one line of body text on the paper of the section it goes into
17
17
  */
18
18
  export declare function insertTable({ rows, columns }: TableSize): Command;
@@ -5,7 +5,7 @@ import {
5
5
  } from "prosemirror-state";
6
6
  import { createTableNode, isTableSide } from "../docx/tableTemplate.js";
7
7
  import { editShut } from "../schema/guards.js";
8
- import { documentGeometry } from "./documentStyles.js";
8
+ import { sectionGeometryAt } from "./documentStyles.js";
9
9
  function isInTable($pos) {
10
10
  for (let depth = $pos.depth; depth > 0; depth -= 1) {
11
11
  if ($pos.node(depth).type.spec.tableRole === "table") return true;
@@ -30,7 +30,7 @@ function insertTable({ rows, columns }) {
30
30
  const tr = state.tr.insert(
31
31
  at,
32
32
  Fragment.fromArray([
33
- createTableNode(rows, columns, documentGeometry(state)),
33
+ createTableNode(rows, columns, sectionGeometryAt(state, at)),
34
34
  state.schema.nodes.paragraph.create()
35
35
  ])
36
36
  );
@@ -1,3 +1,15 @@
1
1
  import type { EditorView } from "prosemirror-view";
2
+ /** A single character that becomes an element, or text that goes in as it is */
3
+ export type Piece = {
4
+ text: string;
5
+ } | {
6
+ node: "hardBreak" | "tab";
7
+ };
8
+ /**
9
+ * Splits the incoming text into the pieces to be put into the document.
10
+ * Control characters that carry meaning become elements, and the rest are stripped because they
11
+ * would make the file impossible to open.
12
+ */
13
+ export declare function toPieces(source: string): Piece[];
2
14
  export declare function insertPlainText(view: EditorView, source: string): void;
3
15
  export declare function insertPlainTextAt(view: EditorView, source: string, from: number, to: number): void;
@@ -46,5 +46,6 @@ function insertPlainTextAt(view, source, from, to) {
46
46
  }
47
47
  export {
48
48
  insertPlainText,
49
- insertPlainTextAt
49
+ insertPlainTextAt,
50
+ toPieces
50
51
  };
@@ -14,7 +14,7 @@ import {
14
14
  shiftedTableWidth,
15
15
  tableGridCols
16
16
  } from "../../table/widths.js";
17
- import { documentGeometry } from "../documentStyles.js";
17
+ import { sectionGeometryAt } from "../documentStyles.js";
18
18
  import { TableNodeView } from "../views/tableView.js";
19
19
  var EDGE_TOLERANCE_PX = 4;
20
20
  var FALLBACK_DXA_PER_PX = 15;
@@ -127,7 +127,7 @@ function columnResize() {
127
127
  col: current.target.col,
128
128
  width: current.width
129
129
  },
130
- documentGeometry(current.view.state)
130
+ sectionGeometryAt(current.view.state, current.target.tablePos)
131
131
  );
132
132
  if (tr) current.view.dispatch(tr);
133
133
  }
@@ -144,7 +144,10 @@ function columnResize() {
144
144
  drag = {
145
145
  view,
146
146
  target,
147
- maxTotal: maxGridTotal(target.table, documentGeometry(view.state)),
147
+ maxTotal: maxGridTotal(
148
+ target.table,
149
+ sectionGeometryAt(view.state, target.tablePos)
150
+ ),
148
151
  startX: event.clientX,
149
152
  dxaPerPx: dxaPerPx(target.gridCols, target.tableDom),
150
153
  cols,
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Where the built-in comment composer stands: the stretch of text it was opened over, or nothing
3
+ * when it is shut.
4
+ *
5
+ * The stretch is the point of holding it here rather than in the component that draws the form.
6
+ * A comment is written about text the reader picked out, and writing it takes as long as it takes:
7
+ * the caret moves on, the page is scrolled, a word elsewhere is fixed. The stretch is moved along
8
+ * by every edit that happens meanwhile, so the comment lands on the text it was written for, and
9
+ * the form goes away by itself once that text is gone or the document stops taking comments.
10
+ *
11
+ * The commands here are the editor's own. A consumer opening its own composer from its own toolbar
12
+ * is a public surface this package has not settled yet, so nothing below is exported from
13
+ * `./commands`.
14
+ */
15
+ import type { Command, EditorState, Plugin } from "prosemirror-state";
16
+ import type { CommentRange } from "../commands/comments/model";
17
+ /**
18
+ * Opens the composer over the selected text. False where a comment could not go there anyway, so
19
+ * the menu entry that runs it is drawn dead rather than opening a form nothing would come of. It
20
+ * is the same question that entry is drawn from, asked of the same selection.
21
+ */
22
+ export declare const openCommentComposer: Command;
23
+ /** Closes it. Called on Cancel, on the comment being written, and on the panel being put away */
24
+ export declare const closeCommentComposer: Command;
25
+ export declare function isCommentComposerOpen(state: EditorState): boolean;
26
+ /** The stretch the open composer is writing about. Null when no composer stands */
27
+ export declare function commentComposerRange(state: EditorState): CommentRange | null;
28
+ export declare function commentComposer(): Plugin<CommentRange | null>;
@@ -0,0 +1,46 @@
1
+ // src/editor/plugins/commentComposer.ts
2
+ import { canAddComment } from "../commands/comments/editing.js";
3
+ import { panelPlugin } from "./panelState.js";
4
+ function isCommentRange(value) {
5
+ if (typeof value !== "object" || value === null) return false;
6
+ const { from, to } = value;
7
+ return typeof from === "number" && typeof to === "number";
8
+ }
9
+ var composer = panelPlugin({
10
+ name: "docxEditorCommentComposer",
11
+ isAnchor: isCommentRange,
12
+ // The stretch moves with the text around it. Its two ends are biased inwards, so text typed
13
+ // against either edge stays outside the comment and an edit that swallows the stretch leaves
14
+ // the two ends together, which is the form closing
15
+ onDocChange: (anchor, tr) => {
16
+ const from = tr.mapping.map(anchor.from, 1);
17
+ const to = tr.mapping.map(anchor.to, -1);
18
+ return to > from ? { from, to } : null;
19
+ },
20
+ // Asked after every transaction, which is what catches the mode being switched to read-only:
21
+ // that changes no text, so the mapping above never sees it. It is also what keeps the form from
22
+ // ever standing over a stretch that takes no comment, whoever opened it
23
+ closeWhen: (state, anchor) => !canAddComment(state, anchor)
24
+ });
25
+ var openCommentComposer = (state, dispatch) => {
26
+ if (!canAddComment(state)) return false;
27
+ const { from, to } = state.selection;
28
+ return composer.open({ from, to })(state, dispatch);
29
+ };
30
+ var closeCommentComposer = composer.close;
31
+ function isCommentComposerOpen(state) {
32
+ return composer.anchor(state) !== null;
33
+ }
34
+ function commentComposerRange(state) {
35
+ return composer.anchor(state);
36
+ }
37
+ function commentComposer() {
38
+ return composer.plugin;
39
+ }
40
+ export {
41
+ closeCommentComposer,
42
+ commentComposer,
43
+ commentComposerRange,
44
+ isCommentComposerOpen,
45
+ openCommentComposer
46
+ };
@@ -1,3 +1,30 @@
1
- /** Draws the text ranges identified by Word comment markers. */
2
- import { Plugin } from "prosemirror-state";
1
+ /**
2
+ * What the comments in a document come to, worked out once per edit: the threads in document
3
+ * order, the lookup a command asking after one of them uses, and the ranges drawn over the text
4
+ * each was written for.
5
+ *
6
+ * All three are read off the same walk. Which comments a document holds is decided by the document
7
+ * and nothing else, so it is a projection (`./documentProjection`) rather than something worked
8
+ * out again for every command asked, every decoration drawn and every render of the list beside
9
+ * the page.
10
+ */
11
+ import type { Plugin } from "prosemirror-state";
12
+ import { DecorationSet } from "prosemirror-view";
13
+ import { type DocumentComment } from "../commands/comments/model";
14
+ export interface DocumentComments {
15
+ comments: readonly DocumentComment[];
16
+ /**
17
+ * The comments by id. A document can hold the same id twice - a reference copied and pasted
18
+ * carries the one it was written with - and the first of them wins, which is the one a search
19
+ * through the list in document order arrived at.
20
+ */
21
+ byId: ReadonlyMap<string, DocumentComment>;
22
+ /** The stretch each comment was written for, drawn as it stood when the edit landed */
23
+ decorations: DecorationSet;
24
+ }
25
+ export declare const commentProjection: import("./documentProjection").DocumentProjection<DocumentComments>;
26
+ /**
27
+ * What a state built by `createEditor` registers: the comments it goes on to answer, and the
28
+ * ranges drawn over the text identified by the Word comment markers holding them.
29
+ */
3
30
  export declare function commentDecorations(): Plugin;
@@ -1,23 +1,97 @@
1
1
  // src/editor/plugins/commentDecorations.ts
2
- import { Plugin } from "prosemirror-state";
3
2
  import { Decoration, DecorationSet } from "prosemirror-view";
3
+ import { storyKey, storyOf, storyText } from "../../docx/story.js";
4
4
  import { editorClassNames } from "../../styles/classNames.js";
5
- import { documentComments } from "../commands/commentCommands.js";
6
- function commentDecorations() {
7
- return new Plugin({
8
- props: {
9
- decorations(state) {
10
- const decorations = documentComments(state).filter((comment) => comment.from < comment.to).map(
11
- (comment) => Decoration.inline(comment.from, comment.to, {
12
- class: editorClassNames.commentRange,
13
- "data-comment-id": comment.id
14
- })
15
- );
16
- return DecorationSet.create(state.doc, decorations);
17
- }
5
+ import {
6
+ repliesAttr,
7
+ stringAttr
8
+ } from "../commands/comments/model.js";
9
+ import { documentProjection } from "./documentProjection.js";
10
+ function markerPositions(doc) {
11
+ const starts = /* @__PURE__ */ new Map();
12
+ const ends = /* @__PURE__ */ new Map();
13
+ const references = [];
14
+ doc.descendants((node, pos) => {
15
+ const id = stringAttr(node.attrs.id);
16
+ if (id === null) return true;
17
+ if (node.type.name === "commentStart" && !starts.has(id)) {
18
+ starts.set(id, pos);
19
+ } else if (node.type.name === "commentEnd" && !ends.has(id)) {
20
+ ends.set(id, pos);
21
+ } else if (node.type.name === "commentReference") {
22
+ references.push({ node, pos });
18
23
  }
24
+ return true;
25
+ });
26
+ return { starts, ends, references };
27
+ }
28
+ function bodyText(doc, id) {
29
+ return storyText(storyOf(doc, storyKey("comment", id)));
30
+ }
31
+ function commentsIn(doc) {
32
+ const markers = markerPositions(doc);
33
+ return markers.references.map(({ node, pos }) => {
34
+ const id = stringAttr(node.attrs.id) ?? "";
35
+ const start = markers.starts.get(id);
36
+ const end = markers.ends.get(id);
37
+ const hasRange = start !== void 0 && end !== void 0 && start < end;
38
+ const point = start ?? end ?? pos;
39
+ return {
40
+ id,
41
+ author: stringAttr(node.attrs.author),
42
+ authorId: stringAttr(node.attrs.authorId),
43
+ initials: stringAttr(node.attrs.initials),
44
+ date: stringAttr(node.attrs.date),
45
+ text: bodyText(doc, id),
46
+ from: hasRange ? start + 1 : point,
47
+ to: hasRange ? end : point,
48
+ referencePos: pos,
49
+ resolved: node.attrs.resolved === true,
50
+ replies: repliesAttr(node.attrs.replies).map((reply) => ({
51
+ id: reply.id,
52
+ author: reply.author,
53
+ authorId: reply.authorId,
54
+ initials: reply.initials,
55
+ date: reply.date,
56
+ text: bodyText(doc, reply.id)
57
+ }))
58
+ };
19
59
  });
20
60
  }
61
+ function deriveComments(doc) {
62
+ const comments = commentsIn(doc);
63
+ const byId = /* @__PURE__ */ new Map();
64
+ for (const comment of comments) {
65
+ if (!byId.has(comment.id)) byId.set(comment.id, comment);
66
+ }
67
+ return {
68
+ comments,
69
+ byId,
70
+ decorations: DecorationSet.create(
71
+ doc,
72
+ comments.filter((comment) => comment.from < comment.to).map(
73
+ (comment) => Decoration.inline(comment.from, comment.to, {
74
+ class: editorClassNames.commentRange,
75
+ "data-comment-id": comment.id
76
+ })
77
+ )
78
+ )
79
+ };
80
+ }
81
+ var commentProjection = documentProjection(
82
+ "docxEditorComments",
83
+ deriveComments,
84
+ {
85
+ // The set the last edit derived. Written as a method so that `this` is the plugin holding it
86
+ decorations(state) {
87
+ return this.getState(state)?.decorations;
88
+ }
89
+ }
90
+ );
91
+ function commentDecorations() {
92
+ return commentProjection.plugin;
93
+ }
21
94
  export {
22
- commentDecorations
95
+ commentDecorations,
96
+ commentProjection
23
97
  };