@blocknote/core 0.12.4 → 0.13.2

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 (51) hide show
  1. package/README.md +6 -2
  2. package/dist/blocknote.js +2362 -1544
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +6 -6
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +2 -2
  8. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +1 -1
  9. package/src/editor/BlockNoteEditor.ts +24 -8
  10. package/src/editor/BlockNoteExtensions.ts +23 -12
  11. package/src/editor/BlockNoteTipTapEditor.ts +9 -6
  12. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +24 -41
  13. package/src/extensions/ImagePanel/ImageToolbarPlugin.ts +27 -30
  14. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +25 -3
  15. package/src/extensions/NonEditableBlocks/NonEditableBlockPlugin.ts +43 -12
  16. package/src/extensions/Placeholder/PlaceholderPlugin.ts +95 -0
  17. package/src/extensions/SideMenu/SideMenuPlugin.ts +3 -2
  18. package/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts +3 -0
  19. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +6 -2
  20. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +18 -44
  21. package/src/extensions/TableHandles/TableHandlesPlugin.ts +1 -1
  22. package/src/i18n/dictionary.ts +17 -0
  23. package/src/i18n/locales/en.ts +196 -0
  24. package/src/i18n/locales/fr.ts +196 -0
  25. package/src/i18n/locales/index.ts +4 -0
  26. package/src/i18n/locales/nl.ts +197 -0
  27. package/src/i18n/locales/zh.ts +213 -0
  28. package/src/index.ts +4 -1
  29. package/src/pm-nodes/BlockContainer.ts +20 -2
  30. package/src/util/browser.ts +2 -2
  31. package/src/util/typescript.ts +8 -0
  32. package/types/src/editor/BlockNoteEditor.d.ts +11 -1
  33. package/types/src/editor/BlockNoteExtensions.d.ts +3 -3
  34. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +5 -4
  35. package/types/src/extensions/ImagePanel/ImageToolbarPlugin.d.ts +7 -5
  36. package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +3 -1
  37. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +3 -0
  38. package/types/src/extensions/SuggestionMenu/DefaultSuggestionItem.d.ts +2 -0
  39. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +2 -1
  40. package/types/src/i18n/dictionary.d.ts +2 -0
  41. package/types/src/i18n/locales/en.d.ts +184 -0
  42. package/types/src/i18n/locales/fr.d.ts +184 -0
  43. package/types/src/i18n/locales/index.d.ts +4 -0
  44. package/types/src/i18n/locales/nl.d.ts +2 -0
  45. package/types/src/i18n/locales/zh.d.ts +2 -0
  46. package/types/src/index.d.ts +4 -1
  47. package/types/src/pm-nodes/BlockContainer.d.ts +1 -1
  48. package/types/src/util/browser.d.ts +1 -1
  49. package/types/src/util/typescript.d.ts +1 -0
  50. package/src/extensions/Placeholder/PlaceholderExtension.ts +0 -124
  51. package/types/src/extensions/Placeholder/PlaceholderExtension.d.ts +0 -12
