@portone/docx-editor 0.4.0 → 0.5.0
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 +159 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +1 -1
- package/dist/DocxEditor.js +44 -25
- package/dist/docx/cloning.js +4 -9
- package/dist/docx/commentOnlyChange.d.ts +5 -3
- package/dist/docx/comments/grammar.d.ts +27 -10
- package/dist/docx/comments/grammar.js +18 -67
- package/dist/docx/comments/model.d.ts +5 -6
- package/dist/docx/comments/model.js +0 -9
- package/dist/docx/comments/parts.d.ts +16 -10
- package/dist/docx/comments/parts.js +65 -13
- package/dist/docx/comments/people.d.ts +11 -3
- package/dist/docx/comments/people.js +20 -6
- package/dist/docx/comments/policy.js +7 -1
- package/dist/docx/comments/reading.d.ts +4 -2
- package/dist/docx/comments/reading.js +1 -14
- package/dist/docx/comments/writing.js +65 -28
- package/dist/docx/exportDocx.js +10 -14
- package/dist/docx/exportRefs.d.ts +8 -3
- package/dist/docx/exportRefs.js +2 -1
- package/dist/docx/fidelity.js +24 -10
- package/dist/docx/fields.d.ts +42 -0
- package/dist/docx/fields.js +76 -0
- package/dist/docx/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +100 -48
- package/dist/docx/importParagraph.d.ts +16 -4
- package/dist/docx/importParagraph.js +87 -93
- package/dist/docx/importPolicy.d.ts +70 -0
- package/dist/docx/importPolicy.js +190 -0
- package/dist/docx/importPreserved.d.ts +30 -0
- package/dist/docx/importPreserved.js +54 -0
- package/dist/docx/importTable.js +56 -12
- package/dist/docx/invariants.js +16 -18
- package/dist/docx/notes.d.ts +6 -1
- package/dist/docx/notes.js +6 -16
- package/dist/docx/pageGeometry.d.ts +5 -14
- package/dist/docx/pageGeometry.js +0 -12
- package/dist/docx/protectionPolicy.d.ts +13 -5
- package/dist/docx/protectionPolicy.js +32 -22
- package/dist/docx/scan.d.ts +18 -8
- package/dist/docx/scan.js +17 -11
- package/dist/docx/sections.d.ts +126 -0
- package/dist/docx/sections.js +207 -0
- package/dist/docx/serializeBlock.d.ts +13 -4
- package/dist/docx/serializeBlock.js +16 -18
- package/dist/docx/serializeParagraph.d.ts +4 -0
- package/dist/docx/serializeParagraph.js +1 -0
- package/dist/docx/serializePreserved.d.ts +14 -0
- package/dist/docx/serializePreserved.js +24 -0
- package/dist/docx/serializeStory.d.ts +17 -0
- package/dist/docx/serializeStory.js +17 -0
- package/dist/docx/serializeTable.js +10 -13
- package/dist/docx/session.d.ts +23 -6
- package/dist/docx/session.js +26 -5
- package/dist/docx/story.d.ts +140 -0
- package/dist/docx/story.js +237 -0
- package/dist/docx/storyProjection.js +6 -1
- package/dist/editor/clipboard/htmlReader.d.ts +30 -0
- package/dist/editor/{externalClipboard.js → clipboard/htmlReader.js} +39 -223
- package/dist/editor/clipboard/internalChannel.d.ts +57 -0
- package/dist/editor/clipboard/internalChannel.js +58 -0
- package/dist/editor/clipboard/normalizers.d.ts +99 -0
- package/dist/editor/clipboard/normalizers.js +199 -0
- package/dist/editor/clipboard/parser.d.ts +34 -0
- package/dist/editor/clipboard/parser.js +58 -0
- package/dist/editor/clipboard/plugin.d.ts +21 -0
- package/dist/editor/clipboard/plugin.js +320 -0
- package/dist/editor/clipboard/readContext.d.ts +24 -0
- package/dist/editor/clipboard/readContext.js +19 -0
- package/dist/editor/clipboard/readers.d.ts +33 -0
- package/dist/editor/clipboard/readers.js +37 -0
- package/dist/editor/commands/comments/editing.d.ts +26 -6
- package/dist/editor/commands/comments/editing.js +175 -105
- package/dist/editor/commands/comments/model.d.ts +34 -17
- package/dist/editor/commands/comments/model.js +11 -0
- package/dist/editor/commands/comments/reading.d.ts +5 -3
- package/dist/editor/commands/comments/reading.js +8 -58
- package/dist/editor/commands/index.d.ts +2 -2
- package/dist/editor/commands/index.js +2 -0
- package/dist/editor/commands/listCommands.d.ts +6 -0
- package/dist/editor/commands/listCommands.js +3 -2
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- package/dist/editor/createEditor.js +13 -3
- package/dist/editor/documentStyles.d.ts +26 -4
- package/dist/editor/documentStyles.js +9 -4
- package/dist/editor/imageFiles.d.ts +2 -2
- package/dist/editor/imageFiles.js +2 -0
- package/dist/editor/insertTable.d.ts +1 -1
- package/dist/editor/insertTable.js +2 -2
- package/dist/editor/plainText.d.ts +12 -0
- package/dist/editor/plainText.js +2 -1
- package/dist/editor/plugins/columnResize.js +6 -3
- package/dist/editor/plugins/commentComposer.d.ts +28 -0
- package/dist/editor/plugins/commentComposer.js +46 -0
- package/dist/editor/plugins/commentDecorations.d.ts +29 -2
- package/dist/editor/plugins/commentDecorations.js +89 -15
- package/dist/editor/plugins/documentProjection.d.ts +31 -0
- package/dist/editor/plugins/documentProjection.js +22 -0
- package/dist/editor/plugins/imagePaste.js +15 -8
- package/dist/editor/plugins/linkPanel.d.ts +7 -3
- package/dist/editor/plugins/linkPanel.js +13 -27
- package/dist/editor/plugins/panelState.d.ts +67 -0
- package/dist/editor/plugins/panelState.js +54 -0
- package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
- package/dist/editor/plugins/tableContextMenu.js +30 -47
- package/dist/editor/plugins/textContextMenu.d.ts +1 -1
- package/dist/editor/plugins/textContextMenu.js +30 -47
- package/dist/ooxml/fragment.d.ts +11 -0
- package/dist/ooxml/fragment.js +14 -4
- package/dist/ooxml/image.d.ts +3 -4
- package/dist/ooxml/image.js +50 -5
- package/dist/ooxml/names.d.ts +6 -0
- package/dist/ooxml/names.js +2 -0
- package/dist/ooxml/props.d.ts +8 -0
- package/dist/ooxml/props.js +14 -4
- package/dist/ooxml/rangeMarkers.d.ts +5 -0
- package/dist/ooxml/rangeMarkers.js +24 -0
- package/dist/page/PageGuides.d.ts +7 -3
- package/dist/page/PageGuides.js +13 -24
- package/dist/page/pageLayout.d.ts +64 -5
- package/dist/page/pageLayout.js +90 -29
- package/dist/page/usePageLayout.d.ts +14 -4
- package/dist/page/usePageLayout.js +19 -20
- package/dist/schema/attrRoles.js +38 -20
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +191 -139
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +2 -2
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/preservedFragments.d.ts +12 -0
- package/dist/schema/preservedFragments.js +38 -0
- package/dist/schema/preservedGuards.d.ts +9 -7
- package/dist/schema/preservedGuards.js +112 -11
- package/dist/schema/protection.d.ts +34 -5
- package/dist/schema/protection.js +42 -14
- package/dist/schema/rendering.js +3 -1
- package/dist/schema/stories.d.ts +49 -0
- package/dist/schema/stories.js +78 -0
- package/dist/styles/classNames.d.ts +3 -5
- package/dist/styles/classNames.js +3 -5
- package/dist/styles.css +74 -12
- package/dist/table/resize.d.ts +5 -3
- package/dist/ui/CommentsPanel.d.ts +1 -3
- package/dist/ui/CommentsPanel.js +24 -15
- package/dist/ui/NotesPanel.js +2 -2
- package/dist/ui/TextMenu.d.ts +1 -3
- package/dist/ui/TextMenu.js +10 -6
- package/dist/ui/comments/CommentComposer.d.ts +6 -1
- package/dist/ui/comments/CommentComposer.js +26 -3
- package/package.json +1 -1
- package/dist/editor/externalClipboard.d.ts +0 -22
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One convention for a value the whole document decides: the comments beside it, the notes under
|
|
3
|
+
* it, the list markers drawn over it.
|
|
4
|
+
*
|
|
5
|
+
* Such a value is worked out by walking the document, and what it is worked out from changes only
|
|
6
|
+
* when the document does. Held here, it is derived once per edit rather than once per read, and a
|
|
7
|
+
* caret moving, a menu opening or a component rendering again all read back the value the last
|
|
8
|
+
* edit left. What a caller writes is unchanged: `read` answers with a value either way.
|
|
9
|
+
*
|
|
10
|
+
* A projection derives from the document alone. Anything that also depends on the selection, the
|
|
11
|
+
* protection or another plugin belongs in that plugin, which is asked again after every
|
|
12
|
+
* transaction rather than after an edit alone.
|
|
13
|
+
*/
|
|
14
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
15
|
+
import { type EditorState, Plugin, type PluginSpec } from "prosemirror-state";
|
|
16
|
+
export interface DocumentProjection<T> {
|
|
17
|
+
plugin: Plugin<T>;
|
|
18
|
+
/**
|
|
19
|
+
* The derived value. A state built without the plugin - one a consumer assembled itself, or one
|
|
20
|
+
* a test made out of a bare document - is answered by deriving on the spot, so a public reader
|
|
21
|
+
* standing on a projection keeps answering whoever asks it.
|
|
22
|
+
*/
|
|
23
|
+
read(state: EditorState): T;
|
|
24
|
+
}
|
|
25
|
+
export declare function documentProjection<T>(name: string, derive: (doc: PMNode) => T,
|
|
26
|
+
/**
|
|
27
|
+
* The view props drawn from the derived value, which is how a projection that paints what it
|
|
28
|
+
* derived - comment ranges over the text they were written for - stays one plugin. `this` in
|
|
29
|
+
* such a prop is the plugin, so it reads the value back with `this.getState(state)`.
|
|
30
|
+
*/
|
|
31
|
+
props?: PluginSpec<T>["props"]): DocumentProjection<T>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/editor/plugins/documentProjection.ts
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
PluginKey
|
|
5
|
+
} from "prosemirror-state";
|
|
6
|
+
function documentProjection(name, derive, props) {
|
|
7
|
+
const key = new PluginKey(name);
|
|
8
|
+
return {
|
|
9
|
+
plugin: new Plugin({
|
|
10
|
+
key,
|
|
11
|
+
state: {
|
|
12
|
+
init: (_config, state) => derive(state.doc),
|
|
13
|
+
apply: (tr, held) => tr.docChanged ? derive(tr.doc) : held
|
|
14
|
+
},
|
|
15
|
+
props
|
|
16
|
+
}),
|
|
17
|
+
read: (state) => key.getState(state) ?? derive(state.doc)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
documentProjection
|
|
22
|
+
};
|
|
@@ -3,12 +3,14 @@ import { isHistoryTransaction } from "prosemirror-history";
|
|
|
3
3
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
4
4
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
5
5
|
import { editShut, selectionIntents } from "../../schema/guards.js";
|
|
6
|
+
import { readHtmlSlice, withPastedContent } from "../clipboard/htmlReader.js";
|
|
6
7
|
import {
|
|
7
8
|
hasResolvableClipboardImages,
|
|
8
9
|
resolveClipboardImages
|
|
9
10
|
} from "../clipboard/images.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { plainTextPaste } from "../clipboard/parser.js";
|
|
12
|
+
import { readContextOf } from "../clipboard/readContext.js";
|
|
13
|
+
import { documentBodyWidthPx, sectionBodyHeightPx } from "../documentStyles.js";
|
|
12
14
|
import { insertPlainTextAt } from "../plainText.js";
|
|
13
15
|
var LOAD_TIMEOUT_MS = 15e3;
|
|
14
16
|
var imagePasteKey = new PluginKey("docxImagePaste");
|
|
@@ -87,11 +89,13 @@ function imagePaste() {
|
|
|
87
89
|
if (!range) return;
|
|
88
90
|
removeMarker(task);
|
|
89
91
|
if (task.view.isDestroyed) return;
|
|
90
|
-
const content = resolved === null || resolved.images.size === 0 ? null :
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const content = resolved === null || resolved.images.size === 0 ? null : readHtmlSlice(
|
|
93
|
+
readContextOf(
|
|
94
|
+
task.view.state,
|
|
95
|
+
task.view.dom.ownerDocument,
|
|
96
|
+
resolved.images
|
|
97
|
+
),
|
|
98
|
+
resolved.source
|
|
95
99
|
);
|
|
96
100
|
if (content !== null) {
|
|
97
101
|
task.view.dispatch(
|
|
@@ -225,6 +229,7 @@ function imagePaste() {
|
|
|
225
229
|
props: {
|
|
226
230
|
decorations: (state) => imagePasteKey.getState(state)?.decorations,
|
|
227
231
|
handlePaste(view, event) {
|
|
232
|
+
if (plainTextPaste(view, event)) return false;
|
|
228
233
|
if (selectionIntents(view.state.selection, "replace").some(
|
|
229
234
|
(intent) => editShut(view.state, intent)
|
|
230
235
|
)) {
|
|
@@ -256,8 +261,10 @@ function imagePaste() {
|
|
|
256
261
|
view,
|
|
257
262
|
html,
|
|
258
263
|
text,
|
|
264
|
+
// One sheet is drawn at one width, the first section's, and a pasted image is fitted
|
|
265
|
+
// to it; how tall a page is, though, is the paper of the section it lands in
|
|
259
266
|
maxWidthPx: documentBodyWidthPx(view.state),
|
|
260
|
-
maxHeightPx:
|
|
267
|
+
maxHeightPx: sectionBodyHeightPx(view.state, from),
|
|
261
268
|
canceled: false,
|
|
262
269
|
controller: null
|
|
263
270
|
});
|
|
@@ -9,15 +9,19 @@
|
|
|
9
9
|
* It closes on the document changing, which is what applying a link or taking one off does: the
|
|
10
10
|
* panel is about the selection it opened over, and after an edit that selection has been answered.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import type { Command, EditorState, Plugin } from "prosemirror-state";
|
|
13
|
+
/** The panel is about the selection it opened over, so standing open is all there is to hold */
|
|
14
|
+
declare const OPEN = true;
|
|
13
15
|
/** Whether the link panel is open */
|
|
14
16
|
export declare function isLinkPanelOpen(state: EditorState): boolean;
|
|
15
17
|
/**
|
|
16
18
|
* Opens the panel. It reports false where a link could not go on anyway - a caret standing in no
|
|
17
19
|
* link, a selection a lock leaves nothing of - so that Cmd+K there is a key this editor did not
|
|
18
|
-
* take, and a control drawn from it is drawn dead.
|
|
20
|
+
* take, and a control drawn from it is drawn dead. A panel already open is one more such place:
|
|
21
|
+
* the button that opened it has nothing left to do while it stands.
|
|
19
22
|
*/
|
|
20
23
|
export declare const openLinkPanel: Command;
|
|
21
24
|
/** Closes it. Called on applying, on Escape, and on a click outside */
|
|
22
25
|
export declare const closeLinkPanel: Command;
|
|
23
|
-
export declare function linkPanel(): Plugin<
|
|
26
|
+
export declare function linkPanel(): Plugin<typeof OPEN | null>;
|
|
27
|
+
export {};
|
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
// src/editor/plugins/linkPanel.ts
|
|
2
|
-
import {
|
|
3
|
-
Plugin,
|
|
4
|
-
PluginKey
|
|
5
|
-
} from "prosemirror-state";
|
|
6
2
|
import { canSetLink } from "../commands/linkCommands.js";
|
|
7
|
-
|
|
3
|
+
import { panelPlugin } from "./panelState.js";
|
|
4
|
+
var OPEN = true;
|
|
5
|
+
var panel = panelPlugin({
|
|
6
|
+
name: "docxEditorLinkPanel",
|
|
7
|
+
isAnchor: (value) => value === OPEN,
|
|
8
|
+
// An edit answers the panel: the link went on, came off, or the text it was about moved
|
|
9
|
+
onDocChange: "close",
|
|
10
|
+
canOpen: (state) => canSetLink(state)
|
|
11
|
+
});
|
|
8
12
|
function isLinkPanelOpen(state) {
|
|
9
|
-
return
|
|
13
|
+
return panel.anchor(state) === OPEN;
|
|
10
14
|
}
|
|
11
|
-
var openLinkPanel = (state, dispatch) =>
|
|
12
|
-
|
|
13
|
-
if (dispatch) dispatch(state.tr.setMeta(panelKey, true));
|
|
14
|
-
return true;
|
|
15
|
-
};
|
|
16
|
-
var closeLinkPanel = (state, dispatch) => {
|
|
17
|
-
if (!isLinkPanelOpen(state)) return false;
|
|
18
|
-
if (dispatch) dispatch(state.tr.setMeta(panelKey, false));
|
|
19
|
-
return true;
|
|
20
|
-
};
|
|
15
|
+
var openLinkPanel = (state, dispatch) => !isLinkPanelOpen(state) && panel.open(OPEN)(state, dispatch);
|
|
16
|
+
var closeLinkPanel = panel.close;
|
|
21
17
|
function linkPanel() {
|
|
22
|
-
return
|
|
23
|
-
key: panelKey,
|
|
24
|
-
state: {
|
|
25
|
-
init: () => false,
|
|
26
|
-
apply: (tr, open) => {
|
|
27
|
-
const meta = tr.getMeta(panelKey);
|
|
28
|
-
if (typeof meta === "boolean") return meta;
|
|
29
|
-
return tr.docChanged ? false : open;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
});
|
|
18
|
+
return panel.plugin;
|
|
33
19
|
}
|
|
34
20
|
export {
|
|
35
21
|
closeLinkPanel,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One convention for every panel the editor opens over a spot in the document: a link panel, a
|
|
3
|
+
* right click menu, the comment composer.
|
|
4
|
+
*
|
|
5
|
+
* Each of them holds the same thing - where it stands, or nothing when it is shut - and answers the
|
|
6
|
+
* same three questions: open it here, close it, where does it stand. What differs between them is
|
|
7
|
+
* what "here" is and what an edit or a change of protection does to a panel already open, and those
|
|
8
|
+
* are the options below rather than four copies of a plugin.
|
|
9
|
+
*
|
|
10
|
+
* The anchor is whatever the panel is about: a point on the screen for a menu, a stretch of the
|
|
11
|
+
* document for the composer. It is read back out of the state as it was put in, so a panel that
|
|
12
|
+
* needs nothing but "open" carries an anchor that says just that.
|
|
13
|
+
*
|
|
14
|
+
* A panel that reads other plugins in `canOpen` or `closeWhen` must be registered after them:
|
|
15
|
+
* the state a plugin is handed while the transaction is being applied holds the fields of the
|
|
16
|
+
* plugins ahead of it and nothing of the ones behind it.
|
|
17
|
+
*/
|
|
18
|
+
import { type Command, type EditorState, Plugin, type PluginSpec, type Transaction } from "prosemirror-state";
|
|
19
|
+
/**
|
|
20
|
+
* What a document change does to an open panel: shut it, or move the anchor along with the change.
|
|
21
|
+
* Answering null from the mapping shuts it, which is what a stretch the edit swallowed comes to.
|
|
22
|
+
*/
|
|
23
|
+
export type PanelDocChange<Anchor> = "close" | ((anchor: Anchor, tr: Transaction, state: EditorState) => Anchor | null);
|
|
24
|
+
export interface PanelOptions<Anchor> {
|
|
25
|
+
/** The plugin key's name, which is what a state debugger and a duplicate registration report */
|
|
26
|
+
name: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether this is an anchor of this panel's. The opening goes through the transaction metadata,
|
|
29
|
+
* which anything may write, so what is read back is judged rather than trusted
|
|
30
|
+
*/
|
|
31
|
+
isAnchor(value: unknown): value is Anchor;
|
|
32
|
+
onDocChange: PanelDocChange<Anchor>;
|
|
33
|
+
/** Whether the panel may open here. `open` reports false where it says no */
|
|
34
|
+
canOpen?(state: EditorState, anchor: Anchor): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the panel must shut where the state now stands, asked after every transaction rather
|
|
37
|
+
* than after an edit alone: a protection switch changes no text (`./documentProtection`), so
|
|
38
|
+
* `onDocChange` never sees it, and a panel that only a commenter is offered has to go with it.
|
|
39
|
+
*/
|
|
40
|
+
closeWhen?(state: EditorState, anchor: Anchor): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The view props the panel needs of its own, which is how a panel opened by a DOM event - a
|
|
43
|
+
* right click - stays one plugin. What that handler does with the event is the panel's business;
|
|
44
|
+
* all this factory asks is that the opening go through `opening` so the state stays this one.
|
|
45
|
+
*/
|
|
46
|
+
props?: PluginSpec<Anchor | null>["props"];
|
|
47
|
+
}
|
|
48
|
+
export interface PanelPlugin<Anchor> {
|
|
49
|
+
plugin: Plugin<Anchor | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Opens the panel over this anchor, or moves an open one there. False where `canOpen` says the
|
|
52
|
+
* panel has nothing to do here, so that a control drawn from it is drawn dead and a key bound to
|
|
53
|
+
* it is a key this editor did not take.
|
|
54
|
+
*/
|
|
55
|
+
open(anchor: Anchor): Command;
|
|
56
|
+
/**
|
|
57
|
+
* The same opening recorded on a transaction already being built, for an opener that moves the
|
|
58
|
+
* selection in the same step. `canOpen` is not asked here: an opener holding the event that
|
|
59
|
+
* asked - a right click landing where the menu belongs - has decided that already.
|
|
60
|
+
*/
|
|
61
|
+
opening(tr: Transaction, anchor: Anchor): Transaction;
|
|
62
|
+
/** Closes it. False where it stands closed already, so a key press falls through to what is behind it */
|
|
63
|
+
close: Command;
|
|
64
|
+
/** Where the panel stands. Null when it is shut, and for a state built without the plugin */
|
|
65
|
+
anchor(state: EditorState): Anchor | null;
|
|
66
|
+
}
|
|
67
|
+
export declare function panelPlugin<Anchor>({ name, isAnchor, onDocChange, canOpen, closeWhen, props, }: PanelOptions<Anchor>): PanelPlugin<Anchor>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/editor/plugins/panelState.ts
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
PluginKey
|
|
5
|
+
} from "prosemirror-state";
|
|
6
|
+
var CLOSE = /* @__PURE__ */ Symbol("panelClose");
|
|
7
|
+
function panelPlugin({
|
|
8
|
+
name,
|
|
9
|
+
isAnchor,
|
|
10
|
+
onDocChange,
|
|
11
|
+
canOpen,
|
|
12
|
+
closeWhen,
|
|
13
|
+
props
|
|
14
|
+
}) {
|
|
15
|
+
const key = new PluginKey(name);
|
|
16
|
+
const anchor = (state) => key.getState(state) ?? null;
|
|
17
|
+
const moved = (tr, held, next) => {
|
|
18
|
+
const meta = tr.getMeta(key);
|
|
19
|
+
if (meta === CLOSE) return null;
|
|
20
|
+
if (isAnchor(meta)) return meta;
|
|
21
|
+
if (held === null || !tr.docChanged) return held;
|
|
22
|
+
return onDocChange === "close" ? null : onDocChange(held, tr, next);
|
|
23
|
+
};
|
|
24
|
+
const opening = (tr, over) => tr.setMeta(key, over);
|
|
25
|
+
return {
|
|
26
|
+
plugin: new Plugin({
|
|
27
|
+
key,
|
|
28
|
+
state: {
|
|
29
|
+
init: () => null,
|
|
30
|
+
apply: (tr, held, _before, next) => {
|
|
31
|
+
const standing = moved(tr, held, next);
|
|
32
|
+
if (standing === null) return null;
|
|
33
|
+
return closeWhen?.(next, standing) ? null : standing;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
props
|
|
37
|
+
}),
|
|
38
|
+
open: (over) => (state, dispatch) => {
|
|
39
|
+
if (canOpen && !canOpen(state, over)) return false;
|
|
40
|
+
dispatch?.(opening(state.tr, over));
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
opening,
|
|
44
|
+
close: (state, dispatch) => {
|
|
45
|
+
if (anchor(state) === null) return false;
|
|
46
|
+
dispatch?.(state.tr.setMeta(key, CLOSE));
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
anchor
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
panelPlugin
|
|
54
|
+
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* selected text is taken first by the text menu (`editor/textContextMenu`), which stands ahead
|
|
9
9
|
* of this one and hands back a click with nothing selected inside a cell.
|
|
10
10
|
*/
|
|
11
|
-
import { type Command, type EditorState, Plugin } from "prosemirror-state";
|
|
11
|
+
import { type Command, type EditorState, type Plugin } from "prosemirror-state";
|
|
12
12
|
/** Where the menu should stand (viewport coordinates) */
|
|
13
13
|
export interface TableMenuAnchor {
|
|
14
14
|
clientX: number;
|
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
// src/editor/plugins/tableContextMenu.ts
|
|
2
2
|
import {
|
|
3
|
-
Plugin,
|
|
4
|
-
PluginKey,
|
|
5
3
|
TextSelection
|
|
6
4
|
} from "prosemirror-state";
|
|
7
5
|
import { editsShut } from "../../schema/protectionState.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return typeof value === "object" && value !== null;
|
|
12
|
-
}
|
|
13
|
-
function toAnchor(value) {
|
|
14
|
-
if (!isRecord(value)) return null;
|
|
6
|
+
import { panelPlugin } from "./panelState.js";
|
|
7
|
+
function isTableMenuAnchor(value) {
|
|
8
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15
9
|
const { clientX, clientY } = value;
|
|
16
|
-
|
|
17
|
-
return { clientX, clientY };
|
|
18
|
-
}
|
|
19
|
-
function tableMenuAnchor(state) {
|
|
20
|
-
return menuKey.getState(state) ?? null;
|
|
10
|
+
return typeof clientX === "number" && typeof clientY === "number";
|
|
21
11
|
}
|
|
22
|
-
var closeTableMenu = (state, dispatch) => {
|
|
23
|
-
if (tableMenuAnchor(state) === null) return false;
|
|
24
|
-
if (dispatch) dispatch(state.tr.setMeta(menuKey, CLOSE));
|
|
25
|
-
return true;
|
|
26
|
-
};
|
|
27
12
|
function cellStart($pos) {
|
|
28
13
|
for (let depth = $pos.depth; depth > 0; depth -= 1) {
|
|
29
14
|
if ($pos.node(depth).type.spec.tableRole === "cell") {
|
|
@@ -46,41 +31,39 @@ function selectionCovers(state, cellPos) {
|
|
|
46
31
|
}
|
|
47
32
|
function openMenu(view, cellPos, anchor) {
|
|
48
33
|
const state = view.state;
|
|
49
|
-
const tr = state.tr
|
|
34
|
+
const tr = menu.opening(state.tr, anchor);
|
|
50
35
|
if (!selectionCovers(state, cellPos)) {
|
|
51
36
|
tr.setSelection(TextSelection.near(tr.doc.resolve(cellPos + 1)));
|
|
52
37
|
}
|
|
53
38
|
view.dispatch(tr);
|
|
54
39
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const cellPos = cellAtEvent(view, event.target);
|
|
73
|
-
if (cellPos === null) return false;
|
|
74
|
-
event.preventDefault();
|
|
75
|
-
openMenu(view, cellPos, {
|
|
76
|
-
clientX: event.clientX,
|
|
77
|
-
clientY: event.clientY
|
|
78
|
-
});
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
40
|
+
var menu = panelPlugin({
|
|
41
|
+
name: "docxEditorTableMenu",
|
|
42
|
+
isAnchor: isTableMenuAnchor,
|
|
43
|
+
// Once the document changes, the cell the menu pointed at may no longer be there
|
|
44
|
+
onDocChange: "close",
|
|
45
|
+
props: {
|
|
46
|
+
handleDOMEvents: {
|
|
47
|
+
contextmenu(view, event) {
|
|
48
|
+
if (editsShut(view.state)) return false;
|
|
49
|
+
const cellPos = cellAtEvent(view, event.target);
|
|
50
|
+
if (cellPos === null) return false;
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
openMenu(view, cellPos, {
|
|
53
|
+
clientX: event.clientX,
|
|
54
|
+
clientY: event.clientY
|
|
55
|
+
});
|
|
56
|
+
return true;
|
|
81
57
|
}
|
|
82
58
|
}
|
|
83
|
-
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
function tableMenuAnchor(state) {
|
|
62
|
+
return menu.anchor(state);
|
|
63
|
+
}
|
|
64
|
+
var closeTableMenu = menu.close;
|
|
65
|
+
function tableContextMenu() {
|
|
66
|
+
return menu.plugin;
|
|
84
67
|
}
|
|
85
68
|
export {
|
|
86
69
|
closeTableMenu,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Replaces the native text context menu and moves the selection to the clicked position. Unselected
|
|
3
3
|
* table cells are left to the table context-menu plugin.
|
|
4
4
|
*/
|
|
5
|
-
import { type Command, type EditorState, Plugin } from "prosemirror-state";
|
|
5
|
+
import { type Command, type EditorState, type Plugin } from "prosemirror-state";
|
|
6
6
|
/** Where the menu should stand (viewport coordinates) */
|
|
7
7
|
export interface TextMenuAnchor {
|
|
8
8
|
clientX: number;
|
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
// src/editor/plugins/textContextMenu.ts
|
|
2
2
|
import {
|
|
3
|
-
Plugin,
|
|
4
|
-
PluginKey,
|
|
5
3
|
TextSelection
|
|
6
4
|
} from "prosemirror-state";
|
|
7
5
|
import { CellSelection } from "prosemirror-tables";
|
|
8
6
|
import { editingProtection, editsShut } from "../../schema/protectionState.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return typeof value === "object" && value !== null;
|
|
13
|
-
}
|
|
14
|
-
function toAnchor(value) {
|
|
15
|
-
if (!isRecord(value)) return null;
|
|
7
|
+
import { panelPlugin } from "./panelState.js";
|
|
8
|
+
function isTextMenuAnchor(value) {
|
|
9
|
+
if (typeof value !== "object" || value === null) return false;
|
|
16
10
|
const { clientX, clientY } = value;
|
|
17
|
-
|
|
18
|
-
return { clientX, clientY };
|
|
19
|
-
}
|
|
20
|
-
function textMenuAnchor(state) {
|
|
21
|
-
return menuKey.getState(state) ?? null;
|
|
11
|
+
return typeof clientX === "number" && typeof clientY === "number";
|
|
22
12
|
}
|
|
23
|
-
var closeTextMenu = (state, dispatch) => {
|
|
24
|
-
if (textMenuAnchor(state) === null) return false;
|
|
25
|
-
if (dispatch) dispatch(state.tr.setMeta(menuKey, CLOSE));
|
|
26
|
-
return true;
|
|
27
|
-
};
|
|
28
13
|
function isInEditor(view, target) {
|
|
29
14
|
return target instanceof Node && view.dom.contains(target);
|
|
30
15
|
}
|
|
@@ -50,43 +35,41 @@ function forTableMenu(view, target, spot) {
|
|
|
50
35
|
return aboutCells && isInTableCell(view, target);
|
|
51
36
|
}
|
|
52
37
|
function openMenu(state, event, spot) {
|
|
53
|
-
const tr = state.tr
|
|
38
|
+
const tr = menu.opening(state.tr, {
|
|
54
39
|
clientX: event.clientX,
|
|
55
40
|
clientY: event.clientY
|
|
56
41
|
});
|
|
57
42
|
if (isInSelection(state, spot)) return tr;
|
|
58
43
|
return tr.setSelection(TextSelection.near(tr.doc.resolve(spot)));
|
|
59
44
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
props: {
|
|
74
|
-
handleDOMEvents: {
|
|
75
|
-
contextmenu(view, event) {
|
|
76
|
-
if (editingProtection(view.state) === "readOnly") return false;
|
|
77
|
-
if (!isInEditor(view, event.target)) return false;
|
|
78
|
-
const spot = clickedSpot(view, event);
|
|
79
|
-
if (editsShut(view.state) && !isInSelection(view.state, spot)) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
if (forTableMenu(view, event.target, spot)) return false;
|
|
83
|
-
event.preventDefault();
|
|
84
|
-
view.dispatch(openMenu(view.state, event, spot));
|
|
85
|
-
return true;
|
|
45
|
+
var menu = panelPlugin({
|
|
46
|
+
name: "docxEditorTextMenu",
|
|
47
|
+
isAnchor: isTextMenuAnchor,
|
|
48
|
+
// Once the document changes, what the menu was aimed at may no longer be there
|
|
49
|
+
onDocChange: "close",
|
|
50
|
+
props: {
|
|
51
|
+
handleDOMEvents: {
|
|
52
|
+
contextmenu(view, event) {
|
|
53
|
+
if (editingProtection(view.state) === "readOnly") return false;
|
|
54
|
+
if (!isInEditor(view, event.target)) return false;
|
|
55
|
+
const spot = clickedSpot(view, event);
|
|
56
|
+
if (editsShut(view.state) && !isInSelection(view.state, spot)) {
|
|
57
|
+
return false;
|
|
86
58
|
}
|
|
59
|
+
if (forTableMenu(view, event.target, spot)) return false;
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
view.dispatch(openMenu(view.state, event, spot));
|
|
62
|
+
return true;
|
|
87
63
|
}
|
|
88
64
|
}
|
|
89
|
-
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
function textMenuAnchor(state) {
|
|
68
|
+
return menu.anchor(state);
|
|
69
|
+
}
|
|
70
|
+
var closeTextMenu = menu.close;
|
|
71
|
+
function textContextMenu() {
|
|
72
|
+
return menu.plugin;
|
|
90
73
|
}
|
|
91
74
|
export {
|
|
92
75
|
closeTextMenu,
|
package/dist/ooxml/fragment.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* What a raw XML string has to look like to be let into the attr that carries it.
|
|
17
17
|
*
|
|
18
18
|
* `element` is a whole element: a properties fragment, a drawing, an annotation reference.
|
|
19
|
+
* `elements` is a run of them standing side by side, as a table carries between its rows.
|
|
19
20
|
* `attributes` is what stood inside an opening tag, as `attrString` writes it.
|
|
20
21
|
* `openTag` is an opening tag with everything it wrapped cut away, which the writer puts back by
|
|
21
22
|
* appending the closing text `closedBy` names. `head` is what may still stand between the two:
|
|
@@ -24,6 +25,8 @@
|
|
|
24
25
|
export type RawXmlShape = {
|
|
25
26
|
kind: "element";
|
|
26
27
|
names: readonly string[] | "any";
|
|
28
|
+
} | {
|
|
29
|
+
kind: "elements";
|
|
27
30
|
} | {
|
|
28
31
|
kind: "attributes";
|
|
29
32
|
} | {
|
|
@@ -42,6 +45,14 @@ export declare function ELEMENT(...names: string[]): RawXmlShape;
|
|
|
42
45
|
* turn a document the editor reads today into a demoted one.
|
|
43
46
|
*/
|
|
44
47
|
export declare const ANY_ELEMENT: RawXmlShape;
|
|
48
|
+
/**
|
|
49
|
+
* A run of one or more elements standing side by side, of any name, in any namespace.
|
|
50
|
+
*
|
|
51
|
+
* A table carries the markers that stood between its rows, and a row those between its cells
|
|
52
|
+
* (`docx/importTable`), and there may be several of them in a row: the end of one bookmark and the
|
|
53
|
+
* start of the next stand together with nothing between them.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ANY_ELEMENTS: RawXmlShape;
|
|
45
56
|
/** The attributes of an opening tag, with neither the tag nor anything it held */
|
|
46
57
|
export declare const ATTRIBUTES: RawXmlShape;
|
|
47
58
|
/**
|
package/dist/ooxml/fragment.js
CHANGED
|
@@ -10,17 +10,22 @@ function ELEMENT(...names) {
|
|
|
10
10
|
return { kind: "element", names };
|
|
11
11
|
}
|
|
12
12
|
var ANY_ELEMENT = { kind: "element", names: "any" };
|
|
13
|
+
var ANY_ELEMENTS = { kind: "elements" };
|
|
13
14
|
var ATTRIBUTES = { kind: "attributes" };
|
|
14
|
-
function
|
|
15
|
+
function wrappedElements(xml) {
|
|
15
16
|
let root;
|
|
16
17
|
try {
|
|
17
18
|
root = parseXml(`<x ${namespaceDecls(xml)}>${xml}</x>`).documentElement;
|
|
18
19
|
} catch {
|
|
19
20
|
return null;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return
|
|
22
|
+
const elements = elementChildren(root);
|
|
23
|
+
if (root.childNodes.length !== elements.length) return null;
|
|
24
|
+
return elements.some(rebindsReservedPrefix) ? null : elements;
|
|
25
|
+
}
|
|
26
|
+
function loneElement(xml) {
|
|
27
|
+
const elements = wrappedElements(xml);
|
|
28
|
+
return elements?.length === 1 ? elements[0] : null;
|
|
24
29
|
}
|
|
25
30
|
function isNamed(el, names) {
|
|
26
31
|
if (!names.includes(el.localName)) return false;
|
|
@@ -54,6 +59,10 @@ function holdsShape(shape, value) {
|
|
|
54
59
|
if (el === null) return false;
|
|
55
60
|
return shape.names === "any" || isNamed(el, shape.names);
|
|
56
61
|
}
|
|
62
|
+
case "elements": {
|
|
63
|
+
const elements = wrappedElements(value);
|
|
64
|
+
return elements !== null && elements.length > 0;
|
|
65
|
+
}
|
|
57
66
|
case "attributes": {
|
|
58
67
|
const el = loneElement(`<y ${value}></y>`);
|
|
59
68
|
return el !== null && el.childNodes.length === 0;
|
|
@@ -70,6 +79,7 @@ function acceptRawXml(shape, value) {
|
|
|
70
79
|
}
|
|
71
80
|
export {
|
|
72
81
|
ANY_ELEMENT,
|
|
82
|
+
ANY_ELEMENTS,
|
|
73
83
|
ATTRIBUTES,
|
|
74
84
|
ELEMENT,
|
|
75
85
|
acceptRawXml
|
package/dist/ooxml/image.d.ts
CHANGED
|
@@ -67,11 +67,10 @@ export interface DrawingPicture {
|
|
|
67
67
|
*/
|
|
68
68
|
export declare function readDrawingPicture(drawing: Element): DrawingPicture | null;
|
|
69
69
|
/**
|
|
70
|
-
* The same drawing XML with
|
|
70
|
+
* The same drawing XML with its inline extent and its picture transform's extent set to this size.
|
|
71
71
|
*
|
|
72
|
-
* Only those two elements are touched; every other byte
|
|
73
|
-
*
|
|
74
|
-
* unchanged, so nothing has to keep track of whether a resize happened.
|
|
72
|
+
* Only those two elements' size attributes are touched; every other byte stays where it was.
|
|
73
|
+
* Values already equal to the requested size keep their original spelling as well.
|
|
75
74
|
*/
|
|
76
75
|
export declare function withExtent(xml: string, extent: ImageExtent): string;
|
|
77
76
|
/** A picture that was inserted during editing and has no original XML to go back to */
|