@portone/docx-editor 0.6.0 → 0.6.2

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 (146) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/DocxEditor.js +84 -26
  3. package/dist/core.d.ts +4 -3
  4. package/dist/docx/exportDocx.d.ts +3 -2
  5. package/dist/docx/exportDocx.js +6 -10
  6. package/dist/docx/headersFooters.js +35 -23
  7. package/dist/docx/identities.d.ts +30 -0
  8. package/dist/docx/identities.js +43 -10
  9. package/dist/docx/importDocx.js +25 -26
  10. package/dist/docx/importParagraph.d.ts +1 -4
  11. package/dist/docx/importParagraph.js +4 -18
  12. package/dist/docx/invariants.d.ts +13 -10
  13. package/dist/docx/invariants.js +300 -116
  14. package/dist/docx/newLists.js +12 -3
  15. package/dist/docx/notes/newNote.d.ts +39 -0
  16. package/dist/docx/notes/newNote.js +122 -0
  17. package/dist/docx/notes/numbering.d.ts +27 -0
  18. package/dist/docx/notes/numbering.js +113 -0
  19. package/dist/docx/notes/reading.d.ts +58 -0
  20. package/dist/docx/notes/reading.js +98 -0
  21. package/dist/docx/notes/references.d.ts +33 -0
  22. package/dist/docx/notes/references.js +37 -0
  23. package/dist/docx/notes/writing.d.ts +19 -0
  24. package/dist/docx/notes/writing.js +95 -0
  25. package/dist/docx/partPlan.d.ts +3 -0
  26. package/dist/docx/partPlanners.d.ts +13 -0
  27. package/dist/docx/partPlanners.js +48 -0
  28. package/dist/docx/sections.d.ts +5 -0
  29. package/dist/docx/sections.js +7 -2
  30. package/dist/docx/serializeParagraph.js +7 -2
  31. package/dist/docx/serializePreserved.d.ts +5 -1
  32. package/dist/docx/serializePreserved.js +24 -7
  33. package/dist/docx/session.d.ts +5 -0
  34. package/dist/docx/session.js +6 -0
  35. package/dist/docx/storyParts.d.ts +133 -0
  36. package/dist/docx/storyParts.js +314 -0
  37. package/dist/editor/clipboard/htmlReader.d.ts +24 -4
  38. package/dist/editor/clipboard/htmlReader.js +39 -4
  39. package/dist/editor/clipboard/internalChannel.d.ts +30 -3
  40. package/dist/editor/clipboard/internalChannel.js +43 -3
  41. package/dist/editor/clipboard/normalizers.d.ts +35 -8
  42. package/dist/editor/clipboard/normalizers.js +60 -11
  43. package/dist/editor/clipboard/plugin.js +42 -22
  44. package/dist/editor/commands/exportQueries.d.ts +4 -3
  45. package/dist/editor/commands/index.d.ts +2 -1
  46. package/dist/editor/commands/index.js +18 -0
  47. package/dist/editor/commands/noteCommands.d.ts +47 -0
  48. package/dist/editor/commands/noteCommands.js +95 -0
  49. package/dist/editor/commands/noteQueries.d.ts +22 -3
  50. package/dist/editor/commands/noteQueries.js +21 -13
  51. package/dist/editor/createEditor.js +21 -3
  52. package/dist/editor/documentStyles.d.ts +3 -0
  53. package/dist/editor/documentStyles.js +2 -1
  54. package/dist/editor/editorDocument.d.ts +24 -0
  55. package/dist/editor/editorDocument.js +34 -4
  56. package/dist/editor/notes/noteSurface.d.ts +53 -0
  57. package/dist/editor/notes/noteSurface.js +246 -0
  58. package/dist/editor/plugins/compositionSelection.d.ts +20 -0
  59. package/dist/editor/plugins/compositionSelection.js +19 -0
  60. package/dist/editor/plugins/keymap.d.ts +14 -0
  61. package/dist/editor/plugins/keymap.js +13 -1
  62. package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
  63. package/dist/editor/plugins/noteLifecycle.js +117 -0
  64. package/dist/editor/plugins/noteNavigation.d.ts +45 -0
  65. package/dist/editor/plugins/noteNavigation.js +86 -0
  66. package/dist/editor/plugins/noteNumbering.d.ts +13 -0
  67. package/dist/editor/plugins/noteNumbering.js +58 -0
  68. package/dist/editor/plugins/rowResize.js +2 -7
  69. package/dist/editor/plugins/tabLayout.js +2 -7
  70. package/dist/editor/stories/storyMarkup.d.ts +24 -0
  71. package/dist/editor/stories/storyMarkup.js +35 -0
  72. package/dist/editor/stories/storyState.d.ts +34 -0
  73. package/dist/editor/stories/storyState.js +74 -0
  74. package/dist/editor/stories/storyView.d.ts +115 -0
  75. package/dist/editor/stories/storyView.js +144 -0
  76. package/dist/index.d.ts +2 -2
  77. package/dist/numbering/spellers.d.ts +1 -1
  78. package/dist/numbering/spellers.js +6 -1
  79. package/dist/ooxml/errors.d.ts +135 -2
  80. package/dist/ooxml/errors.js +6 -0
  81. package/dist/page/blockKinds.d.ts +23 -4
  82. package/dist/page/demands/footnoteDemands.d.ts +18 -0
  83. package/dist/page/demands/footnoteDemands.js +51 -0
  84. package/dist/page/demands/index.d.ts +43 -0
  85. package/dist/page/demands/index.js +8 -0
  86. package/dist/page/kinds/paragraphKind.js +7 -1
  87. package/dist/page/kinds/tableKind.js +9 -1
  88. package/dist/page/measureBlocks.d.ts +2 -0
  89. package/dist/page/measureBlocks.js +31 -10
  90. package/dist/page/pageDecorations.d.ts +9 -2
  91. package/dist/page/pageDecorations.js +18 -5
  92. package/dist/page/pageLayout.d.ts +77 -1
  93. package/dist/page/pageLayout.js +186 -9
  94. package/dist/page/usePageLayout.d.ts +57 -2
  95. package/dist/page/usePageLayout.js +50 -5
  96. package/dist/schema/attrRoles.js +3 -2
  97. package/dist/schema/docxSchema.js +5 -2
  98. package/dist/schema/editGuard.d.ts +1 -1
  99. package/dist/schema/guards.js +1 -2
  100. package/dist/schema/preservedGuards.d.ts +4 -6
  101. package/dist/schema/preservedGuards.js +0 -9
  102. package/dist/schema/stories.d.ts +42 -2
  103. package/dist/schema/stories.js +28 -5
  104. package/dist/styles/classNames.d.ts +21 -6
  105. package/dist/styles/classNames.js +21 -6
  106. package/dist/styles/inlineStyle.d.ts +7 -0
  107. package/dist/styles/inlineStyle.js +11 -5
  108. package/dist/styles/visualScale.d.ts +19 -0
  109. package/dist/styles/visualScale.js +26 -0
  110. package/dist/styles.css +89 -32
  111. package/dist/ui/InsertImageButton.d.ts +3 -1
  112. package/dist/ui/InsertImageButton.js +3 -2
  113. package/dist/ui/TableMenu.d.ts +5 -2
  114. package/dist/ui/TableMenu.js +18 -1
  115. package/dist/ui/TextMenu.d.ts +5 -2
  116. package/dist/ui/TextMenu.js +21 -6
  117. package/dist/ui/Toolbar.d.ts +17 -2
  118. package/dist/ui/Toolbar.js +23 -10
  119. package/dist/ui/comments/useCommentRailLayout.js +2 -4
  120. package/dist/ui/noteItems.d.ts +21 -0
  121. package/dist/ui/noteItems.js +30 -0
  122. package/dist/ui/notes/NoteAreas.d.ts +73 -0
  123. package/dist/ui/notes/NoteAreas.js +131 -0
  124. package/dist/ui/notes/NoteList.d.ts +25 -0
  125. package/dist/ui/notes/NoteList.js +58 -0
  126. package/dist/ui/notes/StoryRow.d.ts +75 -0
  127. package/dist/ui/notes/StoryRow.js +122 -0
  128. package/dist/ui/notes/noteBands.d.ts +100 -0
  129. package/dist/ui/notes/noteBands.js +142 -0
  130. package/dist/ui/notes/noteHeights.d.ts +10 -0
  131. package/dist/ui/notes/noteHeights.js +24 -0
  132. package/dist/ui/notes/noteSeparator.d.ts +12 -0
  133. package/dist/ui/notes/noteSeparator.js +9 -0
  134. package/dist/ui/notes/useStorySurface.d.ts +68 -0
  135. package/dist/ui/notes/useStorySurface.js +95 -0
  136. package/dist/ui/runCommand.d.ts +8 -1
  137. package/dist/ui/runCommand.js +2 -2
  138. package/dist/ui/shortcutLabels.d.ts +16 -0
  139. package/dist/ui/shortcutLabels.js +9 -0
  140. package/dist/ui/usePageRoom.d.ts +13 -0
  141. package/dist/ui/usePageRoom.js +31 -0
  142. package/package.json +1 -1
  143. package/dist/docx/notes.d.ts +0 -27
  144. package/dist/docx/notes.js +0 -55
  145. package/dist/ui/NotesPanel.d.ts +0 -6
  146. package/dist/ui/NotesPanel.js +0 -33
