@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.
Files changed (146) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/CONTRIBUTING.md +8 -2
  3. package/README.md +1 -1
  4. package/dist/DocxEditor.js +87 -29
  5. package/dist/docx/exportDocx.d.ts +2 -1
  6. package/dist/docx/exportDocx.js +3 -9
  7. package/dist/docx/headersFooters.js +35 -23
  8. package/dist/docx/identities.d.ts +28 -0
  9. package/dist/docx/identities.js +42 -10
  10. package/dist/docx/importDocx.js +25 -26
  11. package/dist/docx/importParagraph.d.ts +1 -4
  12. package/dist/docx/importParagraph.js +4 -18
  13. package/dist/docx/invariants.d.ts +1 -1
  14. package/dist/docx/invariants.js +45 -4
  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 +120 -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/writing.d.ts +19 -0
  22. package/dist/docx/notes/writing.js +96 -0
  23. package/dist/docx/partPlan.d.ts +3 -0
  24. package/dist/docx/partPlanners.d.ts +13 -0
  25. package/dist/docx/partPlanners.js +48 -0
  26. package/dist/docx/sections.d.ts +5 -0
  27. package/dist/docx/sections.js +7 -2
  28. package/dist/docx/serializeParagraph.js +7 -2
  29. package/dist/docx/session.d.ts +5 -0
  30. package/dist/docx/session.js +6 -0
  31. package/dist/docx/storyParts.d.ts +112 -0
  32. package/dist/docx/storyParts.js +289 -0
  33. package/dist/editor/clipboard/htmlReader.d.ts +24 -4
  34. package/dist/editor/clipboard/htmlReader.js +39 -4
  35. package/dist/editor/clipboard/internalChannel.d.ts +30 -3
  36. package/dist/editor/clipboard/internalChannel.js +43 -3
  37. package/dist/editor/clipboard/normalizers.d.ts +35 -8
  38. package/dist/editor/clipboard/normalizers.js +60 -11
  39. package/dist/editor/clipboard/plugin.js +42 -22
  40. package/dist/editor/commands/comments/editing.d.ts +4 -1
  41. package/dist/editor/commands/comments/editing.js +1 -3
  42. package/dist/editor/commands/comments/model.d.ts +7 -0
  43. package/dist/editor/commands/index.d.ts +1 -0
  44. package/dist/editor/commands/index.js +18 -0
  45. package/dist/editor/commands/noteCommands.d.ts +47 -0
  46. package/dist/editor/commands/noteCommands.js +95 -0
  47. package/dist/editor/commands/noteQueries.d.ts +22 -3
  48. package/dist/editor/commands/noteQueries.js +21 -13
  49. package/dist/editor/createEditor.js +25 -3
  50. package/dist/editor/documentStyles.d.ts +3 -0
  51. package/dist/editor/documentStyles.js +2 -1
  52. package/dist/editor/editorDocument.d.ts +24 -0
  53. package/dist/editor/editorDocument.js +34 -4
  54. package/dist/editor/notes/noteSurface.d.ts +53 -0
  55. package/dist/editor/notes/noteSurface.js +246 -0
  56. package/dist/editor/plugins/commentDecorations.js +5 -4
  57. package/dist/editor/plugins/commentRestoration.d.ts +20 -0
  58. package/dist/editor/plugins/commentRestoration.js +168 -0
  59. package/dist/editor/plugins/compositionSelection.d.ts +20 -0
  60. package/dist/editor/plugins/compositionSelection.js +19 -0
  61. package/dist/editor/plugins/keymap.d.ts +14 -0
  62. package/dist/editor/plugins/keymap.js +13 -1
  63. package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
  64. package/dist/editor/plugins/noteLifecycle.js +117 -0
  65. package/dist/editor/plugins/noteNavigation.d.ts +45 -0
  66. package/dist/editor/plugins/noteNavigation.js +86 -0
  67. package/dist/editor/plugins/noteNumbering.d.ts +13 -0
  68. package/dist/editor/plugins/noteNumbering.js +58 -0
  69. package/dist/editor/plugins/rowResize.js +2 -7
  70. package/dist/editor/plugins/tabLayout.js +2 -7
  71. package/dist/editor/stories/storyMarkup.d.ts +24 -0
  72. package/dist/editor/stories/storyMarkup.js +35 -0
  73. package/dist/editor/stories/storyState.d.ts +34 -0
  74. package/dist/editor/stories/storyState.js +74 -0
  75. package/dist/editor/stories/storyView.d.ts +115 -0
  76. package/dist/editor/stories/storyView.js +144 -0
  77. package/dist/numbering/spellers.d.ts +1 -1
  78. package/dist/numbering/spellers.js +6 -1
  79. package/dist/page/blockKinds.d.ts +23 -4
  80. package/dist/page/demands/footnoteDemands.d.ts +18 -0
  81. package/dist/page/demands/footnoteDemands.js +51 -0
  82. package/dist/page/demands/index.d.ts +43 -0
  83. package/dist/page/demands/index.js +8 -0
  84. package/dist/page/kinds/paragraphKind.js +7 -1
  85. package/dist/page/kinds/tableKind.js +9 -1
  86. package/dist/page/measureBlocks.d.ts +2 -0
  87. package/dist/page/measureBlocks.js +31 -10
  88. package/dist/page/pageDecorations.d.ts +9 -2
  89. package/dist/page/pageDecorations.js +18 -5
  90. package/dist/page/pageLayout.d.ts +77 -1
  91. package/dist/page/pageLayout.js +186 -9
  92. package/dist/page/usePageLayout.d.ts +57 -2
  93. package/dist/page/usePageLayout.js +50 -5
  94. package/dist/schema/attrRoles.js +3 -2
  95. package/dist/schema/docxSchema.js +5 -2
  96. package/dist/schema/editGuard.d.ts +1 -1
  97. package/dist/schema/guards.js +1 -2
  98. package/dist/schema/preservedGuards.d.ts +4 -6
  99. package/dist/schema/preservedGuards.js +0 -9
  100. package/dist/schema/stories.d.ts +34 -2
  101. package/dist/schema/stories.js +22 -5
  102. package/dist/styles/classNames.d.ts +23 -6
  103. package/dist/styles/classNames.js +23 -6
  104. package/dist/styles/inlineStyle.d.ts +7 -0
  105. package/dist/styles/inlineStyle.js +11 -5
  106. package/dist/styles/visualScale.d.ts +19 -0
  107. package/dist/styles/visualScale.js +26 -0
  108. package/dist/styles.css +96 -36
  109. package/dist/ui/CommentsPanel.d.ts +6 -1
  110. package/dist/ui/CommentsPanel.js +26 -8
  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 +2 -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,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