@@ -0,0 +1,95 @@
1
+ import { Plugin, PluginKey } from "prosemirror-state";
2
+ import { Decoration, DecorationSet } from "prosemirror-view";
3
+ import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
4
+
5
+ const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
6
+
7
+ export const PlaceholderPlugin = (
8
+ editor: BlockNoteEditor<any, any, any>,
9
+ placeholders: Record<string | "default", string>
10
+ ) => {
11
+ return new Plugin({
12
+ key: PLUGIN_KEY,
13
+ view: () => {
14
+ const styleEl = document.createElement("style");
15
+ document.head.appendChild(styleEl);
16
+ const styleSheet = styleEl.sheet!;
17
+
18
+ const getBaseSelector = (additionalSelectors = "") =>
19
+ `.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak):before`;
20
+
21
+ const getSelector = (
22
+ blockType: string | "default",
23
+ mustBeFocused = true
24
+ ) => {
25
+ const mustBeFocusedSelector = mustBeFocused
26
+ ? `[data-is-empty-and-focused]`
27
+ : ``;
28
+
29
+ if (blockType === "default") {
30
+ return getBaseSelector(mustBeFocusedSelector);
31
+ }
32
+
33
+ const blockTypeSelector = `[data-content-type="${blockType}"]`;
34
+ return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
35
+ };
36
+
37
+ for (const [blockType, placeholder] of Object.entries(placeholders)) {
38
+ const mustBeFocused = blockType === "default";
39
+
40
+ styleSheet.insertRule(
41
+ `${getSelector(blockType, mustBeFocused)}{ content: ${JSON.stringify(
42
+ placeholder
43
+ )}; }`
44
+ );
45
+
46
+ // For some reason, the placeholders which show when the block is focused
47
+ // take priority over ones which show depending on block type, so we need
48
+ // to make sure the block specific ones are also used when the block is
49
+ // focused.
50
+ if (!mustBeFocused) {
51
+ styleSheet.insertRule(
52
+ `${getSelector(blockType, true)}{ content: ${JSON.stringify(
53
+ placeholder
54
+ )}; }`
55
+ );
56
+ }
57
+ }
58
+
59
+ return {
60
+ destroy: () => {
61
+ document.head.removeChild(styleEl);
62
+ },
63
+ };
64
+ },
65
+ props: {
66
+ // TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
67
+ decorations: (state) => {
68
+ const { doc, selection } = state;
69
+
70
+ if (!editor.isEditable) {
71
+ return;
72
+ }
73
+
74
+ if (!selection.empty) {
75
+ return;
76
+ }
77
+
78
+ const $pos = selection.$anchor;
79
+ const node = $pos.parent;
80
+
81
+ if (node.content.size > 0) {
82
+ return null;
83
+ }
84
+
85
+ const before = $pos.before();
86
+
87
+ const dec = Decoration.node(before, before + node.nodeSize, {
88
+ "data-is-empty-and-focused": "true",
89
+ });
90
+
91
+ return DecorationSet.create(doc, [dec]);
92
+ },
93
+ },
94
+ });
95
+ };
@@ -373,11 +373,12 @@ export class SideMenuView<
373
373
  };
374
374
 