@@ -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 {};
@@ -0,0 +1,144 @@
1
+ // src/editor/stories/storyView.ts
2
+ import {
3
+ TextSelection
4
+ } from "prosemirror-state";
5
+ import { EditorView } from "prosemirror-view";
6
+ import { editingProtection, editsShut } from "../../schema/protectionState.js";
7
+ import { sameStory, storyNodeOf } from "../../schema/stories.js";
8
+ import { editorClassNames } from "../../styles/classNames.js";
9
+ import { runMarkView } from "../views/runMarkView.js";
10
+ import { storyEditorState } from "./storyState.js";
11
+ var SURFACE_CAPABILITIES = [
12
+ "list",
13
+ "table",
14
+ "image",
15
+ "link",
16
+ "note",
17
+ "comment"
18
+ ];
19
+ var EVERY_CAPABILITY = new Set(
20
+ SURFACE_CAPABILITIES
21
+ );
22
+ var NO_CAPABILITY = /* @__PURE__ */ new Set();
23
+ function caretOf(state) {
24
+ return {
25
+ kind: "at",
26
+ anchor: state.selection.anchor,
27
+ head: state.selection.head
28
+ };
29
+ }
30
+ function placed(state, at, to) {
31
+ const size = state.doc.content.size;
32
+ const $anchor = state.doc.resolve(Math.min(Math.max(at, 0), size));
33
+ const $head = state.doc.resolve(Math.min(Math.max(to, 0), size));
34
+ return state.apply(
35
+ state.tr.setSelection(TextSelection.between($anchor, $head))
36
+ );
37
+ }
38
+ function endComposition(view) {
39
+ if (!view.composing) return;
40
+ const marks = view.state.storedMarks ?? view.state.selection.$from.marks();
41
+ view.dispatch(
42
+ view.state.tr.setStoredMarks(marks).setMeta("addToHistory", false)
43
+ );
44
+ }
45
+ function createStoryView({
46
+ mount,
47
+ host,
48
+ key,
49
+ document,
50
+ fontFallbacks,
51
+ extensions,
52
+ caret = null,
53
+ onStateChange
54
+ }) {
55
+ const standing = () => host.shut(key) ? "readOnly" : editingProtection(host.state());
56
+ let protection = standing();
57
+ const keys = {
58
+ "Mod-z": () => host.undo(),
59
+ "Mod-y": () => host.redo(),
60
+ "Shift-Mod-z": () => host.redo(),
61
+ Escape: () => {
62
+ host.leave(key);
63
+ return true;
64
+ }
65
+ };
66
+ const built = (story) => storyEditorState({
67
+ story,
68
+ document,
69
+ protection,
70
+ keys,
71
+ plugins: extensions.plugins,
72
+ normalizers: extensions.normalizers,
73
+ takes: extensions.takes
74
+ });
75
+ const storyNow = () => storyNodeOf(host.state().doc, key);
76
+ const opening = storyNow();
77
+ if (opening === null) {
78
+ throw new Error(`the document holds no story named ${key}`);
79
+ }
80
+ const view = new EditorView(mount, {
81
+ state: built(opening),
82
+ // A standing that shuts the story shuts typing in it, and is read off the state so that a
83
+ // mode switched while the story is open takes effect without a new view
84
+ editable: (current) => !editsShut(current),
85
+ attributes: { class: editorClassNames.storyBody },
86
+ // The schema can only draw a run with the default fallbacks; this view draws the host's
87
+ markViews: { run: runMarkView(fontFallbacks) },
88
+ nodeViews: extensions.nodeViews,
89
+ dispatchTransaction(transaction) {
90
+ const next = view.state.applyTransaction(transaction);
91
+ if (next.state.doc === view.state.doc) {
92
+ show(next.state);
93
+ return;
94
+ }
95
+ const before = view.state;
96
+ show(next.state);
97
+ if (host.write(key, next.state.doc)) return;
98
+ if (sameStory(storyNow(), next.state.doc)) return;
99
+ show(before);
100
+ endComposition(view);
101
+ }
102
+ });
103
+ const show = (next) => {
104
+ view.updateState(next);
105
+ onStateChange?.(next);
106
+ };
107
+ const open = caret ?? null;
108
+ if (open === null) {
109
+ const end = view.state.doc.content.size;
110
+ show(placed(view.state, end, end));
111
+ } else if (open.kind === "at") {
112
+ show(placed(view.state, open.anchor, open.head));
113
+ } else {
114
+ const at = view.posAtCoords({ left: open.left, top: open.top });
115
+ const pos = at?.pos ?? view.state.doc.content.size;
116
+ show(placed(view.state, pos, pos));
117
+ }
118
+ const registerFocus = () => host.activate(key, view);
119
+ view.dom.addEventListener("focus", registerFocus);
120
+ return {
121
+ view,
122
+ sync() {
123
+ const story = storyNow();
124
+ if (story === null) return;
125
+ const wanted = standing();
126
+ if (wanted === protection && sameStory(story, view.state.doc)) return;
127
+ const { anchor, head } = view.state.selection;
128
+ protection = wanted;
129
+ show(placed(built(story), anchor, head));
130
+ },
131
+ destroy() {
132
+ view.dom.removeEventListener("focus", registerFocus);
133
+ host.activate(key, null);
134
+ view.destroy();
135
+ }
136
+ };
137
+ }
138
+ export {
139
+ EVERY_CAPABILITY,
140
+ NO_CAPABILITY,
141
+ SURFACE_CAPABILITIES,
142
+ caretOf,
143
+ createStoryView
144
+ };
package/dist/index.d.ts CHANGED
@@ -16,12 +16,12 @@ export type { DocxEditorHandle, DocxEditorMode, DocxEditorProps, } from "./DocxE
16
16
  export { DocxEditor } from "./DocxEditor";
