@portone/docx-editor 0.1.1 → 0.2.1
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.
- package/CHANGELOG.md +82 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +17 -5
- package/dist/DocxEditor.d.ts +34 -10
- package/dist/DocxEditor.js +69 -24
- package/dist/core.d.ts +2 -0
- package/dist/core.js +2 -0
- package/dist/docx/commentOnlyChange.d.ts +47 -0
- package/dist/docx/commentOnlyChange.js +162 -0
- package/dist/docx/comments/constants.d.ts +11 -3
- package/dist/docx/comments/constants.js +10 -3
- package/dist/docx/comments/contentTypes.d.ts +7 -0
- package/dist/docx/comments/contentTypes.js +38 -0
- package/dist/docx/comments/grammar.d.ts +80 -0
- package/dist/docx/comments/grammar.js +213 -0
- package/dist/docx/comments/model.d.ts +2 -0
- package/dist/docx/comments/model.js +3 -0
- package/dist/docx/comments/people.d.ts +39 -0
- package/dist/docx/comments/people.js +196 -0
- package/dist/docx/comments/reading.d.ts +18 -1
- package/dist/docx/comments/reading.js +33 -20
- package/dist/docx/comments/verifying.d.ts +44 -0
- package/dist/docx/comments/verifying.js +206 -0
- package/dist/docx/comments/writing.d.ts +4 -1
- package/dist/docx/comments/writing.js +84 -77
- package/dist/docx/exportDocx.js +2 -1
- package/dist/docx/formatting/direct.js +3 -6
- package/dist/docx/headersFooters.js +8 -10
- package/dist/docx/importParagraph.js +1 -0
- package/dist/docx/notes.js +3 -5
- package/dist/docx/paraProps.js +51 -46
- package/dist/docx/propsXml.d.ts +20 -2
- package/dist/docx/propsXml.js +72 -32
- package/dist/docx/relationships.js +8 -3
- package/dist/docx/runProps.js +34 -34
- package/dist/docx/sdt.js +16 -14
- package/dist/docx/serializeBlock.js +1 -2
- package/dist/docx/serializeParagraph.d.ts +2 -1
- package/dist/docx/serializeParagraph.js +25 -14
- package/dist/docx/serializeTable.js +38 -24
- package/dist/docx/storyProjection.d.ts +36 -0
- package/dist/docx/storyProjection.js +23 -0
- package/dist/docx/tableFormatting/editing.js +49 -73
- package/dist/docx/tableTemplate.js +24 -3
- package/dist/docx/theme.d.ts +0 -6
- package/dist/docx/theme.js +0 -8
- package/dist/editor/clipboard/images.js +17 -3
- package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
- package/dist/editor/clipboard/inlineFormatting.js +1 -0
- package/dist/editor/commands/breakCommands.js +6 -2
- package/dist/editor/commands/canRunCommand.d.ts +3 -2
- package/dist/editor/commands/canRunCommand.js +2 -2
- package/dist/editor/commands/comments/editing.js +9 -10
- package/dist/editor/commands/comments/model.d.ts +9 -0
- package/dist/editor/commands/comments/reading.d.ts +7 -0
- package/dist/editor/commands/comments/reading.js +14 -0
- package/dist/editor/commands/formatting/editing.js +6 -1
- package/dist/editor/commands/historyCommands.js +14 -5
- package/dist/editor/commands/index.d.ts +7 -1
- package/dist/editor/commands/index.js +4 -0
- package/dist/editor/commands/linkCommands.js +2 -0
- package/dist/editor/commands/lockCommands.js +12 -1
- package/dist/editor/commands/tabCommands.js +4 -2
- package/dist/editor/createEditor.d.ts +13 -3
- package/dist/editor/createEditor.js +23 -15
- package/dist/editor/externalClipboard.js +166 -4
- package/dist/editor/insertImage.js +6 -2
- package/dist/editor/insertTable.js +2 -1
- package/dist/editor/paragraphEdits.d.ts +2 -0
- package/dist/editor/paragraphEdits.js +2 -0
- package/dist/editor/plugins/documentProtection.d.ts +21 -0
- package/dist/editor/plugins/documentProtection.js +44 -0
- package/dist/editor/plugins/imagePaste.js +4 -1
- package/dist/editor/plugins/keymap.d.ts +11 -1
- package/dist/editor/plugins/keymap.js +24 -4
- package/dist/editor/plugins/lockedContent.d.ts +4 -2
- package/dist/editor/plugins/lockedContent.js +2 -2
- package/dist/editor/plugins/tableContextMenu.js +2 -1
- package/dist/editor/plugins/textContextMenu.js +6 -1
- package/dist/index.d.ts +2 -0
- package/dist/numbering/listTemplate.js +32 -10
- package/dist/ooxml/element.d.ts +52 -0
- package/dist/ooxml/element.js +49 -0
- package/dist/ooxml/fragment.d.ts +53 -0
- package/dist/ooxml/fragment.js +76 -0
- package/dist/ooxml/image.js +4 -3
- package/dist/ooxml/names.d.ts +27 -0
- package/dist/ooxml/names.js +29 -0
- package/dist/ooxml/precedence.d.ts +31 -0
- package/dist/ooxml/precedence.js +51 -0
- package/dist/ooxml/xml.d.ts +31 -0
- package/dist/ooxml/xml.js +27 -0
- package/dist/page/blockKinds.d.ts +42 -0
- package/dist/page/blockKinds.js +0 -0
- package/dist/page/measureBlocks.d.ts +14 -1
- package/dist/page/measureBlocks.js +31 -11
- package/dist/page/pageDecorations.d.ts +16 -0
- package/dist/page/pageDecorations.js +1 -0
- package/dist/page/pageLayout.d.ts +8 -43
- package/dist/page/pageLayout.js +28 -51
- package/dist/page/tableMeasurements.d.ts +10 -2
- package/dist/page/tableMeasurements.js +20 -16
- package/dist/page/usePageLayout.d.ts +15 -0
- package/dist/page/usePageLayout.js +30 -2
- package/dist/schema/attrRoles.d.ts +35 -0
- package/dist/schema/attrRoles.js +106 -0
- package/dist/schema/docxSchema.d.ts +6 -0
- package/dist/schema/docxSchema.js +216 -100
- package/dist/schema/editGuard.d.ts +99 -0
- package/dist/schema/editGuard.js +43 -0
- package/dist/schema/guards.d.ts +68 -0
- package/dist/schema/guards.js +82 -0
- package/dist/schema/locks.d.ts +9 -8
- package/dist/schema/locks.js +32 -16
- package/dist/schema/preservedGuards.d.ts +30 -0
- package/dist/schema/preservedGuards.js +50 -0
- package/dist/schema/protection.d.ts +84 -0
- package/dist/schema/protection.js +171 -0
- package/dist/schema/protectionState.d.ts +20 -0
- package/dist/schema/protectionState.js +37 -0
- package/dist/schema/sourceEquality.d.ts +27 -0
- package/dist/schema/sourceEquality.js +65 -0
- package/dist/table/cellFormatting.js +5 -2
- package/dist/table/commands.d.ts +2 -2
- package/dist/table/commands.js +2 -2
- package/dist/table/index.d.ts +2 -2
- package/dist/table/merge.d.ts +2 -2
- package/dist/table/merge.js +3 -3
- package/dist/ui/CommentsPanel.d.ts +3 -3
- package/dist/ui/CommentsPanel.js +26 -18
- package/dist/ui/LinkCard.d.ts +1 -2
- package/dist/ui/LinkCard.js +3 -4
- package/dist/ui/TextMenu.js +15 -10
- package/package.json +5 -2
- package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
- package/dist/editor/plugins/bookmarkProtection.js +0 -28
- package/dist/editor/plugins/noteProtection.d.ts +0 -3
- package/dist/editor/plugins/noteProtection.js +0 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
# @portone/docx-editor
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#53](https://github.com/portone-io/docx-editor/pull/53) [`d264803`](https://github.com/portone-io/docx-editor/commit/d264803381c3272ae31e1af1faa7fd158b95a81f) Thanks [@Deea222](https://github.com/Deea222)! - Stop text copied out of the editor from carrying the document's private data.
|
|
8
|
+
|
|
9
|
+
A copy was drawn the way the editor draws itself, so the HTML it left on the clipboard held the paragraph and run XML, the name and recorded identity of a comment's author, what the comment and its replies say, and the body of a footnote.
|
|
10
|
+
All of it landed in whatever application it was pasted into, and in `comment` mode a reader who may not change the body could take the body out this way.
|
|
11
|
+
A copy now carries only what a reader of it needs, and the paragraph style it names is the style's id rather than the whole `w:pPr`.
|
|
12
|
+
A link goes out as a link, so it can be followed where it lands and comes back as one when it is pasted here again.
|
|
13
|
+
|
|
14
|
+
The plain text beside it says what was copied: a tab is a tab, a line break is a line, a page break is a form feed, and a table's cells stand apart by a tab and its rows by a line, so a table pasted into a spreadsheet arrives as a table.
|
|
15
|
+
A cell holding a line break is the exception, since the line inside it reads as the start of the next row.
|
|
16
|
+
|
|
17
|
+
- [#50](https://github.com/portone-io/docx-editor/pull/50) [`41d82c5`](https://github.com/portone-io/docx-editor/commit/41d82c5bee74597baedd813c43c8dcfcdfe74fbf) Thanks [@Deea222](https://github.com/Deea222)! - Stop `onlyCommentsChangedBy` from excusing a part a submission relates as a comment part.
|
|
18
|
+
|
|
19
|
+
The three comment parts are left out of the byte comparison, and which parts those were came from the submitted file's own relationships.
|
|
20
|
+
A file could relate a second comments, extended comments or people part at any part it liked and have that part go uncompared, so a rewritten styles part, a settings part pointing at a template off the package, replaced image bytes or forged document properties were all reported as a change to nothing but comments.
|
|
21
|
+
The parts left out are now the ones the reader opened, a comment part may be related once, one related for the first time has to be a part the submission brought with it, and a relationship part naming one id twice is turned down.
|
|
22
|
+
|
|
23
|
+
A file that relates an extended comments part but carries no comments part now has that part read, so writing the first comment into it writes that part rather than a second one beside it.
|
|
24
|
+
Two extended parts related at once was a file whose settled threads a reader would lose, since only the first of them is read.
|
|
25
|
+
|
|
26
|
+
This affects 0.2.0. A server that accepted files on this verdict should upgrade and run the check again over what it accepted, where such a file now answers `part-changed` naming the part, or `relationship-changed` naming the relationship part.
|
|
27
|
+
|
|
28
|
+
- [#56](https://github.com/portone-io/docx-editor/pull/56) [`467f6f5`](https://github.com/portone-io/docx-editor/commit/467f6f5781dbf6eb6739dbf3369155c7287ee608) Thanks [@Deea222](https://github.com/Deea222)! - Commands and `canRunCommand` now report false where a bookmark marker or a note reference would be removed, instead of reporting true and changing nothing.
|
|
29
|
+
|
|
30
|
+
A refusal over one of those markers also ends an open IME composition, the way a refusal over a locked control already did.
|
|
31
|
+
|
|
32
|
+
- [#59](https://github.com/portone-io/docx-editor/pull/59) [`6d7b97f`](https://github.com/portone-io/docx-editor/commit/6d7b97fdb89e0aba7ac13057b503ec510a5550c1) Thanks [@Deea222](https://github.com/Deea222)! - Formatting XML is now written through shared primitives that consistently escape attribute values while preserving existing formatting behavior.
|
|
33
|
+
|
|
34
|
+
- [#58](https://github.com/portone-io/docx-editor/pull/58) [`d9edb75`](https://github.com/portone-io/docx-editor/commit/d9edb75fa45e1a6cc81e5741a5242cf500107091) Thanks [@Deea222](https://github.com/Deea222)! - Pagination measures every block as a list of break candidates; no visible change
|
|
35
|
+
|
|
36
|
+
- [#55](https://github.com/portone-io/docx-editor/pull/55) [`7110baa`](https://github.com/portone-io/docx-editor/commit/7110baae68b75323720a39374c6df58db4367896) Thanks [@Deea222](https://github.com/Deea222)! - Turn down a raw OOXML fragment that does not hold its shape as it enters the document, rather than writing it into the exported file.
|
|
37
|
+
|
|
38
|
+
Keep imported formatting when a document uses an inherited default namespace or an alternative WordprocessingML prefix. Reject nested namespace rebinding that could disconnect hyperlinks from their targets on export.
|
|
39
|
+
|
|
40
|
+
The editor draws the original XML of a paragraph, a run, a table, an image or an annotation into the page as a `data-` attribute, so that it can read the live DOM back after an IME composition or a browser edit. A fragment written into one of those attributes from outside, by a consumer plugin or through `view.pasteHTML`, is now held to what that attribute goes back out as: a whole element under an expected name, an opening tag's attributes, or an opening tag the writer closes itself. One that does not hold it is turned down along with the rule reading it, so the content settles one level plainer - a paragraph keeps its text and loses its properties, a run loses its mark, a content control or a hyperlink loses its wrapper - instead of a `data-ppr` reading `</w:p><w:p>...` writing a second paragraph into the exported body, or one that never closed reaching export and failing the whole document with `malformed-xml`.
|
|
41
|
+
|
|
42
|
+
- [#54](https://github.com/portone-io/docx-editor/pull/54) [`8aea477`](https://github.com/portone-io/docx-editor/commit/8aea477dceab5462c15903b25b29c738980aff47) Thanks [@Deea222](https://github.com/Deea222)! - An untouched table opened in the editor is written back byte for byte, its `w:tblGridChange` included.
|
|
43
|
+
|
|
44
|
+
Export decided whether a block was untouched by comparing it with the one import produced, attrs and all.
|
|
45
|
+
Opening a document works some of those attrs out again from the formatting around them - a table's shared cell borders among them - so a table nobody had touched compared unequal and was rebuilt, and the rebuild dropped the markup the writer does not model.
|
|
46
|
+
|
|
47
|
+
Each attr now declares whether the writer writes from it, whether the editor works it out for the screen, or whether it identifies something in the open document, and the comparison ignores the ones worked out for the screen.
|
|
48
|
+
Exporting through `createEditorState` is held to the same byte identity as exporting straight from `importDocx`.
|
|
49
|
+
|
|
50
|
+
- [#48](https://github.com/portone-io/docx-editor/pull/48) [`de84006`](https://github.com/portone-io/docx-editor/commit/de840061dd60f3b652273410bc2d0a85b9451ad3) Thanks [@Deea222](https://github.com/Deea222)! - Stop `onlyCommentsChangedBy` from refusing a comment written into a table cell.
|
|
51
|
+
|
|
52
|
+
A commented table is rebuilt on the way out, so it came back worded the way this editor words it while the original carried the wording its producer chose, and the two were compared word for word.
|
|
53
|
+
The story is now compared as this editor writes it back, so two blocks this editor would write alike are read alike: the attribute order inside a `w:tcW` or a `w:tblW`, a percentage width written as `100%` or as `5000`, runs a producer split that say the same text, and a table's `w:tblGridChange`.
|
|
54
|
+
Formatting properties keep the line breaks, comments and text a producer wrote between them, so rewriting a run's bold, a paragraph's alignment or a cell of a rebuilt table no longer drops them.
|
|
55
|
+
A rebuilt table keeps what stood inside its width and span properties too.
|
|
56
|
+
|
|
57
|
+
- [#52](https://github.com/portone-io/docx-editor/pull/52) [`991ba48`](https://github.com/portone-io/docx-editor/commit/991ba48501e20e60bfc69b3c76862f4a26123b3e) Thanks [@Deea222](https://github.com/Deea222)! - Read the comment parts entry by entry in `onlyCommentsChangedBy`.
|
|
58
|
+
|
|
59
|
+
The three parts a comment is written across are the ones a comment edit may rewrite, so the package comparison passes over their bytes.
|
|
60
|
+
Nothing read them afterwards, which let a submission carry a field pointing at a remote image inside a comment body, a comment attributed to a third author that nothing refers to, or markup wrapped around a body, and still be answered as a file where only comments changed.
|
|
61
|
+
Each entry now has to arrive as it was, or be one this editor writes for an author who could have written it, and an entry nothing refers to has to stay as it was.
|
|
62
|
+
A file that fails is refused as `part-changed` naming the comment part.
|
|
63
|
+
|
|
64
|
+
Settling or replying to a comment that arrived with the file no longer rewrites its entry as plain text.
|
|
65
|
+
The entry keeps what it said, and its last paragraph gains the `w14:paraId` the thread state is written against.
|
|
66
|
+
The extended comments part carries an entry only for a comment that has thread state.
|
|
67
|
+
|
|
68
|
+
## 0.2.0
|
|
69
|
+
|
|
70
|
+
### Minor Changes
|
|
71
|
+
|
|
72
|
+
- [#45](https://github.com/portone-io/docx-editor/pull/45) [`3190a35`](https://github.com/portone-io/docx-editor/commit/3190a359e0686bdc14166e22b1d96c761dabe251) Thanks [@Deea222](https://github.com/Deea222)! - Add a `comment` mode and record who wrote each comment.
|
|
73
|
+
|
|
74
|
+
**Breaking:** `mode` is now required and the `commentAuthor` prop is removed.
|
|
75
|
+
`author: { id, name, initials? }` moves into `mode` for its `comment` and `edit` kinds.
|
|
76
|
+
`contextMenus` moves off `mode` onto `DocxEditor` itself, so `mode: { kind: "edit", contextMenus: false }` becomes `contextMenus={false}`.
|
|
77
|
+
|
|
78
|
+
In `comment` mode the text can be selected and copied but not changed, while comments can be written, answered, resolved, and reopened.
|
|
79
|
+
A comment records its author's identity in the document, so two authors sharing a display name stay distinct.
|
|
80
|
+
Only a comment's author may edit or delete it, and anyone may reply, resolve, and reopen.
|
|
81
|
+
`mode.editableComments: "all"` opens every comment to a moderator.
|
|
82
|
+
The `canEditComment` and `editingProtection` queries report what the current mode allows.
|
|
83
|
+
A server can check a file coming back with `onlyCommentsChangedBy` from `@portone/docx-editor/core`, which answers `{ ok: true }` or `{ ok: false, reason }` for whether the file differs in nothing but one author's comments.
|
|
84
|
+
|
|
3
85
|
## 0.1.1
|
|
4
86
|
|
|
5
87
|
### Patch Changes
|
package/CONTRIBUTING.md
CHANGED
|
@@ -48,12 +48,16 @@ pnpm test:e2e
|
|
|
48
48
|
|
|
49
49
|
[Testing](https://github.com/portone-io/docx-editor/blob/main/docs/testing.md) explains the scope and prerequisites of every check.
|
|
50
50
|
|
|
51
|
+
A command added to `./commands` or `./table` needs a writer probe in `src/docx/__testing__/writerProbes.ts` with a `check` of its immediate effect, so its export is validated before another command can overwrite it, or an entry in `NOT_A_WRITER` giving the reason it reaches no writer.
|
|
52
|
+
|
|
51
53
|
## Changesets
|
|
52
54
|
|
|
53
55
|
If you changed anything under `src/`, run `pnpm changeset` and commit the file it writes.
|
|
54
56
|
It becomes the CHANGELOG entry, so write it for someone reading release notes rather than the diff.
|
|
55
57
|
Choose `patch` unless the change adds to or breaks the public API.
|
|
56
58
|
Those are `minor` while the package is below 1.0.
|
|
59
|
+
A declaration added, removed, or rewritten in `etc/*.api.md` after running `pnpm api:update` is what that looks like; a line that only gains or loses an `(undocumented)` marker is not.
|
|
60
|
+
Commit the regenerated report in the same commit as the changeset.
|
|
57
61
|
|
|
58
62
|
By contributing, you agree that your contribution is licensed under the Apache License 2.0 used by this project.
|
|
59
63
|
|
package/README.md
CHANGED
|
@@ -18,21 +18,33 @@ import "@portone/docx-editor/styles.css";
|
|
|
18
18
|
import { DocxEditor, type DocxEditorHandle } from "@portone/docx-editor";
|
|
19
19
|
import { useRef } from "react";
|
|
20
20
|
|
|
21
|
-
export function Editor({
|
|
21
|
+
export function Editor({
|
|
22
|
+
file,
|
|
23
|
+
user,
|
|
24
|
+
}: {
|
|
25
|
+
file: File;
|
|
26
|
+
user: { id: string; name: string };
|
|
27
|
+
}) {
|
|
22
28
|
const editorRef = useRef<DocxEditorHandle | null>(null);
|
|
23
29
|
|
|
24
|
-
return
|
|
30
|
+
return (
|
|
31
|
+
<DocxEditor
|
|
32
|
+
ref={editorRef}
|
|
33
|
+
document={file}
|
|
34
|
+
mode={{ kind: "edit", author: { id: user.id, name: user.name } }}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
25
37
|
}
|
|
26
38
|
```
|
|
27
39
|
|
|
28
|
-
`document` accepts a `File`, `Blob`, `ArrayBuffer`, or `Uint8Array`; use the ref to export the edited document as bytes.
|
|
40
|
+
`document` accepts a `File`, `Blob`, `ArrayBuffer`, or `Uint8Array`; `mode` selects read-only, comment-only, or full editing and names the author new comments are attributed to; use the ref to export the edited document as bytes.
|
|
29
41
|
|
|
30
42
|
## What it does
|
|
31
43
|
|
|
32
|
-
- Provides browser editing with built-in controls, read-only
|
|
44
|
+
- Provides browser editing with built-in controls, read-only and comment-only modes, and document locking.
|
|
33
45
|
- Fits the page to the available width by default and lets readers choose a fixed zoom level.
|
|
34
46
|
- Exports edited documents as DOCX bytes or browser downloads.
|
|
35
|
-
- Preserves document structures and package parts across edits.
|
|
47
|
+
- Preserves document structures and package parts across edits, and records who wrote each comment.
|
|
36
48
|
- Reads document styles, theme fonts, page layout, and CJK font information for display.
|
|
37
49
|
|
|
38
50
|
See [Feature support](https://docx-editor.portone.io/docs/features) for the support matrix. Build [custom controls](https://docx-editor.portone.io/docs/custom-controls) or use [programmatic DOCX import and export](https://docx-editor.portone.io/docs/core).
|
package/dist/DocxEditor.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { type CSSProperties, type ReactNode } from "react";
|
|
|
13
13
|
import { type DocxSource } from "./docx/importDocx";
|
|
14
14
|
import { type CommentAuthor } from "./editor/commands/commentCommands";
|
|
15
15
|
import { DocxImportError } from "./ooxml/errors";
|
|
16
|
+
import type { EditableComments } from "./schema/protection";
|
|
16
17
|
import type { FontFallbacks } from "./styles/fontStack";
|
|
17
18
|
import type { DocxEditorPresets } from "./ui/presets";
|
|
18
19
|
import { type DocxEditorZoom } from "./ui/zoom";
|
|
@@ -24,22 +25,37 @@ export interface DocxEditorHandle {
|
|
|
24
25
|
/**
|
|
25
26
|
* What the editor is for, which decides what it offers.
|
|
26
27
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* The three kinds are the standings OOXML document protection names (`ST_DocProtect`) and the
|
|
29
|
+
* ones a shared document gives its readers: a reader, a commenter, an editor. A read-only editor
|
|
30
|
+
* takes no edits, so it has no toolbar and no right click menus either: what used to be three
|
|
31
|
+
* booleans of which one silently emptied the other two is one choice here. A `comment` editor is
|
|
32
|
+
* the `comments` protection - the body may not be changed, comments may be written, answered and
|
|
33
|
+
* settled - which is what a reviewer who must not touch the text is handed. Both kinds that write
|
|
34
|
+
* comments name whose they are, so a reader is never asked for an identity it has no use for.
|
|
35
|
+
*
|
|
36
|
+
* `editableComments` is whose comments the panel offers to edit or delete: one's own, which is a
|
|
37
|
+
* comment carrying no recognised identity or the very identity given here, or every one, which
|
|
38
|
+
* is a moderator's standing. Replying and resolving are open to every commenter either way.
|
|
39
|
+
*
|
|
29
40
|
* `locking` is what a screen where a template is written gets: settling a part of a document is
|
|
30
41
|
* an authoring act, not something every reader of a form should be handed. A lock the document
|
|
31
42
|
* already carries holds whichever mode is chosen.
|
|
32
43
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
44
|
+
* The kind, the author and `editableComments` are read on every render and take effect on the
|
|
45
|
+
* open document. Whether the right click opens the editor's own menus is `contextMenus`, a prop
|
|
46
|
+
* of its own, since the plugins behind it are read when the editor mounts rather than per render.
|
|
36
47
|
*/
|
|
37
48
|
export type DocxEditorMode = {
|
|
38
49
|
kind: "readOnly";
|
|
50
|
+
} | {
|
|
51
|
+
kind: "comment";
|
|
52
|
+
author: CommentAuthor;
|
|
53
|
+
editableComments?: EditableComments;
|
|
39
54
|
} | {
|
|
40
55
|
kind: "edit";
|
|
56
|
+
author: CommentAuthor;
|
|
57
|
+
editableComments?: EditableComments;
|
|
41
58
|
toolbar?: boolean;
|
|
42
|
-
contextMenus?: boolean;
|
|
43
59
|
locking?: boolean;
|
|
44
60
|
};
|
|
45
61
|
export interface DocxEditorProps {
|
|
@@ -58,8 +74,8 @@ export interface DocxEditorProps {
|
|
|
58
74
|
* never silent either way. Hand one in to write the refusal in your own words.
|
|
59
75
|
*/
|
|
60
76
|
renderImportError?: (error: DocxImportError) => ReactNode;
|
|
61
|
-
/** What the editor is for
|
|
62
|
-
mode
|
|
77
|
+
/** What the editor is for: a reader's, a commenter's or an editor's surface */
|
|
78
|
+
mode: DocxEditorMode;
|
|
63
79
|
/** Whether to draw approximate page boundaries over the document's own paper. Drawn when read only too */
|
|
64
80
|
showPageGuides?: boolean;
|
|
65
81
|
/**
|
|
@@ -96,8 +112,6 @@ export interface DocxEditorProps {
|
|
|
96
112
|
* takes effect without a remount.
|
|
97
113
|
*/
|
|
98
114
|
presets?: DocxEditorPresets;
|
|
99
|
-
/** The identity written by the built-in comment and reply composers */
|
|
100
|
-
commentAuthor?: CommentAuthor;
|
|
101
115
|
/**
|
|
102
116
|
* ProseMirror plugins handed in by the consumer, which is how mentions, highlights,
|
|
103
117
|
* autocomplete and the like are added from outside the package.
|
|
@@ -110,6 +124,16 @@ export interface DocxEditorProps {
|
|
|
110
124
|
* `key` and remount.
|
|
111
125
|
*/
|
|
112
126
|
plugins?: readonly Plugin[];
|
|
127
|
+
/**
|
|
128
|
+
* Whether the right click opens the editor's own menus. `false` leaves it to the browser,
|
|
129
|
+
* which is what a consumer drawing menus of its own wants. Defaults to `true`.
|
|
130
|
+
*
|
|
131
|
+
* Read once, when the editor mounts, like `plugins`: the plugins that take the browser's menu
|
|
132
|
+
* away go into the editor state, which is built there. A menu the protection has nothing to
|
|
133
|
+
* offer from stands down by itself, so a mode change needs nothing here. Later changes are
|
|
134
|
+
* ignored; to turn the menus around, change the `key` and remount.
|
|
135
|
+
*/
|
|
136
|
+
contextMenus?: boolean;
|
|
113
137
|
className?: string;
|
|
114
138
|
style?: CSSProperties;
|
|
115
139
|
onReady?: (view: EditorView) => void;
|
package/dist/DocxEditor.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "./editor/commands/commentCommands.js";
|
|
16
16
|
import { activeLinkSpan } from "./editor/commands/linkCommands.js";
|
|
17
17
|
import { createEditorState, createEditorView } from "./editor/createEditor.js";
|
|
18
|
+
import { setProtection } from "./editor/plugins/documentProtection.js";
|
|
18
19
|
import { isLinkPanelOpen } from "./editor/plugins/linkPanel.js";
|
|
19
20
|
import { tableMenuAnchor } from "./editor/plugins/tableContextMenu.js";
|
|
20
21
|
import { textMenuAnchor } from "./editor/plugins/textContextMenu.js";
|
|
@@ -23,6 +24,7 @@ import { DocxImportError } from "./ooxml/errors.js";
|
|
|
23
24
|
import { PageGuides } from "./page/PageGuides.js";
|
|
24
25
|
import { A4_PAGE_PIXELS, pagePixels } from "./page/pageLayout.js";
|
|
25
26
|
import { usePageLayout } from "./page/usePageLayout.js";
|
|
27
|
+
import { editingProtection, protectionOf } from "./schema/protectionState.js";
|
|
26
28
|
import { editorClassNames } from "./styles/classNames.js";
|
|
27
29
|
import { CommentsPanel } from "./ui/CommentsPanel.js";
|
|
28
30
|
import { LinkCard } from "./ui/LinkCard.js";
|
|
@@ -34,6 +36,38 @@ import { Toolbar } from "./ui/Toolbar.js";
|
|
|
34
36
|
import { useFitWidthZoom } from "./ui/useFitWidthZoom.js";
|
|
35
37
|
import { normalizeZoom } from "./ui/zoom.js";
|
|
36
38
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
39
|
+
function affordancesOf(mode) {
|
|
40
|
+
switch (mode.kind) {
|
|
41
|
+
case "readOnly":
|
|
42
|
+
return {
|
|
43
|
+
protection: "readOnly",
|
|
44
|
+
author: null,
|
|
45
|
+
editableComments: "own",
|
|
46
|
+
toolbar: false,
|
|
47
|
+
locking: false
|
|
48
|
+
};
|
|
49
|
+
case "comment":
|
|
50
|
+
return {
|
|
51
|
+
protection: "comments",
|
|
52
|
+
author: mode.author ?? null,
|
|
53
|
+
editableComments: mode.editableComments ?? "own",
|
|
54
|
+
toolbar: false,
|
|
55
|
+
locking: false
|
|
56
|
+
};
|
|
57
|
+
case "edit":
|
|
58
|
+
return {
|
|
59
|
+
protection: "none",
|
|
60
|
+
author: mode.author ?? null,
|
|
61
|
+
editableComments: mode.editableComments ?? "own",
|
|
62
|
+
toolbar: mode.toolbar ?? true,
|
|
63
|
+
locking: mode.locking ?? false
|
|
64
|
+
};
|
|
65
|
+
default: {
|
|
66
|
+
const unmodelled = mode;
|
|
67
|
+
return unmodelled;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
37
71
|
function openDocument(bytes) {
|
|
38
72
|
try {
|
|
39
73
|
const { doc, session } = importDocx(bytes);
|
|
@@ -97,32 +131,28 @@ function zoomVariable(factor) {
|
|
|
97
131
|
function DocxEditorSurface({
|
|
98
132
|
document: source,
|
|
99
133
|
renderImportError,
|
|
100
|
-
mode
|
|
134
|
+
mode,
|
|
101
135
|
showPageGuides = true,
|
|
102
136
|
zoom,
|
|
103
137
|
defaultZoom = "fit-width",
|
|
104
138
|
onZoomChange,
|
|
105
139
|
fontFallbacks,
|
|
106
140
|
presets,
|
|
107
|
-
commentAuthor = { name: "Anonymous" },
|
|
108
141
|
plugins,
|
|
142
|
+
contextMenus,
|
|
109
143
|
className,
|
|
110
144
|
style,
|
|
111
145
|
onReady,
|
|
112
146
|
onChange
|
|
113
147
|
}, ref) {
|
|
114
|
-
const
|
|
115
|
-
const showToolbar = mode.kind === "edit" && (mode.toolbar ?? true);
|
|
116
|
-
const allowLocking = mode.kind === "edit" && (mode.locking ?? false);
|
|
148
|
+
const { protection, author, editableComments, toolbar, locking } = affordancesOf(mode);
|
|
117
149
|
const bytes = useDocumentBytes(source);
|
|
118
150
|
const opened = useMemo(
|
|
119
151
|
() => bytes === null ? null : openDocument(bytes),
|
|
120
152
|
[bytes]
|
|
121
153
|
);
|
|
122
154
|
const mountedPlugins = useRef(plugins).current;
|
|
123
|
-
const mountedContextMenus = useRef(
|
|
124
|
-
mode.kind !== "edit" || (mode.contextMenus ?? true)
|
|
125
|
-
).current;
|
|
155
|
+
const mountedContextMenus = useRef(contextMenus ?? true).current;
|
|
126
156
|
const mountedFontFallbacks = useRef(fontFallbacks).current;
|
|
127
157
|
const layerRef = useRef(null);
|
|
128
158
|
const rootRef = useRef(null);
|
|
@@ -173,11 +203,13 @@ function DocxEditorSurface({
|
|
|
173
203
|
...opened.session.comments.extendedOrdered.map(
|
|
174
204
|
(extension) => extension.paraId
|
|
175
205
|
)
|
|
176
|
-
]
|
|
206
|
+
],
|
|
207
|
+
protection,
|
|
208
|
+
author,
|
|
209
|
+
editableComments
|
|
177
210
|
}),
|
|
178
211
|
defaults: opened.session.defaults,
|
|
179
212
|
geometry: opened.session.geometry,
|
|
180
|
-
readOnly,
|
|
181
213
|
fontFallbacks: mountedFontFallbacks,
|
|
182
214
|
onStateChange: (state) => {
|
|
183
215
|
keptState.current = { of: opened, state };
|
|
@@ -196,19 +228,32 @@ function DocxEditorSurface({
|
|
|
196
228
|
};
|
|
197
229
|
}, [
|
|
198
230
|
opened,
|
|
199
|
-
readOnly,
|
|
200
231
|
mountedContextMenus,
|
|
201
232
|
mountedFontFallbacks,
|
|
202
233
|
mountedPlugins,
|
|
203
234
|
latestOnReady,
|
|
204
235
|
latestOnChange
|
|
205
236
|
]);
|
|
237
|
+
const authorId = author?.id ?? null;
|
|
238
|
+
const mounted = live !== null;
|
|
239
|
+
useLayoutEffect(() => {
|
|
240
|
+
if (protection === "readOnly") setCommentComposerOpen(false);
|
|
241
|
+
const view = viewRef.current;
|
|
242
|
+
if (!view || !mounted) return;
|
|
243
|
+
const held = protectionOf(view.state);
|
|
244
|
+
if (held.protection === protection && held.authorId === authorId && held.editableComments === editableComments) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
view.dispatch(
|
|
248
|
+
setProtection(view.state.tr, { protection, author, editableComments })
|
|
249
|
+
);
|
|
250
|
+
}, [mounted, protection, authorId, editableComments]);
|
|
206
251
|
useImperativeHandle(ref, () => {
|
|
207
252
|
const view = viewRef.current;
|
|
208
253
|
if (!view || opened?.status !== "opened") return null;
|
|
209
254
|
const session = opened.session;
|
|
210
255
|
return { view, exportBytes: () => exportDocx(view.state.doc, session) };
|
|
211
|
-
}, [opened
|
|
256
|
+
}, [opened]);
|
|
212
257
|
const overlay = usePageLayout({
|
|
213
258
|
view: live?.view ?? null,
|
|
214
259
|
layer: layerRef,
|
|
@@ -219,21 +264,22 @@ function DocxEditorSurface({
|
|
|
219
264
|
if (opened?.status === "rejected") {
|
|
220
265
|
return renderImportError ? renderImportError(opened.error) : /* @__PURE__ */ jsx(ImportRejection, { error: opened.error });
|
|
221
266
|
}
|
|
222
|
-
const
|
|
223
|
-
const
|
|
267
|
+
const bodyOpen = live !== null && editingProtection(live.state) === "none";
|
|
268
|
+
const commentsOpenToWrite = live !== null && editingProtection(live.state) !== "readOnly";
|
|
269
|
+
const textAnchor = live ? textMenuAnchor(live.state) : null;
|
|
270
|
+
const tableAnchor = live && !textAnchor ? tableMenuAnchor(live.state) : null;
|
|
224
271
|
const linkAtCursor = live && !live.view.composing && !isLinkPanelOpen(live.state) ? activeLinkSpan(live.state) : null;
|
|
225
272
|
const comments = live?.state === void 0 ? [] : documentComments(live.state);
|
|
226
273
|
const hasUnresolvedComments = comments.some((comment) => !comment.resolved);
|
|
227
|
-
const effectiveComposerOpen = commentComposerOpen &&
|
|
274
|
+
const effectiveComposerOpen = commentComposerOpen && commentsOpenToWrite;
|
|
228
275
|
const showComments = live !== null && (commentsOpen || effectiveComposerOpen || hasUnresolvedComments);
|
|
229
276
|
const commentsPanel = live && showComments && /* @__PURE__ */ jsx(
|
|
230
277
|
CommentsPanel,
|
|
231
278
|
{
|
|
232
279
|
view: live.view,
|
|
233
280
|
state: live.state,
|
|
234
|
-
readOnly,
|
|
235
281
|
composerOpen: effectiveComposerOpen,
|
|
236
|
-
author
|
|
282
|
+
author,
|
|
237
283
|
closeComposer: () => setCommentComposerOpen(false),
|
|
238
284
|
scrollContainer: rootRef.current,
|
|
239
285
|
allCommentsOpen: commentsOpen
|
|
@@ -245,7 +291,7 @@ function DocxEditorSurface({
|
|
|
245
291
|
className: [editorClassNames.frame, className].filter(Boolean).join(" "),
|
|
246
292
|
style,
|
|
247
293
|
children: [
|
|
248
|
-
live &&
|
|
294
|
+
live && toolbar && /* @__PURE__ */ jsx(
|
|
249
295
|
Toolbar,
|
|
250
296
|
{
|
|
251
297
|
view: live.view,
|
|
@@ -270,7 +316,7 @@ function DocxEditorSurface({
|
|
|
270
316
|
"data-comments": showComments ? "visible" : void 0,
|
|
271
317
|
style: zoomVariable(effectiveZoom),
|
|
272
318
|
children: [
|
|
273
|
-
live &&
|
|
319
|
+
live && !bodyOpen && comments.length > 0 && /* @__PURE__ */ jsx(
|
|
274
320
|
"button",
|
|
275
321
|
{
|
|
276
322
|
type: "button",
|
|
@@ -306,14 +352,13 @@ function DocxEditorSurface({
|
|
|
306
352
|
]
|
|
307
353
|
}
|
|
308
354
|
),
|
|
309
|
-
live &&
|
|
355
|
+
live && bodyOpen && isLinkPanelOpen(live.state) && /* @__PURE__ */ jsx(LinkPanel, { view: live.view, state: live.state }),
|
|
310
356
|
live && linkAtCursor && /* @__PURE__ */ jsx(
|
|
311
357
|
LinkCard,
|
|
312
358
|
{
|
|
313
359
|
view: live.view,
|
|
314
360
|
state: live.state,
|
|
315
|
-
link: linkAtCursor
|
|
316
|
-
readOnly
|
|
361
|
+
link: linkAtCursor
|
|
317
362
|
},
|
|
318
363
|
linkAtCursor.from
|
|
319
364
|
),
|
|
@@ -323,7 +368,7 @@ function DocxEditorSurface({
|
|
|
323
368
|
view: live.view,
|
|
324
369
|
state: live.state,
|
|
325
370
|
anchor: textAnchor,
|
|
326
|
-
allowLocking,
|
|
371
|
+
allowLocking: locking,
|
|
327
372
|
onAddComment: () => {
|
|
328
373
|
setCommentComposerOpen(true);
|
|
329
374
|
}
|
|
@@ -335,7 +380,7 @@ function DocxEditorSurface({
|
|
|
335
380
|
view: live.view,
|
|
336
381
|
state: live.state,
|
|
337
382
|
anchor: tableAnchor,
|
|
338
|
-
allowLocking
|
|
383
|
+
allowLocking: locking
|
|
339
384
|
}
|
|
340
385
|
)
|
|
341
386
|
]
|
package/dist/core.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import type { Node as PMNode } from "prosemirror-model";
|
|
6
6
|
import { type DocxBytes } from "./docx/importDocx";
|
|
7
7
|
import type { DocxSession } from "./docx/session";
|
|
8
|
+
export type { CommentOnlyVerdict } from "./docx/commentOnlyChange";
|
|
9
|
+
export { onlyCommentsChangedBy } from "./docx/commentOnlyChange";
|
|
8
10
|
export { exportDocx } from "./docx/exportDocx";
|
|
9
11
|
export type { ParagraphStyleOption } from "./docx/formatting";
|
|
10
12
|
export type { DocxBytes } from "./docx/importDocx";
|
package/dist/core.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
2
|
import { importDocx as openDocx } from "./docx/importDocx.js";
|
|
3
|
+
import { onlyCommentsChangedBy } from "./docx/commentOnlyChange.js";
|
|
3
4
|
import { exportDocx } from "./docx/exportDocx.js";
|
|
4
5
|
import { documentNumbering, documentPartPath } from "./docx/session.js";
|
|
5
6
|
import {
|
|
@@ -26,6 +27,7 @@ export {
|
|
|
26
27
|
emuToPx,
|
|
27
28
|
exportDocx,
|
|
28
29
|
importDocx,
|
|
30
|
+
onlyCommentsChangedBy,
|
|
29
31
|
parseNumbering,
|
|
30
32
|
pxToEmu,
|
|
31
33
|
toCellFormat,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a file handed back changed in nothing but comments one author is allowed to have made.
|
|
3
|
+
*
|
|
4
|
+
* The editor's own refusal under a comment protection (`schema/protection`) is a courtesy to the
|
|
5
|
+
* user: the browser holds the file, so the rule is held again here, where a server takes it in.
|
|
6
|
+
* The judgement is over the package rather than over the document alone, since a submission is
|
|
7
|
+
* free to rewrite anything the story does not carry - the paper the document is written on, its
|
|
8
|
+
* styles, its headers - and a document comparison would see none of it.
|
|
9
|
+
*/
|
|
10
|
+
import { type EditableComments } from "../schema/protection";
|
|
11
|
+
import { type DocxBytes } from "./importDocx";
|
|
12
|
+
/**
|
|
13
|
+
* Why a file is not the one it claims to be. `part-changed` and `relationship-changed` name the
|
|
14
|
+
* part they were reached over; the other three are about the document story itself.
|
|
15
|
+
*/
|
|
16
|
+
export type CommentOnlyVerdict = {
|
|
17
|
+
ok: true;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: "body-changed" | "comment-not-owned" | "comment-author-forged";
|
|
21
|
+
} | {
|
|
22
|
+
ok: false;
|
|
23
|
+
reason: "part-changed" | "relationship-changed";
|
|
24
|
+
part: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Whether the submitted file differs from the original in nothing but comments, every one of them
|
|
28
|
+
* added, edited, moved, deleted, replied to or settled by the author with this identity.
|
|
29
|
+
*
|
|
30
|
+
* Every part of the package has to arrive as it left, save for the three a comment is written
|
|
31
|
+
* across and the relationship and content type they are declared with; the document story itself
|
|
32
|
+
* has to read as it did, comments aside. Those three parts are read entry by entry instead
|
|
33
|
+
* (`./comments/verifying`), since a comment edit is free to rewrite them and something has to say
|
|
34
|
+
* what it may have written there. A comment carrying no recorded identity is everyone's to
|
|
35
|
+
* edit here as it is in the editor (`schema/protection`), while a comment that appeared has to
|
|
36
|
+
* carry this identity: a file can claim any author, and the editor's own hand in writing it is
|
|
37
|
+
* not there to vouch for it. An identity already recorded is nobody's to rewrite.
|
|
38
|
+
*
|
|
39
|
+
* `editableComments: "all"` judges the file of an editor opened for a moderator, where every
|
|
40
|
+
* comment was theirs to edit; an identity is nobody's to rewrite under either setting.
|
|
41
|
+
*
|
|
42
|
+
* Bytes that are not a readable docx are turned down the way opening one is, with a
|
|
43
|
+
* `DocxImportError`, rather than being answered as a file that changed.
|
|
44
|
+
*/
|
|
45
|
+
export declare function onlyCommentsChangedBy(original: DocxBytes, submitted: DocxBytes, authorId: string, { editableComments }?: {
|
|
46
|
+
editableComments?: EditableComments;
|
|
47
|
+
}): CommentOnlyVerdict;
|