375
375
  onKeyDown = (_event: KeyboardEvent) => {
376
- if (this.state?.show) {
376
+ if (this.state?.show && this.editor.isFocused()) {
377
+ // Typing in editor should hide side menu
377
378
  this.state.show = false;
378
379
  this.emitUpdate(this.state);
380
+ this.menuFrozen = false;
379
381
  }
380
- this.menuFrozen = false;
381
382
  };
382
383
 
383
384
  onMouseDown = (_event: MouseEvent) => {
@@ -1,4 +1,7 @@
1
+ import type { Dictionary } from "../../i18n/dictionary";
2
+
1
3
  export type DefaultSuggestionItem = {
4
+ key: keyof Dictionary["slash_menu"];
2
5
  title: string;
3
6
  onItemClick: () => void;
4
7
  subtext?: string;
@@ -3,8 +3,8 @@ import { EditorState, Plugin, PluginKey } from "prosemirror-state";
3
3
  import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
4
4
 
5
5
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
6
- import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
7
6
  import { UiElementPosition } from "../../extensions-shared/UiElementPosition";
7
+ import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
8
8
  import { EventEmitter } from "../../util/EventEmitter";
9
9
 
10
10
  const findBlock = findParentNode((node) => node.type.name === "blockContainer");
@@ -18,7 +18,7 @@ class SuggestionMenuView<
18
18
  I extends InlineContentSchema,
19
19
  S extends StyleSchema
20
20
  > {
21
- private state?: SuggestionMenuState;
21
+ public state?: SuggestionMenuState;
22
22
  public emitUpdate: (triggerCharacter: string) => void;
23
23
 
24
24
  pluginState: SuggestionPluginState;
@@ -339,6 +339,10 @@ export class SuggestionMenuProseMirrorPlugin<
339
339
  closeMenu = () => this.view!.closeMenu();
340
340
 
341
341
  clearQuery = () => this.view!.clearQuery();
342
+
343
+ public get shown() {
344
+ return this.view?.state?.show || false;
345
+ }
342
346
  }
343
347
 
344
348
  export function createSuggestionMenu<
@@ -83,95 +83,82 @@ export function getDefaultSlashMenuItems<
83
83
  if (checkDefaultBlockTypeInSchema("heading", editor)) {
84
84
  items.push(
85
85
  {
86
- title: "Heading 1",
87
86
  onItemClick: () => {
88
87
  insertOrUpdateBlock(editor, {
89
88
  type: "heading",
90
89
  props: { level: 1 },
91
90
  });
92
91
  },
93
- subtext: "Used for a top-level heading",
94
92
  badge: formatKeyboardShortcut("Mod-Alt-1"),
95
- aliases: ["h", "heading1", "h1"],
96
- group: "Headings",
93
+ key: "heading",
94
+ ...editor.dictionary.slash_menu.heading,
97
95
  },
98
96
  {
99
- title: "Heading 2",
100
97
  onItemClick: () => {
101
98
  insertOrUpdateBlock(editor, {
102
99
  type: "heading",
103
100
  props: { level: 2 },
104
101
  });
105
102
  },
106
- subtext: "Used for key sections",
107
103
  badge: formatKeyboardShortcut("Mod-Alt-2"),
108
- aliases: ["h2", "heading2", "subheading"],
109
- group: "Headings",
104
+ key: "heading_2",
105
+ ...editor.dictionary.slash_menu.heading_2,
110
106
  },
111
107
  {
112
- title: "Heading 3",
113
108
  onItemClick: () => {
114
109
  insertOrUpdateBlock(editor, {
115
110
  type: "heading",
116
111
  props: { level: 3 },
117
112
  });
118
113
  },
119
- subtext: "Used for subsections and group headings",
120
114
  badge: formatKeyboardShortcut("Mod-Alt-3"),
121
- aliases: ["h3", "heading3", "subheading"],
122
- group: "Headings",
115
+ key: "heading_3",
116
+ ...editor.dictionary.slash_menu.heading_3,
123
117
  }
124
118
  );
125
119
  }
126
120
 
127
121
  if (checkDefaultBlockTypeInSchema("numberedListItem", editor)) {
128
122
  items.push({
129
- title: "Numbered List",
130
123
  onItemClick: () => {
131
124
  insertOrUpdateBlock(editor, {
132
125
  type: "numberedListItem",
133
126
  });
134
127
  },
135
- subtext: "Used to display a numbered list",
136
128
  badge: formatKeyboardShortcut("Mod-Shift-7"),
137
- aliases: ["ol", "li", "list", "numberedlist", "numbered list"],
138
- group: "Basic blocks",
129
+ key: "numbered_list",
130
+ ...editor.dictionary.slash_menu.numbered_list,
139
131
  });
140
132
  }
141
133
 
142
134
  if (checkDefaultBlockTypeInSchema("bulletListItem", editor)) {
143
135
  items.push({
144
- title: "Bullet List",
145
136
  onItemClick: () => {
146
137
  insertOrUpdateBlock(editor, {
147
138
  type: "bulletListItem",
148
139
  });
149
140
  },
150
- subtext: "Used to display an unordered list",
151
141
  badge: formatKeyboardShortcut("Mod-Shift-8"),
152
- aliases: ["ul", "li", "list", "bulletlist", "bullet list"],
153
- group: "Basic blocks",
142
+ key: "bullet_list",
143
+ ...editor.dictionary.slash_menu.bullet_list,
154
144
  });
155
145
  }
156
146
 
157
147
  if (checkDefaultBlockTypeInSchema("paragraph", editor)) {
158
148
  items.push({
159
- title: "Paragraph",
160
149
  onItemClick: () => {
161
150
  insertOrUpdateBlock(editor, {
162
151
  type: "paragraph",
163
152
  });
164
153
  },
165
- subtext: "Used for the body of your document",
166
154
  badge: formatKeyboardShortcut("Mod-Alt-0"),
167
- aliases: ["p", "paragraph"],
168
- group: "Basic blocks",
155
+ key: "paragraph",
156
+ ...editor.dictionary.slash_menu.paragraph,
169
157
  });
170
158
  }
171
159
 
172
160
  if (checkDefaultBlockTypeInSchema("table", editor)) {
173
161
  items.push({
174
- title: "Table",
175
162
  onItemClick: () => {
176
163
  insertOrUpdateBlock(editor, {
177
164
  type: "table",
@@ -188,16 +175,14 @@ export function getDefaultSlashMenuItems<
188
175
  },
189
176
  });
190
177
  },
191
- subtext: "Used for for tables",
192
- aliases: ["table"],
193
- group: "Advanced",
194
178
  badge: undefined,
179
+ key: "table",
180
+ ...editor.dictionary.slash_menu.table,
195
181
  });
196
182
  }
197
183
 
198
184
  if (checkDefaultBlockTypeInSchema("image", editor)) {
199
185
  items.push({
200
- title: "Image",
201
186
  onItemClick: () => {
202
187
  const insertedBlock = insertOrUpdateBlock(editor, {
203
188
  type: "image",
@@ -210,19 +195,8 @@ export function getDefaultSlashMenuItems<
210
195
  })
211
196
  );
212
197
  },
213
- subtext: "Insert an image",
214
- aliases: [
215
- "image",
216
- "imageUpload",
217
- "upload",
218
- "img",
219
- "picture",
220
- "media",
221
- "url",
222
- "drive",
223
- "dropbox",
224
- ],
225
- group: "Media",
198
+ key: "image",
199
+ ...editor.dictionary.slash_menu.image,
226
200
  });
227
201
  }
228
202
 
@@ -234,10 +208,10 @@ export function filterSuggestionItems<
234
208
  >(items: T[], query: string) {
235
209
  return items.filter(
236
210
  ({ title, aliases }) =>
237
- title.toLowerCase().startsWith(query.toLowerCase()) ||
211
+ title.toLowerCase().includes(query.toLowerCase()) ||
238
212
  (aliases &&
239
213
  aliases.filter((alias) =>
240
- alias.toLowerCase().startsWith(query.toLowerCase())
214
+ alias.toLowerCase().includes(query.toLowerCase())
241
215
  ).length !== 0)
242
216
  );
243
217
  }
@@ -350,7 +350,7 @@ export class TableHandlesView<
350
350
  };
