@blocknote/core 0.9.2 → 0.9.4
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/dist/blocknote.js +1562 -1240
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +5 -5
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/BlockNoteEditor.ts +44 -12
- package/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +21 -21
- package/src/api/blockManipulation/blockManipulation.test.ts +8 -11
- package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +3 -3
- package/src/api/formatConversions/formatConversions.test.ts +5 -5
- package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +3 -3
- package/src/api/nodeConversions/nodeConversions.test.ts +10 -4
- package/src/api/nodeConversions/nodeConversions.ts +9 -7
- package/src/api/nodeConversions/testUtil.ts +3 -3
- package/src/editor.module.css +1 -1
- package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +5 -3
- package/src/extensions/BackgroundColor/BackgroundColorMark.ts +2 -1
- package/src/extensions/Blocks/NonEditableBlockPlugin.ts +17 -0
- package/src/extensions/Blocks/api/block.ts +29 -16
- package/src/extensions/Blocks/api/blockTypes.ts +79 -27
- package/src/extensions/Blocks/api/defaultBlocks.ts +13 -41
- package/src/extensions/Blocks/api/defaultProps.ts +16 -0
- package/src/extensions/Blocks/nodes/Block.module.css +78 -24
- package/src/extensions/Blocks/nodes/BlockContainer.ts +45 -45
- package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +61 -13
- package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.ts +305 -0
- package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts +13 -0
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +26 -2
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +146 -118
- package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +12 -2
- package/src/extensions/ImageToolbar/ImageToolbarPlugin.ts +239 -0
- package/src/extensions/SlashMenu/defaultSlashMenuItems.ts +47 -6
- package/src/extensions/TextColor/TextColorExtension.ts +4 -3
- package/src/extensions/TextColor/TextColorMark.ts +2 -1
- package/src/extensions/TrailingNode/TrailingNodeExtension.ts +13 -1
- package/src/index.ts +4 -0
- package/types/src/BlockNoteEditor.d.ts +9 -0
- package/types/src/BlockNoteExtensions.d.ts +1 -1
- package/types/src/extensions/Blocks/NonEditableBlockPlugin.d.ts +2 -0
- package/types/src/extensions/Blocks/api/block.d.ts +7 -8
- package/types/src/extensions/Blocks/api/blockTypes.d.ts +29 -20
- package/types/src/extensions/Blocks/api/defaultBlocks.d.ts +55 -51
- package/types/src/extensions/Blocks/api/defaultProps.d.ts +14 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +43 -9
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/Image.d.ts +6 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.d.ts +37 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +1 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.d.ts +1 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +35 -9
- package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +35 -9
- package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +36 -1
- package/types/src/extensions/ImageToolbar/ImageToolbarPlugin.d.ts +36 -0
- package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +1 -1
- package/types/src/index.d.ts +4 -0
- package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +1 -1
- package/types/src/extensions/Blocks/nodes/TableCell.d.ts +0 -5
- package/types/src/extensions/Blocks/nodes/TableRow.d.ts +0 -5
|
@@ -1,138 +1,166 @@
|
|
|
1
1
|
import { InputRule, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { defaultProps } from "../../../../api/defaultProps";
|
|
2
3
|
import { createTipTapBlock } from "../../../../api/block";
|
|
4
|
+
import { BlockSpec, PropSchema } from "../../../../api/blockTypes";
|
|
5
|
+
import { mergeCSSClasses } from "../../../../../../shared/utils";
|
|
3
6
|
import { handleEnter } from "../ListItemKeyboardShortcuts";
|
|
4
7
|
import { NumberedListIndexingPlugin } from "./NumberedListIndexingPlugin";
|
|
5
8
|
import styles from "../../../Block.module.css";
|
|
6
|
-
import { mergeCSSClasses } from "../../../../../../shared/utils";
|
|
7
9
|
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
export const numberedListItemPropSchema = {
|
|
11
|
+
...defaultProps,
|
|
12
|
+
} satisfies PropSchema;
|
|
13
|
+
|
|
14
|
+
const NumberedListItemBlockContent = createTipTapBlock<
|
|
15
|
+
"numberedListItem",
|
|
16
|
+
true
|
|
17
|
+
>({
|
|
18
|
+
name: "numberedListItem",
|
|
19
|
+
content: "inline*",
|
|
20
|
+
|
|
21
|
+
addAttributes() {
|
|
22
|
+
return {
|
|
23
|
+
index: {
|
|
24
|
+
default: null,
|
|
25
|
+
parseHTML: (element) => element.getAttribute("data-index"),
|
|
26
|
+
renderHTML: (attributes) => {
|
|
27
|
+
return {
|
|
28
|
+
"data-index": attributes.index,
|
|
29
|
+
};
|
|
23
30
|
},
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
addInputRules() {
|
|
36
|
+
return [
|
|
37
|
+
// Creates an ordered list when starting with "1.".
|
|
38
|
+
new InputRule({
|
|
39
|
+
find: new RegExp(`^1\\.\\s$`),
|
|
40
|
+
handler: ({ state, chain, range }) => {
|
|
41
|
+
chain()
|
|
42
|
+
.BNUpdateBlock(state.selection.from, {
|
|
43
|
+
type: "numberedListItem",
|
|
44
|
+
props: {},
|
|
45
|
+
})
|
|
46
|
+
// Removes the "1." characters used to set the list.
|
|
47
|
+
.deleteRange({ from: range.from, to: range.to });
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
];
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
addKeyboardShortcuts() {
|
|
54
|
+
return {
|
|
55
|
+
Enter: () => handleEnter(this.editor),
|
|
56
|
+
"Mod-Shift-8": () =>
|
|
57
|
+
this.editor.commands.BNUpdateBlock<{
|
|
58
|
+
numberedListItem: BlockSpec<
|
|
59
|
+
"numberedListItem",
|
|
60
|
+
typeof numberedListItemPropSchema,
|
|
61
|
+
true
|
|
62
|
+
>;
|
|
63
|
+
}>(this.editor.state.selection.anchor, {
|
|
64
|
+
type: "numberedListItem",
|
|
65
|
+
props: {},
|
|
41
66
|
}),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
tag: "li",
|
|
61
|
-
getAttrs: (element) => {
|
|
62
|
-
if (typeof element === "string") {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
addProseMirrorPlugins() {
|
|
71
|
+
return [NumberedListIndexingPlugin()];
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
parseHTML() {
|
|
75
|
+
return [
|
|
76
|
+
// Case for regular HTML list structure.
|
|
77
|
+
// (e.g.: when pasting from other apps)
|
|
78
|
+
{
|
|
79
|
+
tag: "li",
|
|
80
|
+
getAttrs: (element) => {
|
|
81
|
+
if (typeof element === "string") {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
65
84
|
|
|
66
|
-
|
|
85
|
+
const parent = element.parentElement;
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
if (parent === null) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
71
90
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
if (parent.tagName === "OL") {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
node: "numberedListItem",
|
|
95
|
+
return false;
|
|
79
96
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
node: "numberedListItem",
|
|
98
|
+
},
|
|
99
|
+
// Case for BlockNote list structure.
|
|
100
|
+
// (e.g.: when pasting from blocknote)
|
|
101
|
+
{
|
|
102
|
+
tag: "p",
|
|
103
|
+
getAttrs: (element) => {
|
|
104
|
+
if (typeof element === "string") {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
88
107
|
|
|
89
|
-
|
|
108
|
+
const parent = element.parentElement;
|
|
90
109
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
if (parent === null) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return {};
|
|
99
|
-
}
|
|
114
|
+
if (parent.getAttribute("data-content-type") === "numberedListItem") {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
100
117
|
|
|
101
|
-
|
|
102
|
-
},
|
|
103
|
-
priority: 300,
|
|
104
|
-
node: "numberedListItem",
|
|
118
|
+
return false;
|
|
105
119
|
},
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
priority: 300,
|
|
121
|
+
node: "numberedListItem",
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
renderHTML({ HTMLAttributes }) {
|
|
127
|
+
const blockContentDOMAttributes =
|
|
128
|
+
this.options.domAttributes?.blockContent || {};
|
|
129
|
+
const inlineContentDOMAttributes =
|
|
130
|
+
this.options.domAttributes?.inlineContent || {};
|
|
131
|
+
|
|
132
|
+
return [
|
|
133
|
+
"div",
|
|
134
|
+
mergeAttributes(HTMLAttributes, {
|
|
135
|
+
...blockContentDOMAttributes,
|
|
136
|
+
class: mergeCSSClasses(
|
|
137
|
+
styles.blockContent,
|
|
138
|
+
blockContentDOMAttributes.class
|
|
139
|
+
),
|
|
140
|
+
"data-content-type": this.name,
|
|
141
|
+
}),
|
|
142
|
+
// we use a <p> tag, because for <li> tags we'd need to add a <ul> parent for around siblings to be semantically correct,
|
|
143
|
+
// which would be quite cumbersome
|
|
144
|
+
[
|
|
145
|
+
"p",
|
|
146
|
+
{
|
|
147
|
+
...inlineContentDOMAttributes,
|
|
118
148
|
class: mergeCSSClasses(
|
|
119
|
-
styles.
|
|
120
|
-
|
|
149
|
+
styles.inlineContent,
|
|
150
|
+
inlineContentDOMAttributes.class
|
|
121
151
|
),
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
},
|
|
138
|
-
});
|
|
152
|
+
},
|
|
153
|
+
0,
|
|
154
|
+
],
|
|
155
|
+
];
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export const NumberedListItem = {
|
|
160
|
+
node: NumberedListItemBlockContent,
|
|
161
|
+
propSchema: numberedListItemPropSchema,
|
|
162
|
+
} satisfies BlockSpec<
|
|
163
|
+
"numberedListItem",
|
|
164
|
+
typeof numberedListItemPropSchema,
|
|
165
|
+
true
|
|
166
|
+
>;
|
package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { defaultProps } from "../../../api/defaultProps";
|
|
2
3
|
import { createTipTapBlock } from "../../../api/block";
|
|
3
|
-
import styles from "../../Block.module.css";
|
|
4
4
|
import { mergeCSSClasses } from "../../../../../shared/utils";
|
|
5
|
+
import styles from "../../Block.module.css";
|
|
5
6
|
|
|
6
|
-
export const
|
|
7
|
+
export const paragraphPropSchema = {
|
|
8
|
+
...defaultProps,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const ParagraphBlockContent = createTipTapBlock<"paragraph", true>({
|
|
7
12
|
name: "paragraph",
|
|
8
13
|
content: "inline*",
|
|
9
14
|
|
|
@@ -50,3 +55,8 @@ export const ParagraphBlockContent = createTipTapBlock({
|
|
|
50
55
|
];
|
|
51
56
|
},
|
|
52
57
|
});
|
|
58
|
+
|
|
59
|
+
export const Paragraph = {
|
|
60
|
+
node: ParagraphBlockContent,
|
|
61
|
+
propSchema: paragraphPropSchema,
|
|
62
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { Node as PMNode } from "prosemirror-model";
|
|
2
|
+
import { EditorState, Plugin, PluginKey } from "prosemirror-state";
|
|
3
|
+
import { EditorView } from "prosemirror-view";
|
|
4
|
+
import {
|
|
5
|
+
BaseUiElementCallbacks,
|
|
6
|
+
BaseUiElementState,
|
|
7
|
+
BlockNoteEditor,
|
|
8
|
+
BlockSchema,
|
|
9
|
+
BlockSpec,
|
|
10
|
+
SpecificBlock,
|
|
11
|
+
} from "../..";
|
|
12
|
+
import { EventEmitter } from "../../shared/EventEmitter";
|
|
13
|
+
|
|
14
|
+
export type ImageToolbarCallbacks = BaseUiElementCallbacks;
|
|
15
|
+
|
|
16
|
+
export type ImageToolbarState = BaseUiElementState & {
|
|
17
|
+
block: SpecificBlock<
|
|
18
|
+
BlockSchema & {
|
|
19
|
+
image: BlockSpec<
|
|
20
|
+
"image",
|
|
21
|
+
{
|
|
22
|
+
src: { default: string };
|
|
23
|
+
},
|
|
24
|
+
false
|
|
25
|
+
>;
|
|
26
|
+
},
|
|
27
|
+
"image"
|
|
28
|
+
>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export class ImageToolbarView {
|
|
32
|
+
private imageToolbarState?: ImageToolbarState;
|
|
33
|
+
public updateImageToolbar: () => void;
|
|
34
|
+
|
|
35
|
+
public prevWasEditable: boolean | null = null;
|
|
36
|
+
|
|
37
|
+
public shouldShow: (state: EditorState) => boolean = (state) =>
|
|
38
|
+
"node" in state.selection &&
|
|
39
|
+
(state.selection.node as PMNode).type.name === "image" &&
|
|
40
|
+
(state.selection.node as PMNode).attrs.src === "";
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
private readonly pluginKey: PluginKey,
|
|
44
|
+
private readonly pmView: EditorView,
|
|
45
|
+
updateImageToolbar: (imageToolbarState: ImageToolbarState) => void
|
|
46
|
+
) {
|
|
47
|
+
this.updateImageToolbar = () => {
|
|
48
|
+
if (!this.imageToolbarState) {
|
|
49
|
+
throw new Error("Attempting to update uninitialized image toolbar");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
updateImageToolbar(this.imageToolbarState);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
pmView.dom.addEventListener("mousedown", this.mouseDownHandler);
|
|
56
|
+
|
|
57
|
+
pmView.dom.addEventListener("dragstart", this.dragstartHandler);
|
|
58
|
+
|
|
59
|
+
pmView.dom.addEventListener("blur", this.blurHandler);
|
|
60
|
+
|
|
61
|
+
document.addEventListener("scroll", this.scrollHandler);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
mouseDownHandler = () => {
|
|
65
|
+
if (this.imageToolbarState?.show) {
|
|
66
|
+
this.imageToolbarState.show = false;
|
|
67
|
+
this.updateImageToolbar();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// For dragging the whole editor.
|
|
72
|
+
dragstartHandler = () => {
|
|
73
|
+
if (this.imageToolbarState?.show) {
|
|
74
|
+
this.imageToolbarState.show = false;
|
|
75
|
+
this.updateImageToolbar();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
blurHandler = (event: FocusEvent) => {
|
|
80
|
+
const editorWrapper = this.pmView.dom.parentElement!;
|
|
81
|
+
|
|
82
|
+
// Checks if the focus is moving to an element outside the editor. If it is,
|
|
83
|
+
// the toolbar is hidden.
|
|
84
|
+
if (
|
|
85
|
+
// An element is clicked.
|
|
86
|
+
event &&
|
|
87
|
+
event.relatedTarget &&
|
|
88
|
+
// Element is inside the editor.
|
|
89
|
+
(editorWrapper === (event.relatedTarget as Node) ||
|
|
90
|
+
editorWrapper.contains(event.relatedTarget as Node))
|
|
91
|
+
) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.imageToolbarState?.show) {
|
|
96
|
+
this.imageToolbarState.show = false;
|
|
97
|
+
this.updateImageToolbar();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
scrollHandler = () => {
|
|
102
|
+
if (this.imageToolbarState?.show) {
|
|
103
|
+
const blockElement = document.querySelector(
|
|
104
|
+
`[data-node-type="blockContainer"][data-id="${this.imageToolbarState.block.id}"]`
|
|
105
|
+
)!;
|
|
106
|
+
|
|
107
|
+
this.imageToolbarState.referencePos =
|
|
108
|
+
blockElement.getBoundingClientRect();
|
|
109
|
+
this.updateImageToolbar();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
update(view: EditorView, prevState: EditorState) {
|
|
114
|
+
const pluginState: {
|
|
115
|
+
block: SpecificBlock<
|
|
116
|
+
BlockSchema & {
|
|
117
|
+
image: BlockSpec<
|
|
118
|
+
"image",
|
|
119
|
+
{
|
|
120
|
+
src: { default: string };
|
|
121
|
+
},
|
|
122
|
+
false
|
|
123
|
+
>;
|
|
124
|
+
},
|
|
125
|
+
"image"
|
|
126
|
+
>;
|
|
127
|
+
} = this.pluginKey.getState(view.state);
|
|
128
|
+
|
|
129
|
+
if (!this.imageToolbarState?.show && pluginState.block) {
|
|
130
|
+
const blockElement = document.querySelector(
|
|
131
|
+
`[data-node-type="blockContainer"][data-id="${pluginState.block.id}"]`
|
|
132
|
+
)!;
|
|
133
|
+
|
|
134
|
+
this.imageToolbarState = {
|
|
135
|
+
show: true,
|
|
136
|
+
referencePos: blockElement.getBoundingClientRect(),
|
|
137
|
+
block: pluginState.block,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
this.updateImageToolbar();
|
|
141
|
+
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
!view.state.selection.eq(prevState.selection) ||
|
|
147
|
+
!view.state.doc.eq(prevState.doc)
|
|
148
|
+
) {
|
|
149
|
+
if (this.imageToolbarState?.show) {
|
|
150
|
+
this.imageToolbarState.show = false;
|
|
151
|
+
|
|
152
|
+
this.updateImageToolbar();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
destroy() {
|
|
158
|
+
this.pmView.dom.removeEventListener("mousedown", this.mouseDownHandler);
|
|
159
|
+
|
|
160
|
+
this.pmView.dom.removeEventListener("dragstart", this.dragstartHandler);
|
|
161
|
+
|
|
162
|
+
this.pmView.dom.removeEventListener("blur", this.blurHandler);
|
|
163
|
+
|
|
164
|
+
document.removeEventListener("scroll", this.scrollHandler);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const imageToolbarPluginKey = new PluginKey("ImageToolbarPlugin");
|
|
169
|
+
|
|
170
|
+
export class ImageToolbarProsemirrorPlugin<
|
|
171
|
+
BSchema extends BlockSchema
|
|
172
|
+
> extends EventEmitter<any> {
|
|
173
|
+
private view: ImageToolbarView | undefined;
|
|
174
|
+
public readonly plugin: Plugin;
|
|
175
|
+
|
|
176
|
+
constructor(_editor: BlockNoteEditor<BSchema>) {
|
|
177
|
+
super();
|
|
178
|
+
this.plugin = new Plugin<{
|
|
179
|
+
block:
|
|
180
|
+
| SpecificBlock<
|
|
181
|
+
BlockSchema & {
|
|
182
|
+
image: BlockSpec<
|
|
183
|
+
"image",
|
|
184
|
+
{
|
|
185
|
+
src: { default: string };
|
|
186
|
+
},
|
|
187
|
+
false
|
|
188
|
+
>;
|
|
189
|
+
},
|
|
190
|
+
"image"
|
|
191
|
+
>
|
|
192
|
+
| undefined;
|
|
193
|
+
}>({
|
|
194
|
+
key: imageToolbarPluginKey,
|
|
195
|
+
view: (editorView) => {
|
|
196
|
+
this.view = new ImageToolbarView(
|
|
197
|
+
// editor,
|
|
198
|
+
imageToolbarPluginKey,
|
|
199
|
+
editorView,
|
|
200
|
+
(state) => {
|
|
201
|
+
this.emit("update", state);
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
return this.view;
|
|
205
|
+
},
|
|
206
|
+
state: {
|
|
207
|
+
init: () => {
|
|
208
|
+
return {
|
|
209
|
+
block: undefined,
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
apply: (transaction) => {
|
|
213
|
+
const block:
|
|
214
|
+
| SpecificBlock<
|
|
215
|
+
BlockSchema & {
|
|
216
|
+
image: BlockSpec<
|
|
217
|
+
"image",
|
|
218
|
+
{
|
|
219
|
+
src: { default: string };
|
|
220
|
+
},
|
|
221
|
+
false
|
|
222
|
+
>;
|
|
223
|
+
},
|
|
224
|
+
"image"
|
|
225
|
+
>
|
|
226
|
+
| undefined = transaction.getMeta(imageToolbarPluginKey)?.block;
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
block,
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public onUpdate(callback: (state: ImageToolbarState) => void) {
|
|
237
|
+
return this.on("update", callback);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -2,6 +2,7 @@ import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
|
2
2
|
import { BlockSchema, PartialBlock } from "../Blocks/api/blockTypes";
|
|
3
3
|
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
4
4
|
import { defaultBlockSchema } from "../Blocks/api/defaultBlocks";
|
|
5
|
+
import { imageToolbarPluginKey } from "../ImageToolbar/ImageToolbarPlugin";
|
|
5
6
|
|
|
6
7
|
function insertOrUpdateBlock<BSchema extends BlockSchema>(
|
|
7
8
|
editor: BlockNoteEditor<BSchema>,
|
|
@@ -9,6 +10,12 @@ function insertOrUpdateBlock<BSchema extends BlockSchema>(
|
|
|
9
10
|
) {
|
|
10
11
|
const currentBlock = editor.getTextCursorPosition().block;
|
|
11
12
|
|
|
13
|
+
if (currentBlock.content === undefined) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Slash Menu open in a block that doesn't contain inline content."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
if (
|
|
13
20
|
(currentBlock.content.length === 1 &&
|
|
14
21
|
currentBlock.content[0].type === "text" &&
|
|
@@ -33,40 +40,40 @@ export const getDefaultSlashMenuItems = <BSchema extends BlockSchema>(
|
|
|
33
40
|
|
|
34
41
|
if ("heading" in schema && "level" in schema.heading.propSchema) {
|
|
35
42
|
// Command for creating a level 1 heading
|
|
36
|
-
if (schema.heading.propSchema.level.values?.includes(
|
|
43
|
+
if (schema.heading.propSchema.level.values?.includes(1)) {
|
|
37
44
|
slashMenuItems.push({
|
|
38
45
|
name: "Heading",
|
|
39
46
|
aliases: ["h", "heading1", "h1"],
|
|
40
47
|
execute: (editor) =>
|
|
41
48
|
insertOrUpdateBlock(editor, {
|
|
42
49
|
type: "heading",
|
|
43
|
-
props: { level:
|
|
50
|
+
props: { level: 1 },
|
|
44
51
|
} as PartialBlock<BSchema>),
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
// Command for creating a level 2 heading
|
|
49
|
-
if (schema.heading.propSchema.level.values?.includes(
|
|
56
|
+
if (schema.heading.propSchema.level.values?.includes(2)) {
|
|
50
57
|
slashMenuItems.push({
|
|
51
58
|
name: "Heading 2",
|
|
52
59
|
aliases: ["h2", "heading2", "subheading"],
|
|
53
60
|
execute: (editor) =>
|
|
54
61
|
insertOrUpdateBlock(editor, {
|
|
55
62
|
type: "heading",
|
|
56
|
-
props: { level:
|
|
63
|
+
props: { level: 2 },
|
|
57
64
|
} as PartialBlock<BSchema>),
|
|
58
65
|
});
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
// Command for creating a level 3 heading
|
|
62
|
-
if (schema.heading.propSchema.level.values?.includes(
|
|
69
|
+
if (schema.heading.propSchema.level.values?.includes(3)) {
|
|
63
70
|
slashMenuItems.push({
|
|
64
71
|
name: "Heading 3",
|
|
65
72
|
aliases: ["h3", "heading3", "subheading"],
|
|
66
73
|
execute: (editor) =>
|
|
67
74
|
insertOrUpdateBlock(editor, {
|
|
68
75
|
type: "heading",
|
|
69
|
-
props: { level:
|
|
76
|
+
props: { level: 3 },
|
|
70
77
|
} as PartialBlock<BSchema>),
|
|
71
78
|
});
|
|
72
79
|
}
|
|
@@ -105,5 +112,39 @@ export const getDefaultSlashMenuItems = <BSchema extends BlockSchema>(
|
|
|
105
112
|
});
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
if ("image" in schema) {
|
|
116
|
+
slashMenuItems.push({
|
|
117
|
+
name: "Image",
|
|
118
|
+
aliases: [
|
|
119
|
+
"image",
|
|
120
|
+
"imageUpload",
|
|
121
|
+
"upload",
|
|
122
|
+
"img",
|
|
123
|
+
"picture",
|
|
124
|
+
"media",
|
|
125
|
+
"url",
|
|
126
|
+
"drive",
|
|
127
|
+
"dropbox",
|
|
128
|
+
],
|
|
129
|
+
execute: (editor) => {
|
|
130
|
+
insertOrUpdateBlock(editor, {
|
|
131
|
+
type: "image",
|
|
132
|
+
} as PartialBlock<BSchema>);
|
|
133
|
+
// Don't want to select the add image button, instead select the block
|
|
134
|
+
// below it
|
|
135
|
+
editor.setTextCursorPosition(
|
|
136
|
+
editor.getTextCursorPosition().nextBlock!,
|
|
137
|
+
"start"
|
|
138
|
+
);
|
|
139
|
+
// Immediately open the image toolbar
|
|
140
|
+
editor._tiptapEditor.view.dispatch(
|
|
141
|
+
editor._tiptapEditor.state.tr.setMeta(imageToolbarPluginKey, {
|
|
142
|
+
block: editor.getTextCursorPosition().prevBlock,
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
108
149
|
return slashMenuItems;
|
|
109
150
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Extension } from "@tiptap/core";
|
|
2
2
|
import { getBlockInfoFromPos } from "../Blocks/helpers/getBlockInfoFromPos";
|
|
3
|
+
import { defaultProps } from "../Blocks/api/defaultProps";
|
|
3
4
|
|
|
4
5
|
declare module "@tiptap/core" {
|
|
5
6
|
interface Commands<ReturnType> {
|
|
@@ -18,13 +19,13 @@ export const TextColorExtension = Extension.create({
|
|
|
18
19
|
types: ["blockContainer"],
|
|
19
20
|
attributes: {
|
|
20
21
|
textColor: {
|
|
21
|
-
default:
|
|
22
|
+
default: defaultProps.textColor.default,
|
|
22
23
|
parseHTML: (element) =>
|
|
23
24
|
element.hasAttribute("data-text-color")
|
|
24
25
|
? element.getAttribute("data-text-color")
|
|
25
|
-
:
|
|
26
|
+
: defaultProps.textColor.default,
|
|
26
27
|
renderHTML: (attributes) =>
|
|
27
|
-
attributes.textColor !==
|
|
28
|
+
attributes.textColor !== defaultProps.textColor.default && {
|
|
28
29
|
"data-text-color": attributes.textColor,
|
|
29
30
|
},
|
|
30
31
|
},
|