@portone/docx-editor 0.6.0 → 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.
Files changed (136) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/DocxEditor.js +84 -26
  3. package/dist/docx/exportDocx.d.ts +2 -1
  4. package/dist/docx/exportDocx.js +3 -9
  5. package/dist/docx/headersFooters.js +35 -23
  6. package/dist/docx/identities.d.ts +28 -0
  7. package/dist/docx/identities.js +42 -10
  8. package/dist/docx/importDocx.js +25 -26
  9. package/dist/docx/importParagraph.d.ts +1 -4
  10. package/dist/docx/importParagraph.js +4 -18
  11. package/dist/docx/invariants.d.ts +1 -1
  12. package/dist/docx/invariants.js +45 -4
  13. package/dist/docx/notes/newNote.d.ts +39 -0
  14. package/dist/docx/notes/newNote.js +122 -0
  15. package/dist/docx/notes/numbering.d.ts +27 -0
  16. package/dist/docx/notes/numbering.js +120 -0
  17. package/dist/docx/notes/reading.d.ts +58 -0
  18. package/dist/docx/notes/reading.js +98 -0
  19. package/dist/docx/notes/writing.d.ts +19 -0
  20. package/dist/docx/notes/writing.js +96 -0
  21. package/dist/docx/partPlan.d.ts +3 -0
  22. package/dist/docx/partPlanners.d.ts +13 -0
  23. package/dist/docx/partPlanners.js +48 -0
  24. package/dist/docx/sections.d.ts +5 -0
  25. package/dist/docx/sections.js +7 -2
  26. package/dist/docx/serializeParagraph.js +7 -2
  27. package/dist/docx/session.d.ts +5 -0
  28. package/dist/docx/session.js +6 -0
  29. package/dist/docx/storyParts.d.ts +112 -0
  30. package/dist/docx/storyParts.js +289 -0
  31. package/dist/editor/clipboard/htmlReader.d.ts +24 -4
  32. package/dist/editor/clipboard/htmlReader.js +39 -4
  33. package/dist/editor/clipboard/internalChannel.d.ts +30 -3
  34. package/dist/editor/clipboard/internalChannel.js +43 -3
  35. package/dist/editor/clipboard/normalizers.d.ts +35 -8
  36. package/dist/editor/clipboard/normalizers.js +60 -11
  37. package/dist/editor/clipboard/plugin.js +42 -22
  38. package/dist/editor/commands/index.d.ts +1 -0
  39. package/dist/editor/commands/index.js +18 -0
  40. package/dist/editor/commands/noteCommands.d.ts +47 -0
  41. package/dist/editor/commands/noteCommands.js +95 -0
  42. package/dist/editor/commands/noteQueries.d.ts +22 -3
  43. package/dist/editor/commands/noteQueries.js +21 -13
  44. package/dist/editor/createEditor.js +21 -3
  45. package/dist/editor/documentStyles.d.ts +3 -0
  46. package/dist/editor/documentStyles.js +2 -1
  47. package/dist/editor/editorDocument.d.ts +24 -0
  48. package/dist/editor/editorDocument.js +34 -4
  49. package/dist/editor/notes/noteSurface.d.ts +53 -0
  50. package/dist/editor/notes/noteSurface.js +246 -0
  51. package/dist/editor/plugins/compositionSelection.d.ts +20 -0
  52. package/dist/editor/plugins/compositionSelection.js +19 -0
  53. package/dist/editor/plugins/keymap.d.ts +14 -0
  54. package/dist/editor/plugins/keymap.js +13 -1
  55. package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
  56. package/dist/editor/plugins/noteLifecycle.js +117 -0
  57. package/dist/editor/plugins/noteNavigation.d.ts +45 -0
  58. package/dist/editor/plugins/noteNavigation.js +86 -0
  59. package/dist/editor/plugins/noteNumbering.d.ts +13 -0
  60. package/dist/editor/plugins/noteNumbering.js +58 -0
  61. package/dist/editor/plugins/rowResize.js +2 -7
  62. package/dist/editor/plugins/tabLayout.js +2 -7
  63. package/dist/editor/stories/storyMarkup.d.ts +24 -0
  64. package/dist/editor/stories/storyMarkup.js +35 -0
  65. package/dist/editor/stories/storyState.d.ts +34 -0
  66. package/dist/editor/stories/storyState.js +74 -0
  67. package/dist/editor/stories/storyView.d.ts +115 -0
  68. package/dist/editor/stories/storyView.js +144 -0
  69. package/dist/numbering/spellers.d.ts +1 -1
  70. package/dist/numbering/spellers.js +6 -1
  71. package/dist/page/blockKinds.d.ts +23 -4
  72. package/dist/page/demands/footnoteDemands.d.ts +18 -0
  73. package/dist/page/demands/footnoteDemands.js +51 -0
  74. package/dist/page/demands/index.d.ts +43 -0
  75. package/dist/page/demands/index.js +8 -0
  76. package/dist/page/kinds/paragraphKind.js +7 -1
  77. package/dist/page/kinds/tableKind.js +9 -1
  78. package/dist/page/measureBlocks.d.ts +2 -0
  79. package/dist/page/measureBlocks.js +31 -10
  80. package/dist/page/pageDecorations.d.ts +9 -2
  81. package/dist/page/pageDecorations.js +18 -5
  82. package/dist/page/pageLayout.d.ts +77 -1
  83. package/dist/page/pageLayout.js +186 -9
  84. package/dist/page/usePageLayout.d.ts +57 -2
  85. package/dist/page/usePageLayout.js +50 -5
  86. package/dist/schema/attrRoles.js +3 -2
  87. package/dist/schema/docxSchema.js +5 -2
  88. package/dist/schema/editGuard.d.ts +1 -1
  89. package/dist/schema/guards.js +1 -2
  90. package/dist/schema/preservedGuards.d.ts +4 -6
  91. package/dist/schema/preservedGuards.js +0 -9
  92. package/dist/schema/stories.d.ts +34 -2
  93. package/dist/schema/stories.js +22 -5
  94. package/dist/styles/classNames.d.ts +21 -6
  95. package/dist/styles/classNames.js +21 -6
  96. package/dist/styles/inlineStyle.d.ts +7 -0
  97. package/dist/styles/inlineStyle.js +11 -5
  98. package/dist/styles/visualScale.d.ts +19 -0
  99. package/dist/styles/visualScale.js +26 -0
  100. package/dist/styles.css +89 -32
  101. package/dist/ui/InsertImageButton.d.ts +3 -1
  102. package/dist/ui/InsertImageButton.js +3 -2
  103. package/dist/ui/TableMenu.d.ts +5 -2
  104. package/dist/ui/TableMenu.js +18 -1
  105. package/dist/ui/TextMenu.d.ts +5 -2
  106. package/dist/ui/TextMenu.js +21 -6
  107. package/dist/ui/Toolbar.d.ts +17 -2
  108. package/dist/ui/Toolbar.js +23 -10
  109. package/dist/ui/comments/useCommentRailLayout.js +2 -4
  110. package/dist/ui/noteItems.d.ts +21 -0
  111. package/dist/ui/noteItems.js +30 -0
  112. package/dist/ui/notes/NoteAreas.d.ts +73 -0
  113. package/dist/ui/notes/NoteAreas.js +131 -0
  114. package/dist/ui/notes/NoteList.d.ts +25 -0
  115. package/dist/ui/notes/NoteList.js +58 -0
  116. package/dist/ui/notes/StoryRow.d.ts +75 -0
  117. package/dist/ui/notes/StoryRow.js +122 -0
  118. package/dist/ui/notes/noteBands.d.ts +100 -0
  119. package/dist/ui/notes/noteBands.js +142 -0
  120. package/dist/ui/notes/noteHeights.d.ts +10 -0
  121. package/dist/ui/notes/noteHeights.js +24 -0
  122. package/dist/ui/notes/noteSeparator.d.ts +12 -0
  123. package/dist/ui/notes/noteSeparator.js +9 -0
  124. package/dist/ui/notes/useStorySurface.d.ts +68 -0
  125. package/dist/ui/notes/useStorySurface.js +95 -0
  126. package/dist/ui/runCommand.d.ts +8 -1
  127. package/dist/ui/runCommand.js +2 -2
  128. package/dist/ui/shortcutLabels.d.ts +16 -0
  129. package/dist/ui/shortcutLabels.js +9 -0
  130. package/dist/ui/usePageRoom.d.ts +13 -0
  131. package/dist/ui/usePageRoom.js +31 -0
  132. package/package.json +1 -1
  133. package/dist/docx/notes.d.ts +0 -27
  134. package/dist/docx/notes.js +0 -55
  135. package/dist/ui/NotesPanel.d.ts +0 -6
  136. package/dist/ui/NotesPanel.js +0 -33
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Getting into a note and asking for one to be opened.
3
+ *
4
+ * A press on a note's number or a command asks for a note, and neither can mount anything itself,
5
+ * since what draws the notes is the surface around the editor; so the request is held in the state
6
+ * and the surface reads it back (`requestedNote`). The nonce is what makes a second request for
7
+ * the note already open reach the surface at all.
8
+ *
9
+ * The request moves the caret to just after the reference as it goes in, which is where Escape
10
+ * hands it back to (`editor/notes/noteSurface`), so leaving a note needs nothing remembered.
11
+ */
12
+ import type { Node as PMNode } from "prosemirror-model";
13
+ import { type Command, type EditorState, Plugin, type Transaction } from "prosemirror-state";
14
+ import { type NoteKind, type StoryKey } from "../../schema/stories";
15
+ /** Which note the reader asked for, and which asking it was */
16
+ export interface RequestedNote {
17
+ readonly key: StoryKey;
18
+ /** Rises with every request, so asking twice for one note is two requests */
19
+ readonly nonce: number;
20
+ }
21
+ /** One reference to a note, as the document holds it */
22
+ export interface PlacedNoteReference {
23
+ readonly pos: number;
24
+ readonly node: PMNode;
25
+ }
26
+ /** Where the first reference to this note stands, and null where the document holds none */
27
+ export declare function noteReferenceAt(doc: PMNode, kind: NoteKind, id: string): PlacedNoteReference | null;
28
+ /** The transaction with the note it opens recorded on it, which `insertFootnote` also carries */
29
+ export declare function requestNote<T extends Transaction>(tr: T, key: StoryKey): T;
30
+ /** The note last asked for, and null where none was. Null for a state built without the plugin */
31
+ export declare function requestedNote(state: EditorState): RequestedNote | null;
32
+ /**
33
+ * Puts the caret just after the note's reference and asks for the note to be opened. It applies
34
+ * under every mode, since opening a note is reading it; what a reader may do once it stands open
35
+ * is the story view's question (`editor/stories`).
36
+ */
37
+ export declare function openNoteCommand(kind: NoteKind, id: string): Command;
38
+ /**
39
+ * Holds the note last asked for, and turns a press on a note's number into that asking.
40
+ *
41
+ * One press is what opens a note, which is what Google Docs does: the number is a small
42
+ * superscript, and a press on one is far more often a reader going to the note than a caret being
43
+ * placed on the digit. A reference to a kind no writer puts back opens nothing.
44
+ */
45
+ export declare function noteNavigation(kinds?: readonly NoteKind[]): Plugin<RequestedNote | null>;
@@ -0,0 +1,86 @@
1
+ // src/editor/plugins/noteNavigation.ts
2
+ import {
3
+ Plugin,
4
+ PluginKey,
5
+ TextSelection
6
+ } from "prosemirror-state";
7
+ import { docxSchema } from "../../schema/index.js";
8
+ import {
9
+ EDITABLE_NOTE_KINDS,
10
+ storyKey,
11
+ storyNodeOf
12
+ } from "../../schema/stories.js";
13
+ import { documentOf } from "../editorDocument.js";
14
+ var navigationKey = new PluginKey(
15
+ "docxEditorNoteNavigation"
16
+ );
17
+ function noteReferenceAt(doc, kind, id) {
18
+ let found = null;
19
+ doc.descendants((node, pos) => {
20
+ if (found !== null) return false;
21
+ if (node.type === docxSchema.nodes.noteReference && node.attrs.kind === kind && node.attrs.id === id) {
22
+ found = { pos, node };
23
+ }
24
+ return found === null;
25
+ });
26
+ return found;
27
+ }
28
+ function isRequest(value) {
29
+ if (typeof value !== "object" || value === null) return false;
30
+ return typeof Reflect.get(value, "key") === "string" && typeof Reflect.get(value, "nonce") === "number";
31
+ }
32
+ var asked = 0;
33
+ function requestNote(tr, key) {
34
+ asked += 1;
35
+ return tr.setMeta(navigationKey, { key, nonce: asked });
36
+ }
37
+ function requestedNote(state) {
38
+ return navigationKey.getState(state) ?? null;
39
+ }
40
+ function openNoteCommand(kind, id) {
41
+ return (state, dispatch) => {
42
+ const key = storyKey(kind, id);
43
+ if (documentOf(state).specialNotes.has(key)) return false;
44
+ if (storyNodeOf(state.doc, key) === null) return false;
45
+ const found = noteReferenceAt(state.doc, kind, id);
46
+ if (found === null) return false;
47
+ if (dispatch) {
48
+ const after = found.pos + found.node.nodeSize;
49
+ const tr = state.tr.setSelection(
50
+ TextSelection.near(state.doc.resolve(after))
51
+ );
52
+ dispatch(requestNote(tr, key).scrollIntoView());
53
+ }
54
+ return true;
55
+ };
56
+ }
57
+ function noteNavigation(kinds = EDITABLE_NOTE_KINDS) {
58
+ return new Plugin({
59
+ key: navigationKey,
60
+ state: {
61
+ init: () => null,
62
+ apply: (tr, held) => {
63
+ const next = tr.getMeta(navigationKey);
64
+ return isRequest(next) ? next : held;
65
+ }
66
+ },
67
+ props: {
68
+ handleClickOn(view, _pos, node, _nodePos, _event, direct) {
69
+ if (!direct || node.type !== docxSchema.nodes.noteReference) {
70
+ return false;
71
+ }
72
+ const kind = kinds.find((named) => named === node.attrs.kind);
73
+ const id = node.attrs.id;
74
+ if (kind === void 0 || typeof id !== "string") return false;
75
+ return openNoteCommand(kind, id)(view.state, view.dispatch);
76
+ }
77
+ }
78
+ });
79
+ }
80
+ export {
81
+ noteNavigation,
82
+ noteReferenceAt,
83
+ openNoteCommand,
84
+ requestNote,
85
+ requestedNote
86
+ };
@@ -0,0 +1,13 @@
1
+ import { Plugin } from "prosemirror-state";
2
+ import { noteLabelsIn } from "../../docx/notes/numbering";
3
+ export type NoteLabeller = typeof noteLabelsIn;
4
+ /**
5
+ * Labels the note references again after a change that moves one, moves a section break, or
6
+ * replaces the numbering the snapshot holds.
7
+ *
8
+ * A label is a display value of an inline node, which the display derivation walk does not reach,
9
+ * so it is written here under the same pass: a relabel goes through a lock around the reference,
10
+ * and one undo takes it back with the edit that caused it. The relabel after a snapshot change
11
+ * follows no edit and goes to the history not at all.
12
+ */
13
+ export declare function noteNumbering(labelsIn?: NoteLabeller): Plugin;
@@ -0,0 +1,58 @@
1
+ // src/editor/plugins/noteNumbering.ts
2
+ import { Plugin } from "prosemirror-state";
3
+ import { DocAttrStep } from "prosemirror-transform";
4
+ import { noteLabelSignature, noteLabelsIn } from "../../docx/notes/numbering.js";
5
+ import { sectionBreakOf } from "../../docx/sections.js";
6
+ import { displayOnly } from "../../schema/displayDerivation.js";
7
+ import { stepReaches } from "../../schema/guards.js";
8
+ import { documentOf } from "../editorDocument.js";
9
+ function countsNotes(node) {
10
+ if (node.type.name === "noteReference") return true;
11
+ const pPr = node.attrs.pPr;
12
+ return node.type.name === "paragraph" && typeof pPr === "string" && sectionBreakOf(pPr) !== null;
13
+ }
14
+ function reachesLabels(tr) {
15
+ return tr.steps.some(
16
+ (step, index) => step instanceof DocAttrStep ? step.attr === "sectPr" : stepReaches(
17
+ step,
18
+ tr.docs[index],
19
+ tr.docs[index + 1] ?? tr.doc,
20
+ countsNotes
21
+ )
22
+ );
23
+ }
24
+ function labelsMayHaveMoved(transactions, oldState, newState) {
25
+ return transactions.some(reachesLabels) && noteLabelSignature(oldState.doc) !== noteLabelSignature(newState.doc);
26
+ }
27
+ function numberingReplaced(before, after) {
28
+ return before.noteNumbering !== after.noteNumbering || before.specialNotes !== after.specialNotes;
29
+ }
30
+ function noteNumbering(labelsIn = noteLabelsIn) {
31
+ return new Plugin({
32
+ appendTransaction(transactions, oldState, newState) {
33
+ const document = documentOf(newState);
34
+ const replaced = numberingReplaced(documentOf(oldState), document);
35
+ if (!replaced && !labelsMayHaveMoved(transactions, oldState, newState)) {
36
+ return null;
37
+ }
38
+ const labels = labelsIn(
39
+ newState.doc,
40
+ document.noteNumbering,
41
+ document.specialNotes
42
+ );
43
+ const tr = newState.tr;
44
+ for (const [pos, label] of labels) {
45
+ if (tr.doc.nodeAt(pos)?.attrs.label !== label) {
46
+ tr.setNodeAttribute(pos, "label", label);
47
+ }
48
+ }
49
+ if (!tr.docChanged) return null;
50
+ tr.setMeta(displayOnly, true);
51
+ if (replaced) tr.setMeta("addToHistory", false);
52
+ return tr;
53
+ }
54
+ });
55
+ }
56
+ export {
57
+ noteNumbering
58
+ };
@@ -4,6 +4,7 @@ import { TableMap } from "prosemirror-tables";
4
4
  import { Decoration, DecorationSet } from "prosemirror-view";