351
351
 
352
352
  destroy() {
353
- this.pmView.dom.removeEventListener("mousedown", this.mouseMoveHandler);
353
+ this.pmView.dom.removeEventListener("mousemove", this.mouseMoveHandler);
354
354
 
355
355
  document.removeEventListener("dragover", this.dragOverHandler);
356
356
  document.removeEventListener("drop", this.dropHandler);
@@ -0,0 +1,17 @@
1
+ // function scramble(dict: any) {
2
+ // const newDict: any = {} as any;
3
+
4
+ import type { en } from "./locales";
5
+
6
+ // for (const key in dict) {
7
+ // if (typeof dict[key] === "object") {
8
+ // newDict[key] = scramble(dict[key]);
9
+ // } else {
10
+ // newDict[key] = dict[key].split("").reverse().join("");
11
+ // }
12
+ // }
13
+
14
+ // return newDict;
15
+ // }
16
+
17
+ export type Dictionary = typeof en;
@@ -0,0 +1,196 @@
1
+ export const en = {
2
+ slash_menu: {
3
+ heading: {
4
+ title: "Heading 1",
5
+ subtext: "Used for a top-level heading",
6
+ aliases: ["h", "heading1", "h1"],
7
+ group: "Headings",
8
+ },
9
+ heading_2: {
10
+ title: "Heading 2",
11
+ subtext: "Used for key sections",
12
+ aliases: ["h2", "heading2", "subheading"],
13
+ group: "Headings",
14
+ },
15
+ heading_3: {
16
+ title: "Heading 3",
17
+ subtext: "Used for subsections and group headings",
18
+ aliases: ["h3", "heading3", "subheading"],
19
+ group: "Headings",
20
+ },
21
+ numbered_list: {
22
+ title: "Numbered List",
23
+ subtext: "Used to display a numbered list",
24
+ aliases: ["ol", "li", "list", "numberedlist", "numbered list"],
25
+ group: "Basic blocks",
26
+ },
27
+ bullet_list: {
28
+ title: "Bullet List",
29
+ subtext: "Used to display an unordered list",
30
+ aliases: ["ul", "li", "list", "bulletlist", "bullet list"],
31
+ group: "Basic blocks",
32
+ },
33
+ paragraph: {
34
+ title: "Paragraph",
35
+ subtext: "Used for the body of your document",
36
+ aliases: ["p", "paragraph"],
37
+ group: "Basic blocks",
38
+ },
39
+ table: {
40
+ title: "Table",
41
+ subtext: "Used for for tables",
42
+ aliases: ["table"],
43
+ group: "Advanced",
44
+ },
45
+ image: {
46
+ title: "Image",
47
+ subtext: "Insert an image",
48
+ aliases: [
49
+ "image",
50
+ "imageUpload",
51
+ "upload",
52
+ "img",
53
+ "picture",
54
+ "media",
55
+ "url",
56
+ "drive",
57
+ "dropbox",
58
+ ],
59
+ group: "Media",
60
+ },
61
+ },
62
+ placeholders: {
63
+ default: "Enter text or type '/' for commands",
64
+ heading: "Heading",
65
+ bulletListItem: "List",
66
+ numberedListItem: "List",
67
+ },
68
+ image: {
69
+ add_button: "Add Image",
70
+ },
71
+ // from react package:
72
+ side_menu: {
73
+ add_block_label: "Add block",
74
+ drag_handle_label: "Open block menu",
75
+ },
76
+ drag_handle: {
77
+ delete_menuitem: "Delete",
78
+ colors_menuitem: "Colors",
79
+ },
80
+ table_handle: {
81
+ delete_column_menuitem: "Delete column",
82
+ delete_row_menuitem: "Delete row",
83
+ add_left_menuitem: "Add column left",
84
+ add_right_menuitem: "Add column right",
85
+ add_above_menuitem: "Add row above",
86
+ add_below_menuitem: "Add row below",
87
+ },
88
+ suggestion_menu: {
89
+ no_items_title: "No items found",
90
+ loading: "Loading…",
91
+ },
92
+ color_picker: {
93
+ text_title: "Text",
94
+ background_title: "Background",
95
+ colors: {
96
+ default: "Default",
97
+ gray: "Gray",
98
+ brown: "Brown",
99
+ red: "Red",
100
+ orange: "Orange",
101
+ yellow: "Yellow",
102
+ green: "Green",
103
+ blue: "Blue",
104
+ purple: "Purple",
105
+ pink: "Pink",
106
+ },
107
+ },
108
+
109
+ formatting_toolbar: {
110
+ bold: {
111
+ tooltip: "Bold",
112
+ secondary_tooltip: "Mod+B",
113
+ },
114
+ italic: {
115
+ tooltip: "Italic",
116
+ secondary_tooltip: "Mod+I",
117
+ },
118
+ underline: {
119
+ tooltip: "Underline",
120
+ secondary_tooltip: "Mod+U",
121
+ },
122
+ strike: {
123
+ tooltip: "Strike",
124
+ secondary_tooltip: "Mod+Shift+X",
125
+ },
126
+ code: {
127
+ tooltip: "Code",
128
+ secondary_tooltip: "",
129
+ },
130
+ colors: {
131
+ tooltip: "Colors",
132
+ },
133
+ link: {
134
+ tooltip: "Create link",
135
+ secondary_tooltip: "Mod+K",
136
+ },
137
+ image_caption: {
138
+ tooltip: "Edit caption",
139
+ input_placeholder: "Edit caption",
140
+ },
141
+ image_replace: {
142
+ tooltip: "Replace image",
143
+ },
144
+ nest: {
145
+ tooltip: "Nest block",
146
+ secondary_tooltip: "Tab",
147
+ },
148
+ unnest: {
149
+ tooltip: "Unnest block",
150
+ secondary_tooltip: "Shift+Tab",
151
+ },
152
+ align_left: {
153
+ tooltip: "Align text left",
154
+ },
155
+ align_center: {
156
+ tooltip: "Align text center",
157
+ },
158
+ align_right: {
159
+ tooltip: "Align text right",
160
+ },
161
+ align_justify: {
162
+ tooltip: "Justify text",
163
+ },
164
+ },
165
+ image_panel: {
166
+ upload: {
167
+ title: "Upload",
168
+ file_placeholder: "Upload image",
169
+ upload_error: "Error: Upload failed",
170
+ },
171
+ embed: {
172
+ title: "Embed",
173
+ embed_button: "Embed image",
174
+ url_placeholder: "Enter URL",
175
+ },
176
+ },
177
+ link_toolbar: {
178
+ delete: {
179
+ tooltip: "Remove link",
180
+ },
181
+ edit: {
182
+ text: "Edit link",
183
+ tooltip: "Edit",
184
+ },
185
+ open: {
186
+ tooltip: "Open in new tab",
187
+ },
188
+ form: {
189
+ title_placeholder: "Edit title",
190
+ url_placeholder: "Edit URL",
191
+ },
192
+ },
193
+ generic: {
194
+ ctrl_shortcut: "Ctrl",
195
+ },
196
+ };