@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.
- package/CHANGELOG.md +40 -0
- package/dist/DocxEditor.js +84 -26
- package/dist/core.d.ts +4 -3
- package/dist/docx/exportDocx.d.ts +3 -2
- package/dist/docx/exportDocx.js +6 -10
- package/dist/docx/headersFooters.js +35 -23
- package/dist/docx/identities.d.ts +30 -0
- package/dist/docx/identities.js +43 -10
- package/dist/docx/importDocx.js +25 -26
- package/dist/docx/importParagraph.d.ts +1 -4
- package/dist/docx/importParagraph.js +4 -18
- package/dist/docx/invariants.d.ts +13 -10
- package/dist/docx/invariants.js +300 -116
- package/dist/docx/newLists.js +12 -3
- package/dist/docx/notes/newNote.d.ts +39 -0
- package/dist/docx/notes/newNote.js +122 -0
- package/dist/docx/notes/numbering.d.ts +27 -0
- package/dist/docx/notes/numbering.js +113 -0
- package/dist/docx/notes/reading.d.ts +58 -0
- package/dist/docx/notes/reading.js +98 -0
- package/dist/docx/notes/references.d.ts +33 -0
- package/dist/docx/notes/references.js +37 -0
- package/dist/docx/notes/writing.d.ts +19 -0
- package/dist/docx/notes/writing.js +95 -0
- package/dist/docx/partPlan.d.ts +3 -0
- package/dist/docx/partPlanners.d.ts +13 -0
- package/dist/docx/partPlanners.js +48 -0
- package/dist/docx/sections.d.ts +5 -0
- package/dist/docx/sections.js +7 -2
- package/dist/docx/serializeParagraph.js +7 -2
- package/dist/docx/serializePreserved.d.ts +5 -1
- package/dist/docx/serializePreserved.js +24 -7
- package/dist/docx/session.d.ts +5 -0
- package/dist/docx/session.js +6 -0
- package/dist/docx/storyParts.d.ts +133 -0
- package/dist/docx/storyParts.js +314 -0
- package/dist/editor/clipboard/htmlReader.d.ts +24 -4
- package/dist/editor/clipboard/htmlReader.js +39 -4
- package/dist/editor/clipboard/internalChannel.d.ts +30 -3
- package/dist/editor/clipboard/internalChannel.js +43 -3
- package/dist/editor/clipboard/normalizers.d.ts +35 -8
- package/dist/editor/clipboard/normalizers.js +60 -11
- package/dist/editor/clipboard/plugin.js +42 -22
- package/dist/editor/commands/exportQueries.d.ts +4 -3
- package/dist/editor/commands/index.d.ts +2 -1
- package/dist/editor/commands/index.js +18 -0
- package/dist/editor/commands/noteCommands.d.ts +47 -0
- package/dist/editor/commands/noteCommands.js +95 -0
- package/dist/editor/commands/noteQueries.d.ts +22 -3
- package/dist/editor/commands/noteQueries.js +21 -13
- package/dist/editor/createEditor.js +21 -3
- package/dist/editor/documentStyles.d.ts +3 -0
- package/dist/editor/documentStyles.js +2 -1
- package/dist/editor/editorDocument.d.ts +24 -0
- package/dist/editor/editorDocument.js +34 -4
- package/dist/editor/notes/noteSurface.d.ts +53 -0
- package/dist/editor/notes/noteSurface.js +246 -0
- package/dist/editor/plugins/compositionSelection.d.ts +20 -0
- package/dist/editor/plugins/compositionSelection.js +19 -0
- package/dist/editor/plugins/keymap.d.ts +14 -0
- package/dist/editor/plugins/keymap.js +13 -1
- package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
- package/dist/editor/plugins/noteLifecycle.js +117 -0
- package/dist/editor/plugins/noteNavigation.d.ts +45 -0
- package/dist/editor/plugins/noteNavigation.js +86 -0
- package/dist/editor/plugins/noteNumbering.d.ts +13 -0
- package/dist/editor/plugins/noteNumbering.js +58 -0
- package/dist/editor/plugins/rowResize.js +2 -7
- package/dist/editor/plugins/tabLayout.js +2 -7
- package/dist/editor/stories/storyMarkup.d.ts +24 -0
- package/dist/editor/stories/storyMarkup.js +35 -0
- package/dist/editor/stories/storyState.d.ts +34 -0
- package/dist/editor/stories/storyState.js +74 -0
- package/dist/editor/stories/storyView.d.ts +115 -0
- package/dist/editor/stories/storyView.js +144 -0
- package/dist/index.d.ts +2 -2
- package/dist/numbering/spellers.d.ts +1 -1
- package/dist/numbering/spellers.js +6 -1
- package/dist/ooxml/errors.d.ts +135 -2
- package/dist/ooxml/errors.js +6 -0
- package/dist/page/blockKinds.d.ts +23 -4
- package/dist/page/demands/footnoteDemands.d.ts +18 -0
- package/dist/page/demands/footnoteDemands.js +51 -0
- package/dist/page/demands/index.d.ts +43 -0
- package/dist/page/demands/index.js +8 -0
- package/dist/page/kinds/paragraphKind.js +7 -1
- package/dist/page/kinds/tableKind.js +9 -1
- package/dist/page/measureBlocks.d.ts +2 -0
- package/dist/page/measureBlocks.js +31 -10
- package/dist/page/pageDecorations.d.ts +9 -2
- package/dist/page/pageDecorations.js +18 -5
- package/dist/page/pageLayout.d.ts +77 -1
- package/dist/page/pageLayout.js +186 -9
- package/dist/page/usePageLayout.d.ts +57 -2
- package/dist/page/usePageLayout.js +50 -5
- package/dist/schema/attrRoles.js +3 -2
- package/dist/schema/docxSchema.js +5 -2
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +1 -2
- package/dist/schema/preservedGuards.d.ts +4 -6
- package/dist/schema/preservedGuards.js +0 -9
- package/dist/schema/stories.d.ts +42 -2
- package/dist/schema/stories.js +28 -5
- package/dist/styles/classNames.d.ts +21 -6
- package/dist/styles/classNames.js +21 -6
- package/dist/styles/inlineStyle.d.ts +7 -0
- package/dist/styles/inlineStyle.js +11 -5
- package/dist/styles/visualScale.d.ts +19 -0
- package/dist/styles/visualScale.js +26 -0
- package/dist/styles.css +89 -32
- package/dist/ui/InsertImageButton.d.ts +3 -1
- package/dist/ui/InsertImageButton.js +3 -2
- package/dist/ui/TableMenu.d.ts +5 -2
- package/dist/ui/TableMenu.js +18 -1
- package/dist/ui/TextMenu.d.ts +5 -2
- package/dist/ui/TextMenu.js +21 -6
- package/dist/ui/Toolbar.d.ts +17 -2
- package/dist/ui/Toolbar.js +23 -10
- package/dist/ui/comments/useCommentRailLayout.js +2 -4
- package/dist/ui/noteItems.d.ts +21 -0
- package/dist/ui/noteItems.js +30 -0
- package/dist/ui/notes/NoteAreas.d.ts +73 -0
- package/dist/ui/notes/NoteAreas.js +131 -0
- package/dist/ui/notes/NoteList.d.ts +25 -0
- package/dist/ui/notes/NoteList.js +58 -0
- package/dist/ui/notes/StoryRow.d.ts +75 -0
- package/dist/ui/notes/StoryRow.js +122 -0
- package/dist/ui/notes/noteBands.d.ts +100 -0
- package/dist/ui/notes/noteBands.js +142 -0
- package/dist/ui/notes/noteHeights.d.ts +10 -0
- package/dist/ui/notes/noteHeights.js +24 -0
- package/dist/ui/notes/noteSeparator.d.ts +12 -0
- package/dist/ui/notes/noteSeparator.js +9 -0
- package/dist/ui/notes/useStorySurface.d.ts +68 -0
- package/dist/ui/notes/useStorySurface.js +95 -0
- package/dist/ui/runCommand.d.ts +8 -1
- package/dist/ui/runCommand.js +2 -2
- package/dist/ui/shortcutLabels.d.ts +16 -0
- package/dist/ui/shortcutLabels.js +9 -0
- package/dist/ui/usePageRoom.d.ts +13 -0
- package/dist/ui/usePageRoom.js +31 -0
- package/package.json +1 -1
- package/dist/docx/notes.d.ts +0 -27
- package/dist/docx/notes.js +0 -55
- package/dist/ui/NotesPanel.d.ts +0 -6
- package/dist/ui/NotesPanel.js +0 -33
package/dist/ui/TextMenu.js
CHANGED
|
@@ -23,13 +23,14 @@ import {
|
|
|
23
23
|
} from "../editor/plugins/textContextMenu.js";
|
|
24
24
|
import { editsShut } from "../schema/protectionState.js";
|
|
25
25
|
import { editorClassNames } from "../styles/classNames.js";
|
|
26
|
+
import { noteItems } from "./noteItems.js";
|
|
26
27
|
import { usePanelAtPoint } from "./panelPlacement.js";
|
|
27
28
|
import { commandRunner } from "./runCommand.js";
|
|
29
|
+
import { modifierLabels } from "./shortcutLabels.js";
|
|
28
30
|
import { ICON_SIZE } from "./ToolbarButton.js";
|
|
29
31
|
import { useDismiss } from "./useDismiss.js";
|
|
30
32
|
import { useMenuKeyboard } from "./useMenuKeyboard.js";
|
|
31
33
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
32
|
-
var MOD = typeof navigator !== "undefined" && navigator.platform.includes("Mac") ? "\u2318" : "Ctrl+";
|
|
33
34
|
function clipboardCommand(view, command) {
|
|
34
35
|
view.focus();
|
|
35
36
|
document.execCommand(command);
|
|
@@ -95,6 +96,7 @@ function TextMenu({
|
|
|
95
96
|
view,
|
|
96
97
|
state,
|
|
97
98
|
anchor,
|
|
99
|
+
takes,
|
|
98
100
|
allowLocking = false
|
|
99
101
|
}) {
|
|
100
102
|
const box = useRef(null);
|
|
@@ -108,11 +110,12 @@ function TextMenu({
|
|
|
108
110
|
const keys = useMenuKeyboard({ menu: box, onClose: close });
|
|
109
111
|
const selected = !state.selection.empty;
|
|
110
112
|
const shut = selectionTouchesLocked(state);
|
|
113
|
+
const { mod } = modifierLabels();
|
|
111
114
|
const bodyOpen = !editsShut(state);
|
|
112
115
|
const copy = {
|
|
113
116
|
label: "Copy",
|
|
114
117
|
icon: Copy,
|
|
115
|
-
hint: `${
|
|
118
|
+
hint: `${mod}C`,
|
|
116
119
|
enabled: selected,
|
|
117
120
|
run: () => clipboardCommand(view, "copy")
|
|
118
121
|
};
|
|
@@ -121,7 +124,7 @@ function TextMenu({
|
|
|
121
124
|
{
|
|
122
125
|
label: "Cut",
|
|
123
126
|
icon: Scissors,
|
|
124
|
-
hint: `${
|
|
127
|
+
hint: `${mod}X`,
|
|
125
128
|
enabled: selected && !shut,
|
|
126
129
|
run: () => clipboardCommand(view, "cut")
|
|
127
130
|
},
|
|
@@ -129,7 +132,7 @@ function TextMenu({
|
|
|
129
132
|
{
|
|
130
133
|
label: "Paste",
|
|
131
134
|
icon: ClipboardPaste,
|
|
132
|
-
hint: `${
|
|
135
|
+
hint: `${mod}V`,
|
|
133
136
|
enabled: !shut,
|
|
134
137
|
run: () => {
|
|
135
138
|
void pasteFromClipboard(view);
|
|
@@ -143,14 +146,26 @@ function TextMenu({
|
|
|
143
146
|
}
|
|
144
147
|
] : [copy]
|
|
145
148
|
];
|
|
146
|
-
|
|
149
|
+
const anchored = [
|
|
147
150
|
{
|
|
148
151
|
label: "Add comment",
|
|
149
152
|
icon: MessageSquarePlus,
|
|
150
153
|
enabled: canAddComment(state),
|
|
151
154
|
run: () => run(openCommentComposer)
|
|
152
155
|
}
|
|
153
|
-
]
|
|
156
|
+
];
|
|
157
|
+
if (bodyOpen) {
|
|
158
|
+
for (const note of noteItems(takes)) {
|
|
159
|
+
anchored.push({
|
|
160
|
+
label: note.label,
|
|
161
|
+
icon: note.icon,
|
|
162
|
+
hint: note.hint,
|
|
163
|
+
enabled: note.command(state),
|
|
164
|
+
run: () => run(note.command)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
groups.push(anchored);
|
|
154
169
|
if (allowLocking && bodyOpen) {
|
|
155
170
|
groups.push([lockItem(selectionLock(state), run)]);
|
|
156
171
|
}
|
package/dist/ui/Toolbar.d.ts
CHANGED
|
@@ -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
|
-
|
|
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({
|
|
34
|
+
export declare function Toolbar({ main, active, fontFallbacks, presets, onToggleComments, commentsOpen, zoom, onZoomChange, }: ToolbarProps): ReactElement;
|
package/dist/ui/Toolbar.js
CHANGED
|
@@ -272,7 +272,11 @@ function ParagraphLayoutGroup({
|
|
|
272
272
|
)
|
|
273
273
|
] });
|
|
274
274
|
}
|
|
275
|
-
function ListGroup({
|
|
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
|
-
|
|
303
|
-
|
|
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(
|
|
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
|
|
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>;
|