@portone/docx-editor 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +205 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +1 -1
- package/dist/DocxEditor.js +45 -25
- package/dist/core.d.ts +1 -1
- package/dist/docx/cloning.js +4 -9
- package/dist/docx/commentOnlyChange.d.ts +5 -3
- package/dist/docx/comments/grammar.d.ts +27 -10
- package/dist/docx/comments/grammar.js +18 -67
- package/dist/docx/comments/model.d.ts +5 -6
- package/dist/docx/comments/model.js +0 -9
- package/dist/docx/comments/parts.d.ts +16 -10
- package/dist/docx/comments/parts.js +65 -13
- package/dist/docx/comments/people.d.ts +11 -3
- package/dist/docx/comments/people.js +20 -6
- package/dist/docx/comments/policy.js +7 -1
- package/dist/docx/comments/reading.d.ts +4 -2
- package/dist/docx/comments/reading.js +1 -14
- package/dist/docx/comments/writing.js +78 -35
- package/dist/docx/exportDocx.js +30 -18
- package/dist/docx/exportRefs.d.ts +8 -3
- package/dist/docx/exportRefs.js +2 -1
- package/dist/docx/fidelity.js +24 -10
- package/dist/docx/fields.d.ts +42 -0
- package/dist/docx/fields.js +76 -0
- package/dist/docx/formatting/attrs.d.ts +20 -2
- package/dist/docx/formatting/attrs.js +57 -2
- package/dist/docx/formatting/context.d.ts +10 -2
- package/dist/docx/formatting/context.js +23 -5
- package/dist/docx/formatting/resolve.d.ts +10 -4
- package/dist/docx/formatting/resolve.js +31 -11
- package/dist/docx/formatting/styles.d.ts +25 -2
- package/dist/docx/formatting/styles.js +84 -3
- package/dist/docx/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/hyperlink.d.ts +14 -4
- package/dist/docx/hyperlink.js +4 -1
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +135 -74
- package/dist/docx/importParagraph.d.ts +16 -4
- package/dist/docx/importParagraph.js +87 -93
- package/dist/docx/importPolicy.d.ts +70 -0
- package/dist/docx/importPolicy.js +190 -0
- package/dist/docx/importPreserved.d.ts +30 -0
- package/dist/docx/importPreserved.js +54 -0
- package/dist/docx/importTable.d.ts +2 -2
- package/dist/docx/importTable.js +107 -66
- package/dist/docx/invariants.js +42 -31
- package/dist/docx/newLists.d.ts +30 -0
- package/dist/docx/newLists.js +24 -1
- package/dist/docx/notes.d.ts +6 -1
- package/dist/docx/notes.js +6 -16
- package/dist/docx/numberingPlanner.d.ts +6 -3
- package/dist/docx/numberingPlanner.js +38 -7
- package/dist/docx/pageGeometry.d.ts +5 -14
- package/dist/docx/pageGeometry.js +0 -12
- package/dist/docx/protectionPolicy.d.ts +13 -5
- package/dist/docx/protectionPolicy.js +32 -22
- package/dist/docx/scan.d.ts +18 -8
- package/dist/docx/scan.js +17 -11
- package/dist/docx/sections.d.ts +126 -0
- package/dist/docx/sections.js +207 -0
- package/dist/docx/serializeBlock.d.ts +13 -4
- package/dist/docx/serializeBlock.js +16 -18
- package/dist/docx/serializeParagraph.d.ts +4 -0
- package/dist/docx/serializeParagraph.js +1 -0
- package/dist/docx/serializePreserved.d.ts +14 -0
- package/dist/docx/serializePreserved.js +24 -0
- package/dist/docx/serializeStory.d.ts +17 -0
- package/dist/docx/serializeStory.js +17 -0
- package/dist/docx/serializeTable.js +10 -13
- package/dist/docx/session.d.ts +24 -7
- package/dist/docx/session.js +37 -6
- package/dist/docx/story.d.ts +140 -0
- package/dist/docx/story.js +237 -0
- package/dist/docx/storyProjection.js +6 -1
- package/dist/docx/tableFormatting/conditions.d.ts +76 -0
- package/dist/docx/tableFormatting/conditions.js +196 -0
- package/dist/docx/tableFormatting/editing.d.ts +7 -3
- package/dist/docx/tableFormatting/editing.js +4 -5
- package/dist/docx/tableFormatting/reading.d.ts +49 -19
- package/dist/docx/tableFormatting/reading.js +109 -35
- package/dist/docx/tableFormatting.d.ts +1 -0
- package/dist/docx/tableFormatting.js +1 -0
- package/dist/docx/tableTemplate.js +12 -12
- package/dist/editor/clipboard/htmlReader.d.ts +30 -0
- package/dist/editor/clipboard/htmlReader.js +302 -0
- package/dist/editor/clipboard/internalChannel.d.ts +57 -0
- package/dist/editor/clipboard/internalChannel.js +58 -0
- package/dist/editor/clipboard/normalizers.d.ts +99 -0
- package/dist/editor/clipboard/normalizers.js +199 -0
- package/dist/editor/clipboard/parser.d.ts +34 -0
- package/dist/editor/clipboard/parser.js +58 -0
- package/dist/editor/clipboard/plugin.d.ts +21 -0
- package/dist/editor/clipboard/plugin.js +320 -0
- package/dist/editor/clipboard/readContext.d.ts +24 -0
- package/dist/editor/clipboard/readContext.js +19 -0
- package/dist/editor/clipboard/readers.d.ts +33 -0
- package/dist/editor/clipboard/readers.js +37 -0
- package/dist/editor/commands/comments/editing.d.ts +26 -6
- package/dist/editor/commands/comments/editing.js +175 -105
- package/dist/editor/commands/comments/model.d.ts +34 -17
- package/dist/editor/commands/comments/model.js +11 -0
- package/dist/editor/commands/comments/reading.d.ts +5 -3
- package/dist/editor/commands/comments/reading.js +8 -58
- package/dist/editor/commands/formatting/propertyCommands.js +6 -1
- package/dist/editor/commands/index.d.ts +2 -2
- package/dist/editor/commands/index.js +2 -0
- package/dist/editor/commands/listCommands.d.ts +12 -3
- package/dist/editor/commands/listCommands.js +76 -30
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- package/dist/editor/commands/paragraphCommands.js +6 -1
- package/dist/editor/createEditor.js +14 -4
- package/dist/editor/documentStyles.d.ts +26 -4
- package/dist/editor/documentStyles.js +9 -4
- package/dist/editor/editorDocument.d.ts +7 -6
- package/dist/editor/editorDocument.js +18 -6
- package/dist/editor/imageFiles.d.ts +2 -2
- package/dist/editor/imageFiles.js +2 -0
- package/dist/editor/insertTable.d.ts +1 -1
- package/dist/editor/insertTable.js +2 -2
- package/dist/editor/paragraphEdits.d.ts +16 -1
- package/dist/editor/paragraphEdits.js +13 -5
- package/dist/editor/paragraphPlacement.d.ts +11 -0
- package/dist/editor/paragraphPlacement.js +18 -0
- package/dist/editor/plainText.d.ts +12 -0
- package/dist/editor/plainText.js +2 -1
- package/dist/editor/plugins/columnResize.js +6 -3
- package/dist/editor/plugins/commentComposer.d.ts +28 -0
- package/dist/editor/plugins/commentComposer.js +46 -0
- package/dist/editor/plugins/commentDecorations.d.ts +29 -2
- package/dist/editor/plugins/commentDecorations.js +89 -15
- package/dist/editor/plugins/documentProjection.d.ts +31 -0
- package/dist/editor/plugins/documentProjection.js +22 -0
- package/dist/editor/plugins/imagePaste.js +20 -10
- package/dist/editor/plugins/linkPanel.d.ts +7 -3
- package/dist/editor/plugins/linkPanel.js +13 -27
- package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
- package/dist/editor/plugins/numberingDecorations.js +43 -2
- package/dist/editor/plugins/panelState.d.ts +67 -0
- package/dist/editor/plugins/panelState.js +54 -0
- package/dist/editor/plugins/paragraphDisplay.js +9 -3
- package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
- package/dist/editor/plugins/tableContextMenu.js +30 -47
- package/dist/editor/plugins/tableDisplay.js +19 -5
- package/dist/editor/plugins/textContextMenu.d.ts +1 -1
- package/dist/editor/plugins/textContextMenu.js +30 -47
- package/dist/model/format.d.ts +50 -0
- package/dist/model/format.js +65 -0
- package/dist/numbering/listRegistry.d.ts +37 -0
- package/dist/numbering/listRegistry.js +112 -0
- package/dist/numbering/listTemplate.d.ts +26 -17
- package/dist/numbering/listTemplate.js +66 -20
- package/dist/numbering/markers.d.ts +15 -6
- package/dist/numbering/markers.js +26 -59
- package/dist/numbering/parseNumbering.d.ts +70 -5
- package/dist/numbering/parseNumbering.js +100 -26
- package/dist/numbering/spellers.d.ts +15 -0
- package/dist/numbering/spellers.js +96 -0
- package/dist/numbering/writeNumbering.d.ts +5 -3
- package/dist/numbering/writeNumbering.js +15 -15
- package/dist/ooxml/conformance.d.ts +28 -0
- package/dist/ooxml/conformance.js +20 -0
- package/dist/ooxml/errors.d.ts +5 -5
- package/dist/ooxml/fragment.d.ts +11 -0
- package/dist/ooxml/fragment.js +14 -4
- package/dist/ooxml/image.d.ts +3 -4
- package/dist/ooxml/image.js +50 -5
- package/dist/ooxml/names.d.ts +15 -0
- package/dist/ooxml/names.js +10 -1
- package/dist/ooxml/partSplice.d.ts +1 -1
- package/dist/ooxml/partSplice.js +10 -3
- package/dist/ooxml/props.d.ts +8 -0
- package/dist/ooxml/props.js +14 -4
- package/dist/ooxml/rangeMarkers.d.ts +5 -0
- package/dist/ooxml/rangeMarkers.js +24 -0
- package/dist/ooxml/xml.d.ts +8 -9
- package/dist/ooxml/xml.js +6 -6
- package/dist/page/PageGuides.d.ts +8 -4
- package/dist/page/PageGuides.js +13 -37
- package/dist/page/pageLayout.d.ts +62 -13
- package/dist/page/pageLayout.js +91 -37
- package/dist/page/usePageLayout.d.ts +14 -11
- package/dist/page/usePageLayout.js +21 -29
- package/dist/schema/attrRoles.js +44 -19
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +220 -139
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +2 -2
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/preservedFragments.d.ts +12 -0
- package/dist/schema/preservedFragments.js +38 -0
- package/dist/schema/preservedGuards.d.ts +9 -7
- package/dist/schema/preservedGuards.js +112 -11
- package/dist/schema/protection.d.ts +34 -5
- package/dist/schema/protection.js +42 -14
- package/dist/schema/rendering.js +3 -1
- package/dist/schema/stories.d.ts +49 -0
- package/dist/schema/stories.js +78 -0
- package/dist/styles/classNames.d.ts +16 -7
- package/dist/styles/classNames.js +16 -7
- package/dist/styles.css +90 -30
- package/dist/table/cellFormatting.js +1 -2
- package/dist/table/gridBorders.d.ts +11 -15
- package/dist/table/gridBorders.js +32 -18
- package/dist/table/resize.d.ts +5 -3
- package/dist/ui/CommentsPanel.d.ts +1 -3
- package/dist/ui/CommentsPanel.js +24 -15
- package/dist/ui/NotesPanel.js +2 -2
- package/dist/ui/TextMenu.d.ts +1 -3
- package/dist/ui/TextMenu.js +10 -6
- package/dist/ui/comments/CommentComposer.d.ts +6 -1
- package/dist/ui/comments/CommentComposer.js +26 -3
- package/package.json +1 -1
- package/dist/editor/externalClipboard.d.ts +0 -11
- package/dist/editor/externalClipboard.js +0 -456
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ResolvedPos, Slice } from "prosemirror-model";
|
|
2
|
+
import { type PastedContent } from "./htmlReader";
|
|
3
|
+
import type { HtmlReadContext } from "./readContext";
|
|
4
|
+
/** What one reader is handed: the markup on the clipboard, and the document it is read into */
|
|
5
|
+
export interface ClipboardInput {
|
|
6
|
+
dom: Node;
|
|
7
|
+
/** The name the markup carries for a slice this editor kept (`./internalChannel`). Null when it carries none */
|
|
8
|
+
token: string | null;
|
|
9
|
+
/** The open document the markup is being read into. Null for a state built without one */
|
|
10
|
+
sessionId: string | null;
|
|
11
|
+
context: HtmlReadContext;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One way of reading a clipboard. The chain is tried in order and the first reader to answer
|
|
15
|
+
* decides what goes in, so a reader that does not recognize what it was handed answers nothing.
|
|
16
|
+
*/
|
|
17
|
+
export type ClipboardReader = (input: ClipboardInput) => PastedContent | null;
|
|
18
|
+
/**
|
|
19
|
+
* A copy made in this same open document, handed back as the slice it was.
|
|
20
|
+
*
|
|
21
|
+
* It leads the chain because it is the only reader that loses nothing: everything below reads
|
|
22
|
+
* markup, and markup is all a copy from anywhere else amounts to here.
|
|
23
|
+
*/
|
|
24
|
+
export declare const internalSliceReader: ClipboardReader;
|
|
25
|
+
/** Everything the editor did not write itself, read for the formatting the markup states */
|
|
26
|
+
export declare const foreignHtmlReader: ClipboardReader;
|
|
27
|
+
export declare const DEFAULT_READERS: readonly ClipboardReader[];
|
|
28
|
+
/**
|
|
29
|
+
* Plain text as content of this document: docx has no character for a line break or a tab stop,
|
|
30
|
+
* so those become the elements it does have, and the rest keeps the formatting in force where it
|
|
31
|
+
* is put.
|
|
32
|
+
*/
|
|
33
|
+
export declare function plainTextSlice(text: string, $context: ResolvedPos): Slice;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/editor/clipboard/readers.ts
|
|
2
|
+
import { Fragment, Slice } from "prosemirror-model";
|
|
3
|
+
import { NO_NEW_LISTS } from "../../numbering/listRegistry.js";
|
|
4
|
+
import { docxSchema } from "../../schema/index.js";
|
|
5
|
+
import { toPieces } from "../plainText.js";
|
|
6
|
+
import { readHtml } from "./htmlReader.js";
|
|
7
|
+
import { recallCopied } from "./internalChannel.js";
|
|
8
|
+
var internalSliceReader = ({ token, sessionId }) => {
|
|
9
|
+
const recalled = recallCopied(token, sessionId);
|
|
10
|
+
return recalled === null ? null : { ...recalled, newLists: NO_NEW_LISTS };
|
|
11
|
+
};
|
|
12
|
+
var foreignHtmlReader = ({ dom, context }) => dom instanceof Element || dom instanceof DocumentFragment ? readHtml(dom, context) : null;
|
|
13
|
+
var DEFAULT_READERS = [
|
|
14
|
+
internalSliceReader,
|
|
15
|
+
foreignHtmlReader
|
|
16
|
+
];
|
|
17
|
+
function plainTextSlice(text, $context) {
|
|
18
|
+
const marks = $context.marks();
|
|
19
|
+
const nodes = toPieces(text).map((piece) => {
|
|
20
|
+
if ("text" in piece) return docxSchema.text(piece.text, marks);
|
|
21
|
+
if (piece.node === "hardBreak") {
|
|
22
|
+
return docxSchema.nodes.hardBreak.create(null, null, marks);
|
|
23
|
+
}
|
|
24
|
+
const beside = marks.filter((mark) => mark.type !== docxSchema.marks.tab);
|
|
25
|
+
return docxSchema.text(
|
|
26
|
+
" ",
|
|
27
|
+
docxSchema.marks.tab.create().addToSet(beside)
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
return nodes.length === 0 ? Slice.empty : new Slice(Fragment.fromArray(nodes), 0, 0);
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
DEFAULT_READERS,
|
|
34
|
+
foreignHtmlReader,
|
|
35
|
+
internalSliceReader,
|
|
36
|
+
plainTextSlice
|
|
37
|
+
};
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
/** Edits Word comments through their range markers and reference nodes. */
|
|
2
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
2
3
|
import { type Command, type EditorState } from "prosemirror-state";
|
|
3
|
-
import type
|
|
4
|
-
/** Adds a plain-text comment to the current text selection. */
|
|
5
|
-
export declare function addComment(comment: NewComment): Command;
|
|
4
|
+
import { type CommentRange, type NewComment } from "./model";
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
6
|
+
* Adds a plain-text comment to a stretch of text: the one given, or the current text selection.
|
|
7
|
+
*
|
|
8
|
+
* A composer that was opened over one stretch and submitted later names it, so that what the
|
|
9
|
+
* comment marks is the text it was written about rather than wherever the caret has since gone.
|
|
10
|
+
*/
|
|
11
|
+
export declare function addComment(comment: NewComment, at?: CommentRange): Command;
|
|
12
|
+
/**
|
|
13
|
+
* Whether that stretch of text, or the current selection, can receive a comment.
|
|
8
14
|
* This is the command itself asked without a dispatch, over a comment standing in for the one the
|
|
9
15
|
* user would write, so the button and the click cannot answer differently.
|
|
10
16
|
*/
|
|
11
|
-
export declare function canAddComment(state: EditorState): boolean;
|
|
17
|
+
export declare function canAddComment(state: EditorState, at?: CommentRange): boolean;
|
|
12
18
|
/** Replaces the plain-text body of one comment, retaining its author and anchor. */
|
|
13
19
|
export declare function updateComment(id: string, text: string): Command;
|
|
20
|
+
/**
|
|
21
|
+
* Replaces the body of one comment with a formatted one, retaining its author and anchor.
|
|
22
|
+
*
|
|
23
|
+
* The body is a document of `docxSchema`, the same schema the page is edited in, so a paragraph
|
|
24
|
+
* style, a bold run and a second paragraph all survive being written, exported and read back.
|
|
25
|
+
* `updateComment` is the same command for a body that is only text.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setCommentBody(id: string, body: PMNode): Command;
|
|
14
28
|
/** Marks a comment thread resolved or open without deleting it. */
|
|
15
29
|
export declare function setCommentResolved(id: string, resolved: boolean): Command;
|
|
16
30
|
/** Adds a plain-text reply and reopens the thread when it was resolved. */
|
|
@@ -19,7 +33,13 @@ export declare function addCommentReply(id: string, reply: NewComment): Command;
|
|
|
19
33
|
export declare function updateCommentReply(commentId: string, replyId: string, text: string): Command;
|
|
20
34
|
/** Removes one reply while retaining its root comment and anchor. */
|
|
21
35
|
export declare function removeCommentReply(commentId: string, replyId: string): Command;
|
|
22
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Removes a comment's range markers, reference and Comments-part entry.
|
|
38
|
+
*
|
|
39
|
+
* What the comment and its replies said goes with them: a body is a story on the document node,
|
|
40
|
+
* and one left behind for a comment nothing refers to would ride every later comparison of the
|
|
41
|
+
* document without standing for anything.
|
|
42
|
+
*/
|
|
23
43
|
export declare function removeComment(id: string): Command;
|
|
24
44
|
/** Selects the text anchored by a comment, or places the caret at a point comment. */
|
|
25
45
|
export declare function selectComment(id: string): Command;
|
|
@@ -3,16 +3,39 @@ import {
|
|
|
3
3
|
TextSelection
|
|
4
4
|
} from "prosemirror-state";
|
|
5
5
|
import { commentParaId } from "../../../docx/comments.js";
|
|
6
|
+
import {
|
|
7
|
+
sameStory,
|
|
8
|
+
setStory,
|
|
9
|
+
storyFromText,
|
|
10
|
+
storyKey,
|
|
11
|
+
storyOf,
|
|
12
|
+
storyText,
|
|
13
|
+
withoutStories
|
|
14
|
+
} from "../../../docx/story.js";
|
|
6
15
|
import { docxSchema } from "../../../schema/index.js";
|
|
7
16
|
import { guardedCommand } from "../../../schema/guards.js";
|
|
8
17
|
import {
|
|
9
18
|
reservedCommentIds,
|
|
10
19
|
reservedCommentParaIds
|
|
11
20
|
} from "../../editorDocument.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
21
|
+
import {
|
|
22
|
+
repliesAttr,
|
|
23
|
+
stringAttr
|
|
24
|
+
} from "./model.js";
|
|
25
|
+
import { commentById } from "./reading.js";
|
|
26
|
+
function anchorableRange(doc, { from, to }) {
|
|
27
|
+
if (!Number.isInteger(from) || !Number.isInteger(to) || from >= to || from < 0 || to > doc.content.size)
|
|
28
|
+
return null;
|
|
29
|
+
const $from = doc.resolve(from);
|
|
30
|
+
if (!$from.sameParent(doc.resolve(to))) return null;
|
|
31
|
+
if ($from.parent.type.name !== "paragraph") return null;
|
|
32
|
+
return { from, to };
|
|
33
|
+
}
|
|
34
|
+
function commentedRange(state, at) {
|
|
35
|
+
if (at) return anchorableRange(state.doc, at);
|
|
14
36
|
const { selection } = state;
|
|
15
|
-
|
|
37
|
+
if (!(selection instanceof TextSelection)) return null;
|
|
38
|
+
return anchorableRange(state.doc, selection);
|
|
16
39
|
}
|
|
17
40
|
function nextCommentId(state) {
|
|
18
41
|
const taken = /* @__PURE__ */ new Set();
|
|
@@ -73,14 +96,13 @@ function wrapperMarksAt(state, pos, side) {
|
|
|
73
96
|
(mark) => mark.type.name === "sdt" || mark.type.name === "link"
|
|
74
97
|
);
|
|
75
98
|
}
|
|
76
|
-
function addCommentTransaction(state, comment) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
99
|
+
function addCommentTransaction(state, comment, at) {
|
|
100
|
+
const range = commentedRange(state, at);
|
|
101
|
+
if (range === null || comment.text.trim().length === 0) return null;
|
|
80
102
|
const id = nextCommentId(state);
|
|
81
103
|
const date = comment.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
82
104
|
const paraId = nextCommentParaId(state, `comment-${id}-${date}`);
|
|
83
|
-
const { from, to } =
|
|
105
|
+
const { from, to } = range;
|
|
84
106
|
const startMarks = wrapperMarksAt(state, from, "after");
|
|
85
107
|
const endMarks = wrapperMarksAt(state, to, "before");
|
|
86
108
|
const end = docxSchema.nodes.commentEnd.create(
|
|
@@ -96,9 +118,6 @@ function addCommentTransaction(state, comment) {
|
|
|
96
118
|
authorId: comment.authorId ?? null,
|
|
97
119
|
initials: comment.initials ?? null,
|
|
98
120
|
date,
|
|
99
|
-
text: comment.text,
|
|
100
|
-
commentXml: null,
|
|
101
|
-
imported: false,
|
|
102
121
|
paraId,
|
|
103
122
|
resolved: false,
|
|
104
123
|
extensionXml: null,
|
|
@@ -113,39 +132,63 @@ function addCommentTransaction(state, comment) {
|
|
|
113
132
|
null,
|
|
114
133
|
startMarks
|
|
115
134
|
);
|
|
116
|
-
return
|
|
135
|
+
return setStory(
|
|
136
|
+
state.tr.insert(to, end).insert(to + 1, reference).insert(from, start),
|
|
137
|
+
storyKey("comment", id),
|
|
138
|
+
storyFromText(comment.text)
|
|
139
|
+
);
|
|
117
140
|
}
|
|
118
|
-
function addComment(comment) {
|
|
119
|
-
return guardedCommand((state) => addCommentTransaction(state, comment));
|
|
141
|
+
function addComment(comment, at) {
|
|
142
|
+
return guardedCommand((state) => addCommentTransaction(state, comment, at));
|
|
120
143
|
}
|
|
121
|
-
function canAddComment(state) {
|
|
122
|
-
return addComment(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
144
|
+
function canAddComment(state, at) {
|
|
145
|
+
return addComment(
|
|
146
|
+
{
|
|
147
|
+
text: "comment",
|
|
148
|
+
author: "Author",
|
|
149
|
+
date: "1970-01-01T00:00:00.000Z"
|
|
150
|
+
},
|
|
151
|
+
at
|
|
152
|
+
)(state);
|
|
127
153
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
...node.attrs,
|
|
137
|
-
text,
|
|
138
|
-
commentXml: null,
|
|
139
|
-
imported: false
|
|
140
|
-
});
|
|
141
|
-
changed = true;
|
|
142
|
-
}
|
|
143
|
-
return true;
|
|
144
|
-
});
|
|
145
|
-
return changed ? transaction : null;
|
|
154
|
+
function referenceAt(state, id) {
|
|
155
|
+
let at = null;
|
|
156
|
+
state.doc.descendants((node, pos) => {
|
|
157
|
+
if (at !== null) return false;
|
|
158
|
+
if (node.type.name === "commentReference" && stringAttr(node.attrs.id) === id) {
|
|
159
|
+
at = pos;
|
|
160
|
+
}
|
|
161
|
+
return at === null;
|
|
146
162
|
});
|
|
163
|
+
return at;
|
|
164
|
+
}
|
|
165
|
+
function bodyTransaction(state, referenceId, bodyId, body) {
|
|
166
|
+
if (body.type !== docxSchema.nodes.doc) return null;
|
|
167
|
+
if (storyText(body).trim().length === 0) return null;
|
|
168
|
+
const at = referenceAt(state, referenceId);
|
|
169
|
+
const reference = at === null ? null : state.doc.nodeAt(at);
|
|
170
|
+
if (at === null || reference === null) return null;
|
|
171
|
+
const key = storyKey("comment", bodyId);
|
|
172
|
+
if (sameStory(storyOf(state.doc, key), body)) return null;
|
|
173
|
+
return setStory(state.tr, key, body).setNodeMarkup(
|
|
174
|
+
at,
|
|
175
|
+
null,
|
|
176
|
+
reference.attrs,
|
|
177
|
+
reference.marks
|
|
178
|
+
);
|
|
147
179
|
}
|
|
148
|
-
function
|
|
180
|
+
function textBodyTransaction(state, referenceId, bodyId, text) {
|
|
181
|
+
const held = storyOf(state.doc, storyKey("comment", bodyId));
|
|
182
|
+
if (held !== null && storyText(held) === text) return null;
|
|
183
|
+
return bodyTransaction(state, referenceId, bodyId, storyFromText(text));
|
|
184
|
+
}
|
|
185
|
+
function updateComment(id, text) {
|
|
186
|
+
return guardedCommand((state) => textBodyTransaction(state, id, id, text));
|
|
187
|
+
}
|
|
188
|
+
function setCommentBody(id, body) {
|
|
189
|
+
return guardedCommand((state) => bodyTransaction(state, id, id, body));
|
|
190
|
+
}
|
|
191
|
+
function updateReference(id, change, alongside = (tr) => tr) {
|
|
149
192
|
return guardedCommand((state) => {
|
|
150
193
|
let transaction = state.tr;
|
|
151
194
|
let changed = false;
|
|
@@ -160,7 +203,7 @@ function updateReference(id, change) {
|
|
|
160
203
|
}
|
|
161
204
|
return false;
|
|
162
205
|
});
|
|
163
|
-
return changed ? transaction : null;
|
|
206
|
+
return changed ? alongside(transaction) : null;
|
|
164
207
|
});
|
|
165
208
|
}
|
|
166
209
|
function setCommentResolved(id, resolved) {
|
|
@@ -179,84 +222,110 @@ function addCommentReply(id, reply) {
|
|
|
179
222
|
if (reply.text.trim().length === 0) return false;
|
|
180
223
|
const replyId = nextCommentId(state);
|
|
181
224
|
const date = reply.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
182
|
-
return updateReference(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
parentParaId
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
225
|
+
return updateReference(
|
|
226
|
+
id,
|
|
227
|
+
(node) => {
|
|
228
|
+
const parentParaId = stringAttr(node.attrs.paraId) ?? nextCommentParaId(state, `comment-${id}`);
|
|
229
|
+
const paraId = nextCommentParaId(state, `comment-${replyId}-${date}`, [
|
|
230
|
+
parentParaId
|
|
231
|
+
]);
|
|
232
|
+
return {
|
|
233
|
+
...node.attrs,
|
|
234
|
+
paraId: parentParaId,
|
|
235
|
+
resolved: false,
|
|
236
|
+
extensionXml: null,
|
|
237
|
+
threadImported: false,
|
|
238
|
+
replies: [
|
|
239
|
+
...repliesAttr(node.attrs.replies),
|
|
240
|
+
{
|
|
241
|
+
id: replyId,
|
|
242
|
+
author: reply.author,
|
|
243
|
+
authorId: reply.authorId ?? null,
|
|
244
|
+
initials: reply.initials ?? null,
|
|
245
|
+
date,
|
|
246
|
+
paraId,
|
|
247
|
+
parentParaId,
|
|
248
|
+
extensionXml: null
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
(tr) => setStory(tr, storyKey("comment", replyId), storyFromText(reply.text))
|
|
254
|
+
)(state, dispatch);
|
|
211
255
|
};
|
|
212
256
|
}
|
|
213
257
|
function updateCommentReply(commentId, replyId, text) {
|
|
214
|
-
return
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
return
|
|
220
|
-
...node.attrs,
|
|
221
|
-
replies: replies.map(
|
|
222
|
-
(reply, at) => at === index ? { ...reply, text, commentXml: null, imported: false } : reply
|
|
223
|
-
)
|
|
224
|
-
};
|
|
258
|
+
return guardedCommand((state) => {
|
|
259
|
+
const holdsReply = repliesOf(state, commentId).some(
|
|
260
|
+
(reply) => reply.id === replyId
|
|
261
|
+
);
|
|
262
|
+
if (!holdsReply) return null;
|
|
263
|
+
return textBodyTransaction(state, commentId, replyId, text);
|
|
225
264
|
});
|
|
226
265
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
const removedParaIds = new Set(
|
|
233
|
-
replies.filter((reply) => removedIds.has(reply.id)).map((reply) => reply.paraId)
|
|
234
|
-
);
|
|
235
|
-
let changed = true;
|
|
236
|
-
while (changed) {
|
|
237
|
-
changed = false;
|
|
238
|
-
for (const reply of replies) {
|
|
239
|
-
if (!removedIds.has(reply.id) && removedParaIds.has(reply.parentParaId)) {
|
|
240
|
-
removedIds.add(reply.id);
|
|
241
|
-
removedParaIds.add(reply.paraId);
|
|
242
|
-
changed = true;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
266
|
+
function repliesOf(state, commentId) {
|
|
267
|
+
let replies = [];
|
|
268
|
+
state.doc.descendants((node) => {
|
|
269
|
+
if (node.type.name !== "commentReference" || stringAttr(node.attrs.id) !== commentId) {
|
|
270
|
+
return true;
|
|
245
271
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
replies: replies.filter((reply) => !removedIds.has(reply.id)),
|
|
249
|
-
threadImported: false
|
|
250
|
-
};
|
|
272
|
+
replies = repliesAttr(node.attrs.replies);
|
|
273
|
+
return false;
|
|
251
274
|
});
|
|
275
|
+
return replies;
|
|
276
|
+
}
|
|
277
|
+
function dropBodies(ids) {
|
|
278
|
+
return (tr) => withoutStories(
|
|
279
|
+
tr,
|
|
280
|
+
Array.from(ids, (id) => storyKey("comment", id))
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
function removeCommentReply(commentId, replyId) {
|
|
284
|
+
return (state, dispatch) => {
|
|
285
|
+
const removedReplyIds = /* @__PURE__ */ new Set();
|
|
286
|
+
return updateReference(
|
|
287
|
+
commentId,
|
|
288
|
+
(node) => {
|
|
289
|
+
const replies = repliesAttr(node.attrs.replies);
|
|
290
|
+
if (!replies.some((reply) => reply.id === replyId)) return null;
|
|
291
|
+
const removedIds = /* @__PURE__ */ new Set([replyId]);
|
|
292
|
+
const removedParaIds = new Set(
|
|
293
|
+
replies.filter((reply) => removedIds.has(reply.id)).map((reply) => reply.paraId)
|
|
294
|
+
);
|
|
295
|
+
let changed = true;
|
|
296
|
+
while (changed) {
|
|
297
|
+
changed = false;
|
|
298
|
+
for (const reply of replies) {
|
|
299
|
+
if (!removedIds.has(reply.id) && removedParaIds.has(reply.parentParaId)) {
|
|
300
|
+
removedIds.add(reply.id);
|
|
301
|
+
removedParaIds.add(reply.paraId);
|
|
302
|
+
changed = true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const id of removedIds) removedReplyIds.add(id);
|
|
307
|
+
return {
|
|
308
|
+
...node.attrs,
|
|
309
|
+
replies: replies.filter((reply) => !removedIds.has(reply.id)),
|
|
310
|
+
threadImported: false
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
dropBodies(removedReplyIds)
|
|
314
|
+
)(state, dispatch);
|
|
315
|
+
};
|
|
252
316
|
}
|
|
253
317
|
function removeComment(id) {
|
|
254
318
|
return guardedCommand((state) => {
|
|
255
319
|
const positions = [];
|
|
320
|
+
const bodies = /* @__PURE__ */ new Set([id]);
|
|
256
321
|
state.doc.descendants((node, pos) => {
|
|
257
322
|
if ((node.type.name === "commentStart" || node.type.name === "commentEnd" || node.type.name === "commentReference") && stringAttr(node.attrs.id) === id) {
|
|
258
323
|
positions.push({ pos, size: node.nodeSize });
|
|
259
324
|
}
|
|
325
|
+
if (node.type.name === "commentReference" && stringAttr(node.attrs.id) === id) {
|
|
326
|
+
for (const reply of repliesAttr(node.attrs.replies))
|
|
327
|
+
bodies.add(reply.id);
|
|
328
|
+
}
|
|
260
329
|
return true;
|
|
261
330
|
});
|
|
262
331
|
if (positions.length === 0) return null;
|
|
@@ -264,12 +333,12 @@ function removeComment(id) {
|
|
|
264
333
|
for (const marker of positions.sort((a, b) => b.pos - a.pos)) {
|
|
265
334
|
transaction.delete(marker.pos, marker.pos + marker.size);
|
|
266
335
|
}
|
|
267
|
-
return transaction;
|
|
336
|
+
return dropBodies(bodies)(transaction);
|
|
268
337
|
});
|
|
269
338
|
}
|
|
270
339
|
function selectComment(id) {
|
|
271
340
|
return (state, dispatch) => {
|
|
272
|
-
const comment =
|
|
341
|
+
const comment = commentById(state, id);
|
|
273
342
|
if (!comment) return false;
|
|
274
343
|
dispatch?.(
|
|
275
344
|
state.tr.setSelection(
|
|
@@ -286,6 +355,7 @@ export {
|
|
|
286
355
|
removeComment,
|
|
287
356
|
removeCommentReply,
|
|
288
357
|
selectComment,
|
|
358
|
+
setCommentBody,
|
|
289
359
|
setCommentResolved,
|
|
290
360
|
updateComment,
|
|
291
361
|
updateCommentReply
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Public comment values and shared attribute readers. */
|
|
2
|
+
import type { CommentReplyData } from "../../../docx/comments";
|
|
3
|
+
/** Who a comment is written by: the name it is shown under, and the identity behind it. */
|
|
2
4
|
export interface CommentAuthor {
|
|
3
5
|
/**
|
|
4
6
|
* The identity behind the name, an opaque string the host application chooses. Recorded in the
|
|
@@ -8,6 +10,14 @@ export interface CommentAuthor {
|
|
|
8
10
|
name: string;
|
|
9
11
|
initials?: string;
|
|
10
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* A stretch of text a comment is written for: a pair of document positions, the way a comment
|
|
15
|
+
* already in the document reports its own (`DocumentComment.from`, `.to`).
|
|
16
|
+
*/
|
|
17
|
+
export interface CommentRange {
|
|
18
|
+
from: number;
|
|
19
|
+
to: number;
|
|
20
|
+
}
|
|
11
21
|
export interface NewComment {
|
|
12
22
|
text: string;
|
|
13
23
|
author: string;
|
|
@@ -17,24 +27,31 @@ export interface NewComment {
|
|
|
17
27
|
/** ISO 8601 timestamp. The current time is used when omitted. */
|
|
18
28
|
date?: string;
|
|
19
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* One comment as a reader of the document sees it. Every field is readonly: the record is the
|
|
32
|
+
* editor's own, worked out once per edit and handed to whoever asks (`editor/plugins/documentProjection`).
|
|
33
|
+
*/
|
|
20
34
|
export interface DocumentComment {
|
|
21
|
-
id: string;
|
|
22
|
-
author: string | null;
|
|
23
|
-
authorId: string | null;
|
|
24
|
-
initials: string | null;
|
|
25
|
-
date: string | null;
|
|
26
|
-
text
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly author: string | null;
|
|
37
|
+
readonly authorId: string | null;
|
|
38
|
+
readonly initials: string | null;
|
|
39
|
+
readonly date: string | null;
|
|
40
|
+
/** What it says, as plain text. The formatting behind it is `setCommentBody`'s to write */
|
|
41
|
+
readonly text: string;
|
|
42
|
+
readonly from: number;
|
|
43
|
+
readonly to: number;
|
|
44
|
+
readonly referencePos: number;
|
|
45
|
+
readonly resolved: boolean;
|
|
46
|
+
readonly replies: readonly DocumentCommentReply[];
|
|
32
47
|
}
|
|
33
48
|
export interface DocumentCommentReply {
|
|
34
|
-
id: string;
|
|
35
|
-
author: string | null;
|
|
36
|
-
authorId: string | null;
|
|
37
|
-
initials: string | null;
|
|
38
|
-
date: string | null;
|
|
39
|
-
text: string;
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly author: string | null;
|
|
51
|
+
readonly authorId: string | null;
|
|
52
|
+
readonly initials: string | null;
|
|
53
|
+
readonly date: string | null;
|
|
54
|
+
readonly text: string;
|
|
40
55
|
}
|
|
56
|
+
export declare function stringAttr(value: unknown): string | null;
|
|
57
|
+
export declare function repliesAttr(value: unknown): readonly CommentReplyData[];
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/** Reads comment threads and anchors from the editor document. */
|
|
2
2
|
import type { EditorState } from "prosemirror-state";
|
|
3
|
-
import type { CommentReplyData } from "../../../docx/comments";
|
|
4
3
|
import type { DocumentComment } from "./model";
|
|
5
|
-
export declare function stringAttr(value: unknown): string | null;
|
|
6
|
-
export declare function repliesAttr(value: unknown): readonly CommentReplyData[];
|
|
7
4
|
/** Comments in document order, including point comments that have no explicit range. */
|
|
8
5
|
export declare function documentComments(state: EditorState): readonly DocumentComment[];
|
|
6
|
+
/**
|
|
7
|
+
* The comment this id names, or undefined where the document holds none. Answered off the lookup
|
|
8
|
+
* the projection built, so a command asking after one comment does not read the whole list.
|
|
9
|
+
*/
|
|
10
|
+
export declare function commentById(state: EditorState, id: string): DocumentComment | undefined;
|
|
9
11
|
/**
|
|
10
12
|
* Whether the body of this comment, or of one of its replies, may be edited or deleted here: the
|
|
11
13
|
* protection lets comments be edited at all, and the body is one of one's own to edit
|
|
@@ -1,72 +1,22 @@
|
|
|
1
1
|
// src/editor/commands/comments/reading.ts
|
|
2
2
|
import { commentOwned } from "../../../schema/protection.js";
|
|
3
3
|
import { protectionOf } from "../../../schema/protectionState.js";
|
|
4
|
-
|
|
5
|
-
return typeof value === "string" ? value : null;
|
|
6
|
-
}
|
|
7
|
-
function repliesAttr(value) {
|
|
8
|
-
return Array.isArray(value) ? value : [];
|
|
9
|
-
}
|
|
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 });
|
|
23
|
-
}
|
|
24
|
-
return true;
|
|
25
|
-
});
|
|
26
|
-
return { starts, ends, references };
|
|
27
|
-
}
|
|
4
|
+
import { commentProjection } from "../../plugins/commentDecorations.js";
|
|
28
5
|
function documentComments(state) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const end = markers.ends.get(id);
|
|
34
|
-
const hasRange = start !== void 0 && end !== void 0 && start < end;
|
|
35
|
-
const point = start ?? end ?? pos;
|
|
36
|
-
return {
|
|
37
|
-
id,
|
|
38
|
-
author: stringAttr(node.attrs.author),
|
|
39
|
-
authorId: stringAttr(node.attrs.authorId),
|
|
40
|
-
initials: stringAttr(node.attrs.initials),
|
|
41
|
-
date: stringAttr(node.attrs.date),
|
|
42
|
-
text: stringAttr(node.attrs.text) ?? "",
|
|
43
|
-
from: hasRange ? start + 1 : point,
|
|
44
|
-
to: hasRange ? end : point,
|
|
45
|
-
referencePos: pos,
|
|
46
|
-
resolved: node.attrs.resolved === true,
|
|
47
|
-
replies: repliesAttr(node.attrs.replies).map((reply) => ({
|
|
48
|
-
id: reply.id,
|
|
49
|
-
author: reply.author,
|
|
50
|
-
authorId: reply.authorId,
|
|
51
|
-
initials: reply.initials,
|
|
52
|
-
date: reply.date,
|
|
53
|
-
text: reply.text
|
|
54
|
-
}))
|
|
55
|
-
};
|
|
56
|
-
});
|
|
6
|
+
return commentProjection.read(state).comments;
|
|
7
|
+
}
|
|
8
|
+
function commentById(state, id) {
|
|
9
|
+
return commentProjection.read(state).byId.get(id);
|
|
57
10
|
}
|
|
58
11
|
function canEditComment(state, commentId, replyId = null) {
|
|
59
12
|
const rules = protectionOf(state);
|
|
60
13
|
if (rules.protection === "readOnly") return false;
|
|
61
|
-
const comment =
|
|
62
|
-
(entry) => entry.id === commentId
|
|
63
|
-
);
|
|
14
|
+
const comment = commentById(state, commentId);
|
|
64
15
|
const body = replyId === null ? comment : comment?.replies.find((reply) => reply.id === replyId);
|
|
65
16
|
return body !== void 0 && commentOwned(rules, body.authorId);
|
|
66
17
|
}
|
|
67
18
|
export {
|
|
68
19
|
canEditComment,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
stringAttr
|
|
20
|
+
commentById,
|
|
21
|
+
documentComments
|
|
72
22
|
};
|