@portone/docx-editor 0.5.1 → 0.6.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 +47 -0
- package/CONTRIBUTING.md +8 -2
- package/README.md +1 -1
- package/dist/DocxEditor.js +87 -29
- package/dist/docx/exportDocx.d.ts +2 -1
- package/dist/docx/exportDocx.js +3 -9
- package/dist/docx/headersFooters.js +35 -23
- package/dist/docx/identities.d.ts +28 -0
- package/dist/docx/identities.js +42 -10
- package/dist/docx/importDocx.js +25 -26
- package/dist/docx/importParagraph.d.ts +1 -4
- package/dist/docx/importParagraph.js +4 -18
- package/dist/docx/invariants.d.ts +1 -1
- package/dist/docx/invariants.js +45 -4
- package/dist/docx/notes/newNote.d.ts +39 -0
- package/dist/docx/notes/newNote.js +122 -0
- package/dist/docx/notes/numbering.d.ts +27 -0
- package/dist/docx/notes/numbering.js +120 -0
- package/dist/docx/notes/reading.d.ts +58 -0
- package/dist/docx/notes/reading.js +98 -0
- package/dist/docx/notes/writing.d.ts +19 -0
- package/dist/docx/notes/writing.js +96 -0
- package/dist/docx/partPlan.d.ts +3 -0
- package/dist/docx/partPlanners.d.ts +13 -0
- package/dist/docx/partPlanners.js +48 -0
- package/dist/docx/sections.d.ts +5 -0
- package/dist/docx/sections.js +7 -2
- package/dist/docx/serializeParagraph.js +7 -2
- package/dist/docx/session.d.ts +5 -0
- package/dist/docx/session.js +6 -0
- package/dist/docx/storyParts.d.ts +112 -0
- package/dist/docx/storyParts.js +289 -0
- package/dist/editor/clipboard/htmlReader.d.ts +24 -4
- package/dist/editor/clipboard/htmlReader.js +39 -4
- package/dist/editor/clipboard/internalChannel.d.ts +30 -3
- package/dist/editor/clipboard/internalChannel.js +43 -3
- package/dist/editor/clipboard/normalizers.d.ts +35 -8
- package/dist/editor/clipboard/normalizers.js +60 -11
- package/dist/editor/clipboard/plugin.js +42 -22
- package/dist/editor/commands/comments/editing.d.ts +4 -1
- package/dist/editor/commands/comments/editing.js +1 -3
- package/dist/editor/commands/comments/model.d.ts +7 -0
- package/dist/editor/commands/index.d.ts +1 -0
- package/dist/editor/commands/index.js +18 -0
- package/dist/editor/commands/noteCommands.d.ts +47 -0
- package/dist/editor/commands/noteCommands.js +95 -0
- package/dist/editor/commands/noteQueries.d.ts +22 -3
- package/dist/editor/commands/noteQueries.js +21 -13
- package/dist/editor/createEditor.js +25 -3
- package/dist/editor/documentStyles.d.ts +3 -0
- package/dist/editor/documentStyles.js +2 -1
- package/dist/editor/editorDocument.d.ts +24 -0
- package/dist/editor/editorDocument.js +34 -4
- package/dist/editor/notes/noteSurface.d.ts +53 -0
- package/dist/editor/notes/noteSurface.js +246 -0
- package/dist/editor/plugins/commentDecorations.js +5 -4
- package/dist/editor/plugins/commentRestoration.d.ts +20 -0
- package/dist/editor/plugins/commentRestoration.js +168 -0
- package/dist/editor/plugins/compositionSelection.d.ts +20 -0
- package/dist/editor/plugins/compositionSelection.js +19 -0
- package/dist/editor/plugins/keymap.d.ts +14 -0
- package/dist/editor/plugins/keymap.js +13 -1
- package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
- package/dist/editor/plugins/noteLifecycle.js +117 -0
- package/dist/editor/plugins/noteNavigation.d.ts +45 -0
- package/dist/editor/plugins/noteNavigation.js +86 -0
- package/dist/editor/plugins/noteNumbering.d.ts +13 -0
- package/dist/editor/plugins/noteNumbering.js +58 -0
- package/dist/editor/plugins/rowResize.js +2 -7
- package/dist/editor/plugins/tabLayout.js +2 -7
- package/dist/editor/stories/storyMarkup.d.ts +24 -0
- package/dist/editor/stories/storyMarkup.js +35 -0
- package/dist/editor/stories/storyState.d.ts +34 -0
- package/dist/editor/stories/storyState.js +74 -0
- package/dist/editor/stories/storyView.d.ts +115 -0
- package/dist/editor/stories/storyView.js +144 -0
- package/dist/numbering/spellers.d.ts +1 -1
- package/dist/numbering/spellers.js +6 -1
- package/dist/page/blockKinds.d.ts +23 -4
- package/dist/page/demands/footnoteDemands.d.ts +18 -0
- package/dist/page/demands/footnoteDemands.js +51 -0
- package/dist/page/demands/index.d.ts +43 -0
- package/dist/page/demands/index.js +8 -0
- package/dist/page/kinds/paragraphKind.js +7 -1
- package/dist/page/kinds/tableKind.js +9 -1
- package/dist/page/measureBlocks.d.ts +2 -0
- package/dist/page/measureBlocks.js +31 -10
- package/dist/page/pageDecorations.d.ts +9 -2
- package/dist/page/pageDecorations.js +18 -5
- package/dist/page/pageLayout.d.ts +77 -1
- package/dist/page/pageLayout.js +186 -9
- package/dist/page/usePageLayout.d.ts +57 -2
- package/dist/page/usePageLayout.js +50 -5
- package/dist/schema/attrRoles.js +3 -2
- package/dist/schema/docxSchema.js +5 -2
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +1 -2
- package/dist/schema/preservedGuards.d.ts +4 -6
- package/dist/schema/preservedGuards.js +0 -9
- package/dist/schema/stories.d.ts +34 -2
- package/dist/schema/stories.js +22 -5
- package/dist/styles/classNames.d.ts +23 -6
- package/dist/styles/classNames.js +23 -6
- package/dist/styles/inlineStyle.d.ts +7 -0
- package/dist/styles/inlineStyle.js +11 -5
- package/dist/styles/visualScale.d.ts +19 -0
- package/dist/styles/visualScale.js +26 -0
- package/dist/styles.css +96 -36
- package/dist/ui/CommentsPanel.d.ts +6 -1
- package/dist/ui/CommentsPanel.js +26 -8
- package/dist/ui/InsertImageButton.d.ts +3 -1
- package/dist/ui/InsertImageButton.js +3 -2
- package/dist/ui/TableMenu.d.ts +5 -2
- package/dist/ui/TableMenu.js +18 -1
- package/dist/ui/TextMenu.d.ts +5 -2
- package/dist/ui/TextMenu.js +21 -6
- package/dist/ui/Toolbar.d.ts +17 -2
- package/dist/ui/Toolbar.js +23 -10
- package/dist/ui/comments/useCommentRailLayout.js +2 -4
- package/dist/ui/noteItems.d.ts +21 -0
- package/dist/ui/noteItems.js +30 -0
- package/dist/ui/notes/NoteAreas.d.ts +73 -0
- package/dist/ui/notes/NoteAreas.js +131 -0
- package/dist/ui/notes/NoteList.d.ts +25 -0
- package/dist/ui/notes/NoteList.js +58 -0
- package/dist/ui/notes/StoryRow.d.ts +75 -0
- package/dist/ui/notes/StoryRow.js +122 -0
- package/dist/ui/notes/noteBands.d.ts +100 -0
- package/dist/ui/notes/noteBands.js +142 -0
- package/dist/ui/notes/noteHeights.d.ts +10 -0
- package/dist/ui/notes/noteHeights.js +24 -0
- package/dist/ui/notes/noteSeparator.d.ts +12 -0
- package/dist/ui/notes/noteSeparator.js +9 -0
- package/dist/ui/notes/useStorySurface.d.ts +68 -0
- package/dist/ui/notes/useStorySurface.js +95 -0
- package/dist/ui/runCommand.d.ts +8 -1
- package/dist/ui/runCommand.js +2 -2
- package/dist/ui/shortcutLabels.d.ts +16 -0
- package/dist/ui/shortcutLabels.js +9 -0
- package/dist/ui/usePageRoom.d.ts +13 -0
- package/dist/ui/usePageRoom.js +31 -0
- package/package.json +2 -1
- package/dist/docx/notes.d.ts +0 -27
- package/dist/docx/notes.js +0 -55
- package/dist/ui/NotesPanel.d.ts +0 -6
- package/dist/ui/NotesPanel.js +0 -33
|
@@ -6,6 +6,7 @@ import { keymap } from "prosemirror-keymap";
|
|
|
6
6
|
import { EditorState } from "prosemirror-state";
|
|
7
7
|
import { tableEditing } from "prosemirror-tables";
|
|
8
8
|
import { EditorView } from "prosemirror-view";
|
|
9
|
+
import { withNoteLabels } from "../docx/notes/numbering.js";
|
|
9
10
|
import { pageDecorations } from "../page/pageDecorations.js";
|
|
10
11
|
import { pageGeometryStyle, pagePixels } from "../page/pageLayout.js";
|
|
11
12
|
import { editsShut } from "../schema/protectionState.js";
|
|
@@ -26,6 +27,8 @@ import { imageFiles } from "./imageFiles.js";
|
|
|
26
27
|
import { columnResize } from "./plugins/columnResize.js";
|
|
27
28
|
import { commentComposer } from "./plugins/commentComposer.js";
|
|
28
29
|
import { commentDecorations } from "./plugins/commentDecorations.js";
|
|
30
|
+
import { commentRestoration } from "./plugins/commentRestoration.js";
|
|
31
|
+
import { compositionSelection } from "./plugins/compositionSelection.js";
|
|
29
32
|
import {
|
|
30
33
|
displayDerivation,
|
|
31
34
|
withDerivedDisplay
|
|
@@ -36,6 +39,9 @@ import { docxKeymap, historyKeys } from "./plugins/keymap.js";
|
|
|
36
39
|
import { linkPanel } from "./plugins/linkPanel.js";
|
|
37
40
|
import { listInputRules } from "./plugins/listInputRules.js";
|
|
38
41
|
import { lockedContent } from "./plugins/lockedContent.js";
|
|
42
|
+
import { noteLifecycle } from "./plugins/noteLifecycle.js";
|
|
43
|
+
import { noteNavigation } from "./plugins/noteNavigation.js";
|
|
44
|
+
import { noteNumbering } from "./plugins/noteNumbering.js";
|
|
39
45
|
import { numberingMarkers } from "./plugins/numberingDecorations.js";
|
|
40
46
|
import { rowResize } from "./plugins/rowResize.js";
|
|
41
47
|
import { tabCaret } from "./plugins/tabCaret.js";
|
|
@@ -57,8 +63,13 @@ function createEditorState(doc, options = {}) {
|
|
|
57
63
|
} = options;
|
|
58
64
|
return EditorState.create({
|
|
59
65
|
// The values the document arrived with were worked out against whatever opened it; the
|
|
60
|
-
// state's are worked out against its own snapshot, tables' shared lines
|
|
61
|
-
|
|
66
|
+
// state's are worked out against its own snapshot, tables' shared lines and note labels
|
|
67
|
+
// included
|
|
68
|
+
doc: withNoteLabels(
|
|
69
|
+
withDerivedDisplay(doc, document),
|
|
70
|
+
document.noteNumbering,
|
|
71
|
+
document.specialNotes
|
|
72
|
+
),
|
|
62
73
|
plugins: [
|
|
63
74
|
// Consumer plugins lead the array. ProseMirror walks the plugins in order and takes the
|
|
64
75
|
// first answer for a keypress, a paste, a drop or any other DOM event, so this is the
|
|
@@ -69,9 +80,12 @@ function createEditorState(doc, options = {}) {
|
|
|
69
80
|
editorDocument(document),
|
|
70
81
|
// Refuses every edit no guard in `schema/guards` lets through, whoever asked for it. It is
|
|
71
82
|
// not optional: a document that locked a part of itself stays locked in every consumer, and
|
|
72
|
-
// the preserved bookmark markers
|
|
83
|
+
// the preserved bookmark markers stay where the file put them.
|
|
73
84
|
lockedContent(),
|
|
74
85
|
documentProtection({ protection, author, editableComments }),
|
|
86
|
+
// Ahead of the keys and the clipboard: the plugin registered first sees a DOM event first,
|
|
87
|
+
// and this one hands every composition on
|
|
88
|
+
compositionSelection(),
|
|
75
89
|
history(),
|
|
76
90
|
keymap(docxKeymap),
|
|
77
91
|
historyKeys(),
|
|
@@ -96,8 +110,16 @@ function createEditorState(doc, options = {}) {
|
|
|
96
110
|
// The list beside the page, the lookup the comment commands ask, and these ranges are the
|
|
97
111
|
// one walk this plugin holds (`plugins/commentDecorations`)
|
|
98
112
|
commentDecorations(),
|
|
113
|
+
// A comment outlives the text it was written for: an edit that sweeps its reference away has
|
|
114
|
+
// it put back where the deletion left, detached from the page (`plugins/commentRestoration`)
|
|
115
|
+
commentRestoration(),
|
|
116
|
+
// A note goes with the last reference to it, and a copied reference gets a note of its own.
|
|
117
|
+
// It stands ahead of the numbering, which then labels what it settled
|
|
118
|
+
noteLifecycle(),
|
|
119
|
+
noteNumbering(),
|
|
99
120
|
// What the notes under the page are, worked out from the document the same way
|
|
100
121
|
noteProjection.plugin,
|
|
122
|
+
noteNavigation(),
|
|
101
123
|
// Adjacent text tabs still need separate DOM ranges for layout and pointer selection.
|
|
102
124
|
tabDecorations(),
|
|
103
125
|
tabPointer(),
|
|
@@ -36,6 +36,9 @@ export declare function documentGeometry(state: EditorState): PageGeometry;
|
|
|
36
36
|
* section is where the document says it is, and the snapshot holds the first section's paper
|
|
37
37
|
* alone. Reading it walks every block, so a caller drawing a whole document asks once and looks
|
|
38
38
|
* its positions up (`page/pageLayout`) rather than calling this per block.
|
|
39
|
+
*
|
|
40
|
+
* A side story writes down no section, so its own blocks say nothing about the paper: what it is
|
|
41
|
+
* drawn on is the paper of the place it is called from, which its snapshot carries.
|
|
39
42
|
*/
|
|
40
43
|
export declare function sectionGeometryAt(state: EditorState, pos: number): PageGeometry;
|
|
41
44
|
/** The document-wide interval between automatic tab stops, in points. */
|
|
@@ -19,7 +19,8 @@ function documentGeometry(state) {
|
|
|
19
19
|
return documentOf(state).geometry;
|
|
20
20
|
}
|
|
21
21
|
function sectionGeometryAt(state, pos) {
|
|
22
|
-
|
|
22
|
+
const document = documentOf(state);
|
|
23
|
+
return document.sideStory ? document.geometry : sectionAt(state.doc, pos).props.geometry;
|
|
23
24
|
}
|
|
24
25
|
function documentDefaultTabStopPt(state) {
|
|
25
26
|
return documentOf(state).defaultTabStopPt;
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
import type { Node as PMNode } from "prosemirror-model";
|
|
11
11
|
import { type EditorState, Plugin } from "prosemirror-state";
|
|
12
12
|
import { type FormattingContext, type ParagraphStyleOption } from "../docx/formatting";
|
|
13
|
+
import { type NoteNumbering } from "../docx/notes/reading";
|
|
13
14
|
import { type PageGeometry } from "../docx/pageGeometry";
|
|
14
15
|
import type { SessionStore } from "../docx/session";
|
|
15
16
|
import type { DocumentDefaults } from "../model/format";
|
|
17
|
+
import { type StoryKey } from "../schema/stories";
|
|
16
18
|
/** The document-level values one editing state is built on */
|
|
17
19
|
export interface EditorDocument {
|
|
18
20
|
/** The document these values were read from. Null for a state built without one */
|
|
@@ -30,12 +32,26 @@ export interface EditorDocument {
|
|
|
30
32
|
readonly canStartNewList: boolean;
|
|
31
33
|
/** The paper the document is written on, which decides the width a new table is fitted to */
|
|
32
34
|
readonly geometry: PageGeometry;
|
|
35
|
+
/**
|
|
36
|
+
* Whether these values stand for a side story rather than for the body, whose paper is then the
|
|
37
|
+
* geometry above rather than anything the story's own blocks say (`editor/documentStyles`).
|
|
38
|
+
*/
|
|
39
|
+
readonly sideStory: boolean;
|
|
33
40
|
/** The distance between automatic tab stops when no custom stop applies */
|
|
34
41
|
readonly defaultTabStopPt: number;
|
|
35
42
|
/** Every id already present in the opened Comments part, including unreferenced entries */
|
|
36
43
|
readonly reservedCommentIds: ReadonlySet<string>;
|
|
37
44
|
/** Every paragraph id present in the opened comment parts, including orphan extension entries */
|
|
38
45
|
readonly reservedCommentParaIds: ReadonlySet<string>;
|
|
46
|
+
/** How the document counts its footnotes and endnotes, which the note labels are spelled in */
|
|
47
|
+
readonly noteNumbering: NoteNumbering;
|
|
48
|
+
/** The note entries that lay out the page rather than number a note: separators and the continuation notice */
|
|
49
|
+
readonly specialNotes: ReadonlySet<StoryKey>;
|
|
50
|
+
/**
|
|
51
|
+
* Every entry of an editable kind the opened notes parts hold, separators and entries no
|
|
52
|
+
* reference names included, which a new note may not take the id of
|
|
53
|
+
*/
|
|
54
|
+
readonly reservedNoteKeys: ReadonlySet<StoryKey>;
|
|
39
55
|
}
|
|
40
56
|
/** What a state built without an opened document answers: the same as nothing being written down */
|
|
41
57
|
export declare const NO_DOCUMENT: EditorDocument;
|
|
@@ -51,6 +67,14 @@ export declare function documentOf(state: EditorState): EditorDocument;
|
|
|
51
67
|
* both leave the same object behind, allowing the sheet's style to be cached against that identity.
|
|
52
68
|
*/
|
|
53
69
|
export declare function editorDocument(document: EditorDocument): Plugin<EditorDocument>;
|
|
70
|
+
/**
|
|
71
|
+
* The snapshot one side story is edited against: the document's own values, with the two a story
|
|
72
|
+
* answers differently.
|
|
73
|
+
*
|
|
74
|
+
* A story has nowhere of its own to write a list definition, and the paper its text wraps at is
|
|
75
|
+
* the paper of the place it is called from rather than anything the story says.
|
|
76
|
+
*/
|
|
77
|
+
export declare function storyDocument(document: EditorDocument, geometry: PageGeometry): EditorDocument;
|
|
54
78
|
/** The comment ids the opened document already spent, which a new comment may not take */
|
|
55
79
|
export declare function reservedCommentIds(state: EditorState): ReadonlySet<string>;
|
|
56
80
|
/** The paragraph ids the opened comment parts already spent */
|
|
@@ -6,9 +6,17 @@ import {
|
|
|
6
6
|
NO_FORMATTING
|
|
7
7
|
} from "../docx/formatting.js";
|
|
8
8
|
import { canDefineNewList } from "../docx/newLists.js";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_NOTE_NUMBERING
|
|
11
|
+
} from "../docx/notes/reading.js";
|
|
9
12
|
import { A4_PORTRAIT } from "../docx/pageGeometry.js";
|
|
10
13
|
import { NEW_LISTS_ATTR, newListsOf } from "../numbering/listRegistry.js";
|
|
14
|
+
import {
|
|
15
|
+
EDITABLE_NOTE_KINDS,
|
|
16
|
+
STORIES_ATTR
|
|
17
|
+
} from "../schema/stories.js";
|
|
11
18
|
var NO_IDS = /* @__PURE__ */ new Set();
|
|
19
|
+
var NO_NOTE_KEYS = /* @__PURE__ */ new Set();
|
|
12
20
|
var NO_PARAGRAPH_STYLES = [];
|
|
13
21
|
var NO_DOCUMENT = {
|
|
14
22
|
session: null,
|
|
@@ -17,9 +25,13 @@ var NO_DOCUMENT = {
|
|
|
17
25
|
paragraphStyles: NO_PARAGRAPH_STYLES,
|
|
18
26
|
canStartNewList: true,
|
|
19
27
|
geometry: A4_PORTRAIT,
|
|
28
|
+
sideStory: false,
|
|
20
29
|
defaultTabStopPt: DEFAULT_TAB_STOP_PT,
|
|
21
30
|
reservedCommentIds: NO_IDS,
|
|
22
|
-
reservedCommentParaIds: NO_IDS
|
|
31
|
+
reservedCommentParaIds: NO_IDS,
|
|
32
|
+
noteNumbering: DEFAULT_NOTE_NUMBERING,
|
|
33
|
+
specialNotes: NO_NOTE_KEYS,
|
|
34
|
+
reservedNoteKeys: NO_NOTE_KEYS
|
|
23
35
|
};
|
|
24
36
|
function reservedParaIds(session) {
|
|
25
37
|
const paraIds = /* @__PURE__ */ new Set();
|
|
@@ -46,9 +58,17 @@ function editorDocumentOf(session, doc) {
|
|
|
46
58
|
paragraphStyles: session.paragraphStyles,
|
|
47
59
|
canStartNewList: canDefineNewList(session),
|
|
48
60
|
geometry: session.geometry,
|
|
61
|
+
sideStory: false,
|
|
49
62
|
defaultTabStopPt: session.defaultTabStopPt,
|
|
50
63
|
reservedCommentIds: new Set(session.comments.byId.keys()),
|
|
51
|
-
reservedCommentParaIds: reservedParaIds(session)
|
|
64
|
+
reservedCommentParaIds: reservedParaIds(session),
|
|
65
|
+
noteNumbering: session.noteNumbering,
|
|
66
|
+
specialNotes: session.specialNotes,
|
|
67
|
+
reservedNoteKeys: new Set(
|
|
68
|
+
Array.from(session.stories.values()).flatMap(
|
|
69
|
+
(story) => EDITABLE_NOTE_KINDS.some((kind) => kind === story.kind) ? [story.key] : []
|
|
70
|
+
)
|
|
71
|
+
)
|
|
52
72
|
};
|
|
53
73
|
}
|
|
54
74
|
var editorDocumentKey = new PluginKey("docxEditorDocument");
|
|
@@ -61,15 +81,24 @@ function derived(current, doc) {
|
|
|
61
81
|
formatting: formattingWithNewLists(current.formatting, doc)
|
|
62
82
|
} : editorDocumentOf(current.session, doc);
|
|
63
83
|
}
|
|
84
|
+
function onlyStoriesMoved(next, before) {
|
|
85
|
+
const names = Object.keys(next.attrs);
|
|
86
|
+
return next.attrs[STORIES_ATTR] !== before.attrs[STORIES_ATTR] && names.length === Object.keys(before.attrs).length && names.every(
|
|
87
|
+
(name) => name === STORIES_ATTR || next.attrs[name] === before.attrs[name]
|
|
88
|
+
);
|
|
89
|
+
}
|
|
64
90
|
function editorDocument(document) {
|
|
65
91
|
return new Plugin({
|
|
66
92
|
key: editorDocumentKey,
|
|
67
93
|
state: {
|
|
68
94
|
init: () => document,
|
|
69
|
-
apply: (tr, current, old) => tr.doc.attrs === old.doc.attrs ? current : derived(current, tr.doc)
|
|
95
|
+
apply: (tr, current, old) => tr.doc.attrs === old.doc.attrs || onlyStoriesMoved(tr.doc, old.doc) ? current : derived(current, tr.doc)
|
|
70
96
|
}
|
|
71
97
|
});
|
|
72
98
|
}
|
|
99
|
+
function storyDocument(document, geometry) {
|
|
100
|
+
return { ...document, sideStory: true, canStartNewList: false, geometry };
|
|
101
|
+
}
|
|
73
102
|
function reservedCommentIds(state) {
|
|
74
103
|
return documentOf(state).reservedCommentIds;
|
|
75
104
|
}
|
|
@@ -82,5 +111,6 @@ export {
|
|
|
82
111
|
editorDocument,
|
|
83
112
|
editorDocumentOf,
|
|
84
113
|
reservedCommentIds,
|
|
85
|
-
reservedCommentParaIds
|
|
114
|
+
reservedCommentParaIds,
|
|
115
|
+
storyDocument
|
|
86
116
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a footnote or an endnote draws and does differently from any other story.
|
|
3
|
+
*
|
|
4
|
+
* A note's story opens with a mark of its own (`w:footnoteRef`, `w:endnoteRef`), which the file
|
|
5
|
+
* keeps as a preserved chip. Word draws that mark as the number the reference carries, so it is
|
|
6
|
+
* drawn here as that label in superscript, the way the reference in the body is.
|
|
7
|
+
*
|
|
8
|
+
* The rest is what the story view asks a kind of story for (`editor/stories`): the host every
|
|
9
|
+
* edit leaves through, and the extensions that give a note its chip, its key rule, the number it
|
|
10
|
+
* keeps through an edit, and what a paste into one may carry.
|
|
11
|
+
*/
|
|
12
|
+
import type { EditorView } from "prosemirror-view";
|
|
13
|
+
import { type StoryKey } from "../../schema/stories";
|
|
14
|
+
import { type SliceNormalizer } from "../clipboard/normalizers";
|
|
15
|
+
import type { StoryNodeSpecs } from "../stories/storyMarkup";
|
|
16
|
+
import { type StoryExtensions, type StoryHost } from "../stories/storyView";
|
|
17
|
+
/** The node specs a note's story is drawn with, its own reference mark drawn as `labelOf` answers */
|
|
18
|
+
export declare function noteNodeSpecs(labelOf: () => string): StoryNodeSpecs;
|
|
19
|
+
/**
|
|
20
|
+
* Takes out of a paste what the notes part cannot be written with: an image, and a link.
|
|
21
|
+
*
|
|
22
|
+
* Either needs a relationship of the part it stands in, and the notes part writer writes none, so
|
|
23
|
+
* both would go out naming a relationship the file does not hold. A link or an image the file
|
|
24
|
+
* itself wrote inside a note stays; this is about what a paste would add.
|
|
25
|
+
*/
|
|
26
|
+
export declare const dropUnwritableContent: SliceNormalizer;
|
|
27
|
+
/**
|
|
28
|
+
* Takes a note reference out of a paste: a note may not hold a note.
|
|
29
|
+
*
|
|
30
|
+
* The lifecycle, the numbering and the writer all walk the body alone, so a reference inside a
|
|
31
|
+
* story would call a note nothing numbers, counts or settles, and one copied from beside the note
|
|
32
|
+
* it was pasted into would have that note calling itself. The text it stood in stays, the way the
|
|
33
|
+
* other anchors leave theirs (`editor/clipboard/normalizers`).
|
|
34
|
+
*/
|
|
35
|
+
export declare const dropNoteReferences: SliceNormalizer;
|
|
36
|
+
/**
|
|
37
|
+
* Puts the caret back just after the reference that calls this note and takes the body's focus,
|
|
38
|
+
* which is what leaves the note: Escape and a press on the note's own number both run it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function returnToReference(main: EditorView, key: StoryKey): void;
|
|
41
|
+
/**
|
|
42
|
+
* The host a note's editing view writes through, whichever kind of note stands under the key.
|
|
43
|
+
*
|
|
44
|
+
* Every edit leaves as that kind's own body command, so a lock around the reference and the
|
|
45
|
+
* editing protection judge a note edit where they judge a body edit, and the main document keeps
|
|
46
|
+
* the one history both share.
|
|
47
|
+
*/
|
|
48
|
+
export declare function noteHost(main: EditorView, activate: StoryHost["activate"]): StoryHost;
|
|
49
|
+
/**
|
|
50
|
+
* What a note adds to the story view: its chip, the number it keeps first, its key rule, and what
|
|
51
|
+
* a paste may bring in
|
|
52
|
+
*/
|
|
53
|
+
export declare function noteExtensions(main: EditorView, key: StoryKey, labelOf: () => string): StoryExtensions;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// src/editor/notes/noteSurface.ts
|
|
2
|
+
import { keymap } from "prosemirror-keymap";
|
|
3
|
+
import {
|
|
4
|
+
DOMSerializer,
|
|
5
|
+
Mark
|
|
6
|
+
} from "prosemirror-model";
|
|
7
|
+
import { Plugin, TextSelection } from "prosemirror-state";
|
|
8
|
+
import { NOTE_NUMBER_ELEMENTS } from "../../docx/notes/newNote.js";
|
|
9
|
+
import { docxSchema } from "../../schema/index.js";
|
|
10
|
+
import { editShut, transactionAllowed } from "../../schema/guards.js";
|
|
11
|
+
import { editsShut } from "../../schema/protectionState.js";
|
|
12
|
+
import { noteKeyOf } from "../../schema/stories.js";
|
|
13
|
+
import { editorClassNames } from "../../styles/classNames.js";
|
|
14
|
+
import {
|
|
15
|
+
detachAnchors,
|
|
16
|
+
dropSourceIdentity,
|
|
17
|
+
mapSliceNodes,
|
|
18
|
+
rederiveDisplay,
|
|
19
|
+
rekeyNumbering
|
|
20
|
+
} from "../clipboard/normalizers.js";
|
|
21
|
+
import { redo, undo } from "../commands/historyCommands.js";
|
|
22
|
+
import { noteBodyCommand } from "../commands/noteCommands.js";
|
|
23
|
+
import { onMac } from "../plugins/keymap.js";
|
|
24
|
+
import { noteReferenceAt } from "../plugins/noteNavigation.js";
|
|
25
|
+
import {
|
|
26
|
+
NO_CAPABILITY
|
|
27
|
+
} from "../stories/storyView.js";
|
|
28
|
+
function isOwnMark(node) {
|
|
29
|
+
const element = node.attrs.element;
|
|
30
|
+
return node.type === docxSchema.nodes.rawRunContent && typeof element === "string" && NOTE_NUMBER_ELEMENTS.includes(element);
|
|
31
|
+
}
|
|
32
|
+
function drawnBySchema(node) {
|
|
33
|
+
return docxSchema.nodes.rawRunContent.spec.toDOM?.(node) ?? ["span"];
|
|
34
|
+
}
|
|
35
|
+
function markSpec(node, labelOf) {
|
|
36
|
+
return isOwnMark(node) ? ["sup", { class: editorClassNames.noteMark }, labelOf()] : drawnBySchema(node);
|
|
37
|
+
}
|
|
38
|
+
function noteNodeSpecs(labelOf) {
|
|
39
|
+
return { rawRunContent: (node) => markSpec(node, labelOf) };
|
|
40
|
+
}
|
|
41
|
+
function noteMarkView(labelOf, back) {
|
|
42
|
+
return (node) => {
|
|
43
|
+
const { dom } = DOMSerializer.renderSpec(document, markSpec(node, labelOf));
|
|
44
|
+
if (isOwnMark(node) && dom instanceof HTMLElement) {
|
|
45
|
+
dom.addEventListener("mousedown", (event) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
back();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return { dom };
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
var dropUnwritableContent = (slice) => mapSliceNodes(slice, (node) => {
|
|
54
|
+
if (node.type === docxSchema.nodes.image) return null;
|
|
55
|
+
const marks = node.marks.filter(
|
|
56
|
+
(mark) => mark.type !== docxSchema.marks.link
|
|
57
|
+
);
|
|
58
|
+
return Mark.sameSet(marks, node.marks) ? node : node.mark(marks);
|
|
59
|
+
});
|
|
60
|
+
var dropNoteReferences = (slice, { move }) => move ? slice : mapSliceNodes(
|
|
61
|
+
slice,
|
|
62
|
+
(node) => node.type === docxSchema.nodes.noteReference ? null : node
|
|
63
|
+
);
|
|
64
|
+
var NOTE_TAKES = NO_CAPABILITY;
|
|
65
|
+
var NOTE_NORMALIZERS = [
|
|
66
|
+
dropSourceIdentity,
|
|
67
|
+
detachAnchors,
|
|
68
|
+
dropNoteReferences,
|
|
69
|
+
dropUnwritableContent,
|
|
70
|
+
rekeyNumbering,
|
|
71
|
+
rederiveDisplay
|
|
72
|
+
];
|
|
73
|
+
function runOn(main, command) {
|
|
74
|
+
return command(main.state, (tr) => main.dispatch(tr), main);
|
|
75
|
+
}
|
|
76
|
+
function referenceOf(main, key) {
|
|
77
|
+
const note = noteKeyOf(key);
|
|
78
|
+
return note === null ? null : noteReferenceAt(main.state.doc, note.kind, note.id);
|
|
79
|
+
}
|
|
80
|
+
function returnToReference(main, key) {
|
|
81
|
+
const found = referenceOf(main, key);
|
|
82
|
+
if (found !== null) {
|
|
83
|
+
const after = found.pos + found.node.nodeSize;
|
|
84
|
+
main.dispatch(
|
|
85
|
+
main.state.tr.setSelection(TextSelection.near(main.state.doc.resolve(after))).scrollIntoView()
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
main.focus();
|
|
89
|
+
}
|
|
90
|
+
function leadingChipSize(story) {
|
|
91
|
+
const first = story.firstChild?.firstChild ?? null;
|
|
92
|
+
return first !== null && isOwnMark(first) ? first.nodeSize : 0;
|
|
93
|
+
}
|
|
94
|
+
function ownMarkIn(story) {
|
|
95
|
+
let found = null;
|
|
96
|
+
story.descendants((node, pos) => {
|
|
97
|
+
if (found === null && isOwnMark(node)) found = { pos, node };
|
|
98
|
+
return found === null;
|
|
99
|
+
});
|
|
100
|
+
return found;
|
|
101
|
+
}
|
|
102
|
+
function caretFloor(story) {
|
|
103
|
+
const held = ownMarkIn(story);
|
|
104
|
+
return held === null || held.pos !== markHome(story) ? null : held.pos + held.node.nodeSize;
|
|
105
|
+
}
|
|
106
|
+
function markHome(story) {
|
|
107
|
+
const type = docxSchema.nodes.rawRunContent;
|
|
108
|
+
let at = null;
|
|
109
|
+
story.descendants((node, pos) => {
|
|
110
|
+
if (at !== null) return false;
|
|
111
|
+
if (!node.isTextblock) return true;
|
|
112
|
+
if (node.type.contentMatch.matchType(type) !== null) at = pos + 1;
|
|
113
|
+
return false;
|
|
114
|
+
});
|
|
115
|
+
return at;
|
|
116
|
+
}
|
|
117
|
+
function pressesTowardsTheNumber() {
|
|
118
|
+
return [
|
|
119
|
+
"ArrowLeft",
|
|
120
|
+
"ArrowUp",
|
|
121
|
+
"Home",
|
|
122
|
+
"PageUp",
|
|
123
|
+
"Mod-ArrowLeft",
|
|
124
|
+
"Mod-ArrowUp",
|
|
125
|
+
"Mod-Home",
|
|
126
|
+
...onMac() ? ["Ctrl-a"] : []
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
function holdAtTheNumber(extend) {
|
|
130
|
+
return (state, dispatch) => {
|
|
131
|
+
const floor = caretFloor(state.doc);
|
|
132
|
+
const home = markHome(state.doc);
|
|
133
|
+
if (floor === null || home === null) return false;
|
|
134
|
+
const { selection } = state;
|
|
135
|
+
if (extend ? selection.head > home : selection.from > floor) return false;
|
|
136
|
+
if (dispatch && !extend) {
|
|
137
|
+
dispatch(state.tr.setSelection(TextSelection.create(state.doc, floor)));
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function keysOffTheNumber() {
|
|
143
|
+
return Object.fromEntries(
|
|
144
|
+
pressesTowardsTheNumber().flatMap((press) => [
|
|
145
|
+
[press, holdAtTheNumber(false)],
|
|
146
|
+
[`Shift-${press}`, holdAtTheNumber(true)]
|
|
147
|
+
])
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
function ownMarkFirst() {
|
|
151
|
+
return new Plugin({
|
|
152
|
+
appendTransaction(transactions, oldState, newState) {
|
|
153
|
+
const tr = newState.tr;
|
|
154
|
+
const home = markHome(newState.doc);
|
|
155
|
+
if (transactions.some((changed) => changed.docChanged) && home !== null) {
|
|
156
|
+
const held = ownMarkIn(newState.doc);
|
|
157
|
+
const lost = ownMarkIn(oldState.doc);
|
|
158
|
+
if (held === null) {
|
|
159
|
+
if (lost !== null) tr.insert(home, lost.node);
|
|
160
|
+
} else if (held.pos !== home) {
|
|
161
|
+
tr.delete(held.pos, held.pos + held.node.nodeSize).insert(
|
|
162
|
+
home,
|
|
163
|
+
held.node
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const floor = caretFloor(tr.doc);
|
|
168
|
+
if (floor !== null && tr.selection.empty && tr.selection.from < floor) {
|
|
169
|
+
tr.setSelection(
|
|
170
|
+
TextSelection.create(tr.doc, Math.min(floor, tr.doc.content.size))
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return tr.docChanged || tr.selectionSet ? tr : null;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function holdsNothing(story) {
|
|
178
|
+
const only = story.childCount === 1 ? story.firstChild : null;
|
|
179
|
+
return only !== null && only.type === docxSchema.nodes.paragraph && only.content.size === leadingChipSize(story);
|
|
180
|
+
}
|
|
181
|
+
function deleteEmptyNote(main, key) {
|
|
182
|
+
return (state, dispatch) => {
|
|
183
|
+
if (!state.selection.empty) return false;
|
|
184
|
+
if (!holdsNothing(state.doc)) return false;
|
|
185
|
+
const $at = state.selection.$from;
|
|
186
|
+
if ($at.index(0) !== 0 || $at.parentOffset > leadingChipSize(state.doc)) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const found = referenceOf(main, key);
|
|
190
|
+
if (found === null) return false;
|
|
191
|
+
const tr = main.state.tr.delete(found.pos, found.pos + found.node.nodeSize);
|
|
192
|
+
if (!transactionAllowed(tr, main.state)) return false;
|
|
193
|
+
if (dispatch) {
|
|
194
|
+
main.dispatch(
|
|
195
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(found.pos))).scrollIntoView()
|
|
196
|
+
);
|
|
197
|
+
main.focus();
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function noteHost(main, activate) {
|
|
203
|
+
return {
|
|
204
|
+
state: () => main.state,
|
|
205
|
+
write(key, story) {
|
|
206
|
+
const note = noteKeyOf(key);
|
|
207
|
+
return note !== null && runOn(main, noteBodyCommand(note.kind, note.id, story));
|
|
208
|
+
},
|
|
209
|
+
undo: () => runOn(main, undo),
|
|
210
|
+
redo: () => runOn(main, redo),
|
|
211
|
+
leave: (key) => returnToReference(main, key),
|
|
212
|
+
shut(key) {
|
|
213
|
+
if (editsShut(main.state)) return true;
|
|
214
|
+
const found = referenceOf(main, key);
|
|
215
|
+
if (found === null) return true;
|
|
216
|
+
return editShut(main.state, {
|
|
217
|
+
kind: "replace",
|
|
218
|
+
from: found.pos,
|
|
219
|
+
to: found.pos + found.node.nodeSize
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
activate
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function noteExtensions(main, key, labelOf) {
|
|
226
|
+
return {
|
|
227
|
+
plugins: [
|
|
228
|
+
ownMarkFirst(),
|
|
229
|
+
keymap(keysOffTheNumber()),
|
|
230
|
+
keymap({ Backspace: deleteEmptyNote(main, key) })
|
|
231
|
+
],
|
|
232
|
+
nodeViews: {
|
|
233
|
+
rawRunContent: noteMarkView(labelOf, () => returnToReference(main, key))
|
|
234
|
+
},
|
|
235
|
+
normalizers: NOTE_NORMALIZERS,
|
|
236
|
+
takes: NOTE_TAKES
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
export {
|
|
240
|
+
dropNoteReferences,
|
|
241
|
+
dropUnwritableContent,
|
|
242
|
+
noteExtensions,
|
|
243
|
+
noteHost,
|
|
244
|
+
noteNodeSpecs,
|
|
245
|
+
returnToReference
|
|
246
|
+
};
|
|
@@ -34,7 +34,7 @@ function commentsIn(doc) {
|
|
|
34
34
|
const id = stringAttr(node.attrs.id) ?? "";
|
|
35
35
|
const start = markers.starts.get(id);
|
|
36
36
|
const end = markers.ends.get(id);
|
|
37
|
-
const
|
|
37
|
+
const anchored = start !== void 0 && end !== void 0 && start < end;
|
|
38
38
|
const point = start ?? end ?? pos;
|
|
39
39
|
return {
|
|
40
40
|
id,
|
|
@@ -43,9 +43,10 @@ function commentsIn(doc) {
|
|
|
43
43
|
initials: stringAttr(node.attrs.initials),
|
|
44
44
|
date: stringAttr(node.attrs.date),
|
|
45
45
|
text: bodyText(doc, id),
|
|
46
|
-
from:
|
|
47
|
-
to:
|
|
46
|
+
from: anchored ? start + 1 : point,
|
|
47
|
+
to: anchored ? end : point,
|
|
48
48
|
referencePos: pos,
|
|
49
|
+
anchored,
|
|
49
50
|
resolved: node.attrs.resolved === true,
|
|
50
51
|
replies: repliesAttr(node.attrs.replies).map((reply) => ({
|
|
51
52
|
id: reply.id,
|
|
@@ -69,7 +70,7 @@ function deriveComments(doc) {
|
|
|
69
70
|
byId,
|
|
70
71
|
decorations: DecorationSet.create(
|
|
71
72
|
doc,
|
|
72
|
-
comments.filter((comment) => comment.from < comment.to).map(
|
|
73
|
+
comments.filter((comment) => !comment.resolved && comment.from < comment.to).map(
|
|
73
74
|
(comment) => Decoration.inline(comment.from, comment.to, {
|
|
74
75
|
class: editorClassNames.commentRange,
|
|
75
76
|
"data-comment-id": comment.id
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps a comment when the text it was written for goes away.
|
|
3
|
+
*
|
|
4
|
+
* Deleting commented text is an edit of the body, not a decision about the thread, so a body edit
|
|
5
|
+
* that takes a comment's nodes away has them put back at the spots the edit left: the thread stays
|
|
6
|
+
* anchored where one range marker survived, detached where both went (`DocumentComment.anchored`).
|
|
7
|
+
*
|
|
8
|
+
* The comment's story (`docx/story`) tells such an edit from `removeComment`: a body deletion does
|
|
9
|
+
* not reach the story, so a comment is put back while its story stands, which only `removeComment`
|
|
10
|
+
* takes away. The cost is that a reference the comments part held no body for is never put back.
|
|
11
|
+
*
|
|
12
|
+
* A restoration is an ordinary transaction the guards judge (`editor/plugins/lockedContent`): the
|
|
13
|
+
* nodes take the nearest spot left open, and a document leaving none loses the comment. A range
|
|
14
|
+
* marker with no home takes its surviving counterpart down, so no export carries half a range.
|
|
15
|
+
*
|
|
16
|
+
* A restoration needs no deferral around an open IME composition: the nodes go in beside the
|
|
17
|
+
* composed text rather than rewriting it (`e2e/hangulComposition.spec.ts`).
|
|
18
|
+
*/
|
|
19
|
+
import { Plugin } from "prosemirror-state";
|
|
20
|
+
export declare function commentRestoration(): Plugin;
|