17
17
  export type { DocxBytes, DocxSource } from "./docx/importDocx";
18
18
  /** What the handle and a `blocked` download report: each reason the document cannot be written, under the code `DocxExportError` would throw it with */
19
- export type { ExportProblem } from "./docx/invariants";
19
+ export type { ExportPartName, ExportProblem, ExportProblemReason, ExportProblemStory, ExportStoryKind, } from "./docx/invariants";
20
20
  export type { DownloadDocxOptions, DownloadDocxResult, } from "./download";
21
21
  export { downloadDocx } from "./download";
22
22
  export type { CommentAuthor } from "./editor/commands/commentCommands";
23
23
  export type { CellFormat, CellVerticalAlign, DocumentDefaults, HighlightName, LineSpacing, NumberingRef, ParagraphAlign, ParagraphFormat, RowFormat, RowHeight, RunFormat, TableFormat, TableWidth, TableWidthType, UnderlineKind, VerticalAlign, } from "./model/format";
24
- export type { DocxExportErrorCode, DocxImportErrorCode, } from "./ooxml/errors";
24
+ export type { DocxExportErrorCode, DocxExportErrorOptions, DocxImportErrorCode, } from "./ooxml/errors";
25
25
  export { DocxExportError, DocxImportError } from "./ooxml/errors";