+ };
@@ -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);
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { Node as PMNode } from "prosemirror-model";
12
12
  import type { Decoration, EditorView } from "prosemirror-view";
13
+ import type { PageDemand } from "./demands";
13
14
  /** A place inside a block where the next page may, or must, start */
14
15
  export interface BreakCandidate {
15
16
  /**
@@ -41,13 +42,21 @@ export interface MeasuredBlock {
41
42
  minFirstPiece: number;
42
43
  /** The document asks for this block to stand on the same page as the start of the block after it */
43
44
  keepWithNext: boolean;
45
+ /**
46
+ * The places in the block that ask the page they land on for room at its foot, their offsets read
47
+ * like a candidate's with no space in the block. A block asking for none may leave it out
48
+ */
49
+ demands?: readonly PageDemand[];
44
50
  }
45
51
  /** A page cut the layout decided on: the space opened before the continued piece */
46
52
  export interface PageCut {
47
53
  at: number;
48
54
  height: number;
49
55
  }
50
- /** One block as it stands on the sheet, handed to the kind about to measure it */
56
+ /**
57
+ * One block as it stands on the sheet, handed to the kind about to measure it and then to every
58
+ * demand source (`page/demands`)
59
+ */
51
60
  export interface MeasureTarget {
52
61
  view: EditorView;
53
62
  node: PMNode;
@@ -72,6 +81,15 @@ export interface KindMeasure {
72
81
  breakAfter: boolean;
73
82
  /** Height the engine's own marks add inside this block, taken off its measured bottom */
74
83
  appliedHeight: number;
84
+ /**
85
+ * What the engine's marks have opened at each place this block was cut, as the browser drew it,
86
+ * by the candidate's position. What is drawn there can come out taller than the cut asked for -
87
+ * a table's spacer row takes half of a collapsed border on each side - and everything else a
88
+ * kind reports is read off the sheet as drawn, so a place below a cut is taken back to its
89
+ * natural offset by this rather than by what the layout asked for (`page/measureBlocks`).
90
+ * A kind that opens nothing leaves it out
91
+ */
92
+ opened?: ReadonlyMap<number, number>;
75
93
  /**
76
94
  * Whether the document asks for this block to stand on the same page as the start of the block
77
95
  * after it. A kind whose blocks never do leaves it out
@@ -82,9 +100,10 @@ export interface KindMeasure {
82
100
  * Everything the engine does with one shape of breakable block: how one is measured, and how the
83
101
  * cuts the layout gave it are drawn.
84
102
  *
85
- * A shape the engine learns later - a footnote area, a paragraph with a floating object hanging
86
- * out of it - is one more kind rather than another branch in the measurer, in the decorations
87
- * and in the plugin state at once.
103
+ * A shape the engine learns later is one more kind rather than another branch in the measurer, in
104
+ * the decorations and in the plugin state at once. Room a place inside a block asks of the page it
105
+ * lands on is not a shape: a block has one kind while such a place can stand inside a block of any
106
+ * kind, so it is asked of every block by a demand source (`page/demands`) instead.
88
107
  */
89
108
  export interface BlockKind {
90
109
  readonly name: string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The room a footnote reference asks of the page it lands on: its footnote, drawn at the foot of
3
+ * that page (`ui/notes`).
4
+ *
5
+ * The sheet is measured on every layout pass and nearly every block holds no reference, so which
6
+ * references a block holds is read off the block node and remembered against it. A node an edit
7
+ * did not touch is the same node afterwards, so a pass reads nothing off the page for a block with
8
+ * no reference and walks only the blocks an edit rebuilt.
9
+ */
10
+ import type { DemandSource } from "./index";
11
+ /** The band footnotes are kept in, which the layout's caller hands the heights of */
12
+ export declare const FOOTNOTE_BAND = "footnote";
13
+ /**
14
+ * Where footnotes stand among the bands a page keeps (`DemandBand.order`). Word sets them at the
15
+ * very foot of the body, under anything else the page holds there, so this is the last place
16
+ */
17
+ export declare const FOOTNOTE_BAND_ORDER = 0;
18
+ export declare const footnoteDemands: DemandSource;
@@ -0,0 +1,51 @@
1
+ // src/page/demands/footnoteDemands.ts
2
+ import { docxSchema } from "../../schema/index.js";
3
+ import { storyKey } from "../../schema/stories.js";
4
+ var FOOTNOTE_BAND = "footnote";
5
+ var FOOTNOTE_BAND_ORDER = 0;
6
+ var BAND_NOTE_KINDS = ["footnote"];
7
+ var NO_REFERENCES = [];
8
+ var NO_DEMANDS = [];
9
+ var held = /* @__PURE__ */ new WeakMap();
10
+ function footnoteReferencesIn(block) {
11
+ const remembered = held.get(block);
12
+ if (remembered) return remembered;
13
+ const found = [];
14
+ block.descendants((node, offset) => {
15
+ if (node.type !== docxSchema.nodes.noteReference) return true;
16
+ const id = node.attrs.id;
17
+ const kind = BAND_NOTE_KINDS.find(
18
+ (candidate) => candidate === node.attrs.kind
19
+ );
20
+ if (kind !== void 0 && typeof id === "string") {
21
+ found.push({ offset, key: storyKey(kind, id) });
22
+ }
23
+ return false;
24
+ });
25
+ const references = found.length === 0 ? NO_REFERENCES : found;
26
+ held.set(block, references);
27
+ return references;
28
+ }
29
+ var footnoteDemands = {
30
+ name: "footnote",
31
+ demandsIn({ view, node, pos, sheetY, top }) {
32
+ const references = footnoteReferencesIn(node);
33
+ if (references.length === 0) return NO_DEMANDS;
34
+ return references.flatMap(({ offset, key }) => {
35
+ const drawn = view.nodeDOM(pos + 1 + offset);
36
+ if (!(drawn instanceof Element)) return [];
37
+ return [
38
+ {
39
+ offset: sheetY(drawn.getBoundingClientRect().top) - top,
40
+ id: key,
41
+ band: FOOTNOTE_BAND
42
+ }
43
+ ];
44
+ });
45
+ }
46
+ };
47
+ export {
48
+ FOOTNOTE_BAND,
49
+ FOOTNOTE_BAND_ORDER,
50
+ footnoteDemands
51
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Room a place inside a block asks the page it lands on to keep at the foot of its body.
3
+ *
4
+ * A block has one kind (`page/blockKinds`), and a place asking for room can stand inside a block of
5
+ * any kind: a paragraph, or a paragraph in a table cell. So what asks is not a kind but a source,
6
+ * and every registered source is asked about every block whatever its kind. The layout
7
+ * (`page/pageLayout`) knows a demand by its band alone, never by what put it there.
8
+ */
9
+ import type { MeasureTarget } from "../blockKinds";
10
+ /** Room one place in a block asks the page it lands on to keep at the foot */
11
+ export interface PageDemand {
12
+ /**
13
+ * The place's top from the block's natural top. A source reads it off the sheet as drawn, the
14
+ * way a kind reads a candidate (`target.sheetY(viewportY) - target.top`), and the measurement
15
+ * takes back off the spaces the engine opened inside the block above that place
16
+ * (`page/measureBlocks`), so no source has to know what any kind draws
17
+ */
18
+ readonly offset: number;
19
+ /** What is kept. One id is kept once on a page, however many places on it ask for it */
20
+ readonly id: string;
21
+ /** The band at the foot of the page that keeps it (`DemandBand`) */
22
+ readonly band: string;
23
+ }
24
+ export interface DemandSource {
25
+ readonly name: string;
26
+ demandsIn(target: MeasureTarget): readonly PageDemand[];
27
+ }
28
+ export interface DemandBand {
29
+ /**
30
+ * Where this band stands among the bands one page keeps, counted up from the foot of the body:
31
+ * 0 stands at the very foot and a higher number above it, between the text and the band below.
32
+ * Without it a page would stack its bands in the order its text happened to reach them, so the
33
+ * same two bands could come out one way round on one page and the other way round on the next.
34
+ * Two bands a page may hold together name two different places
35
+ */
36
+ readonly order: number;
37
+ /** Height a page adds once when it holds any demand of this band */
38
+ readonly overhead: number;
39
+ /** The height each id takes in the band. An id not measured yet takes none */
40
+ readonly heights: ReadonlyMap<string, number>;
41
+ }
42
+ /** The sources the editor asks about every block, in the order their demands are listed */
43
+ export declare const DEFAULT_DEMAND_SOURCES: readonly DemandSource[];
@@ -0,0 +1,8 @@
1
+ // src/page/demands/index.ts
2
+ import { footnoteDemands } from "./footnoteDemands.js";
3
+ var DEFAULT_DEMAND_SOURCES = [
4
+ footnoteDemands
5
+ ];
6
+ export {
7
+ DEFAULT_DEMAND_SOURCES
8
+ };