5
5
  import { isLockedCell } from "../../schema/locks.js";
6
6
  import { editorClassNames } from "../../styles/classNames.js";
7
+ import { pageScaleAround } from "../../styles/visualScale.js";
7
8
  import {
8
9
  buildResizeRowTransaction,
9
10
  resizedRowHeight,
@@ -52,12 +53,6 @@ function edgeUnder(view, event) {
52
53
  if (!(rowDom instanceof HTMLTableRowElement)) return null;
53
54
  return { ...spot, ...hit, tableDom, rowDom, rowPos };
54
55
  }
55
- function visualScale(view) {
56
- const layer = view.dom.closest(`.${editorClassNames.pageLayer}`);
57
- const raw = layer instanceof HTMLElement ? getComputedStyle(layer).zoom : "";
58
- const parsed = Number.parseFloat(raw);
59
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
60
- }
61
56
  function previewDecorations(doc, preview) {
62
57
  const row = doc.nodeAt(preview.rowPos);
63
58
  if (row?.type.spec.tableRole !== "row") return DecorationSet.empty;
@@ -137,7 +132,7 @@ function rowResize() {
137
132
  function start(view, event) {
138
133
  const target = edgeUnder(view, event);
139
134
  if (!target) return false;
140
- const scale = visualScale(view);
135
+ const scale = pageScaleAround(view.dom);
141
136
  const startHeightPt = target.rowDom.getBoundingClientRect().height / scale * (72 / 96);
142
137
  drag = {
143
138
  view,
@@ -5,6 +5,7 @@ import { PX_PER_PT } from "../../docx/pageGeometry.js";
5
5
  import { toParagraphFormat } from "../../model/format.js";
6
6
  import { docxSchema } from "../../schema/index.js";
7
7
  import { editorClassNames, editorCssVariables } from "../../styles/classNames.js";
8
+ import { pageScaleAround } from "../../styles/visualScale.js";
8
9
  import { documentDefaultTabStopPt } from "../documentStyles.js";
9
10
  import { setTabWidths, tabWidths } from "./tabDecorations.js";
10
11
  var POSITION_EPSILON_PT = 0.01;
@@ -46,11 +47,6 @@ function numeric(value) {
46
47
  function computedStyle(element) {
47
48
  return element.ownerDocument.defaultView?.getComputedStyle(element) ?? getComputedStyle(element);
48
49
  }
49
- function scaleOf(view) {
50
- const layer = view.dom.closest(`.${editorClassNames.pageLayer}`);
51
- const parsed = Number.parseFloat(layer ? computedStyle(layer).zoom : "");
52
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
53
- }
54
50
  function paragraphLayoutContext(view, slot) {
55
51
  const $pos = view.state.doc.resolve(view.posAtDOM(slot, 0));
56
52
  for (let depth = $pos.depth; depth > 0; depth -= 1) {
@@ -84,7 +80,6 @@ function cloneParagraph(paragraph, widthPx, resetTabs) {
84
80
  clone.style.textAlign = "start";
85
81
  clone.style.visibility = "hidden";
86
82
  clone.style.pointerEvents = "none";
87
- clone.style.zoom = "1";
88
83
  clone.setAttribute("aria-hidden", "true");
89
84
  if (resetTabs) {
90
85
  for (const slot of clone.querySelectorAll(
@@ -221,7 +216,7 @@ function sameWidths(left, right) {
221
216
  return true;
222
217
  }
223
218
  function measureTabs(view, cache, generation) {
224
- const scale = scaleOf(view);
219
+ const scale = pageScaleAround(view.dom);
225
220
  const defaultStopPt = documentDefaultTabStopPt(view.state);
226
221
  const widths = new Map(tabWidths(view.state));
227
222
  const paragraphs = /* @__PURE__ */ new Map();
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A side story drawn as markup no editor view stands behind: a note the caret is not in, and later
3
+ * a comment body or a header preview.
4
+ *
5
+ * The runs are drawn by the same spec the editor's run view draws (`editor/views/runMarkView`), so
6
+ * a story set down here wraps where it would in an editor, and what one kind of story draws
7
+ * differently comes in as a node spec. Nothing here knows which kind of story it is drawing, or
8
+ * where the markup goes.
9
+ *
10
+ * The schema writes the document's own source onto what it draws (`w:pPr`, a run's `w:rPr`, a
11
+ * preserved fragment's XML) for an editor to read back, and nothing reads this markup back. So
12
+ * none of it is kept: a selection the browser copies out of a note, with no copy serializer of the
13
+ * editor behind it, then carries no more of the document than a copy out of the editor does
14
+ * (`schema/clipboard`).
15
+ */
16
+ import { type DOMOutputSpec, type Node as PMNode } from "prosemirror-model";
17
+ import type { FontFallbacks } from "../../styles/fontStack";
18
+ /** How a node type is drawn in place of the way the schema draws it, by node type name */
19
+ export type StoryNodeSpecs = Readonly<Record<string, (node: PMNode) => DOMOutputSpec>>;
20
+ export interface StoryMarkupOptions {
21
+ readonly fontFallbacks: FontFallbacks;
22
+ readonly nodeSpecs?: StoryNodeSpecs;
23
+ }
24
+ export declare function storyMarkup(story: PMNode, { fontFallbacks, nodeSpecs }: StoryMarkupOptions): DocumentFragment;
@@ -0,0 +1,35 @@
1
+ // src/editor/stories/storyMarkup.ts
2
+ import {
3
+ DOMSerializer
4
+ } from "prosemirror-model";
5
+ import { runMarkSpec } from "../../schema/index.js";
6
+ var STYLED_ATTRIBUTES = /* @__PURE__ */ new Set([
7
+ "data-display",
8
+ "data-underline"
9
+ ]);
10
+ function withoutSource(markup) {
11
+ markup.querySelectorAll("*").forEach((element) => {
12
+ for (const name of element.getAttributeNames()) {
13
+ if (name.startsWith("data-") && !STYLED_ATTRIBUTES.has(name)) {
14
+ element.removeAttribute(name);
15
+ }
16
+ }
17
+ });
18
+ return markup;
19
+ }
20
+ function storyMarkup(story, { fontFallbacks, nodeSpecs = {} }) {
21
+ const schema = story.type.schema;
22
+ const serializer = new DOMSerializer(
23
+ { ...DOMSerializer.nodesFromSchema(schema), ...nodeSpecs },
24
+ {
25
+ ...DOMSerializer.marksFromSchema(schema),
26
+ run: (mark) => runMarkSpec(mark.attrs, fontFallbacks)
27
+ }
28
+ );
29
+ const markup = document.createDocumentFragment();
30
+ serializer.serializeFragment(story.content, {}, markup);
31
+ return withoutSource(markup);
32
+ }
33
+ export {
34
+ storyMarkup
35
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The state one side story is edited in.
3
+ *
4
+ * A story is a document of the same schema as the body, so it is edited by the same plugins: the
5
+ * guards, the clipboard, the tabs, the display derivation. What it is not is a document of its
6
+ * own, so the ones that answer for a whole document stay out - the history, which the main
7
+ * document keeps for both (`./storyView`), the note and comment plugins, whose subjects a story
8
+ * does not hold, and the page decorations, since a story is drawn wherever its host puts it.
9
+ *
10
+ * The snapshot handed in is the main document's, which is what makes a run resolve here as it
11
+ * would in the body. Its caller narrows the two values a story answers differently: a story has
12
+ * nowhere to write a list definition, and the paper it wraps at is the one its anchor stands on.
13
+ */
14
+ import type { Node as PMNode } from "prosemirror-model";
15
+ import { type Command, EditorState, type Plugin } from "prosemirror-state";
16
+ import type { EditingProtection } from "../../schema/protection";
17
+ import type { SliceNormalizer } from "../clipboard/normalizers";
18
+ import { type EditorDocument } from "../editorDocument";
19
+ import type { SurfaceCapabilities } from "./storyView";
20
+ export interface StoryStateOptions {
21
+ /** The story as the main document holds it */
22
+ readonly story: PMNode;
23
+ /** The main document's snapshot, which the story's display values are resolved against */
24
+ readonly document: EditorDocument;
25
+ readonly protection: EditingProtection;
26
+ /** The keys the surface binds ahead of the editor's own: undo, redo and Escape */
27
+ readonly keys: Record<string, Command>;
28
+ /** What the kind of story adds, which is asked before the editor's own keymaps */
29
+ readonly plugins: readonly Plugin[];
30
+ readonly normalizers: readonly SliceNormalizer[];
31
+ /** What this kind of story takes, which the editor's own keys are bound from */
32
+ readonly takes: SurfaceCapabilities;
33
+ }
34
+ export declare function storyEditorState({ story, document, protection, keys, plugins, normalizers, takes, }: StoryStateOptions): EditorState;
@@ -0,0 +1,74 @@
1
+ // src/editor/stories/storyState.ts
2
+ import { baseKeymap } from "prosemirror-commands";
3
+ import { dropCursor } from "prosemirror-dropcursor";
4
+ import { keymap } from "prosemirror-keymap";
5
+ import { EditorState } from "prosemirror-state";
6
+ import { tableEditing } from "prosemirror-tables";
7
+ import { docxClipboard } from "../clipboard/plugin.js";
8
+ import { editorDocument } from "../editorDocument.js";
9
+ import { compositionSelection } from "../plugins/compositionSelection.js";
10
+ import {
11
+ displayDerivation,
12
+ withDerivedDisplay
13
+ } from "../plugins/displayDerivation.js";
14
+ import { documentProtection } from "../plugins/documentProtection.js";
15
+ import { docxKeymap, NOTE_KEYS } from "../plugins/keymap.js";
16
+ import { lockedContent } from "../plugins/lockedContent.js";
17
+ import { numberingMarkers } from "../plugins/numberingDecorations.js";
18
+ import { tabCaret } from "../plugins/tabCaret.js";
19
+ import { tabDecorations } from "../plugins/tabDecorations.js";
20
+ import { tabLayout } from "../plugins/tabLayout.js";
21
+ import { tabPointer } from "../plugins/tabPointer.js";
22
+ var CAPABILITY_KEYS = {
23
+ "Mod-k": "link",
24
+ [NOTE_KEYS.footnote]: "note",
25
+ [NOTE_KEYS.endnote]: "note"
26
+ };
27
+ var KEYS_NO_STORY_TAKES = ["Mod-Enter"];
28
+ function storyKeymap(takes) {
29
+ return Object.fromEntries(
30
+ Object.entries(docxKeymap).filter(([key]) => {
31
+ if (KEYS_NO_STORY_TAKES.includes(key)) return false;
32
+ const needed = CAPABILITY_KEYS[key];
33
+ return needed === void 0 || takes.has(needed);
34
+ })
35
+ );
36
+ }
37
+ function storyEditorState({
38
+ story,
39
+ document,
40
+ protection,
41
+ keys,
42
+ plugins,
43
+ normalizers,
44
+ takes
45
+ }) {
46
+ return EditorState.create({
47
+ doc: withDerivedDisplay(story, document),
48
+ plugins: [
49
+ editorDocument(document),
50
+ lockedContent(),
51
+ documentProtection({ protection, author: null, editableComments: "own" }),
52
+ compositionSelection(),
53
+ // A kind's own key rule stands ahead of the keymaps below: the base keymap answers
54
+ // Backspace and Enter for every document, so a rule about an empty story would never be
55
+ // asked behind it
56
+ ...plugins,
57
+ keymap(keys),
58
+ keymap(storyKeymap(takes)),
59
+ keymap(baseKeymap),
60
+ dropCursor(),
61
+ docxClipboard({ normalizers }),
62
+ tableEditing(),
63
+ tabDecorations(),
64
+ tabPointer(),
65
+ tabLayout(),
66
+ tabCaret(),
67
+ displayDerivation(),
68
+ numberingMarkers()
69
+ ]
70
+ });
71
+ }
72
+ export {
73
+ storyEditorState
74
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The one editing view a side story is edited in, mounted over the story the caret went into and
3
+ * taken down when it leaves.
4
+ *
5
+ * Everything typed here leaves through the host, which writes it into the main document as one
6
+ * story change. That is what keeps the guards, the locks and the protection judging a story edit
7
+ * where they judge a body edit, and what leaves the main document holding the only history: the
8
+ * undo keys here run the host's.
9
+ *
10
+ * This file does not know which kind of story it is drawing or where it is drawn: a note stands
11
+ * at the foot of a page, a comment body in a rail beside it, a header in a page margin. What
12
+ * differs between the kinds arrives as `StoryExtensions`.
13
+ */
14
+ import type { Node as PMNode } from "prosemirror-model";
15
+ import { type EditorState, type Plugin } from "prosemirror-state";
16
+ import { EditorView, type NodeViewConstructor } from "prosemirror-view";
17
+ import { type StoryKey } from "../../schema/stories";
18
+ import type { FontFallbacks } from "../../styles/fontStack";
19
+ import type { SliceNormalizer } from "../clipboard/normalizers";
20
+ import type { EditorDocument } from "../editorDocument";
21
+ export type NodeViewMap = Record<string, NodeViewConstructor>;
22
+ /** What every surface is read and dispatched through, whichever of the two it is */
23
+ interface SurfaceContents {
24
+ readonly view: EditorView;
25
+ /** The state on screen, which a control drawn from the surface re-decides itself from */
26
+ readonly state: EditorState;
27
+ readonly takes: SurfaceCapabilities;
28
+ }
29
+ /**
30
+ * The surface holding the caret, which is either the body or one story with the view over it.
31
+ *
32
+ * Two editing views cannot stand at once, and this is where that rule is written down: a caller
33
+ * holding this value has no way to name a second one. What holds it true is the state that decides
34
+ * which story is open (`ui/notes/useStorySurface`).
35
+ */
36
+ export type ActiveSurface = ({
37
+ readonly surface: "body";
38
+ } & SurfaceContents) | ({
39
+ readonly surface: "story";
40
+ readonly key: StoryKey;
41
+ } & SurfaceContents);
42
+ export interface StoryHost {
43
+ /** The main state the story is read from and judged against */
44
+ readonly state: () => EditorState;
45
+ /** Writes the story into the main document; false when the main document refused it */
46
+ write(key: StoryKey, story: PMNode): boolean;
47
+ undo(): boolean;
48
+ redo(): boolean;
49
+ /** Hands the caret back to the main document (Escape) */
50
+ leave(key: StoryKey): void;
51
+ /** Whether edits to this story are closed where it is anchored */
52
+ shut(key: StoryKey): boolean;
53
+ /** Registers the view that holds the caret, null when it lets go */
54
+ activate(key: StoryKey, view: EditorView | null): void;
55
+ }
56
+ /**
57
+ * What a surface takes beyond character and paragraph formatting.
58
+ *
59
+ * Every one of these puts something into the document that the part the surface is written back as
60
+ * has to carry - a relationship, a definition, a part of its own - so which of them a surface takes
61
+ * is the surface's own to declare, and a control or a key that puts one in asks here rather than
62
+ * asking which kind of surface it is.
63
+ */
64
+ export declare const SURFACE_CAPABILITIES: readonly ["list", "table", "image", "link", "note", "comment"];
65
+ export type SurfaceCapability = (typeof SURFACE_CAPABILITIES)[number];
66
+ export type SurfaceCapabilities = ReadonlySet<SurfaceCapability>;
67
+ /** What the body takes, which is everything: it is written back as the document part itself */
68
+ export declare const EVERY_CAPABILITY: SurfaceCapabilities;
69
+ /** What a surface whose writer carries none of them takes */
70
+ export declare const NO_CAPABILITY: SurfaceCapabilities;
71
+ export interface StoryExtensions {
72
+ readonly plugins: readonly Plugin[];
73
+ readonly nodeViews: NodeViewMap;
74
+ readonly normalizers: readonly SliceNormalizer[];
75
+ /** What this kind of story takes, which its keys and the controls over it are drawn from */
76
+ readonly takes: SurfaceCapabilities;
77
+ }
78
+ /**
79
+ * Where the caret goes when the view is mounted: back where it stood, or at the point the reader
80
+ * pressed, which is what a press on a story no view stands over yet has to say.
81
+ */
82
+ export type StoryCaret = {
83
+ readonly kind: "at";
84
+ readonly anchor: number;
85
+ readonly head: number;
86
+ } | {
87
+ readonly kind: "point";
88
+ readonly left: number;
89
+ readonly top: number;
90
+ };
91
+ export interface StoryViewOptions {
92
+ readonly mount: HTMLElement;
93
+ readonly host: StoryHost;
94
+ readonly key: StoryKey;
95
+ readonly document: EditorDocument;
96
+ readonly fontFallbacks: FontFallbacks;
97
+ readonly extensions: StoryExtensions;
98
+ /** Where the caret opens. The end of the story when none is given */
99
+ readonly caret?: StoryCaret | null;
100
+ /**
101
+ * Called with every state this view goes to, which is what a control drawn from the story - a
102
+ * toolbar acting on it - re-decides itself from.
103
+ */
104
+ readonly onStateChange?: (state: EditorState) => void;
105
+ }
106
+ export interface StoryView {
107
+ readonly view: EditorView;
108
+ /** Takes a changed main state in; replaces the content only when the story says something else */
109
+ sync(): void;
110
+ destroy(): void;
111
+ }
112
+ /** Where the caret stands, in the shape a view mounted again over the same story opens at */
113
+ export declare function caretOf(state: EditorState): StoryCaret;
114
+ export declare function createStoryView({ mount, host, key, document, fontFallbacks, extensions, caret, onStateChange, }: StoryViewOptions): StoryView;
115
+ export {};