26
26
  export { docxSchema } from "./schema";
27
27
  /** Whose comments the panel offers to edit, which `DocxEditorMode` takes */
@@ -6,7 +6,7 @@
6
6
  * because it has a speller here, so a marker can never be counted in a format nothing can write.
7
7
  */
8
8
  /** A `w:numFmt` this editor spells out. Every other one is drawn as a decimal */
9
- export type NumberFormat = "decimal" | "decimalZero" | "bullet" | "lowerLetter" | "upperLetter" | "lowerRoman" | "upperRoman" | "ganada" | "koreanDigital" | "chineseCounting";
9
+ export type NumberFormat = "decimal" | "decimalZero" | "bullet" | "lowerLetter" | "upperLetter" | "lowerRoman" | "upperRoman" | "ganada" | "koreanDigital" | "chineseCounting" | "chicago";
10
10
  export declare function isNumberFormat(value: string | null): value is NumberFormat;
11
11
  /**
12
12
  * The count as this format writes it, and as a decimal where spelling it out would run past the
@@ -31,6 +31,7 @@ function repeated(symbols, count) {
31
31
  return (symbols[index] ?? "").repeat(times);
32
32
  }
33
33
  var LATIN = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
34
+ var CHICAGO = "*\u2020\u2021\xA7";
34
35
  var GANADA = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
35
36
  var KOREAN_DIGITS = "\uC601\uC77C\uC774\uC0BC\uC0AC\uC624\uC721\uCE60\uD314\uAD6C";
36
37
  function digits(symbols, count) {
@@ -80,7 +81,11 @@ var NUMBER_SPELLERS = {
80
81
  spell: (count) => digits(KOREAN_DIGITS, count),
81
82
  countsPerChar: POSITIONAL
82
83
  },
83
- chineseCounting: { spell: chineseCounting, countsPerChar: POSITIONAL }
84
+ chineseCounting: { spell: chineseCounting, countsPerChar: POSITIONAL },
85
+ chicago: {
86
+ spell: (count) => repeated(CHICAGO, count),
87
+ countsPerChar: CHICAGO.length
88
+ }
84
89
  };
85
90
  function isNumberFormat(value) {
86
91
  return value !== null && Object.hasOwn(NUMBER_SPELLERS, value);