@blocknote/core 0.9.3 → 0.9.5

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.
Files changed (53) hide show
  1. package/dist/blocknote.js +2603 -2267
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +7 -7
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +2 -2
  7. package/src/BlockNoteEditor.ts +44 -12
  8. package/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +21 -21
  9. package/src/api/blockManipulation/blockManipulation.test.ts +8 -11
  10. package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +3 -3
  11. package/src/api/formatConversions/formatConversions.test.ts +5 -5
  12. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +3 -3
  13. package/src/api/nodeConversions/nodeConversions.test.ts +10 -4
  14. package/src/api/nodeConversions/nodeConversions.ts +9 -7
  15. package/src/api/nodeConversions/testUtil.ts +3 -3
  16. package/src/editor.module.css +1 -1
  17. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +5 -3
  18. package/src/extensions/BackgroundColor/BackgroundColorMark.ts +2 -1
  19. package/src/extensions/Blocks/NonEditableBlockPlugin.ts +17 -0
  20. package/src/extensions/Blocks/api/block.ts +62 -17
  21. package/src/extensions/Blocks/api/blockTypes.ts +79 -27
  22. package/src/extensions/Blocks/api/defaultBlocks.ts +13 -41
  23. package/src/extensions/Blocks/api/defaultProps.ts +16 -0
  24. package/src/extensions/Blocks/nodes/Block.module.css +78 -24
  25. package/src/extensions/Blocks/nodes/BlockContainer.ts +66 -42
  26. package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +59 -13
  27. package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.ts +305 -0
  28. package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts +13 -0
  29. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +24 -2
  30. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +146 -120
  31. package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +12 -2
  32. package/src/extensions/ImageToolbar/ImageToolbarPlugin.ts +239 -0
  33. package/src/extensions/SlashMenu/defaultSlashMenuItems.ts +47 -6
  34. package/src/extensions/TextColor/TextColorExtension.ts +4 -3
  35. package/src/extensions/TextColor/TextColorMark.ts +2 -1
  36. package/src/index.ts +4 -0
  37. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +4 -0
  38. package/types/src/BlockNoteEditor.d.ts +9 -0
  39. package/types/src/BlockNoteExtensions.d.ts +1 -1
  40. package/types/src/extensions/Blocks/api/block.d.ts +7 -8
  41. package/types/src/extensions/Blocks/api/blockTypes.d.ts +29 -20
  42. package/types/src/extensions/Blocks/api/defaultBlocks.d.ts +55 -51
  43. package/types/src/extensions/Blocks/api/defaultProps.d.ts +2 -2
  44. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +43 -9
  45. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.d.ts +2 -2
  46. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +1 -0
  47. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +35 -9
  48. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +35 -9
  49. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +36 -1
  50. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +1 -1
  51. package/types/src/index.d.ts +4 -0
  52. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +1 -1
  53. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/Image.d.ts +0 -6
@@ -1,140 +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 NumberedListItemBlockContent =
9
- createTipTapBlock<"numberedListItem">({
10
- name: "numberedListItem",
11
- content: "inline*",
12
-
13
- addAttributes() {
14
- return {
15
- index: {
16
- default: null,
17
- parseHTML: (element) => element.getAttribute("data-index"),
18
- renderHTML: (attributes) => {
19
- return {
20
- "data-index": attributes.index,
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
- addInputRules() {
28
- return [
29
- // Creates an ordered list when starting with "1.".
30
- new InputRule({
31
- find: new RegExp(`^1\\.\\s$`),
32
- handler: ({ state, chain, range }) => {
33
- chain()
34
- .BNUpdateBlock(state.selection.from, {
35
- type: "numberedListItem",
36
- props: {},
37
- })
38
- // Removes the "1." characters used to set the list.
39
- .deleteRange({ from: range.from, to: range.to });
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
- addKeyboardShortcuts() {
46
- return {
47
- Enter: () => handleEnter(this.editor),
48
- };
49
- },
50
-
51
- addProseMirrorPlugins() {
52
- return [NumberedListIndexingPlugin()];
53
- },
54
-
55
- parseHTML() {
56
- return [
57
- // Case for regular HTML list structure.
58
- // (e.g.: when pasting from other apps)
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
- const parent = element.parentElement;
85
+ const parent = element.parentElement;
67
86
 
68
- if (parent === null) {
69
- return false;
70
- }
87
+ if (parent === null) {
88
+ return false;
89
+ }
71
90
 
72
- if (parent.tagName === "OL") {
73
- return {};
74
- }
91
+ if (parent.tagName === "OL") {
92
+ return {};
93
+ }
75
94
 
76
- return false;
77
- },
78
- node: "numberedListItem",
95
+ return false;
79
96
  },
80
- // Case for BlockNote list structure.
81
- // (e.g.: when pasting from blocknote)
82
- {
83
- tag: "p",
84
- getAttrs: (element) => {
85
- if (typeof element === "string") {
86
- return false;
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
- const parent = element.parentElement;
108
+ const parent = element.parentElement;
90
109
 
91
- if (parent === null) {
92
- return false;
93
- }
110
+ if (parent === null) {
111
+ return false;
112
+ }
94
113
 
95
- if (
96
- parent.getAttribute("data-content-type") === "numberedListItem"
97
- ) {
98
- return {};
99
- }
114
+ if (parent.getAttribute("data-content-type") === "numberedListItem") {
115
+ return {};
116
+ }
100
117
 
101
- return false;
102
- },
103
- priority: 300,
104
- node: "numberedListItem",
118
+ return false;
105
119
  },
106
- ];
107
- },
108
-
109
- renderHTML({ HTMLAttributes }) {
110
- const blockContentDOMAttributes =
111
- this.options.domAttributes?.blockContent || {};
112
- const inlineContentDOMAttributes =
113
- this.options.domAttributes?.inlineContent || {};
114
-
115
- return [
116
- "div",
117
- mergeAttributes(HTMLAttributes, {
118
- ...blockContentDOMAttributes,
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,
119
148
  class: mergeCSSClasses(
120
- styles.blockContent,
121
- blockContentDOMAttributes.class
149
+ styles.inlineContent,
150
+ inlineContentDOMAttributes.class
122
151
  ),
123
- "data-content-type": this.name,
124
- }),
125
- // we use a <p> tag, because for <li> tags we'd need to add a <ul> parent for around siblings to be semantically correct,
126
- // which would be quite cumbersome
127
- [
128
- "p",
129
- {
130
- ...inlineContentDOMAttributes,
131
- class: mergeCSSClasses(
132
- styles.inlineContent,
133
- inlineContentDOMAttributes.class
134
- ),
135
- },
136
- 0,
137
- ],
138
- ];
139
- },
140
- });
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
+ >;
@@ -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 ParagraphBlockContent = createTipTapBlock({
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("1")) {
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: "1" },
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("2")) {
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: "2" },
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("3")) {
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: "3" },
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: "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
- : "default",
26
+ : defaultProps.textColor.default,
26
27
  renderHTML: (attributes) =>
27
- attributes.textColor !== "default" && {
28
+ attributes.textColor !== defaultProps.textColor.default && {
28
29
  "data-text-color": attributes.textColor,
29
30
  },
30
31
  },