@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
@@ -2,12 +2,27 @@
2
2
  import type { EditorState } from "prosemirror-state";
3
3
  import type { EditorView } from "prosemirror-view";
4
4
  import { type ReactElement } from "react";
5
+ import type { ActiveSurface } from "../editor/stories/storyView";
5
6
  import type { FontFallbacks } from "../styles/fontStack";
6
7
  import type { DocxEditorPresets } from "./presets";
7
8
  import type { DocxEditorZoom } from "./zoom";
8
- export interface ToolbarProps {
9
+ /** One editing surface the toolbar can act on: the document's own, or a story's */
10
+ export interface ToolbarSurface {
9
11
  view: EditorView;
10
12
  state: EditorState;
13
+ }
14
+ export interface ToolbarProps {
15
+ /**
16
+ * The document itself, which undo and redo always read: a story edit is written into the
17
+ * document as one change, so the document's history is the one both surfaces share
18
+ * (`editor/stories`).
19
+ */
20
+ main: ToolbarSurface;
21
+ /**
22
+ * Where the caret is. Everything but undo and redo is asked of it and dispatched to it, so the
23
+ * formatting controls act on the note being edited rather than on the text behind it.
24
+ */
25
+ active: ActiveSurface;
11
26
  fontFallbacks?: FontFallbacks;
12
27
  /** Optional values offered by toolbar pickers. */
13
28
  presets?: DocxEditorPresets;
@@ -16,4 +31,4 @@ export interface ToolbarProps {
16
31
  zoom: DocxEditorZoom;
17
32
  onZoomChange: (zoom: DocxEditorZoom) => void;
18
33
  }
19
- export declare function Toolbar({ view, state, fontFallbacks, presets, onToggleComments, commentsOpen, zoom, onZoomChange, }: ToolbarProps): ReactElement;
34
+ export declare function Toolbar({ main, active, fontFallbacks, presets, onToggleComments, commentsOpen, zoom, onZoomChange, }: ToolbarProps): ReactElement;
@@ -272,7 +272,11 @@ function ParagraphLayoutGroup({
272
272
  )
273
273
  ] });
274
274
  }
