@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
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
insertionInsideLocked,
|
|
10
10
|
rangeTouchesLocked
|
|
11
11
|
} from "../../../schema/locks.js";
|
|
12
|
+
import { editsShut } from "../../../schema/protectionState.js";
|
|
12
13
|
import {
|
|
13
14
|
activePieces,
|
|
14
15
|
caretPiece,
|
|
@@ -69,9 +70,13 @@ function applyToCaret(state, dispatch, edit) {
|
|
|
69
70
|
return true;
|
|
70
71
|
}
|
|
71
72
|
function runEditCommand(edit) {
|
|
72
|
-
return (state, dispatch) =>
|
|
73
|
+
return (state, dispatch) => {
|
|
74
|
+
if (editsShut(state)) return false;
|
|
75
|
+
return state.selection.empty ? applyToCaret(state, dispatch, edit) : applyToSelection(state, dispatch, edit);
|
|
76
|
+
};
|
|
73
77
|
}
|
|
74
78
|
function canFormatText(state) {
|
|
79
|
+
if (editsShut(state)) return false;
|
|
75
80
|
if (state.selection.empty) {
|
|
76
81
|
return !insertionInsideLocked(state.doc, state.selection.from);
|
|
77
82
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
// src/editor/commands/historyCommands.ts
|
|
2
2
|
import { redo as historyRedo, undo as historyUndo } from "prosemirror-history";
|
|
3
|
+
import { transactionAllowed } from "../../schema/guards.js";
|
|
3
4
|
import { historyReplay } from "../../schema/locks.js";
|
|
4
5
|
function replaying(command) {
|
|
5
|
-
return (state, dispatch, view) =>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
return (state, dispatch, view) => {
|
|
7
|
+
const built = [];
|
|
8
|
+
const answered = command(
|
|
9
|
+
state,
|
|
10
|
+
(tr) => built.push(tr.setMeta(historyReplay, true)),
|
|
11
|
+
view
|
|
12
|
+
);
|
|
13
|
+
if (!answered || !built.every((tr) => transactionAllowed(tr, state))) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (dispatch) for (const tr of built) dispatch(tr);
|
|
17
|
+
return true;
|
|
18
|
+
};
|
|
10
19
|
}
|
|
11
20
|
var undo = replaying(historyUndo);
|
|
12
21
|
var redo = replaying(historyRedo);
|
|
@@ -15,6 +15,12 @@ export type { LineSpacing, ParagraphAlign } from "../../model/format";
|
|
|
15
15
|
export type { ListKind } from "../../numbering/listTemplate";
|
|
16
16
|
/** The size an image is shown at, which the insert command asks for */
|
|
17
17
|
export type { ImageExtent } from "../../ooxml/image";
|
|
18
|
+
/**
|
|
19
|
+
* What the editor as a whole may receive - everything, comments alone, or nothing - which a control
|
|
20
|
+
* of your own reads before it offers an edit, the way the built-in ones do.
|
|
21
|
+
*/
|
|
22
|
+
export type { EditableComments, EditingProtection, } from "../../schema/protection";
|
|
23
|
+
export { editingProtection } from "../../schema/protectionState";
|
|
18
24
|
/** The fonts to draw with where a document names one the machine does not have, which the font queries read */
|
|
19
25
|
export type { FontFallbacks } from "../../styles/fontStack";
|
|
20
26
|
/**
|
|
@@ -49,7 +55,7 @@ export { insertLineBreak, insertPageBreak } from "./breakCommands";
|
|
|
49
55
|
*/
|
|
50
56
|
export { canRunCommand } from "./canRunCommand";
|
|
51
57
|
export type { CommentAuthor, DocumentComment, DocumentCommentReply, NewComment, } from "./commentCommands";
|
|
52
|
-
export { addComment, addCommentReply, canAddComment, documentComments, removeComment, removeCommentReply, selectComment, setCommentResolved, updateComment, updateCommentReply, } from "./commentCommands";
|
|
58
|
+
export { addComment, addCommentReply, canAddComment, canEditComment, documentComments, removeComment, removeCommentReply, selectComment, setCommentResolved, updateComment, updateCommentReply, } from "./commentCommands";
|
|
53
59
|
export type { ActiveFontFamily, ActiveFontSize, } from "./formattingCommands";
|
|
54
60
|
export { activeFontFamily, activeFontSize, activeTextBackground, activeTextColor, canFormatText, documentFontNames, isBoldActive, isItalicActive, isStrikeActive, isUnderlineActive, setFontFamily, setFontSize, setTextBackground, setTextColor, toggleBold, toggleItalic, toggleStrike, toggleUnderline, } from "./formattingCommands";
|
|
55
61
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/editor/commands/index.ts
|
|
2
|
+
import { editingProtection } from "../../schema/protectionState.js";
|
|
2
3
|
import {
|
|
3
4
|
documentBodyWidthPx,
|
|
4
5
|
documentDefaults,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
addComment,
|
|
20
21
|
addCommentReply,
|
|
21
22
|
canAddComment,
|
|
23
|
+
canEditComment,
|
|
22
24
|
documentComments,
|
|
23
25
|
removeComment,
|
|
24
26
|
removeCommentReply,
|
|
@@ -108,6 +110,7 @@ export {
|
|
|
108
110
|
addCommentReply,
|
|
109
111
|
canAddComment,
|
|
110
112
|
canDecreaseIndent,
|
|
113
|
+
canEditComment,
|
|
111
114
|
canFormatText,
|
|
112
115
|
canIncreaseIndent,
|
|
113
116
|
canInsertImage,
|
|
@@ -125,6 +128,7 @@ export {
|
|
|
125
128
|
documentHasLocked,
|
|
126
129
|
documentNotes,
|
|
127
130
|
documentParagraphStyles,
|
|
131
|
+
editingProtection,
|
|
128
132
|
fittedExtent,
|
|
129
133
|
imageFilesIn,
|
|
130
134
|
increaseIndent,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/editor/commands/linkCommands.ts
|
|
2
2
|
import { docxSchema } from "../../schema/index.js";
|
|
3
3
|
import { rangeTouchesLocked } from "../../schema/locks.js";
|
|
4
|
+
import { editsShut } from "../../schema/protectionState.js";
|
|
4
5
|
var linkType = docxSchema.marks.link;
|
|
5
6
|
function text(value) {
|
|
6
7
|
return typeof value === "string" ? value : null;
|
|
@@ -74,6 +75,7 @@ function linkSpansTouching(doc, from, to) {
|
|
|
74
75
|
return spans;
|
|
75
76
|
}
|
|
76
77
|
function openPieces(state) {
|
|
78
|
+
if (editsShut(state)) return [];
|
|
77
79
|
const found = state.selection.empty ? caretPieces(state) : selectionPieces(state);
|
|
78
80
|
return found.filter(
|
|
79
81
|
(piece) => !rangeTouchesLocked(state.doc, piece.from, piece.to)
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
} from "prosemirror-state";
|
|
5
5
|
import { namesNothing, newControlId } from "../../docx/sdt.js";
|
|
6
6
|
import { withContentLock } from "../../docx/sdtProps.js";
|
|
7
|
+
import { elementXml, openTagXml } from "../../ooxml/element.js";
|
|
8
|
+
import { wName } from "../../ooxml/names.js";
|
|
7
9
|
import { docxSchema } from "../../schema/index.js";
|
|
8
10
|
import {
|
|
9
11
|
carriesLock,
|
|
@@ -13,9 +15,17 @@ import {
|
|
|
13
15
|
selectionShut,
|
|
14
16
|
unlockAllowed
|
|
15
17
|
} from "../../schema/locks.js";
|
|
18
|
+
import { editsShut } from "../../schema/protectionState.js";
|
|
16
19
|
function lockedControlMark(id) {
|
|
17
20
|
return docxSchema.marks.sdt.create({
|
|
18
|
-
sdtPrefix:
|
|
21
|
+
sdtPrefix: openTagXml(wName("sdt"), null) + elementXml(
|
|
22
|
+
wName("sdtPr"),
|
|
23
|
+
[],
|
|
24
|
+
[
|
|
25
|
+
elementXml(wName("id"), [[wName("val"), `${id}`]]),
|
|
26
|
+
elementXml(wName("lock"), [[wName("val"), "sdtContentLocked"]])
|
|
27
|
+
]
|
|
28
|
+
),
|
|
19
29
|
contentsLocked: true,
|
|
20
30
|
deletionLocked: true
|
|
21
31
|
});
|
|
@@ -88,6 +98,7 @@ function blockLockEdits(doc, block, spans, reach) {
|
|
|
88
98
|
return [...shut, ...fresh];
|
|
89
99
|
}
|
|
90
100
|
function selectionLockDetail(state) {
|
|
101
|
+
if (editsShut(state)) return { kind: "none" };
|
|
91
102
|
const selection = state.selection;
|
|
92
103
|
const locking = !selection.empty && selection instanceof TextSelection;
|
|
93
104
|
const edits = [];
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "prosemirror-state";
|
|
5
5
|
import { toParagraphFormat } from "../../model/format.js";
|
|
6
6
|
import { docxSchema } from "../../schema/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { editShut, selectionIntents } from "../../schema/guards.js";
|
|
8
8
|
function isOrdinaryParagraph(state) {
|
|
9
9
|
const { $from } = state.selection;
|
|
10
10
|
if ($from.parent.type !== docxSchema.nodes.paragraph || toParagraphFormat($from.parent.attrs.format)?.numbering) {
|
|
@@ -17,7 +17,9 @@ function isOrdinaryParagraph(state) {
|
|
|
17
17
|
}
|
|
18
18
|
var insertTab = (state, dispatch) => {
|
|
19
19
|
const { selection } = state;
|
|
20
|
-
if (!(selection instanceof TextSelection) || !selection.$from.sameParent(selection.$to) || !isOrdinaryParagraph(state) ||
|
|
20
|
+
if (!(selection instanceof TextSelection) || !selection.$from.sameParent(selection.$to) || !isOrdinaryParagraph(state) || selectionIntents(selection, "replace").some(
|
|
21
|
+
(intent) => editShut(state, intent)
|
|
22
|
+
)) {
|
|
21
23
|
return false;
|
|
22
24
|
}
|
|
23
25
|
if (dispatch) {
|
|
@@ -8,7 +8,9 @@ import { type ParagraphFormatLayer, type ParagraphStyleOption, type StyleTable }
|
|
|
8
8
|
import { type PageGeometry } from "../docx/pageGeometry";
|
|
9
9
|
import type { DocumentDefaults } from "../model/format";
|
|
10
10
|
import { type Numbering } from "../numbering/parseNumbering";
|
|
11
|
+
import type { EditableComments, EditingProtection } from "../schema/protection";
|
|
11
12
|
import { type FontFallbacks } from "../styles/fontStack";
|
|
13
|
+
import type { CommentAuthor } from "./commands/comments/model";
|
|
12
14
|
export interface EditorStateOptions {
|
|
13
15
|
numbering?: Numbering;
|
|
14
16
|
styles?: StyleTable;
|
|
@@ -38,22 +40,30 @@ export interface EditorStateOptions {
|
|
|
38
40
|
reservedCommentIds?: Iterable<string>;
|
|
39
41
|
/** Every paragraph id present in the opened comment parts, including orphan extension entries. */
|
|
40
42
|
reservedCommentParaIds?: Iterable<string>;
|
|
43
|
+
/**
|
|
44
|
+
* What the document as a whole may receive (`schema/protection`): everything, comments alone,
|
|
45
|
+
* or nothing. Everything when none is given, which is what every state was before.
|
|
46
|
+
*/
|
|
47
|
+
protection?: EditingProtection;
|
|
48
|
+
/** Whose comments are written, and so whose may be edited under `editableComments: "own"` */
|
|
49
|
+
author?: CommentAuthor | null;
|
|
50
|
+
/** Whose comments may be edited or deleted. One's own when none is given */
|
|
51
|
+
editableComments?: EditableComments;
|
|
41
52
|
}
|
|
42
53
|
/**
|
|
43
54
|
* Creates a single editing state.
|
|
44
55
|
* The list definitions, the style table, and the document defaults are what the document
|
|
45
56
|
* wrote down, and commands and the toolbar read them through plugins.
|
|
46
57
|
*/
|
|
47
|
-
export declare function createEditorState(doc: PMNode, { numbering, styles, defaults, paragraphDefaults, canStartNewList, consumerPlugins, paragraphStyles, contextMenus, geometry, defaultTabStopPt, reservedCommentIds, reservedCommentParaIds, }?: EditorStateOptions): EditorState;
|
|
58
|
+
export declare function createEditorState(doc: PMNode, { numbering, styles, defaults, paragraphDefaults, canStartNewList, consumerPlugins, paragraphStyles, contextMenus, geometry, defaultTabStopPt, reservedCommentIds, reservedCommentParaIds, protection, author, editableComments, }?: EditorStateOptions): EditorState;
|
|
48
59
|
export interface EditorOptions {
|
|
49
60
|
mount: HTMLElement;
|
|
50
61
|
state: EditorState;
|
|
51
62
|
defaults: DocumentDefaults;
|
|
52
63
|
/** The paper the document names. The sheet is drawn from it, A4 where a document names none */
|
|
53
64
|
geometry?: PageGeometry;
|
|
54
|
-
readOnly: boolean;
|
|
55
65
|
/** The fonts stood in for the ones the document declares. The built-in set when none is given */
|
|
56
66
|
fontFallbacks?: FontFallbacks;
|
|
57
67
|
onStateChange: (state: EditorState) => void;
|
|
58
68
|
}
|
|
59
|
-
export declare function createEditorView({ mount, state, defaults, geometry,
|
|
69
|
+
export declare function createEditorView({ mount, state, defaults, geometry, fontFallbacks, onStateChange, }: EditorOptions): EditorView;
|
|
@@ -15,6 +15,7 @@ import { A4_PORTRAIT } from "../docx/pageGeometry.js";
|
|
|
15
15
|
import { EMPTY_NUMBERING } from "../numbering/parseNumbering.js";
|
|
16
16
|
import { pageDecorations } from "../page/pageDecorations.js";
|
|
17
17
|
import { pageGeometryStyle, pagePixels } from "../page/pageLayout.js";
|
|
18
|
+
import { editsShut } from "../schema/protectionState.js";
|
|
18
19
|
import { editorClassNames } from "../styles/classNames.js";
|
|
19
20
|
import {
|
|
20
21
|
DEFAULT_FONT_FALLBACKS
|
|
@@ -24,16 +25,15 @@ import { gridBorders, withDerivedGridBorders } from "../table/gridBorders.js";
|
|
|
24
25
|
import { documentDefaultTabStopPt, documentStyleTable } from "./documentStyles.js";
|
|
25
26
|
import { externalClipboard } from "./externalClipboard.js";
|
|
26
27
|
import { imageFiles } from "./imageFiles.js";
|
|
27
|
-
import { bookmarkProtection } from "./plugins/bookmarkProtection.js";
|
|
28
28
|
import { columnResize } from "./plugins/columnResize.js";
|
|
29
29
|
import { commentDecorations } from "./plugins/commentDecorations.js";
|
|
30
30
|
import { commentReservations } from "./plugins/commentReservations.js";
|
|
31
|
+
import { documentProtection } from "./plugins/documentProtection.js";
|
|
31
32
|
import { imagePaste } from "./plugins/imagePaste.js";
|
|
32
|
-
import { docxKeymap } from "./plugins/keymap.js";
|
|
33
|
+
import { docxKeymap, historyKeys } from "./plugins/keymap.js";
|
|
33
34
|
import { linkPanel } from "./plugins/linkPanel.js";
|
|
34
35
|
import { listInputRules } from "./plugins/listInputRules.js";
|
|
35
36
|
import { lockedContent } from "./plugins/lockedContent.js";
|
|
36
|
-
import { noteProtection } from "./plugins/noteProtection.js";
|
|
37
37
|
import { numberingMarkers } from "./plugins/numberingDecorations.js";
|
|
38
38
|
import { rowResize } from "./plugins/rowResize.js";
|
|
39
39
|
import { styledParagraphs } from "./plugins/styledParagraphs.js";
|
|
@@ -57,7 +57,10 @@ function createEditorState(doc, {
|
|
|
57
57
|
geometry = A4_PORTRAIT,
|
|
58
58
|
defaultTabStopPt = DEFAULT_TAB_STOP_PT,
|
|
59
59
|
reservedCommentIds = [],
|
|
60
|
-
reservedCommentParaIds = []
|
|
60
|
+
reservedCommentParaIds = [],
|
|
61
|
+
protection = "none",
|
|
62
|
+
author = null,
|
|
63
|
+
editableComments = "own"
|
|
61
64
|
} = {}) {
|
|
62
65
|
return EditorState.create({
|
|
63
66
|
doc: withDerivedGridBorders(doc),
|
|
@@ -66,17 +69,16 @@ function createEditorState(doc, {
|
|
|
66
69
|
// first answer for a keypress, a paste, a drop or any other DOM event, so this is the
|
|
67
70
|
// only place from which a consumer handler can win over the built-in one.
|
|
68
71
|
...consumerPlugins,
|
|
69
|
-
// Refuses every edit
|
|
70
|
-
// optional: a document that locked a part of itself stays locked in every consumer
|
|
72
|
+
// Refuses every edit no guard in `schema/guards` lets through, whoever asked for it. It is
|
|
73
|
+
// not optional: a document that locked a part of itself stays locked in every consumer, and
|
|
74
|
+
// the preserved bookmark markers and note references stay where the file put them.
|
|
71
75
|
lockedContent(),
|
|
72
|
-
|
|
73
|
-
bookmarkProtection(),
|
|
74
|
-
// Note bodies are display-only, so their imported main-story references stay paired.
|
|
75
|
-
noteProtection(),
|
|
76
|
+
documentProtection({ protection, author, editableComments }),
|
|
76
77
|
// An orphan Comments-part entry still owns its id and must not be replaced by a new comment.
|
|
77
78
|
commentReservations(reservedCommentIds, reservedCommentParaIds),
|
|
78
79
|
history(),
|
|
79
80
|
keymap(docxKeymap),
|
|
81
|
+
historyKeys(),
|
|
80
82
|
keymap(baseKeymap),
|
|
81
83
|
// Holds whether the link panel is open, which Cmd+K above and the toolbar button both set
|
|
82
84
|
linkPanel(),
|
|
@@ -128,18 +130,24 @@ function createEditorView({
|
|
|
128
130
|
state,
|
|
129
131
|
defaults,
|
|
130
132
|
geometry = A4_PORTRAIT,
|
|
131
|
-
readOnly,
|
|
132
133
|
fontFallbacks = DEFAULT_FONT_FALLBACKS,
|
|
133
134
|
onStateChange
|
|
134
135
|
}) {
|
|
136
|
+
const sheetStyle = `${pageGeometryStyle(pagePixels(geometry))};${documentDefaultsStyle(defaults, fontFallbacks)};`;
|
|
135
137
|
const view = new EditorView(mount, {
|
|
136
138
|
state,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
// A protection that shuts the body shuts typing with it, and is read off the state so that a
|
|
140
|
+
// mode switched on an open document takes effect without a new view. Selecting stays open
|
|
141
|
+
// either way, which is what a reader marking a stretch for a comment needs
|
|
142
|
+
editable: (current) => !editsShut(current),
|
|
143
|
+
attributes: (current) => ({
|
|
139
144
|
class: editorClassNames.sheet,
|
|
140
145
|
// The paper first, so a document that names one is drawn on it from the first frame
|
|
141
|
-
style: `${
|
|
142
|
-
|
|
146
|
+
style: `${sheetStyle}tab-size:${documentDefaultTabStopPt(current)}pt`,
|
|
147
|
+
// A sheet that takes no typing is no longer focusable of itself, so a reader or a commenter
|
|
148
|
+
// is handed the focus another way: the keys reach it, and the selection stays its own
|
|
149
|
+
...editsShut(current) ? { tabindex: "0" } : {}
|
|
150
|
+
}),
|
|
143
151
|
// The schema can only draw a run with the default fallback fonts, so this editor draws its own
|
|
144
152
|
markViews: { run: runMarkView(fontFallbacks) },
|
|
145
153
|
// An image is drawn by a view of its own, which is what carries the resize handles
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
// src/editor/externalClipboard.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DOMSerializer,
|
|
4
|
+
Fragment,
|
|
5
|
+
Slice
|
|
6
|
+
} from "prosemirror-model";
|
|
3
7
|
import { Plugin } from "prosemirror-state";
|
|
4
8
|
import { effectiveParagraphFormat, styleIdOf } from "../docx/formatting.js";
|
|
5
9
|
import { withParagraphStyle } from "../docx/paraProps.js";
|
|
6
10
|
import { MAX_ILVL, nextNumId } from "../numbering/listTemplate.js";
|
|
7
|
-
import {
|
|
11
|
+
import { elementXml } from "../ooxml/element.js";
|
|
12
|
+
import { wName } from "../ooxml/names.js";
|
|
13
|
+
import { docxSchema, isPageBreak } from "../schema/index.js";
|
|
8
14
|
import { editorClassNames } from "../styles/classNames.js";
|
|
9
15
|
import { PASTED_IMAGE_ATTRIBUTE } from "./clipboard/images.js";
|
|
10
16
|
import {
|
|
11
17
|
appendInline,
|
|
12
18
|
contextFor,
|
|
13
19
|
marksFor,
|
|
20
|
+
safeHref,
|
|
14
21
|
withInlineStyle
|
|
15
22
|
} from "./clipboard/inlineFormatting.js";
|
|
16
23
|
import { listRefOf } from "./commands/listCommands.js";
|
|
@@ -59,11 +66,149 @@ var HEADING_FALLBACKS = {
|
|
|
59
66
|
5: { bold: true, fontSizePt: 10 },
|
|
60
67
|
6: { bold: true, fontSizePt: 8 }
|
|
61
68
|
};
|
|
69
|
+
var PUBLIC_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
70
|
+
"alt",
|
|
71
|
+
"aria-label",
|
|
72
|
+
"class",
|
|
73
|
+
"colspan",
|
|
74
|
+
"height",
|
|
75
|
+
"href",
|
|
76
|
+
"lang",
|
|
77
|
+
"rowspan",
|
|
78
|
+
"src",
|
|
79
|
+
"style",
|
|
80
|
+
"width"
|
|
81
|
+
]);
|
|
82
|
+
var COPIED_STYLE_ATTRIBUTE = "data-style";
|
|
83
|
+
function isAttributes(value) {
|
|
84
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Node);
|
|
85
|
+
}
|
|
86
|
+
function publicAttributes(attrs) {
|
|
87
|
+
return Object.fromEntries(
|
|
88
|
+
Object.entries(attrs).filter(([name]) => PUBLIC_ATTRIBUTES.has(name))
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function isSpecArray(value) {
|
|
92
|
+
return Array.isArray(value) && typeof value[0] === "string";
|
|
93
|
+
}
|
|
94
|
+
function stripPrivateAttributes(spec) {
|
|
95
|
+
if (!isSpecArray(spec)) return spec;
|
|
96
|
+
const [tag, ...rest] = spec;
|
|
97
|
+
const attrs = isAttributes(rest[0]) ? publicAttributes(rest[0]) : null;
|
|
98
|
+
const children = (attrs === null ? rest : rest.slice(1)).map(
|
|
99
|
+
(child) => isSpecArray(child) ? stripPrivateAttributes(child) : child
|
|
100
|
+
);
|
|
101
|
+
return attrs === null ? [tag, ...children] : [tag, attrs, ...children];
|
|
102
|
+
}
|
|
103
|
+
function withDomAttribute(spec, name, value) {
|
|
104
|
+
if (!isSpecArray(spec)) return spec;
|
|
105
|
+
const [tag, ...rest] = spec;
|
|
106
|
+
return isAttributes(rest[0]) ? [tag, { ...rest[0], [name]: value }, ...rest.slice(1)] : [tag, { [name]: value }, ...rest];
|
|
107
|
+
}
|
|
108
|
+
function asTag(spec, tag) {
|
|
109
|
+
return isSpecArray(spec) ? [tag, ...spec.slice(1)] : spec;
|
|
110
|
+
}
|
|
111
|
+
function copiedMarkSpec(mark, spec) {
|
|
112
|
+
const stripped = stripPrivateAttributes(spec);
|
|
113
|
+
if (mark.type !== docxSchema.marks.link) return stripped;
|
|
114
|
+
const href = safeHref(
|
|
115
|
+
typeof mark.attrs.href === "string" ? mark.attrs.href : null
|
|
116
|
+
);
|
|
117
|
+
return href === null ? stripped : asTag(withDomAttribute(stripped, "href", href), "a");
|
|
118
|
+
}
|
|
119
|
+
function copiedNodeSpec(node, spec) {
|
|
120
|
+
const stripped = stripPrivateAttributes(spec);
|
|
121
|
+
if (node.type !== docxSchema.nodes.paragraph) return stripped;
|
|
122
|
+
const styleId = styleIdOf(node.attrs.pPr);
|
|
123
|
+
return styleId === null ? stripped : withDomAttribute(stripped, COPIED_STYLE_ATTRIBUTE, styleId);
|
|
124
|
+
}
|
|
125
|
+
function clipboardSerializer() {
|
|
126
|
+
const drawn = DOMSerializer.fromSchema(docxSchema);
|
|
127
|
+
const nodes = Object.fromEntries(
|
|
128
|
+
Object.entries(drawn.nodes).map(([name, toDOM]) => [
|
|
129
|
+
name,
|
|
130
|
+
(node) => copiedNodeSpec(node, toDOM(node))
|
|
131
|
+
])
|
|
132
|
+
);
|
|
133
|
+
const marks = Object.fromEntries(
|
|
134
|
+
Object.entries(drawn.marks).map(([name, toDOM]) => [
|
|
135
|
+
name,
|
|
136
|
+
(mark, inline) => copiedMarkSpec(mark, toDOM(mark, inline))
|
|
137
|
+
])
|
|
138
|
+
);
|
|
139
|
+
return new DOMSerializer(nodes, marks);
|
|
140
|
+
}
|
|
141
|
+
var UNREADABLE_BLOCKS = /* @__PURE__ */ new Set([
|
|
142
|
+
"bookmarkBlock",
|
|
143
|
+
"docxRaw",
|
|
144
|
+
"rawBlock"
|
|
145
|
+
]);
|
|
146
|
+
function stringAttr(value) {
|
|
147
|
+
return typeof value === "string" ? value : "";
|
|
148
|
+
}
|
|
149
|
+
function inlineText(node) {
|
|
150
|
+
if (node.isText) return node.text ?? "";
|
|
151
|
+
if (node.type === docxSchema.nodes.hardBreak) {
|
|
152
|
+
return isPageBreak(node.attrs.brAttrs) ? "\f" : "\n";
|
|
153
|
+
}
|
|
154
|
+
if (node.type === docxSchema.nodes.image) return stringAttr(node.attrs.alt);
|
|
155
|
+
if (node.type === docxSchema.nodes.noteReference) {
|
|
156
|
+
return node.attrs.customMarkFollows === true ? "" : stringAttr(node.attrs.label);
|
|
157
|
+
}
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
function rowsText(table) {
|
|
161
|
+
const rows = [];
|
|
162
|
+
table.forEach((row) => {
|
|
163
|
+
const cells = [];
|
|
164
|
+
row.forEach((cell) => {
|
|
165
|
+
cells.push(fragmentText(cell.content));
|
|
166
|
+
});
|
|
167
|
+
rows.push(cells.join(" "));
|
|
168
|
+
});
|
|
169
|
+
return rows.join("\n");
|
|
170
|
+
}
|
|
171
|
+
function blockText(node) {
|
|
172
|
+
if (node.type === docxSchema.nodes.table) return rowsText(node);
|
|
173
|
+
if (UNREADABLE_BLOCKS.has(node.type.name)) return "";
|
|
174
|
+
return fragmentText(node.content);
|
|
175
|
+
}
|
|
176
|
+
function fragmentText(fragment) {
|
|
177
|
+
const pieces = [];
|
|
178
|
+
fragment.forEach((child) => {
|
|
179
|
+
pieces.push(child.isInline ? inlineText(child) : blockText(child));
|
|
180
|
+
});
|
|
181
|
+
return pieces.join(fragment.firstChild?.isInline === true ? "" : "\n");
|
|
182
|
+
}
|
|
183
|
+
function bareWrappers(content, openStart, openEnd) {
|
|
184
|
+
const wrapper = content.firstChild;
|
|
185
|
+
if (openStart <= 1 || openEnd <= 1 || content.childCount !== 1)
|
|
186
|
+
return content;
|
|
187
|
+
if (wrapper === null || wrapper.childCount !== 1) return content;
|
|
188
|
+
return Fragment.from(
|
|
189
|
+
wrapper.type.create(
|
|
190
|
+
null,
|
|
191
|
+
bareWrappers(wrapper.content, openStart - 1, openEnd - 1),
|
|
192
|
+
wrapper.marks
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function copiedSlice(slice) {
|
|
197
|
+
return new Slice(
|
|
198
|
+
bareWrappers(slice.content, slice.openStart, slice.openEnd),
|
|
199
|
+
slice.openStart,
|
|
200
|
+
slice.openEnd
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
function clipboardText(slice) {
|
|
204
|
+
return fragmentText(slice.content);
|
|
205
|
+
}
|
|
62
206
|
function normalizedStyleName(value) {
|
|
63
207
|
return value.toLowerCase().replace(/[\s_-]+/g, "");
|
|
64
208
|
}
|
|
65
209
|
function copiedStyleId(element) {
|
|
66
|
-
|
|
210
|
+
if (!element.classList.contains(editorClassNames.paragraph)) return null;
|
|
211
|
+
return element.getAttribute(COPIED_STYLE_ATTRIBUTE) ?? styleIdOf(element.getAttribute("data-ppr"));
|
|
67
212
|
}
|
|
68
213
|
function headingLevel(element, sourceStyleId) {
|
|
69
214
|
const direct = HEADING_LEVELS[element.tagName];
|
|
@@ -116,7 +261,20 @@ function blockContext(state, parent, element, preserveParagraphStyle) {
|
|
|
116
261
|
function listParagraphAttrs(state, list) {
|
|
117
262
|
if (!list || list.numId === null) return null;
|
|
118
263
|
const ilvl = Math.min(MAX_ILVL, list.level);
|
|
119
|
-
const pPr =
|
|
264
|
+
const pPr = elementXml(
|
|
265
|
+
wName("pPr"),
|
|
266
|
+
[],
|
|
267
|
+
[
|
|
268
|
+
elementXml(
|
|
269
|
+
wName("numPr"),
|
|
270
|
+
[],
|
|
271
|
+
[
|
|
272
|
+
elementXml(wName("ilvl"), [[wName("val"), `${ilvl}`]]),
|
|
273
|
+
elementXml(wName("numId"), [[wName("val"), `${list.numId}`]])
|
|
274
|
+
]
|
|
275
|
+
)
|
|
276
|
+
]
|
|
277
|
+
);
|
|
120
278
|
return {
|
|
121
279
|
pPr,
|
|
122
280
|
format: effectiveParagraphFormat(pPr, documentParagraphFormatting(state))
|
|
@@ -294,8 +452,12 @@ function insertClipboardData(view, data) {
|
|
|
294
452
|
insertPlainText(view, data.text ?? "");
|
|
295
453
|
}
|
|
296
454
|
function externalClipboard() {
|
|
455
|
+
const serializer = clipboardSerializer();
|
|
297
456
|
return new Plugin({
|
|
298
457
|
props: {
|
|
458
|
+
clipboardSerializer: serializer,
|
|
459
|
+
clipboardTextSerializer: clipboardText,
|
|
460
|
+
transformCopied: copiedSlice,
|
|
299
461
|
handlePaste(view, event) {
|
|
300
462
|
insertClipboardData(view, {
|
|
301
463
|
html: event.clipboardData?.getData("text/html"),
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
import { insertPoint } from "prosemirror-transform";
|
|
3
3
|
import { toImageExtent, toImageSrc } from "../ooxml/image.js";
|
|
4
4
|
import { docxSchema } from "../schema/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { editShut, selectionIntents } from "../schema/guards.js";
|
|
6
6
|
function insertPosition(state) {
|
|
7
|
-
if (
|
|
7
|
+
if (selectionIntents(state.selection, "replace").some(
|
|
8
|
+
(intent) => editShut(state, intent)
|
|
9
|
+
)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
8
12
|
return insertPoint(state.doc, state.selection.from, docxSchema.nodes.image);
|
|
9
13
|
}
|
|
10
14
|
function canInsertImage(state) {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
TextSelection
|
|
5
5
|
} from "prosemirror-state";
|
|
6
6
|
import { createTableNode, isTableSide } from "../docx/tableTemplate.js";
|
|
7
|
+
import { editsShut } from "../schema/protectionState.js";
|
|
7
8
|
import { documentGeometry } from "./documentStyles.js";
|
|
8
9
|
function isInTable($pos) {
|
|
9
10
|
for (let depth = $pos.depth; depth > 0; depth -= 1) {
|
|
@@ -13,7 +14,7 @@ function isInTable($pos) {
|
|
|
13
14
|
}
|
|
14
15
|
function insertPosition(state) {
|
|
15
16
|
const $from = state.selection.$from;
|
|
16
|
-
if ($from.depth === 0 || isInTable($from)) return null;
|
|
17
|
+
if (editsShut(state) || $from.depth === 0 || isInTable($from)) return null;
|
|
17
18
|
return $from.after(1);
|
|
18
19
|
}
|
|
19
20
|
function canInsertTable(state) {
|
|
@@ -28,6 +28,8 @@ export declare function selectedParagraphs(state: EditorState): ParagraphSpot[];
|
|
|
28
28
|
* Only the paragraphs of a locked cell are shut. A paragraph merely holding a locked control keeps
|
|
29
29
|
* its own alignment, indent and style, which is what `insideLockedCell` says and
|
|
30
30
|
* `rangeTouchesLocked` would not (`schema/locks`).
|
|
31
|
+
*
|
|
32
|
+
* A protection that shuts the body leaves no paragraph editable (`schema/protection`).
|
|
31
33
|
*/
|
|
32
34
|
export declare function editableParagraphs(state: EditorState): ParagraphSpot[];
|
|
33
35
|
/** The original formatting XML this paragraph holds. Null when there is none */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { effectiveParagraphFormat } from "../docx/formatting.js";
|
|
3
3
|
import { docxSchema } from "../schema/index.js";
|
|
4
4
|
import { insideLockedCell } from "../schema/locks.js";
|
|
5
|
+
import { editsShut } from "../schema/protectionState.js";
|
|
5
6
|
import {
|
|
6
7
|
defaultParagraphStyleId,
|
|
7
8
|
documentParagraphFormatting,
|
|
@@ -19,6 +20,7 @@ function selectedParagraphs(state) {
|
|
|
19
20
|
return Array.from(spots, ([pos, node]) => ({ pos, node }));
|
|
20
21
|
}
|
|
21
22
|
function editableParagraphs(state) {
|
|
23
|
+
if (editsShut(state)) return [];
|
|
22
24
|
return selectedParagraphs(state).filter(
|
|
23
25
|
(spot) => !insideLockedCell(state.doc, spot.pos)
|
|
24
26
|
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds the protection the editor runs under (`schema/protection`) in the state, where the guards
|
|
3
|
+
* and the commands read it. The runtime filter stands in `lockedContent`, which enforces every
|
|
4
|
+
* guard `schema/guards` registers, the protection among them, in one pass.
|
|
5
|
+
*/
|
|
6
|
+
import { Plugin, type Transaction } from "prosemirror-state";
|
|
7
|
+
import type { EditableComments, EditingProtection, ProtectionState } from "../../schema/protection";
|
|
8
|
+
import type { CommentAuthor } from "../commands/comments/model";
|
|
9
|
+
export interface DocumentProtectionOptions {
|
|
10
|
+
protection: EditingProtection;
|
|
11
|
+
/** Whose comments are being written. Null where none are, a reader above all */
|
|
12
|
+
author: CommentAuthor | null;
|
|
13
|
+
editableComments: EditableComments;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The transaction that puts the editor under another protection, which is how a mode is switched
|
|
17
|
+
* on a document already open: `reconfigure` keeps the state a plugin of the same key already
|
|
18
|
+
* holds, so a new plugin instance would not do it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function setProtection(tr: Transaction, options: DocumentProtectionOptions): Transaction;
|
|
21
|
+
export declare function documentProtection(options: DocumentProtectionOptions): Plugin<ProtectionState>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/editor/plugins/documentProtection.ts
|
|
2
|
+
import { Plugin } from "prosemirror-state";
|
|
3
|
+
import { protectionKey } from "../../schema/protectionState.js";
|
|
4
|
+
function rulesOf({
|
|
5
|
+
protection,
|
|
6
|
+
author,
|
|
7
|
+
editableComments
|
|
8
|
+
}) {
|
|
9
|
+
return { protection, authorId: author?.id ?? null, editableComments };
|
|
10
|
+
}
|
|
11
|
+
var PROTECTIONS = [
|
|
12
|
+
"none",
|
|
13
|
+
"readOnly",
|
|
14
|
+
"comments"
|
|
15
|
+
];
|
|
16
|
+
var EDITABLE_COMMENTS = ["own", "all"];
|
|
17
|
+
function isOneOf(values, candidate) {
|
|
18
|
+
return typeof candidate === "string" && values.some((value) => value === candidate);
|
|
19
|
+
}
|
|
20
|
+
function isRules(value) {
|
|
21
|
+
if (typeof value !== "object" || value === null) return false;
|
|
22
|
+
const authorId = Reflect.get(value, "authorId");
|
|
23
|
+
return isOneOf(PROTECTIONS, Reflect.get(value, "protection")) && (authorId === null || typeof authorId === "string") && isOneOf(EDITABLE_COMMENTS, Reflect.get(value, "editableComments"));
|
|
24
|
+
}
|
|
25
|
+
function setProtection(tr, options) {
|
|
26
|
+
return tr.setMeta(protectionKey, rulesOf(options));
|
|
27
|
+
}
|
|
28
|
+
function documentProtection(options) {
|
|
29
|
+
const rules = rulesOf(options);
|
|
30
|
+
return new Plugin({
|
|
31
|
+
key: protectionKey,
|
|
32
|
+
state: {
|
|
33
|
+
init: () => rules,
|
|
34
|
+
apply: (transaction, value) => {
|
|
35
|
+
const next = transaction.getMeta(protectionKey);
|
|
36
|
+
return isRules(next) ? next : value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
documentProtection,
|
|
43
|
+
setProtection
|
|
44
|
+
};
|