@portone/docx-editor 0.1.0 → 0.2.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 (72) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +17 -5
  3. package/dist/DocxEditor.d.ts +34 -10
  4. package/dist/DocxEditor.js +69 -24
  5. package/dist/core.d.ts +2 -0
  6. package/dist/core.js +2 -0
  7. package/dist/docx/commentOnlyChange.d.ts +45 -0
  8. package/dist/docx/commentOnlyChange.js +145 -0
  9. package/dist/docx/comments/constants.d.ts +8 -0
  10. package/dist/docx/comments/constants.js +6 -0
  11. package/dist/docx/comments/contentTypes.d.ts +7 -0
  12. package/dist/docx/comments/contentTypes.js +38 -0
  13. package/dist/docx/comments/model.d.ts +2 -0
  14. package/dist/docx/comments/model.js +3 -0
  15. package/dist/docx/comments/people.d.ts +39 -0
  16. package/dist/docx/comments/people.js +198 -0
  17. package/dist/docx/comments/reading.d.ts +16 -1
  18. package/dist/docx/comments/reading.js +15 -5
  19. package/dist/docx/comments/writing.d.ts +4 -1
  20. package/dist/docx/comments/writing.js +12 -32
  21. package/dist/docx/importParagraph.js +1 -0
  22. package/dist/editor/commands/breakCommands.js +4 -1
  23. package/dist/editor/commands/canRunCommand.d.ts +3 -2
  24. package/dist/editor/commands/canRunCommand.js +1 -1
  25. package/dist/editor/commands/comments/editing.js +6 -4
  26. package/dist/editor/commands/comments/model.d.ts +9 -0
  27. package/dist/editor/commands/comments/reading.d.ts +7 -0
  28. package/dist/editor/commands/comments/reading.js +14 -0
  29. package/dist/editor/commands/formatting/editing.js +6 -1
  30. package/dist/editor/commands/formatting/reading.js +13 -4
  31. package/dist/editor/commands/historyCommands.js +14 -6
  32. package/dist/editor/commands/index.d.ts +7 -1
  33. package/dist/editor/commands/index.js +4 -0
  34. package/dist/editor/commands/linkCommands.js +2 -0
  35. package/dist/editor/commands/lockCommands.js +2 -0
  36. package/dist/editor/commands/tabCommands.js +2 -1
  37. package/dist/editor/createEditor.d.ts +13 -3
  38. package/dist/editor/createEditor.js +21 -7
  39. package/dist/editor/insertImage.js +4 -1
  40. package/dist/editor/insertTable.js +2 -1
  41. package/dist/editor/paragraphEdits.d.ts +2 -0
  42. package/dist/editor/paragraphEdits.js +2 -0
  43. package/dist/editor/plugins/documentProtection.d.ts +21 -0
  44. package/dist/editor/plugins/documentProtection.js +44 -0
  45. package/dist/editor/plugins/imagePaste.js +4 -1
  46. package/dist/editor/plugins/keymap.d.ts +11 -1
  47. package/dist/editor/plugins/keymap.js +24 -4
  48. package/dist/editor/plugins/lockedContent.d.ts +4 -2
  49. package/dist/editor/plugins/lockedContent.js +1 -1
  50. package/dist/editor/plugins/tableContextMenu.js +2 -1
  51. package/dist/editor/plugins/textContextMenu.js +6 -1
  52. package/dist/index.d.ts +2 -0
  53. package/dist/numbering/markers.js +13 -1
  54. package/dist/schema/docxSchema.js +5 -0
  55. package/dist/schema/locks.d.ts +31 -3
  56. package/dist/schema/locks.js +54 -3
  57. package/dist/schema/protection.d.ts +77 -0
  58. package/dist/schema/protection.js +170 -0
  59. package/dist/schema/protectionState.d.ts +20 -0
  60. package/dist/schema/protectionState.js +37 -0
  61. package/dist/styles/fontStack.d.ts +9 -0
  62. package/dist/styles/fontStack.js +9 -8
  63. package/dist/table/cellFormatting.js +3 -1
  64. package/dist/table/commands.js +1 -1
  65. package/dist/table/merge.js +2 -2
  66. package/dist/ui/CommentsPanel.d.ts +3 -3
  67. package/dist/ui/CommentsPanel.js +26 -18
  68. package/dist/ui/FontFamilySelect.js +21 -7
  69. package/dist/ui/LinkCard.d.ts +1 -2
  70. package/dist/ui/LinkCard.js +3 -4
  71. package/dist/ui/TextMenu.js +15 -10
  72. package/package.json +2 -2
@@ -12,6 +12,8 @@ import {
12
12
  updateComment,
13
13
  updateCommentReply
14
14
  } from "../editor/commands/commentCommands.js";