275
- function ListGroup({ state, run }) {
275
+ function ListGroup({
276
+ state,
277
+ run,
278
+ disabled
279
+ }) {
276
280
  const kind = activeListKind(state);
277
281
  return /* @__PURE__ */ jsx("div", { className: editorClassNames.toolbarGroup, children: LIST_TOGGLES.map((toggle) => /* @__PURE__ */ jsx(
278
282
  ToolbarButton,
@@ -280,7 +284,7 @@ function ListGroup({ state, run }) {
280
284
  label: toggle.label,
281
285
  icon: toggle.icon,
282
286
  pressed: kind === toggle.kind,
283
- disabled: !toggle.command(state),
287
+ disabled: disabled || !toggle.command(state),
284
288
  onRun: () => run(toggle.command)
285
289
  },
286
290
  toggle.kind
@@ -299,8 +303,8 @@ function IndentGroup({ state, run }) {
299
303
  )) });
300
304
  }
301
305
  function Toolbar({
302
- view,
303
- state,
306
+ main,
307
+ active,
304
308
  fontFallbacks,
305
309
  presets,
306
310
  onToggleComments,
@@ -308,7 +312,9 @@ function Toolbar({
308
312
  zoom,
309
313
  onZoomChange
310
314
  }) {
315
+ const { view, state, takes } = active;
311
316
  const run = commandRunner(view);
317
+ const runOnMain = commandRunner(main.view, view);
312
318
  const bar = useRef(null);
313
319
  const keys = useLinearWalk({
314
320
  container: bar,
@@ -340,7 +346,7 @@ function Toolbar({
340
346
  onKeyDown,
341
347
  ...hushedAttribute(hushed),
342
348
  children: [
343
- /* @__PURE__ */ jsx(HistoryGroup, { state, run }),
349
+ /* @__PURE__ */ jsx(HistoryGroup, { state: main.state, run: runOnMain }),
344
350
  /* @__PURE__ */ jsx(ZoomSelect, { zoom, onZoomChange }),
345
351
  /* @__PURE__ */ jsx(Separator, {}),
346
352
  /* @__PURE__ */ jsxs("div", { className: editorClassNames.toolbarGroup, children: [
@@ -394,7 +400,7 @@ function Toolbar({
394
400
  }
395
401
  ),
396
402
  /* @__PURE__ */ jsx(Separator, {}),
397
- /* @__PURE__ */ jsx(ListGroup, { state, run }),
403
+ /* @__PURE__ */ jsx(ListGroup, { state, run, disabled: !takes.has("list") }),
398
404
  /* @__PURE__ */ jsx(IndentGroup, { state, run }),
399
405
  /* @__PURE__ */ jsx(Separator, {}),
400
406
  /* @__PURE__ */ jsx(
@@ -414,17 +420,24 @@ function Toolbar({
414
420
  label: "Insert table",
415
421
  icon: Table,
416
422
  panel: "dialog",
417
- disabled: !canInsertTable(state),
423
+ disabled: !takes.has("table") || !canInsertTable(state),
418
424
  children: ({ close, takeFocus }) => /* @__PURE__ */ jsx(TableSizePicker, { run, close, takeFocus })
419
425
  }
420
426
  ),
421
- /* @__PURE__ */ jsx(InsertImageButton, { view, state }),
427
+ /* @__PURE__ */ jsx(
428
+ InsertImageButton,
429
+ {
430
+ view,
431
+ state,
432
+ disabled: !takes.has("image")
433
+ }
434
+ ),
422
435
  /* @__PURE__ */ jsx(
423
436
  ToolbarButton,
424
437
  {
425
438
  label: "Link",
426
439
  icon: Link,
427
- disabled: !openLinkPanel(state),
440
+ disabled: !takes.has("link") || !openLinkPanel(state),
428
441
  onRun: () => run(openLinkPanel)
429
442
  }
430
443
  ),
@@ -434,7 +447,7 @@ function Toolbar({
434
447
  label: commentsOpen ? "Hide comments" : "Show comments",
435
448
  icon: MessagesSquare,
436
449
  pressed: commentsOpen,
437
- disabled: !onToggleComments,
450
+ disabled: !takes.has("comment") || !onToggleComments,
438
451
  onRun: () => onToggleComments?.()
439
452
  }
440
453
  )
@@ -1,6 +1,7 @@
1
1
  // src/ui/comments/useCommentRailLayout.ts
2
2
  import { useLayoutEffect, useRef, useState } from "react";
3
3
  import { editorClassNames } from "../../styles/classNames.js";
4
+ import { visualScaleOf } from "../../styles/visualScale.js";
4
5
  var COMPOSER_POSITION = "composer";
5
6
  var VERTICAL_SCROLLBAR_CLEARANCE = 16;
6
7
  function estimatedCardHeight(comment) {
@@ -90,10 +91,7 @@ function useCommentRailLayout({
90
91
  const reserveCommentWidth = () => {
91
92
  if (!pageLayer) return;
92
93
  const drawnWidth = rail.getBoundingClientRect().width || rail.offsetWidth;
93
- const computedZoom = Number.parseFloat(
94
- frameWindow?.getComputedStyle(pageLayer).zoom ?? ""
95
- );
96
- const scale = Number.isFinite(computedZoom) && computedZoom > 0 ? computedZoom : 1;
94
+ const scale = visualScaleOf(pageLayer);
97
95
  pageLayer.style.paddingRight = `${(drawnWidth + 16 + VERTICAL_SCROLLBAR_CLEARANCE) / scale}px`;
98
96
  };
99
97
  const placeBesidePaper = () => {
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The controls that put a note in, which every menu over the text draws its rows from.
3
+ *
4
+ * A right click is answered by one of two menus - the text menu, and the table menu wherever the
5
+ * click lands in a cell with nothing selected (`editor/plugins/textContextMenu`) - and a note goes
6
+ * into a table cell as readily as into a paragraph, so each row is written down once here. The
7
+ * command asked without a dispatch is what each menu draws a dead row from, and whether the rows
8
+ * are there at all is the surface's own declaration (`editor/stories/storyView`).
9
+ */
10
+ import type { LucideIcon } from "lucide-react";
11
+ import type { Command } from "prosemirror-state";
12
+ import type { SurfaceCapabilities } from "../editor/stories/storyView";
13
+ export interface NoteItem {
14
+ readonly label: string;
15
+ readonly icon: LucideIcon;
16
+ /** The shortcut for the same action, for a menu that labels its rows with theirs */
17
+ readonly hint: string;
18
+ readonly command: Command;
19
+ }
20
+ /** The rows a menu draws, and none for a surface that takes no note */
21
+ export declare function noteItems(takes: SurfaceCapabilities): readonly NoteItem[];
@@ -0,0 +1,30 @@
1
+ // src/ui/noteItems.ts
2
+ import { ListEnd, Superscript } from "lucide-react";
3
+ import { insertEndnote, insertFootnote } from "../editor/commands/noteCommands.js";
4
+ import { NOTE_KEYS } from "../editor/plugins/keymap.js";
5
+ import { modifierLabels } from "./shortcutLabels.js";
6
+ function keyLetter(binding) {
7
+ return (binding.split("-").at(-1) ?? "").toUpperCase();
8
+ }
9
+ var NO_ITEMS = [];
10
+ function noteItems(takes) {
11
+ if (!takes.has("note")) return NO_ITEMS;
12
+ const { mod, alt } = modifierLabels();
13
+ return [
14
+ {
15
+ label: "Insert footnote",
16
+ icon: Superscript,
17
+ hint: `${mod}${alt}${keyLetter(NOTE_KEYS.footnote)}`,
18
+ command: insertFootnote
19
+ },
20
+ {
21
+ label: "Insert endnote",
22
+ icon: ListEnd,
23
+ hint: `${mod}${alt}${keyLetter(NOTE_KEYS.endnote)}`,
24
+ command: insertEndnote
25
+ }
26
+ ];
27
+ }
28
+ export {
29
+ noteItems
30
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The notes drawn over the paper, in the room the page layout kept for them.
3
+ *
4
+ * One component draws both kinds, since they differ only in where a page's room comes from, what
5
+ * the area is called, its class, whether the rule above the notes is drawn on that page, and what
6
+ * is drawn in it; the layer over the sheet, the row that takes its room before it is measured, the
7
+ * one editing view and the press that opens a note hold for both.
8
+ *
9
+ * Each area is given the height the layout kept, so one asking for more room than the page had
10
+ * left scrolls inside it.
11
+ */
12
+ import type { CSSProperties, ReactElement } from "react";
13
+ import type { NoteRow } from "../../editor/commands/noteQueries";
14
+ import type { PageOverlay } from "../../page/usePageLayout";
15
+ import { type StoryKey } from "../../schema/stories";
16
+ import type { FontFallbacks } from "../../styles/fontStack";
17
+ import { type NoteHeightReport, type RowEditing } from "./StoryRow";
18
+ /** The room one page keeps for notes, as the notes drawn in it are placed and named */
19
+ export interface NoteArea {
20
+ readonly page: number;
21
+ /** What assistive technology calls the area */
22
+ readonly name: string;
23
+ /** What the page keeps there, by story key, in the order the layout laid them */
24
+ readonly ids: readonly string[];
25
+ readonly top: number;
26
+ readonly left: number;
27
+ readonly width: number;
28
+ readonly height: number;
29
+ /** Whether the notes ask for more room than the page had left, so the area scrolls them */
30
+ readonly scrolls: boolean;
31
+ /** Whether the rule the notes are set under is drawn above this page's */
32
+ readonly rule: boolean;
33
+ }
34
+ /** The rooms each page keeps for its footnotes, which every page holding one is given */
35
+ export declare function footnoteAreasOf(overlay: PageOverlay): readonly NoteArea[];
36
+ /**
37
+ * The rooms the endnotes take after the last paragraph of the document.
38
+ *
39
+ * They are one run of notes rather than a page's own, so the rule is drawn once, above the page
40
+ * they begin on.
41
+ */
42
+ export declare function endnoteAreasOf(overlay: PageOverlay): readonly NoteArea[];
43
+ export interface NoteAreasProps {
44
+ /** Where the pages stand, which is the box the areas are placed inside */
45
+ readonly overlay: PageOverlay;
46
+ readonly areas: readonly NoteArea[];
47
+ /** The class each area carries, which is what tells the two kinds apart on the screen */
48
+ readonly areaClassName: string;
49
+ /** The notes these areas draw, by story key, which is what the layout laid out by id */
50
+ readonly rows: ReadonlyMap<string, NoteRow>;
51
+ readonly heights: ReadonlyMap<StoryKey, number>;
52
+ readonly onHeight: NoteHeightReport;
53
+ readonly fontFallbacks: FontFallbacks;
54
+ /** How the document sets its text, which the sheet's own box does not pass down to these */
55
+ readonly textStyle: CSSProperties;
56
+ readonly zoom: number;
57
+ /** The note the caret is in, which is the one row an editing view stands over */
58
+ readonly open?: StoryKey | null;
59
+ /** What mounts that view. Null while no note is open */
60
+ readonly editing?: RowEditing | null;
61
+ /** Whether the open note stands where nothing may be edited */
62
+ readonly readOnly?: boolean;
63
+ /** A value that differs every time the main document changes, which the open row syncs on */
64
+ readonly revision?: unknown;
65
+ /** Called for a press on a note no view stands over yet */
66
+ readonly onOpen?: (key: StoryKey, at: {
67
+ left: number;
68
+ top: number;
69
+ }) => void;
70
+ /** Called for a press on the number a note is drawn by, which is the way back to its reference */
71
+ readonly onReturn?: (key: StoryKey) => void;
72
+ }
73
+ export declare function NoteAreas({ overlay, areas, areaClassName, rows, heights, onHeight, fontFallbacks, textStyle, zoom, open, editing, readOnly, revision, onOpen, onReturn, }: NoteAreasProps): ReactElement;
@@ -0,0 +1,131 @@
1
+ // src/ui/notes/NoteAreas.tsx
2
+ import { FOOTNOTE_BAND } from "../../page/demands/footnoteDemands.js";
3
+ import { noteName } from "../../schema/stories.js";
4
+ import { editorClassNames } from "../../styles/classNames.js";
5
+ import { NOTE_SEPARATOR_HEIGHT, noteSeparatorWidth } from "./noteSeparator.js";
6
+ import { StoryRow } from "./StoryRow.js";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ function footnoteAreasOf(overlay) {
9
+ return overlay.pages.flatMap(
10
+ (page) => page.reserved.filter((room) => room.band === FOOTNOTE_BAND).map((room) => ({
11
+ page: page.page,
12
+ name: `Footnotes on page ${page.page}`,
13
+ ids: room.ids,
14
+ top: room.top,
15
+ left: page.left,
16
+ width: page.width,
17
+ height: room.height,
18
+ scrolls: room.clipped,
19
+ // A page's footnotes are set under a rule of their own, wherever they stand
20
+ rule: true
21
+ }))
22
+ );
23
+ }
24
+ function endnoteAreasOf(overlay) {
25
+ const opening = overlay.pages.find((page) => page.trailing !== null)?.page;
26
+ return overlay.pages.flatMap((page) => {
27
+ const room = page.trailing;
28
+ return room === null ? [] : [
29
+ {
30
+ page: page.page,
31
+ name: `Endnotes on page ${page.page}`,
32
+ ids: room.ids,
33
+ top: room.top,
34
+ left: page.left,
35
+ width: page.width,
36
+ height: room.height,
37
+ scrolls: room.clipped,
38
+ rule: page.page === opening
39
+ }
40
+ ];
41
+ });
42
+ }
43
+ function NoteAreas({
44
+ overlay,
45
+ areas,
46
+ areaClassName,
47
+ rows,
48
+ heights,
49
+ onHeight,
50
+ fontFallbacks,
51
+ textStyle,
52
+ zoom,
53
+ open = null,
54
+ editing = null,
55
+ readOnly = false,
56
+ revision,
57
+ onOpen,
58
+ onReturn
59
+ }) {
60
+ return /* @__PURE__ */ jsx(
61
+ "div",
62
+ {
63
+ className: editorClassNames.noteAreas,
64
+ style: {
65
+ left: `${overlay.left}px`,
66
+ top: `${overlay.top}px`,
67
+ width: `${overlay.width}px`,
68
+ height: `${overlay.sheetHeight}px`
69
+ },
70
+ children: areas.map((area) => /* @__PURE__ */ jsxs(
71
+ "section",
72
+ {
73
+ className: areaClassName,
74
+ "aria-label": area.name,
75
+ style: {
76
+ ...textStyle,
77
+ top: `${area.top}px`,
78
+ left: `${area.left}px`,
79
+ width: `${area.width}px`,
80
+ height: `${area.height}px`,
81
+ overflowY: area.scrolls ? "auto" : "hidden"
82
+ },
83
+ children: [
84
+ area.rule && /* @__PURE__ */ jsx(
85
+ "div",
86
+ {
87
+ className: editorClassNames.noteSeparator,
88
+ style: {
89
+ height: `${NOTE_SEPARATOR_HEIGHT}px`,
90
+ width: `${noteSeparatorWidth(area.width)}px`
91
+ }
92
+ }
93
+ ),
94
+ area.ids.flatMap((id) => {
95
+ const row = rows.get(id);
96
+ if (row === void 0) return [];
97
+ const entered = row.key === open;
98
+ return [
99
+ /* @__PURE__ */ jsx(
100
+ StoryRow,
101
+ {
102
+ noteKey: row.key,
103
+ story: row.story,
104
+ label: row.label,
105
+ name: noteName(row.kind, row.label),
106
+ fontFallbacks,
107
+ hidden: !entered && !heights.has(row.key),
108
+ onHeight,
109
+ zoom,
110
+ editing: entered ? editing : null,
111
+ readOnly,
112
+ revision: entered ? revision : void 0,
113
+ onPress: onOpen === void 0 ? void 0 : (at) => onOpen(row.key, at),
114
+ onReturn: onReturn === void 0 ? void 0 : () => onReturn(row.key)
115
+ },
116
+ row.key
117
+ )
118
+ ];
119
+ })
120
+ ]
121
+ },
122
+ area.page
123
+ ))
124
+ }
125
+ );
126
+ }
127
+ export {
128
+ NoteAreas,
129
+ endnoteAreasOf,
130
+ footnoteAreasOf
131
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The notes listed under the sheet while no page has kept room for them: the footnotes ahead of
3
+ * the endnotes, the order they are called in.
4
+ *
5
+ * A note stands where its own page keeps room for it (`./NoteAreas`), which needs a page that has
6
+ * been laid out: the guides may be off, or the first frame may not have been measured yet. Until
7
+ * then they are listed here, where none of them is edited in place.
8
+ *
9
+ * Each note is drawn from its story (`./StoryRow`) across the width the body is set at, so a note
10
+ * wraps here where it would on the page.
11
+ */
12
+ import type { CSSProperties, ReactElement } from "react";
13
+ import type { NoteRow } from "../../editor/commands/noteQueries";
14
+ import type { PagePixels } from "../../page/pageLayout";
15
+ import type { FontFallbacks } from "../../styles/fontStack";
16
+ export interface NoteListProps {
17
+ readonly footnotes: readonly NoteRow[];
18
+ readonly endnotes: readonly NoteRow[];
19
+ /** The paper the first section is drawn on, which the list is as wide as */
20
+ readonly page: PagePixels;
21
+ readonly fontFallbacks: FontFallbacks;
22
+ /** How the document sets its text, which the sheet's own box does not pass down to this */
23
+ readonly textStyle: CSSProperties;
24
+ }
25
+ export declare function NoteList({ footnotes, endnotes, page, fontFallbacks, textStyle, }: NoteListProps): ReactElement | null;
@@ -0,0 +1,58 @@
1
+ // src/ui/notes/NoteList.tsx
2
+ import { noteName } from "../../schema/stories.js";
3
+ import { editorClassNames } from "../../styles/classNames.js";
4
+ import { NOTE_SEPARATOR_HEIGHT, noteSeparatorWidth } from "./noteSeparator.js";
5
+ import { StoryRow } from "./StoryRow.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function listName(footnotes, endnotes) {
8
+ if (footnotes && endnotes) return "Footnotes and endnotes";
9
+ return footnotes ? "Footnotes" : "Endnotes";
10
+ }
11
+ function NoteList({
12
+ footnotes,
13
+ endnotes,
14
+ page,
15
+ fontFallbacks,
16
+ textStyle
17
+ }) {
18
+ if (footnotes.length === 0 && endnotes.length === 0) return null;
19
+ return /* @__PURE__ */ jsxs(
20
+ "section",
21
+ {
22
+ className: editorClassNames.noteList,
23
+ "aria-label": listName(footnotes.length > 0, endnotes.length > 0),
24
+ style: {
25
+ ...textStyle,
26
+ width: `${page.pageWidth}px`,
27
+ paddingLeft: `${page.marginLeft}px`,
28
+ paddingRight: `${page.marginRight}px`
29
+ },
30
+ children: [
31
+ /* @__PURE__ */ jsx(
32
+ "div",
33
+ {
34
+ className: editorClassNames.noteSeparator,
35
+ style: {
36
+ height: `${NOTE_SEPARATOR_HEIGHT}px`,
37
+ width: `${noteSeparatorWidth(page.bodyWidth)}px`
38
+ }
39
+ }
40
+ ),
41
+ [...footnotes, ...endnotes].map((row) => /* @__PURE__ */ jsx(
42
+ StoryRow,
43
+ {
44
+ noteKey: row.key,
45
+ story: row.story,
46
+ label: row.label,
47
+ name: noteName(row.kind, row.label),
48
+ fontFallbacks
49
+ },
50
+ row.key
51
+ ))
52
+ ]
53
+ }
54
+ );
55
+ }
56
+ export {
57
+ NoteList
58
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * One note drawn from its story: as markup no editor view stands behind (`editor/stories`), and as
3
+ * the one editing view while the caret is in it.
4
+ *
5
+ * A row is mounted again whenever its note moves to another page, and a story no edit touched is
6
+ * the same node afterwards (`schema/stories`), so the markup is kept against the story node rather
7
+ * than against the row: only a note whose story or label changed is drawn again.
8
+ *
9
+ * A row carrying the editing view is mounted again by such a move too, so where the caret stood is
10
+ * handed over in `editing.caret` and the view opens there. The page is not laid out again while
11
+ * that view is composing (`DocxEditor`), so a move never reaches an open composition.
12
+ */
13
+ import type { Node as PMNode } from "prosemirror-model";
14
+ import type { EditorState } from "prosemirror-state";
15
+ import { type ReactElement } from "react";
16
+ import type { EditorDocument } from "../../editor/editorDocument";
17
+ import { type StoryMarkupOptions } from "../../editor/stories/storyMarkup";
18
+ import { type StoryCaret, type StoryExtensions, type StoryHost } from "../../editor/stories/storyView";
19
+ import type { StoryKey } from "../../schema/stories";
20
+ import type { FontFallbacks } from "../../styles/fontStack";
21
+ export type DrawStory = (story: PMNode, options: StoryMarkupOptions) => DocumentFragment;
22
+ /** Hands on the height a row stands at on the paper, the zoom taken back out */
23
+ export type NoteHeightReport = (key: StoryKey, height: number) => void;
24
+ /**
25
+ * What mounts the editing view over this row, which only the row the caret is in is given.
26
+ *
27
+ * It is held apart from the row's own props because a row is drawn again on every change to the
28
+ * document while the view over it has to stand: every field here is the same object for as long as
29
+ * one note is open, and what changes per edit reaches the view through `revision`.
30
+ */
31
+ export interface RowEditing {
32
+ readonly host: StoryHost;
33
+ /** The values the story is edited against (`editor/editorDocument`) */
34
+ readonly document: EditorDocument;
35
+ readonly extensions: StoryExtensions;
36
+ /** Where the caret opens, and where it stood when the row was last taken down */
37
+ readonly caret: {
38
+ take(): StoryCaret | null;
39
+ keep(caret: StoryCaret): void;
40
+ };
41
+ readonly onStateChange: (state: EditorState) => void;
42
+ }
43
+ export interface StoryRowProps {
44
+ readonly noteKey: StoryKey;
45
+ readonly story: PMNode;
46
+ readonly label: string;
47
+ readonly fontFallbacks: FontFallbacks;
48
+ /** What assistive technology calls the row */
49
+ readonly name?: string;
50
+ /** A hidden row still takes its room, which is what lets it be measured before it is placed */
51
+ readonly hidden?: boolean;
52
+ /** Called whenever the row's height changes. Left out, the row is not measured */
53
+ readonly onHeight?: NoteHeightReport;
54
+ /** The visual scale the row is drawn at, which a height read off the screen is divided by */
55
+ readonly zoom?: number;
56
+ /** What draws the story; `storyMarkup` unless a test counts the drawings */
57
+ readonly draw?: DrawStory;
58
+ /** Mounted over this row while the caret is in it, and null while the row is markup */
59
+ readonly editing?: RowEditing | null;
60
+ /** A value that differs every time the main document changes, which is when the view is synced */
61
+ readonly revision?: unknown;
62
+ /** Whether the note stands where nothing may be edited, which a reader is told rather than shown */
63
+ readonly readOnly?: boolean;
64
+ /** Called for a press on a row no view stands over yet, with where on the screen it landed */
65
+ readonly onPress?: (at: {
66
+ left: number;
67
+ top: number;
68
+ }) => void;
69
+ /**
70
+ * Called for a press on the number the note is drawn by, which is the way back to the text that
71
+ * calls it rather than a place to put the caret
72
+ */
73
+ readonly onReturn?: () => void;
74
+ }
75
+ export declare const StoryRow: import("react").MemoExoticComponent<({ noteKey, story, label, fontFallbacks, name, hidden, onHeight, zoom, draw, editing, revision, readOnly, onPress, onReturn, }: StoryRowProps) => ReactElement>;