@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
@@ -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.4.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,22 +0,0 @@
1
- import { Slice } from "prosemirror-model";
2
- import { type EditorState, Plugin, type Transaction } from "prosemirror-state";
3
- import type { EditorView } from "prosemirror-view";
4
- import { type NewLists } from "../numbering/listRegistry";
5
- import type { ImageToInsert } from "./insertImage";
6
- /** What a paste puts in: the content, and the definitions of the lists it started */
7
- export interface PastedContent {
8
- slice: Slice;
9
- newLists: NewLists;
10
- }
11
- export declare function richHtmlSlice(state: EditorState, document: Document, source: string, images?: ReadonlyMap<string, ImageToInsert>): PastedContent | null;
12
- /**
13
- * The transaction a paste is put in with: the content, and the definitions of the lists it started
14
- * recorded on the document node so that the export writes them out and undo takes them back.
15
- */
16
- export declare function withPastedContent(tr: Transaction, content: PastedContent): Transaction;
17
- export declare function insertRichHtml(view: EditorView, source: string): boolean;
18
- export declare function insertClipboardData(view: EditorView, data: {
19
- html?: string;
20
- text?: string;
21
- }): void;
22
- export declare function externalClipboard(): Plugin;