@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.
- package/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +8 -2
- package/README.md +1 -1
- package/dist/DocxEditor.js +87 -29
- package/dist/docx/exportDocx.d.ts +2 -1
- package/dist/docx/exportDocx.js +3 -9
- package/dist/docx/headersFooters.js +35 -23
- package/dist/docx/identities.d.ts +28 -0
- package/dist/docx/identities.js +42 -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 +1 -1
- package/dist/docx/invariants.js +45 -4
- 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 +120 -0
- package/dist/docx/notes/reading.d.ts +58 -0
- package/dist/docx/notes/reading.js +98 -0
- package/dist/docx/notes/writing.d.ts +19 -0
- package/dist/docx/notes/writing.js +96 -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/session.d.ts +5 -0
- package/dist/docx/session.js +6 -0
- package/dist/docx/storyParts.d.ts +112 -0
- package/dist/docx/storyParts.js +289 -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/comments/editing.d.ts +4 -1
- package/dist/editor/commands/comments/editing.js +1 -3
- package/dist/editor/commands/comments/model.d.ts +7 -0
- package/dist/editor/commands/index.d.ts +1 -0
- 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 +25 -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/commentDecorations.js +5 -4
- package/dist/editor/plugins/commentRestoration.d.ts +20 -0
- package/dist/editor/plugins/commentRestoration.js +168 -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/numbering/spellers.d.ts +1 -1
- package/dist/numbering/spellers.js +6 -1
- 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 +34 -2
- package/dist/schema/stories.js +22 -5
- package/dist/styles/classNames.d.ts +23 -6
- package/dist/styles/classNames.js +23 -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 +96 -36
- package/dist/ui/CommentsPanel.d.ts +6 -1
- package/dist/ui/CommentsPanel.js +26 -8
- 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 +2 -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
|
@@ -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>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/ui/notes/StoryRow.tsx
|
|
2
|
+
import {
|
|
3
|
+
memo,
|
|
4
|
+
useEffect,
|
|
5
|
+
useLayoutEffect,
|
|
6
|
+
useRef
|
|
7
|
+
} from "react";
|
|
8
|
+
import { noteNodeSpecs } from "../../editor/notes/noteSurface.js";
|
|
9
|
+
import {
|
|
10
|
+
storyMarkup
|
|
11
|
+
} from "../../editor/stories/storyMarkup.js";
|
|
12
|
+
import {
|
|
13
|
+
caretOf,
|
|
14
|
+
createStoryView
|
|
15
|
+
} from "../../editor/stories/storyView.js";
|
|
16
|
+
import { editorClassNames } from "../../styles/classNames.js";
|
|
17
|
+
import { jsx } from "react/jsx-runtime";
|
|
18
|
+
var drawn = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
function markupOf(story, label, fontFallbacks, draw) {
|
|
20
|
+
const held = drawn.get(story);
|
|
21
|
+
if (held?.label === label && held.fontFallbacks === fontFallbacks && held.draw === draw) {
|
|
22
|
+
return held.markup;
|
|
23
|
+
}
|
|
24
|
+
const markup = draw(story, {
|
|
25
|
+
fontFallbacks,
|
|
26
|
+
nodeSpecs: noteNodeSpecs(() => label)
|
|
27
|
+
});
|
|
28
|
+
drawn.set(story, { label, fontFallbacks, draw, markup });
|
|
29
|
+
return markup;
|
|
30
|
+
}
|
|
31
|
+
function pressedTheNumber(target) {
|
|
32
|
+
return target instanceof Element && target.closest(`.${editorClassNames.noteMark}`) !== null;
|
|
33
|
+
}
|
|
34
|
+
var StoryRow = memo(function StoryRow2({
|
|
35
|
+
noteKey,
|
|
36
|
+
story,
|
|
37
|
+
label,
|
|
38
|
+
fontFallbacks,
|
|
39
|
+
name,
|
|
40
|
+
hidden = false,
|
|
41
|
+
onHeight,
|
|
42
|
+
zoom = 1,
|
|
43
|
+
draw = storyMarkup,
|
|
44
|
+
editing = null,
|
|
45
|
+
revision,
|
|
46
|
+
readOnly = false,
|
|
47
|
+
onPress,
|
|
48
|
+
onReturn
|
|
49
|
+
}) {
|
|
50
|
+
const box = useRef(null);
|
|
51
|
+
const scale = useRef(zoom);
|
|
52
|
+
const live = useRef(null);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
scale.current = zoom;
|
|
55
|
+
});
|
|
56
|
+
useLayoutEffect(() => {
|
|
57
|
+
if (editing) return;
|
|
58
|
+
box.current?.replaceChildren(
|
|
59
|
+
markupOf(story, label, fontFallbacks, draw).cloneNode(true)
|
|
60
|
+
);
|
|
61
|
+
}, [editing, story, label, fontFallbacks, draw]);
|
|
62
|
+
useLayoutEffect(() => {
|
|
63
|
+
const row = box.current;
|
|
64
|
+
if (!row || !editing) return;
|
|
65
|
+
const mount = document.createElement("div");
|
|
66
|
+
row.replaceChildren(mount);
|
|
67
|
+
const opened = createStoryView({
|
|
68
|
+
mount,
|
|
69
|
+
host: editing.host,
|
|
70
|
+
key: noteKey,
|
|
71
|
+
document: editing.document,
|
|
72
|
+
fontFallbacks,
|
|
73
|
+
extensions: editing.extensions,
|
|
74
|
+
caret: editing.caret.take(),
|
|
75
|
+
onStateChange: editing.onStateChange
|
|
76
|
+
});
|
|
77
|
+
live.current = opened;
|
|
78
|
+
opened.view.focus();
|
|
79
|
+
return () => {
|
|
80
|
+
editing.caret.keep(caretOf(opened.view.state));
|
|
81
|
+
live.current = null;
|
|
82
|
+
opened.destroy();
|
|
83
|
+
};
|
|
84
|
+
}, [editing, noteKey, fontFallbacks]);
|
|
85
|
+
useLayoutEffect(() => {
|
|
86
|
+
live.current?.sync();
|
|
87
|
+
}, [revision]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const row = box.current;
|
|
90
|
+
if (!row || !onHeight || typeof ResizeObserver === "undefined") return;
|
|
91
|
+
const observer = new ResizeObserver(() => {
|
|
92
|
+
onHeight(noteKey, row.getBoundingClientRect().height / scale.current);
|
|
93
|
+
});
|
|
94
|
+
observer.observe(row);
|
|
95
|
+
return () => observer.disconnect();
|
|
96
|
+
}, [noteKey, onHeight]);
|
|
97
|
+
return /* @__PURE__ */ jsx(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
ref: box,
|
|
101
|
+
className: [
|
|
102
|
+
editorClassNames.noteRow,
|
|
103
|
+
editing ? editorClassNames.noteRowOpen : null
|
|
104
|
+
].filter(Boolean).join(" "),
|
|
105
|
+
role: name === void 0 ? void 0 : "group",
|
|
106
|
+
"aria-label": name,
|
|
107
|
+
"aria-readonly": editing && readOnly ? true : void 0,
|
|
108
|
+
style: hidden ? { visibility: "hidden" } : void 0,
|
|
109
|
+
onMouseDown: editing || !onPress && !onReturn ? void 0 : (event) => {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
if (onReturn && pressedTheNumber(event.target)) {
|
|
112
|
+
onReturn();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
onPress?.({ left: event.clientX, top: event.clientY });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
export {
|
|
121
|
+
StoryRow
|
|
122
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The footnote band: what a page keeps room for at its foot, and what is drawn in that room and
|
|
3
|
+
* after the last page.
|
|
4
|
+
*
|
|
5
|
+
* A band is two halves that have to agree. The layout is told a name, a place at the foot, the
|
|
6
|
+
* height a page adds once for the band and the height of each thing kept in it (`page/demands`),
|
|
7
|
+
* and something has to draw exactly that much. Both halves live here so a page's room and the
|
|
8
|
+
* notes drawn in it cannot drift apart, and so the component that mounts the editor asks only for
|
|
9
|
+
* the bands a document keeps and renders what it is handed.
|
|
10
|
+
*
|
|
11
|
+
* A document referring to no footnote keeps no band at all, so it is laid out through the same
|
|
12
|
+
* pass it took before a page kept room for anything.
|
|
13
|
+
*/
|
|
14
|
+
import type { EditorState } from "prosemirror-state";
|
|
15
|
+
import type { CSSProperties, ReactElement } from "react";
|
|
16
|
+
import { type NoteRow } from "../../editor/commands/noteQueries";
|
|
17
|
+
import type { DemandBand } from "../../page/demands";
|
|
18
|
+
import type { PagePixels, TrailingRows } from "../../page/pageLayout";
|
|
19
|
+
import type { PageOverlay } from "../../page/usePageLayout";
|
|
20
|
+
import type { StoryKey } from "../../schema/stories";
|
|
21
|
+
import { type FontFallbacks } from "../../styles/fontStack";
|
|
22
|
+
import type { NoteHeightReport, RowEditing } from "./StoryRow";
|
|
23
|
+
/** What a document's notes ask of the page layout, and what is needed to draw them */
|
|
24
|
+
export interface NoteBands {
|
|
25
|
+
/**
|
|
26
|
+
* The bands to lay the pages out with, by name, or undefined where the document asks for no
|
|
27
|
+
* room at all. A new value lays the pages out again, so it changes only when a height does
|
|
28
|
+
*/
|
|
29
|
+
readonly bands: ReadonlyMap<string, DemandBand> | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The endnotes as rows laid after the last block of the document, or undefined where it refers
|
|
32
|
+
* to none. A new value lays the pages out again, so it changes only when a height does
|
|
33
|
+
*/
|
|
34
|
+
readonly trailing: TrailingRows | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Every note the document refers to, by story key, in first-reference order.
|
|
37
|
+
*
|
|
38
|
+
* Which one a caret stands in is the mounting component's to hold, so this is the one half of
|
|
39
|
+
* the notes it reads; everything else about how they are drawn stays in `drawn`.
|
|
40
|
+
*/
|
|
41
|
+
readonly rows: ReadonlyMap<StoryKey, NoteRow>;
|
|
42
|
+
/** Held for `NotesAroundPage`; nothing else reads it */
|
|
43
|
+
readonly drawn: DrawnNotes;
|
|
44
|
+
}
|
|
45
|
+
/** What `NotesAroundPage` draws with, kept opaque so a caller cannot take the two halves apart */
|
|
46
|
+
interface DrawnNotes {
|
|
47
|
+
/** The footnotes, which are the notes drawn at the foot of the page their reference stands on */
|
|
48
|
+
readonly footnotes: ReadonlyMap<StoryKey, NoteRow>;
|
|
49
|
+
readonly endnotes: readonly NoteRow[];
|
|
50
|
+
readonly anyNotes: boolean;
|
|
51
|
+
readonly heights: ReadonlyMap<StoryKey, number>;
|
|
52
|
+
readonly onHeight: NoteHeightReport;
|
|
53
|
+
readonly fontFallbacks: FontFallbacks;
|
|
54
|
+
readonly textStyle: CSSProperties;
|
|
55
|
+
}
|
|
56
|
+
export interface NoteBandsOptions {
|
|
57
|
+
/** The live document's state, or null before one is opened */
|
|
58
|
+
readonly state: EditorState | null;
|
|
59
|
+
/** A value that differs for every document opened, which the measured heights are held against */
|
|
60
|
+
readonly of: unknown;
|
|
61
|
+
readonly fontFallbacks: FontFallbacks | undefined;
|
|
62
|
+
}
|
|
63
|
+
export declare function useNoteBands({ state, of, fontFallbacks, }: NoteBandsOptions): NoteBands;
|
|
64
|
+
export interface NotesAroundPageProps {
|
|
65
|
+
readonly notes: NoteBands;
|
|
66
|
+
/**
|
|
67
|
+
* Where the pages stand, or null while none are measured, which is what decides where the notes
|
|
68
|
+
* go: a page keeps room for them only once it has been laid out
|
|
69
|
+
*/
|
|
70
|
+
readonly overlay: PageOverlay | null;
|
|
71
|
+
/** The paper the first section is drawn on, which the list under the sheet is as wide as */
|
|
72
|
+
readonly page: PagePixels;
|
|
73
|
+
readonly zoom: number;
|
|
74
|
+
/** The note the caret is in, which is the one row an editing view stands over */
|
|
75
|
+
readonly open?: StoryKey | null;
|
|
76
|
+
/** What mounts that view. Null while no note is open */
|
|
77
|
+
readonly editing?: RowEditing | null;
|
|
78
|
+
/** Whether the open note stands where nothing may be edited */
|
|
79
|
+
readonly readOnly?: boolean;
|
|
80
|
+
/** A value that differs every time the main document changes, which the open row syncs on */
|
|
81
|
+
readonly revision?: unknown;
|
|
82
|
+
/** Called for a press on a note no view stands over yet */
|
|
83
|
+
readonly onOpen?: (key: StoryKey, at: {
|
|
84
|
+
left: number;
|
|
85
|
+
top: number;
|
|
86
|
+
}) => void;
|
|
87
|
+
/** Called for a press on the number a note is drawn by, which is the way back to its reference */
|
|
88
|
+
readonly onReturn?: (key: StoryKey) => void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The notes around the paper: each footnote over the room its page keeps, and the endnotes over
|
|
92
|
+
* the room kept after the last paragraph.
|
|
93
|
+
*
|
|
94
|
+
* Until a page has been measured there is no room to stand in - the guides may be off, or the
|
|
95
|
+
* first frame may not have been laid out yet - and a note drawn nowhere is a note a reader cannot
|
|
96
|
+
* read, so both kinds are listed under the sheet until there is. Only a note standing in its own
|
|
97
|
+
* room is edited in place.
|
|
98
|
+
*/
|
|
99
|
+
export declare function NotesAroundPage({ notes, overlay, page, zoom, open, editing, readOnly, revision, onOpen, onReturn, }: NotesAroundPageProps): ReactElement | null;
|
|
100
|
+
export {};
|