15
+ import { commentOwned } from "../schema/protection.js";
16
+ import { protectionOf } from "../schema/protectionState.js";
15
17
  import { editorClassNames } from "../styles/classNames.js";
16
18
  import { CommentComposer, FocusedTextarea } from "./comments/CommentComposer.js";
17
19
  import {
@@ -41,7 +43,6 @@ function sameTarget(left, right) {
41
43
  function CommentsPanel({
42
44
  view,
43
45
  state,
44
- readOnly,
45
46
  composerOpen,
46
47
  author,
47
48
  closeComposer,
@@ -49,6 +50,9 @@ function CommentsPanel({
49
50
  allCommentsOpen
50
51
  }) {
51
52
  const comments = useMemo(() => documentComments(state), [state.doc]);
53
+ const rules = protectionOf(state);
54
+ const writer = author != null && rules.protection !== "readOnly" ? author : null;
55
+ const owned = (authorId) => writer !== null && commentOwned(rules, authorId);
52
56
  const panel = useRef(null);
53
57
  const [editing, setEditing] = useState(null);
54
58
  const [draft, setDraft] = useState("");
@@ -108,7 +112,7 @@ function CommentsPanel({
108
112
  className: editorClassNames.commentsCanvas,
109
113
  style: { height: allCommentsOpen ? void 0 : `${canvasHeight}px` },
110
114
  children: [
111
- composerOpen && !readOnly && /* @__PURE__ */ jsx(
115
+ composerOpen && writer !== null && /* @__PURE__ */ jsx(
112
116
  "div",
113
117
  {
114
118
  className: editorClassNames.commentPosition,
@@ -119,7 +123,7 @@ function CommentsPanel({
119
123
  children: /* @__PURE__ */ jsx(
120
124
  CommentComposer,
121
125
  {
122
- author,
126
+ author: writer,
123
127
  label: "Comment text",
124
128
  submitLabel: "Comment",
125
129
  onClose: closeComposer,
@@ -127,8 +131,9 @@ function CommentsPanel({
127
131
  view,
128
132
  addComment({
129
133
  text,
130
- author: author.name,
131
- initials: author.initials
134
+ author: writer.name,
135
+ authorId: writer.id,
136
+ initials: writer.initials
132
137
  })
133
138
  )
134
139
  }
@@ -137,7 +142,8 @@ function CommentsPanel({
137
142
  ),
138
143
  visible.map((comment) => {
139
144
  const rootTarget = { commentId: comment.id, replyId: null };
140
- const rootEditing = !readOnly && sameTarget(editing, rootTarget);
145
+ const rootOwned = owned(comment.authorId);
146
+ const rootEditing = rootOwned && sameTarget(editing, rootTarget);
141
147
  return /* @__PURE__ */ jsx(
142
148
  "article",
143
149
  {
@@ -160,8 +166,8 @@ function CommentsPanel({
160
166
  ]
161
167
  }
162
168
  ),
163
- !readOnly && !rootEditing && /* @__PURE__ */ jsxs("div", { className: editorClassNames.commentIconActions, children: [
164
- !comment.resolved && /* @__PURE__ */ jsx(
169
+ writer !== null && !rootEditing && /* @__PURE__ */ jsxs("div", { className: editorClassNames.commentIconActions, children: [
170
+ rootOwned && !comment.resolved && /* @__PURE__ */ jsx(
165
171
  "button",
166
172
  {
167
173
  type: "button",
@@ -190,7 +196,7 @@ function CommentsPanel({
190
196
  children: comment.resolved ? /* @__PURE__ */ jsx(RotateCcw, { size: 16, "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" })
191
197
  }
192
198
  ),
193
- !comment.resolved && /* @__PURE__ */ jsx(
199
+ rootOwned && !comment.resolved && /* @__PURE__ */ jsx(
194
200
  "button",
195
201
  {
196
202
  type: "button",
@@ -209,7 +215,7 @@ function CommentsPanel({
209
215
  value: draft,
210
216
  onChange: setDraft
211
217
  }
212
- ) : readOnly || comment.resolved ? /* @__PURE__ */ jsx("p", { className: editorClassNames.commentBody, children: comment.text }) : /* @__PURE__ */ jsx(
218
+ ) : writer === null || comment.resolved ? /* @__PURE__ */ jsx("p", { className: editorClassNames.commentBody, children: comment.text }) : /* @__PURE__ */ jsx(
213
219
  "button",
214
220
  {
215
221
  type: "button",
@@ -222,7 +228,7 @@ function CommentsPanel({
222
228
  children: comment.text
223
229
  }
224
230
  ),
225
- !readOnly && /* @__PURE__ */ jsx("div", { className: editorClassNames.commentActions, children: rootEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
231
+ rootEditing && /* @__PURE__ */ jsxs("div", { className: editorClassNames.commentActions, children: [
226
232
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setEditing(null), children: "Cancel" }),
227
233
  /* @__PURE__ */ jsx(
228
234
  "button",
@@ -234,13 +240,14 @@ function CommentsPanel({
234
240
  children: "Save"
235
241
  }
236
242
  )
237
- ] }) : null }),
243
+ ] }),
238
244
  comment.replies.map((reply) => {
239
245
  const target = {
240
246
  commentId: comment.id,
241
247
  replyId: reply.id
242
248
  };
243
- const isEditing = !readOnly && sameTarget(editing, target);
249
+ const replyOwned = owned(reply.authorId);
250
+ const isEditing = replyOwned && sameTarget(editing, target);
244
251
  return /* @__PURE__ */ jsxs(
245
252
  "div",
246
253
  {
@@ -258,7 +265,7 @@ function CommentsPanel({
258
265
  onChange: setDraft
259
266
  }
260
267
  ) : /* @__PURE__ */ jsx("p", { className: editorClassNames.commentBody, children: reply.text }),
261
- !readOnly && !comment.resolved && /* @__PURE__ */ jsx("div", { className: editorClassNames.commentActions, children: isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
268
+ replyOwned && !comment.resolved && /* @__PURE__ */ jsx("div", { className: editorClassNames.commentActions, children: isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
262
269
  /* @__PURE__ */ jsx(
263
270
  "button",
264
271
  {
@@ -313,10 +320,10 @@ function CommentsPanel({
313
320
  reply.id
314
321
  );
315
322
  }),
316
- replying === comment.id && !readOnly && !comment.resolved && /* @__PURE__ */ jsx(
323
+ replying === comment.id && writer !== null && !comment.resolved && /* @__PURE__ */ jsx(
317
324
  CommentComposer,
318
325
  {
319
- author,
326
+ author: writer,
320
327
  label: "Reply text",
321
328
  submitLabel: "Reply",
322
329
  onClose: () => setReplying(null),
@@ -324,8 +331,9 @@ function CommentsPanel({
324
331
  view,
325
332
  addCommentReply(comment.id, {
326
333
  text,
327
- author: author.name,
328
- initials: author.initials
334
+ author: writer.name,
335
+ authorId: writer.id,
336
+ initials: writer.initials
329
337
  })
330
338
  )
331
339
  }
@@ -3,7 +3,10 @@ import {
3
3
  setFontFamily
4
4
  } from "../editor/commands/formattingCommands.js";
5
5
  import { editorClassNames } from "../styles/classNames.js";
6
- import { withFontFallback } from "../styles/fontStack.js";
6
+ import {
7
+ comparableFontName,
8
+ withFontFallback
9
+ } from "../styles/fontStack.js";
7
10
  import { DEFAULT_FONTS } from "../styles/fonts.js";
8
11
  import { Tooltip } from "./Tooltip.js";
9
12
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -11,11 +14,21 @@ var LABEL = "Font";
11
14
  var CLEAR = ";default";
12
15
  var MIXED = "";
13
16
  function optionNames(current, fonts, documentFonts) {
14
- const names = [...fonts, ...documentFonts];
15
- if (current.kind !== "mixed" && !names.includes(current.name)) {
16
- names.push(current.name);
17
+ const names = [
18
+ ...fonts,
19
+ ...documentFonts,
20
+ ...current.kind === "mixed" ? [] : [current.name]
21
+ ];
22
+ const byComparable = /* @__PURE__ */ new Map();
23
+ for (const name of names) {
24
+ const comparable = comparableFontName(name);
25
+ if (!byComparable.has(comparable)) byComparable.set(comparable, name);
17
26
  }
18
- return Array.from(new Set(names));
27
+ return Array.from(byComparable.values());
28
+ }
29
+ function selectedValue(options, name) {
30
+ const comparable = comparableFontName(name);
31
+ return options.find((option) => comparableFontName(option) === comparable) ?? name;
19
32
  }
20
33
  function FontFamilySelect({
21
34
  current,
@@ -25,7 +38,8 @@ function FontFamilySelect({
25
38
  disabled,
26
39
  run
27
40
  }) {
28
- const value = current.kind === "mixed" ? MIXED : current.name;
41
+ const options = optionNames(current, fonts, documentFonts);
42
+ const value = current.kind === "mixed" ? MIXED : selectedValue(options, current.name);
29
43
  return /* @__PURE__ */ jsx(Tooltip, { label: LABEL, children: /* @__PURE__ */ jsxs(
30
44
  "select",
31
45
  {
@@ -41,7 +55,7 @@ function FontFamilySelect({
41
55
  children: [
42
56
  current.kind === "mixed" && /* @__PURE__ */ jsx("option", { value: MIXED }),
43
57
  /* @__PURE__ */ jsx("option", { value: CLEAR, children: "Default" }),
44
- optionNames(current, fonts, documentFonts).map((name) => /* @__PURE__ */ jsx(
58
+ options.map((name) => /* @__PURE__ */ jsx(
45
59
  "option",
46
60
  {
47
61
  value: name,
@@ -7,6 +7,5 @@ export interface LinkCardProps {
7
7
  view: EditorView;
8
8
  state: EditorState;
9
9
  link: ActiveLinkSpan;
10
- readOnly?: boolean;
11
10
  }
12
- export declare function LinkCard({ view, state, link, readOnly, }: LinkCardProps): ReactElement | null;
11
+ export declare function LinkCard({ view, state, link, }: LinkCardProps): ReactElement | null;
@@ -49,8 +49,7 @@ function useEscape(onEscape) {
49
49
  function LinkCard({
50
50
  view,
51
51
  state,
52
- link,
53
- readOnly = false
52
+ link
54
53
  }) {
55
54
  const box = useRef(null);
56
55
  const [hidden, setHidden] = useState(false);
@@ -94,7 +93,7 @@ function LinkCard({
94
93
  children: /* @__PURE__ */ jsx(ExternalLink, { size: ICON_SIZE, "aria-hidden": "true" })
95
94
  }
96
95
  ),
97
- !readOnly && openLinkPanel(state) && /* @__PURE__ */ jsx(
96
+ openLinkPanel(state) && /* @__PURE__ */ jsx(
98
97
  "button",
99
98
  {
100
99
  type: "button",
@@ -106,7 +105,7 @@ function LinkCard({
106
105
  children: /* @__PURE__ */ jsx(Pencil, { size: ICON_SIZE, "aria-hidden": "true" })
107
106
  }
108
107
  ),
109
- !readOnly && removeLink(state) && /* @__PURE__ */ jsx(
108
+ removeLink(state) && /* @__PURE__ */ jsx(
110
109
  "button",
111
110
  {
112
111
  type: "button",
@@ -21,6 +21,7 @@ import { insertClipboardData } from "../editor/externalClipboard.js";
21
21
  import {
22
22
  closeTextMenu
23
23
  } from "../editor/plugins/textContextMenu.js";
24
+ import { editsShut } from "../schema/protectionState.js";
24
25
  import { editorClassNames } from "../styles/classNames.js";
25
26
  import { usePanelAtPoint } from "./panelPlacement.js";
26
27
  import { commandRunner } from "./runCommand.js";
@@ -103,8 +104,16 @@ function TextMenu({
103
104
  const keys = useMenuKeyboard({ menu: box, onClose: close });
104
105
  const selected = !state.selection.empty;
105
106
  const shut = selectionTouchesLocked(state);
107
+ const bodyOpen = !editsShut(state);
108
+ const copy = {
109
+ label: "Copy",
110
+ icon: Copy,
111
+ hint: `${MOD}C`,
112
+ enabled: selected,
113
+ run: () => clipboardCommand(view, "copy")
114
+ };
106
115
  const groups = [
107
- [
116
+ bodyOpen ? [
108
117
  {
109
118
  label: "Cut",
110
119
  icon: Scissors,
@@ -112,13 +121,7 @@ function TextMenu({
112
121
  enabled: selected && !shut,
113
122
  run: () => clipboardCommand(view, "cut")
114
123
  },
115
- {
116
- label: "Copy",
117
- icon: Copy,
118
- hint: `${MOD}C`,
119
- enabled: selected,
120
- run: () => clipboardCommand(view, "copy")
121
- },
124
+ copy,
122
125
  {
123
126
  label: "Paste",
124
127
  icon: ClipboardPaste,
@@ -134,7 +137,7 @@ function TextMenu({
134
137
  enabled: selected && !shut,
135
138
  run: () => run(deleteSelection)
136
139
  }
137
- ]
140
+ ] : [copy]
138
141
  ];
139
142
  groups.push([
140
143
  {
@@ -144,7 +147,9 @@ function TextMenu({
144
147
  run: () => onAddComment?.()
145
148
  }
146
149
  ]);
147
- if (allowLocking) groups.push([lockItem(selectionLock(state), run)]);
150
+ if (allowLocking && bodyOpen) {
151
+ groups.push([lockItem(selectionLock(state), run)]);
152
+ }
148
153
  const choose = (item) => {
149
154
  if (!item.enabled) return;
150
155
  item.run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portone/docx-editor",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A simple DOCX editor for React, built directly on OOXML.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -79,7 +79,7 @@
79
79
  "@playwright/test": "^1.62.1",
80
80
  "@types/node": "^26.2.0",
81
81
  "@types/react": "19.2.18",
82
- "@types/react-dom": "19.2.4",
82
+ "@types/react-dom": "19.2.5",
83
83
  "@vitejs/plugin-react": "^6.0.5",
84
84
  "esbuild": "^0.28.1",
85
85
  "jsdom": "^30.0.1",