@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,142 @@
|
|
|
1
|
+
// src/ui/notes/noteBands.tsx
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import {
|
|
4
|
+
noteProjection
|
|
5
|
+
} from "../../editor/commands/noteQueries.js";
|
|
6
|
+
import { documentOf } from "../../editor/editorDocument.js";
|
|
7
|
+
import {
|
|
8
|
+
FOOTNOTE_BAND,
|
|
9
|
+
FOOTNOTE_BAND_ORDER
|
|
10
|
+
} from "../../page/demands/footnoteDemands.js";
|
|
11
|
+
import { editorClassNames } from "../../styles/classNames.js";
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_FONT_FALLBACKS
|
|
14
|
+
} from "../../styles/fontStack.js";
|
|
15
|
+
import { documentDefaultsVariables } from "../../styles/inlineStyle.js";
|
|
16
|
+
import { endnoteAreasOf, footnoteAreasOf, NoteAreas } from "./NoteAreas.js";
|
|
17
|
+
import { NoteList } from "./NoteList.js";
|
|
18
|
+
import { useNoteHeights } from "./noteHeights.js";
|
|
19
|
+
import { NOTE_SEPARATOR_HEIGHT } from "./noteSeparator.js";
|
|
20
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
var NO_ROWS = /* @__PURE__ */ new Map();
|
|
22
|
+
var NO_NOTE_ROWS = [];
|
|
23
|
+
function useNoteBands({
|
|
24
|
+
state,
|
|
25
|
+
of,
|
|
26
|
+
fontFallbacks
|
|
27
|
+
}) {
|
|
28
|
+
const notes = state === null ? null : noteProjection.read(state);
|
|
29
|
+
const footnotes = notes?.footnotes ?? NO_ROWS;
|
|
30
|
+
const hasFootnotes = footnotes.size > 0;
|
|
31
|
+
const [heights, onHeight] = useNoteHeights(of);
|
|
32
|
+
const bands = useMemo(
|
|
33
|
+
() => hasFootnotes ? /* @__PURE__ */ new Map([
|
|
34
|
+
[
|
|
35
|
+
FOOTNOTE_BAND,
|
|
36
|
+
{
|
|
37
|
+
order: FOOTNOTE_BAND_ORDER,
|
|
38
|
+
overhead: NOTE_SEPARATOR_HEIGHT,
|
|
39
|
+
heights
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
]) : void 0,
|
|
43
|
+
[hasFootnotes, heights]
|
|
44
|
+
);
|
|
45
|
+
const endnotes = notes?.endnotes ?? NO_NOTE_ROWS;
|
|
46
|
+
const trailing = useMemo(
|
|
47
|
+
() => endnotes.length === 0 ? void 0 : {
|
|
48
|
+
overhead: NOTE_SEPARATOR_HEIGHT,
|
|
49
|
+
rows: endnotes.map((row) => ({
|
|
50
|
+
id: row.key,
|
|
51
|
+
height: heights.get(row.key) ?? 0
|
|
52
|
+
}))
|
|
53
|
+
},
|
|
54
|
+
[endnotes, heights]
|
|
55
|
+
);
|
|
56
|
+
const snapshot = state === null ? null : documentOf(state);
|
|
57
|
+
const noteFontFallbacks = fontFallbacks ?? DEFAULT_FONT_FALLBACKS;
|
|
58
|
+
const textStyle = useMemo(
|
|
59
|
+
() => snapshot === null ? {} : {
|
|
60
|
+
...documentDefaultsVariables(snapshot.defaults, noteFontFallbacks),
|
|
61
|
+
tabSize: `${snapshot.defaultTabStopPt}pt`
|
|
62
|
+
},
|
|
63
|
+
[snapshot, noteFontFallbacks]
|
|
64
|
+
);
|
|
65
|
+
return {
|
|
66
|
+
bands,
|
|
67
|
+
trailing,
|
|
68
|
+
rows: notes?.rows ?? NO_ROWS,
|
|
69
|
+
drawn: {
|
|
70
|
+
footnotes,
|
|
71
|
+
endnotes,
|
|
72
|
+
anyNotes: notes !== null,
|
|
73
|
+
heights,
|
|
74
|
+
onHeight,
|
|
75
|
+
fontFallbacks: noteFontFallbacks,
|
|
76
|
+
textStyle
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function NotesAroundPage({
|
|
81
|
+
notes,
|
|
82
|
+
overlay,
|
|
83
|
+
page,
|
|
84
|
+
zoom,
|
|
85
|
+
open,
|
|
86
|
+
editing,
|
|
87
|
+
readOnly,
|
|
88
|
+
revision,
|
|
89
|
+
onOpen,
|
|
90
|
+
onReturn
|
|
91
|
+
}) {
|
|
92
|
+
const { footnotes, endnotes, anyNotes, heights, onHeight } = notes.drawn;
|
|
93
|
+
const drawing = {
|
|
94
|
+
heights,
|
|
95
|
+
onHeight,
|
|
96
|
+
fontFallbacks: notes.drawn.fontFallbacks,
|
|
97
|
+
textStyle: notes.drawn.textStyle,
|
|
98
|
+
zoom,
|
|
99
|
+
open,
|
|
100
|
+
editing,
|
|
101
|
+
readOnly,
|
|
102
|
+
revision,
|
|
103
|
+
onOpen,
|
|
104
|
+
onReturn
|
|
105
|
+
};
|
|
106
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
107
|
+
overlay !== null && footnotes.size > 0 && /* @__PURE__ */ jsx(
|
|
108
|
+
NoteAreas,
|
|
109
|
+
{
|
|
110
|
+
overlay,
|
|
111
|
+
areas: footnoteAreasOf(overlay),
|
|
112
|
+
areaClassName: editorClassNames.footnoteArea,
|
|
113
|
+
rows: footnotes,
|
|
114
|
+
...drawing
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
overlay !== null && endnotes.length > 0 && /* @__PURE__ */ jsx(
|
|
118
|
+
NoteAreas,
|
|
119
|
+
{
|
|
120
|
+
overlay,
|
|
121
|
+
areas: endnoteAreasOf(overlay),
|
|
122
|
+
areaClassName: editorClassNames.endnoteArea,
|
|
123
|
+
rows: notes.rows,
|
|
124
|
+
...drawing
|
|
125
|
+
}
|
|
126
|
+
),
|
|
127
|
+
anyNotes && overlay === null && /* @__PURE__ */ jsx(
|
|
128
|
+
NoteList,
|
|
129
|
+
{
|
|
130
|
+
footnotes: [...footnotes.values()],
|
|
131
|
+
endnotes,
|
|
132
|
+
page,
|
|
133
|
+
fontFallbacks: notes.drawn.fontFallbacks,
|
|
134
|
+
textStyle: notes.drawn.textStyle
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
] });
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
NotesAroundPage,
|
|
141
|
+
useNoteBands
|
|
142
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StoryKey } from "../../schema/stories";
|
|
2
|
+
import type { NoteHeightReport } from "./StoryRow";
|
|
3
|
+
/**
|
|
4
|
+
* The height each note stands at, as its row last reported it, for the document `of` names.
|
|
5
|
+
*
|
|
6
|
+
* A height is a fact about what the browser drew, so it can only arrive after a row is drawn. The
|
|
7
|
+
* map handed back is the same object until a height really changes, which is what lets the page
|
|
8
|
+
* layout that reads it run again only then, and a document swapped in starts from none.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useNoteHeights(of: unknown): readonly [ReadonlyMap<StoryKey, number>, NoteHeightReport];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/ui/notes/noteHeights.ts
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
var NO_HEIGHTS = /* @__PURE__ */ new Map();
|
|
4
|
+
function round(value) {
|
|
5
|
+
return Math.round(value * 100) / 100;
|
|
6
|
+
}
|
|
7
|
+
function useNoteHeights(of) {
|
|
8
|
+
const [held, setHeld] = useState({ of, heights: NO_HEIGHTS });
|
|
9
|
+
const report = useCallback(
|
|
10
|
+
(key, height) => {
|
|
11
|
+
const rounded = round(height);
|
|
12
|
+
setHeld((previous) => {
|
|
13
|
+
const heights = previous.of === of ? previous.heights : NO_HEIGHTS;
|
|
14
|
+
if (heights.get(key) === rounded && previous.of === of) return previous;
|
|
15
|
+
return { of, heights: new Map(heights).set(key, rounded) };
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
[of]
|
|
19
|
+
);
|
|
20
|
+
return [held.of === of ? held.heights : NO_HEIGHTS, report];
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
useNoteHeights
|
|
24
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rule notes are set under, wherever they are drawn: in the room a page keeps for them
|
|
3
|
+
* (`./NoteAreas`) and at the head of the list under the sheet (`./NoteList`).
|
|
4
|
+
*
|
|
5
|
+
* Its height is also what the page layout is told a page adds once when it holds any footnote
|
|
6
|
+
* (`DemandBand.overhead`), and what the endnotes are given once where they begin
|
|
7
|
+
* (`TrailingRows.overhead`), so the room kept and the notes drawn in it are the same size.
|
|
8
|
+
*/
|
|
9
|
+
/** The height a page's notes are set under, the rule drawn across the middle of it */
|
|
10
|
+
export declare const NOTE_SEPARATOR_HEIGHT = 16;
|
|
11
|
+
/** Word's rule runs a third of the body and no further than two inches */
|
|
12
|
+
export declare function noteSeparatorWidth(bodyWidth: number): number;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which side story the caret is in, and what the one view over it is built from.
|
|
3
|
+
*
|
|
4
|
+
* One story is open at a time (`editor/stories/storyView`), and this is what holds that true: the
|
|
5
|
+
* state here names one story key. Everything the view is built from arrives as one binding, so a
|
|
6
|
+
* second kind of story is another binding rather than more of the component that renders this.
|
|
7
|
+
* Where a story stands on the screen is the drawing side's (`./noteBands`).
|
|
8
|
+
*/
|
|
9
|
+
import type { EditorState } from "prosemirror-state";
|
|
10
|
+
import type { EditorView } from "prosemirror-view";
|
|
11
|
+
import type { NoteRow } from "../../editor/commands/noteQueries";
|
|
12
|
+
import { type EditorDocument } from "../../editor/editorDocument";
|
|
13
|
+
import { type ActiveSurface, type StoryExtensions, type StoryHost } from "../../editor/stories/storyView";
|
|
14
|
+
import type { StoryKey } from "../../schema/stories";
|
|
15
|
+
import type { RowEditing } from "./StoryRow";
|
|
16
|
+
/** A view and the state on screen, which is what the mounting component holds as React state */
|
|
17
|
+
export interface LiveSurface {
|
|
18
|
+
readonly view: EditorView;
|
|
19
|
+
readonly state: EditorState;
|
|
20
|
+
}
|
|
21
|
+
/** What one kind of story hands the view over one of its rows, which is all that differs per kind */
|
|
22
|
+
export interface StorySurfaceBinding {
|
|
23
|
+
/** The host every edit in this kind of story leaves through */
|
|
24
|
+
hostOf(main: EditorView, activate: StoryHost["activate"]): StoryHost;
|
|
25
|
+
/** What the view over this row adds, and null for a row this binding does not answer for */
|
|
26
|
+
extensionsOf(main: EditorView, row: NoteRow): StoryExtensions | null;
|
|
27
|
+
/** The values this row's story is edited against, worked out from the document's own snapshot */
|
|
28
|
+
documentFor(main: EditorState, snapshot: EditorDocument, row: NoteRow): EditorDocument;
|
|
29
|
+
/**
|
|
30
|
+
* Which story the document is asking to have opened, read off the state the mounting component
|
|
31
|
+
* holds. A fresh value for every request, so asking twice for one story opens it twice.
|
|
32
|
+
*/
|
|
33
|
+
requestedIn(main: EditorState): {
|
|
34
|
+
readonly key: StoryKey;
|
|
35
|
+
} | null;
|
|
36
|
+
/** Puts the caret in this row's story, which is what opens it */
|
|
37
|
+
openIn(main: EditorView, row: NoteRow): void;
|
|
38
|
+
/** Hands the caret back to the text this row's story is called from, which is what closes it */
|
|
39
|
+
returnFrom(main: EditorView, row: NoteRow): void;
|
|
40
|
+
}
|
|
41
|
+
export interface StorySurfaceOptions {
|
|
42
|
+
/** The body's view and the state on screen, or null before a document is opened */
|
|
43
|
+
readonly main: LiveSurface | null;
|
|
44
|
+
/** The rows one of which a view may stand over, by the key naming each one's story */
|
|
45
|
+
readonly rows: ReadonlyMap<StoryKey, NoteRow>;
|
|
46
|
+
/** The kind of story these rows hold. A new value takes an open view down, so keep one */
|
|
47
|
+
readonly binding: StorySurfaceBinding;
|
|
48
|
+
}
|
|
49
|
+
export interface StorySurface {
|
|
50
|
+
/** Where the caret is, and null before a document is opened */
|
|
51
|
+
readonly active: ActiveSurface | null;
|
|
52
|
+
/** The story a view stands over, and null while the caret is in the body */
|
|
53
|
+
readonly open: StoryKey | null;
|
|
54
|
+
/** What mounts that view, which only the open row is given. Null while none is open */
|
|
55
|
+
readonly editing: RowEditing | null;
|
|
56
|
+
/** Whether the open story stands where nothing may be edited */
|
|
57
|
+
readonly readOnly: boolean;
|
|
58
|
+
/** Whether the open view is composing, which a page laid out again would take down */
|
|
59
|
+
composing(): boolean;
|
|
60
|
+
/** Opens the story of this row, with the caret where the press landed */
|
|
61
|
+
onOpen(key: StoryKey, at: {
|
|
62
|
+
left: number;
|
|
63
|
+
top: number;
|
|
64
|
+
}): void;
|
|
65
|
+
/** Hands the caret back to the text this row's story is called from */
|
|
66
|
+
onReturn(key: StoryKey): void;
|
|
67
|
+
}
|
|
68
|
+
export declare function useStorySurface({ main, rows, binding, }: StorySurfaceOptions): StorySurface;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/ui/notes/useStorySurface.ts
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { documentOf } from "../../editor/editorDocument.js";
|
|
4
|
+
import {
|
|
5
|
+
EVERY_CAPABILITY
|
|
6
|
+
} from "../../editor/stories/storyView.js";
|
|
7
|
+
function useStorySurface({
|
|
8
|
+
main,
|
|
9
|
+
rows,
|
|
10
|
+
binding
|
|
11
|
+
}) {
|
|
12
|
+
const [asked, setAsked] = useState(null);
|
|
13
|
+
const [live, setLive] = useState(null);
|
|
14
|
+
const held = useRef(null);
|
|
15
|
+
const requested = main === null ? null : binding.requestedIn(main.state);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (requested !== null) setAsked(requested.key);
|
|
18
|
+
}, [requested]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const body = main?.view;
|
|
21
|
+
if (!body) return;
|
|
22
|
+
const leave = () => setAsked(null);
|
|
23
|
+
body.dom.addEventListener("focus", leave);
|
|
24
|
+
return () => body.dom.removeEventListener("focus", leave);
|
|
25
|
+
}, [main?.view]);
|
|
26
|
+
const open = asked !== null && rows.has(asked) ? asked : null;
|
|
27
|
+
const row = open === null ? void 0 : rows.get(open);
|
|
28
|
+
const activate = useCallback((_key, view) => {
|
|
29
|
+
setLive(view === null ? null : { view, state: view.state });
|
|
30
|
+
}, []);
|
|
31
|
+
const onStateChange = useCallback((state) => {
|
|
32
|
+
setLive(
|
|
33
|
+
(current) => current === null ? current : { view: current.view, state }
|
|
34
|
+
);
|
|
35
|
+
}, []);
|
|
36
|
+
const caret = useMemo(
|
|
37
|
+
() => ({
|
|
38
|
+
take: () => held.current?.key === open ? held.current.caret : null,
|
|
39
|
+
keep: (at) => {
|
|
40
|
+
if (open !== null) held.current = { key: open, caret: at };
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
[open]
|
|
44
|
+
);
|
|
45
|
+
const host = useMemo(
|
|
46
|
+
() => main === null ? null : binding.hostOf(main.view, activate),
|
|
47
|
+
[main?.view, activate, binding]
|
|
48
|
+
);
|
|
49
|
+
const extensions = useMemo(
|
|
50
|
+
() => main === null || row === void 0 ? null : binding.extensionsOf(main.view, row),
|
|
51
|
+
[main?.view, row?.key, row?.label, binding]
|
|
52
|
+
);
|
|
53
|
+
const snapshot = main === null ? null : documentOf(main.state);
|
|
54
|
+
const document = useMemo(
|
|
55
|
+
() => main === null || snapshot === null || row === void 0 ? null : binding.documentFor(main.state, snapshot, row),
|
|
56
|
+
[main?.view, snapshot, row?.referencePos, binding]
|
|
57
|
+
);
|
|
58
|
+
const editing = useMemo(
|
|
59
|
+
() => host === null || extensions === null || document === null ? null : { host, document, extensions, caret, onStateChange },
|
|
60
|
+
[host, extensions, document, caret, onStateChange]
|
|
61
|
+
);
|
|
62
|
+
const active = main === null ? null : open !== null && live !== null && extensions !== null ? {
|
|
63
|
+
surface: "story",
|
|
64
|
+
key: open,
|
|
65
|
+
view: live.view,
|
|
66
|
+
state: live.state,
|
|
67
|
+
takes: extensions.takes
|
|
68
|
+
} : {
|
|
69
|
+
surface: "body",
|
|
70
|
+
view: main.view,
|
|
71
|
+
state: main.state,
|
|
72
|
+
takes: EVERY_CAPABILITY
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
active,
|
|
76
|
+
open,
|
|
77
|
+
editing,
|
|
78
|
+
readOnly: open !== null && host !== null && host.shut(open),
|
|
79
|
+
composing: () => live?.view.composing === true,
|
|
80
|
+
onOpen(key, at) {
|
|
81
|
+
const pressed = rows.get(key);
|
|
82
|
+
if (main === null || pressed === void 0) return;
|
|
83
|
+
held.current = { key, caret: { kind: "point", ...at } };
|
|
84
|
+
binding.openIn(main.view, pressed);
|
|
85
|
+
},
|
|
86
|
+
onReturn(key) {
|
|
87
|
+
const pressed = rows.get(key);
|
|
88
|
+
if (main === null || pressed === void 0) return;
|
|
89
|
+
binding.returnFrom(main.view, pressed);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
useStorySurface
|
|
95
|
+
};
|
package/dist/ui/runCommand.d.ts
CHANGED
|
@@ -2,4 +2,11 @@
|
|
|
2
2
|
import type { Command } from "prosemirror-state";
|
|
3
3
|
import type { EditorView } from "prosemirror-view";
|
|
4
4
|
export type RunCommand = (command: Command) => void;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Runs commands on `view` and leaves the focus on `focus`, which is `view` itself unless the
|
|
7
|
+
* command acts somewhere other than where the caret stands.
|
|
8
|
+
*
|
|
9
|
+
* Undo and redo are the one case: they run on the document while the caret is in a story, and
|
|
10
|
+
* handing the focus to the document is what closes an open story (`DocxEditor`).
|
|
11
|
+
*/
|
|
12
|
+
export declare function commandRunner(view: EditorView, focus?: EditorView): RunCommand;
|
package/dist/ui/runCommand.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
function apply(view, command) {
|
|
3
3
|
command(view.state, (tr) => view.dispatch(tr), view);
|
|
4
4
|
}
|
|
5
|
-
function commandRunner(view) {
|
|
5
|
+
function commandRunner(view, focus = view) {
|
|
6
6
|
return (command) => {
|
|
7
7
|
apply(view, command);
|
|
8
|
-
|
|
8
|
+
focus.focus();
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
export {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a menu row spells the modifiers of a key binding.
|
|
3
|
+
*
|
|
4
|
+
* `Mod` is Command on Apple platforms and Control elsewhere, and which one a binding answers to is
|
|
5
|
+
* decided by `prosemirror-keymap` off the platform name. A label is read off the same platform
|
|
6
|
+
* test, so a row cannot promise a key the bindings do not hold - an iPad keyboard sends Command
|
|
7
|
+
* for every `Mod` binding the editor declares (`editor/clipboard/plugin` reads it for the same
|
|
8
|
+
* reason).
|
|
9
|
+
*/
|
|
10
|
+
export interface ModifierLabels {
|
|
11
|
+
/** `Mod`, as a row writes it before the key */
|
|
12
|
+
readonly mod: string;
|
|
13
|
+
readonly alt: string;
|
|
14
|
+
}
|
|
15
|
+
/** The spellings this platform's bindings are labelled with */
|
|
16
|
+
export declare function modifierLabels(): ModifierLabels;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// src/ui/shortcutLabels.ts
|
|
2
|
+
var APPLE = /Mac|iP(hone|[oa]d)/;
|
|
3
|
+
function modifierLabels() {
|
|
4
|
+
const platform = typeof navigator === "undefined" ? "" : navigator.platform;
|
|
5
|
+
return APPLE.test(platform) ? { mod: "\u2318", alt: "\u2325" } : { mod: "Ctrl+", alt: "Alt+" };
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
modifierLabels
|
|
9
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds the room the page layer takes in the scroll box.
|
|
3
|
+
*
|
|
4
|
+
* The layer is scaled with a transform and stands outside the flow (`styles/editor.css`), so
|
|
5
|
+
* nothing in the flow reaches down to where the paper ends and the scroll box would stop short of
|
|
6
|
+
* it. The box the layer stands in is given the height the layer is drawn at, and the scroll box
|
|
7
|
+
* then reaches the paper and keeps its own padding below it.
|
|
8
|
+
*
|
|
9
|
+
* The two elements are taken rather than refs to them, so that a surface which drew neither - a
|
|
10
|
+
* file that was refused, before the reader opens one that is not - is measured the moment they
|
|
11
|
+
* arrive rather than never again.
|
|
12
|
+
*/
|
|
13
|
+
export declare function usePageRoom(box: HTMLElement | null, layer: HTMLElement | null, scale: number): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/ui/usePageRoom.ts
|
|
2
|
+
import { useLayoutEffect } from "react";
|
|
3
|
+
function usePageRoom(box, layer, scale) {
|
|
4
|
+
useLayoutEffect(() => {
|
|
5
|
+
if (!box || !layer) return;
|
|
6
|
+
const frameWindow = box.ownerDocument.defaultView;
|
|
7
|
+
let frame = 0;
|
|
8
|
+
const hold = () => {
|
|
9
|
+
const next = `${layer.getBoundingClientRect().height}px`;
|
|
10
|
+
if (box.style.height !== next) box.style.height = next;
|
|
11
|
+
};
|
|
12
|
+
const schedule = () => {
|
|
13
|
+
if (frame !== 0 || !frameWindow) return;
|
|
14
|
+
frame = frameWindow.requestAnimationFrame(() => {
|
|
15
|
+
frame = 0;
|
|
16
|
+
hold();
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
hold();
|
|
20
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
|
|
21
|
+
observer?.observe(layer);
|
|
22
|
+
return () => {
|
|
23
|
+
observer?.disconnect();
|
|
24
|
+
if (frame !== 0) frameWindow?.cancelAnimationFrame(frame);
|
|
25
|
+
box.style.removeProperty("height");
|
|
26
|
+
};
|
|
27
|
+
}, [box, layer, scale]);
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
usePageRoom
|
|
31
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portone/docx-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "A simple DOCX editor for React, built directly on OOXML.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"build:demo": "vite build --config demo/vite.config.ts",
|
|
108
108
|
"dev:site": "pnpm --filter @portone/docx-editor-site dev",
|
|
109
109
|
"build:site": "pnpm --filter @portone/docx-editor-site build",
|
|
110
|
+
"bench": "vitest run --config vitest.bench.config.ts --disableConsoleIntercept",
|
|
110
111
|
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm test:fixtures && pnpm test:site-release && pnpm test:release && pnpm check:demo-library",
|
|
111
112
|
"typecheck": "tsc --noEmit && tsc --noEmit -p e2e && tsc --noEmit -p demo && pnpm --filter @portone/docx-editor-site typecheck",
|
|
112
113
|
"lint": "pnpm biome check . --diagnostic-level=error",
|
package/dist/docx/notes.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/** Reads footnote and endnote bodies related from the main document story. */
|
|
2
|
-
export type NoteKind = "footnote" | "endnote";
|
|
3
|
-
/**
|
|
4
|
-
* One entry of a notes part as it arrived. What the note says is a story of its own, held on the
|
|
5
|
-
* document node under `footnote:<id>` or `endnote:<id>` (`docx/story`).
|
|
6
|
-
*/
|
|
7
|
-
export interface ImportedNote {
|
|
8
|
-
kind: NoteKind;
|
|
9
|
-
id: string;
|
|
10
|
-
label: string;
|
|
11
|
-
type: string | null;
|
|
12
|
-
}
|
|
13
|
-
export interface ImportedNotePart {
|
|
14
|
-
partPath: string | null;
|
|
15
|
-
/** The raw text of the part, which is what the note stories are sliced out of. null for none */
|
|
16
|
-
xml: string | null;
|
|
17
|
-
ordered: readonly ImportedNote[];
|
|
18
|
-
byId: ReadonlyMap<string, ImportedNote>;
|
|
19
|
-
}
|
|
20
|
-
export interface ImportedNotes {
|
|
21
|
-
footnotes: ImportedNotePart;
|
|
22
|
-
endnotes: ImportedNotePart;
|
|
23
|
-
}
|
|
24
|
-
export declare const NO_NOTES: ImportedNotes;
|
|
25
|
-
/** Reads both note parts without modifying their original package bytes. */
|
|
26
|
-
export declare function readNotes(parts: Map<string, Uint8Array>, mainPartPath: string): ImportedNotes;
|
|
27
|
-
export declare function noteById(notes: ImportedNotes, kind: NoteKind, id: string): ImportedNote | undefined;
|
package/dist/docx/notes.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// src/docx/notes.ts
|
|
2
|
-
import {
|
|
3
|
-
attributeByLocalName,
|
|
4
|
-
decodeUtf8,
|
|
5
|
-
elementChildren,
|
|
6
|
-
parseXml,
|
|
7
|
-
R_NS
|
|
8
|
-
} from "../ooxml/xml.js";
|
|
9
|
-
import { relatedPartPath } from "./packageParts.js";
|
|
10
|
-
var EMPTY_PART = {
|
|
11
|
-
partPath: null,
|
|
12
|
-
xml: null,
|
|
13
|
-
ordered: [],
|
|
14
|
-
byId: /* @__PURE__ */ new Map()
|
|
15
|
-
};
|
|
16
|
-
var NO_NOTES = {
|
|
17
|
-
footnotes: EMPTY_PART,
|
|
18
|
-
endnotes: EMPTY_PART
|
|
19
|
-
};
|
|
20
|
-
function notePart(parts, mainPartPath, kind) {
|
|
21
|
-
const partPath = relatedPartPath(parts, mainPartPath, `${R_NS}/${kind}s`);
|
|
22
|
-
if (partPath === null) return EMPTY_PART;
|
|
23
|
-
const bytes = parts.get(partPath);
|
|
24
|
-
if (!bytes) return { ...EMPTY_PART, partPath };
|
|
25
|
-
const xml = decodeUtf8(bytes).text;
|
|
26
|
-
const root = parseXml(xml).documentElement;
|
|
27
|
-
const elements = elementChildren(root).filter((el) => el.localName === kind);
|
|
28
|
-
const ordered = elements.flatMap((el) => {
|
|
29
|
-
const id = attributeByLocalName(el, "id");
|
|
30
|
-
if (id === null) return [];
|
|
31
|
-
const type = attributeByLocalName(el, "type");
|
|
32
|
-
const regular = type === null || type === "normal";
|
|
33
|
-
const label = regular ? "?" : "";
|
|
34
|
-
return [{ kind, id, label, type }];
|
|
35
|
-
});
|
|
36
|
-
const byId = /* @__PURE__ */ new Map();
|
|
37
|
-
for (const note of ordered) {
|
|
38
|
-
if (!byId.has(note.id)) byId.set(note.id, note);
|
|
39
|
-
}
|
|
40
|
-
return { partPath, xml, ordered, byId };
|
|
41
|
-
}
|
|
42
|
-
function readNotes(parts, mainPartPath) {
|
|
43
|
-
return {
|
|
44
|
-
footnotes: notePart(parts, mainPartPath, "footnote"),
|
|
45
|
-
endnotes: notePart(parts, mainPartPath, "endnote")
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function noteById(notes, kind, id) {
|
|
49
|
-
return notes[`${kind}s`].byId.get(id);
|
|
50
|
-
}
|
|
51
|
-
export {
|
|
52
|
-
NO_NOTES,
|
|
53
|
-
noteById,
|
|
54
|
-
readNotes
|
|
55
|
-
};
|
package/dist/ui/NotesPanel.d.ts
DELETED
package/dist/ui/NotesPanel.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// src/ui/NotesPanel.tsx
|
|
2
|
-
import { noteProjection } from "../editor/commands/noteQueries.js";
|
|
3
|
-
import { editorClassNames } from "../styles/classNames.js";
|
|
4
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
function NotesPanel({
|
|
6
|
-
state,
|
|
7
|
-
pageWidth
|
|
8
|
-
}) {
|
|
9
|
-
const { notes } = noteProjection.read(state);
|
|
10
|
-
if (notes.length === 0) return null;
|
|
11
|
-
return /* @__PURE__ */ jsxs(
|
|
12
|
-
"section",
|
|
13
|
-
{
|
|
14
|
-
className: editorClassNames.notesPanel,
|
|
15
|
-
"aria-label": "Document notes",
|
|
16
|
-
style: { width: `${pageWidth}px` },
|
|
17
|
-
children: [
|
|
18
|
-
/* @__PURE__ */ jsx("h2", { className: editorClassNames.notesHeading, children: "Footnotes and endnotes" }),
|
|
19
|
-
/* @__PURE__ */ jsx("ol", { className: editorClassNames.notesList, children: notes.map((note) => /* @__PURE__ */ jsxs("li", { children: [
|
|
20
|
-
/* @__PURE__ */ jsxs("span", { className: editorClassNames.noteLabel, children: [
|
|
21
|
-
note.kind === "footnote" ? "Footnote" : "Endnote",
|
|
22
|
-
" ",
|
|
23
|
-
note.label
|
|
24
|
-
] }),
|
|
25
|
-
/* @__PURE__ */ jsx("p", { className: editorClassNames.noteBody, children: note.text })
|
|
26
|
-
] }, `${note.kind}:${note.id}`)) })
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
export {
|
|
32
|
-
NotesPanel
|
|
33
|
-
};
|