Skip to content

Commit cc991cc

Browse files
committed
Merge branch 'refs/heads/main' into releases
2 parents 570cdb4 + 99bedb8 commit cc991cc

File tree

14 files changed

+119
-40
lines changed

14 files changed

+119
-40
lines changed

docs/pages/docs/editor-basics/default-schema.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ type TableBlock = {
117117
id: string;
118118
type: "table";
119119
props: DefaultProps;
120-
content: TableContent[];
120+
content: TableContent;
121121
children: Block[];
122122
};
123123
```

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Examples
22

3-
Here, we collect a number of examples using BlockNote. You can run the examples via the Playground (`npm start` or [playground.blocknotejs.org](https://playground.blocknotejs.org/)).
3+
Here, we collect a number of examples using BlockNote. You can run the examples via the Playground (`npm start` or [online version](https://blocknote-main.vercel.app/)).
44

55
### (contributors) Adding examples
66

packages/core/src/api/parsers/handleFileInsertion.ts

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
22
import { PartialBlock } from "../../blocks/defaultBlocks";
3-
import { insertOrUpdateBlock } from "../../extensions/SuggestionMenu/getDefaultSlashMenuItems";
43
import {
54
BlockSchema,
65
FileBlockConfig,
76
InlineContentSchema,
87
StyleSchema,
98
} from "../../schema";
9+
import { getBlockInfoFromPos } from "../getBlockInfoFromPos";
1010
import { acceptedMIMETypes } from "./acceptedMIMETypes";
1111

12+
function checkFileExtensionsMatch(
13+
fileExtension1: string,
14+
fileExtension2: string
15+
) {
16+
if (!fileExtension1.startsWith(".") || !fileExtension2.startsWith(".")) {
17+
throw new Error(`The strings provided are not valid file extensions.`);
18+
}
19+
20+
return fileExtension1 === fileExtension2;
21+
}
22+
1223
function checkMIMETypesMatch(mimeType1: string, mimeType2: string) {
1324
const types1 = mimeType1.split("/");
1425
const types2 = mimeType2.split("/");
@@ -71,10 +82,24 @@ export async function handleFileInsertion<
7182
// Gets file block corresponding to MIME type.
7283
let fileBlockType = "file";
7384
for (const fileBlockConfig of fileBlockConfigs) {
74-
for (const mimeType of fileBlockConfig.fileBlockAcceptMimeTypes || []) {
75-
if (checkMIMETypesMatch(items[i].type, mimeType)) {
76-
fileBlockType = fileBlockConfig.type;
77-
break;
85+
for (const mimeType of fileBlockConfig.fileBlockAccept || []) {
86+
const isFileExtension = mimeType.startsWith(".");
87+
const file = items[i].getAsFile();
88+
89+
if (file) {
90+
if (
91+
(!isFileExtension &&
92+
file.type &&
93+
checkMIMETypesMatch(items[i].type, mimeType)) ||
94+
(isFileExtension &&
95+
checkFileExtensionsMatch(
96+
"." + file.name.split(".").pop(),
97+
mimeType
98+
))
99+
) {
100+
fileBlockType = fileBlockConfig.type;
101+
break;
102+
}
78103
}
79104
}
80105
}
@@ -83,16 +108,42 @@ export async function handleFileInsertion<
83108
if (file) {
84109
const updateData = await editor.uploadFile(file);
85110

86-
if (typeof updateData === "string") {
87-
const fileBlock = {
88-
type: fileBlockType,
89-
props: {
90-
name: file.name,
91-
url: updateData,
92-
},
93-
} as PartialBlock<BSchema, I, S>;
111+
const fileBlock =
112+
typeof updateData === "string"
113+
? ({
114+
type: fileBlockType,
115+
props: {
116+
name: file.name,
117+
url: updateData,
118+
},
119+
} as PartialBlock<BSchema, I, S>)
120+
: { type: fileBlockType, ...updateData };
121+
122+
if (event.type === "paste") {
123+
editor.insertBlocks(
124+
[fileBlock],
125+
editor.getTextCursorPosition().block,
126+
"after"
127+
);
128+
}
129+
130+
if (event.type === "drop") {
131+
const coords = {
132+
left: (event as DragEvent).clientX,
133+
top: (event as DragEvent).clientY,
134+
};
135+
136+
const pos = editor._tiptapEditor.view.posAtCoords(coords);
137+
if (!pos) {
138+
return;
139+
}
140+
141+
const blockInfo = getBlockInfoFromPos(
142+
editor._tiptapEditor.state.doc,
143+
pos.pos
144+
);
94145

95-
insertOrUpdateBlock(editor, fileBlock);
146+
editor.insertBlocks([fileBlock], blockInfo.id, "after");
96147
}
97148
}
98149
}

packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const audioBlockConfig = {
4343
propSchema: audioPropSchema,
4444
content: "none",
4545
isFileBlock: true,
46-
fileBlockAcceptMimeTypes: ["audio/*"],
46+
fileBlockAccept: ["audio/*"],
4747
} satisfies FileBlockConfig;
4848

4949
export const audioRender = (

packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const imageBlockConfig = {
4949
propSchema: imagePropSchema,
5050
content: "none",
5151
isFileBlock: true,
52-
fileBlockAcceptMimeTypes: ["image/*"],
52+
fileBlockAccept: ["image/*"],
5353
} satisfies FileBlockConfig;
5454

5555
export const imageRender = (

packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const videoBlockConfig = {
4949
propSchema: videoPropSchema,
5050
content: "none",
5151
isFileBlock: true,
52-
fileBlockAcceptMimeTypes: ["video/*"],
52+
fileBlockAccept: ["video/*"],
5353
} satisfies FileBlockConfig;
5454

5555
export const videoRender = (

packages/core/src/editor/BlockNoteEditor.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,15 +1159,26 @@ export class BlockNoteEditor<
11591159
};
11601160
}
11611161

1162-
public openSelectionMenu(triggerCharacter: string) {
1162+
public openSuggestionMenu(
1163+
triggerCharacter: string,
1164+
pluginState?: {
1165+
deleteTriggerCharacter?: boolean;
1166+
ignoreQueryLength?: boolean;
1167+
}
1168+
) {
1169+
const tr = this.prosemirrorView.state.tr;
1170+
const transaction =
1171+
pluginState && pluginState.deleteTriggerCharacter
1172+
? tr.insertText(triggerCharacter)
1173+
: tr;
1174+
11631175
this.prosemirrorView.focus();
11641176
this.prosemirrorView.dispatch(
1165-
this.prosemirrorView.state.tr
1166-
.scrollIntoView()
1167-
.setMeta(this.suggestionMenus.plugin, {
1168-
triggerCharacter: triggerCharacter,
1169-
fromUserInput: false,
1170-
})
1177+
transaction.scrollIntoView().setMeta(this.suggestionMenus.plugin, {
1178+
triggerCharacter: triggerCharacter,
1179+
deleteTriggerCharacter: pluginState?.deleteTriggerCharacter || false,
1180+
ignoreQueryLength: pluginState?.ignoreQueryLength || false,
1181+
})
11711182
);
11721183
}
11731184
}

packages/core/src/extensions/SideMenu/SideMenuPlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ export class SideMenuView<
664664
}
665665

666666
// Focuses and activates the slash menu.
667-
this.editor.openSelectionMenu("/");
667+
this.editor.openSuggestionMenu("/");
668668
}
669669
}
670670

packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const findBlock = findParentNode((node) => node.type.name === "blockContainer");
1111

1212
export type SuggestionMenuState = UiElementPosition & {
1313
query: string;
14+
ignoreQueryLength?: boolean;
1415
};
1516

1617
class SuggestionMenuView<
@@ -34,7 +35,10 @@ class SuggestionMenuView<
3435
throw new Error("Attempting to update uninitialized suggestions menu");
3536
}
3637

37-
emitUpdate(menuName, this.state);
38+
emitUpdate(menuName, {
39+
...this.state,
40+
ignoreQueryLength: this.pluginState?.ignoreQueryLength,
41+
});
3842
};
3943

4044
this.rootEl = this.editor._tiptapEditor.view.root;
@@ -120,7 +124,7 @@ class SuggestionMenuView<
120124
.deleteRange({
121125
from:
122126
this.pluginState.queryStartPos! -
123-
(this.pluginState.fromUserInput
127+
(this.pluginState.deleteTriggerCharacter
124128
? this.pluginState.triggerCharacter!.length
125129
: 0),
126130
to: this.editor._tiptapEditor.state.selection.from,
@@ -132,10 +136,11 @@ class SuggestionMenuView<
132136
type SuggestionPluginState =
133137
| {
134138
triggerCharacter: string;
135-
fromUserInput: boolean;
139+
deleteTriggerCharacter: boolean;
136140
queryStartPos: number;
137141
query: string;
138142
decorationId: string;
143+
ignoreQueryLength?: boolean;
139144
}
140145
| undefined;
141146

@@ -194,7 +199,8 @@ export class SuggestionMenuProseMirrorPlugin<
194199
// or null if it should be hidden.
195200
const suggestionPluginTransactionMeta: {
196201
triggerCharacter: string;
197-
fromUserInput?: boolean;
202+
deleteTriggerCharacter?: boolean;
203+
ignoreQueryLength?: boolean;
198204
} | null = transaction.getMeta(suggestionMenuPluginKey);
199205

200206
// Only opens a menu of no menu is already open
@@ -206,11 +212,14 @@ export class SuggestionMenuProseMirrorPlugin<
206212
return {
207213
triggerCharacter:
208214
suggestionPluginTransactionMeta.triggerCharacter,
209-
fromUserInput:
210-
suggestionPluginTransactionMeta.fromUserInput !== false,
215+
deleteTriggerCharacter:
216+
suggestionPluginTransactionMeta.deleteTriggerCharacter !==
217+
false,
211218
queryStartPos: newState.selection.from,
212219
query: "",
213220
decorationId: `id_${Math.floor(Math.random() * 0xffffffff)}`,
221+
ignoreQueryLength:
222+
suggestionPluginTransactionMeta?.ignoreQueryLength,
214223
};
215224
}
216225

@@ -285,7 +294,7 @@ export class SuggestionMenuProseMirrorPlugin<
285294

286295
// If the menu was opened programmatically by another extension, it may not use a trigger character. In this
287296
// case, the decoration is set on the whole block instead, as the decoration range would otherwise be empty.
288-
if (!suggestionPluginState.fromUserInput) {
297+
if (!suggestionPluginState.deleteTriggerCharacter) {
289298
const blockNode = findBlock(state.selection);
290299
if (blockNode) {
291300
return DecorationSet.create(state.doc, [

packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,12 @@ export function getDefaultSlashMenuItems<
272272
}
273273

274274
items.push({
275-
onItemClick: () => editor.openSelectionMenu(":"),
275+
onItemClick: () => {
276+
editor.openSuggestionMenu(":", {
277+
deleteTriggerCharacter: true,
278+
ignoreQueryLength: true,
279+
});
280+
},
276281
key: "emoji",
277282
...editor.dictionary.slash_menu.emoji,
278283
});

packages/core/src/schema/blocks/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export type FileBlockConfig = {
5050
};
5151
content: "none";
5252
isFileBlock: true;
53-
fileBlockAcceptMimeTypes?: string[];
53+
fileBlockAccept?: string[];
5454
};
5555

5656
// BlockConfig contains the "schema" info about a Block type

packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export const UploadTab = <
7676

7777
const config = editor.schema.blockSchema[block.type];
7878
const accept =
79-
config.isFileBlock && config.fileBlockAcceptMimeTypes?.length
80-
? config.fileBlockAcceptMimeTypes.join(",")
79+
config.isFileBlock && config.fileBlockAccept?.length
80+
? config.fileBlockAccept.join(",")
8181
: "*/*";
8282

8383
return (

packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ export function GridSuggestionMenuController<
130130
if (
131131
!isMounted ||
132132
!state ||
133-
(minQueryLength &&
133+
(!state?.ignoreQueryLength &&
134+
minQueryLength &&
134135
(state.query.startsWith(" ") || state.query.length < minQueryLength))
135136
) {
136137
return null;

packages/server-util/src/context/ServerBlockNoteEditor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ export class ServerBlockNoteEditor<
8787
>;
8888
}
8989

90-
private constructor(options: Partial<BlockNoteEditorOptions<any, any, any>>) {
90+
protected constructor(
91+
options: Partial<BlockNoteEditorOptions<any, any, any>>
92+
) {
9193
this.editor = BlockNoteEditor.create({
9294
...options,
9395
_headless: true,

0 commit comments

Comments
 (0)