Skip to content

Commit 97295ce

Browse files
committed
Updated type guard definitions
1 parent c51767a commit 97295ce

File tree

11 files changed

+189
-171
lines changed

11 files changed

+189
-171
lines changed

examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
BlockSchema,
3-
blockHasProps,
3+
blockHasType,
44
InlineContentSchema,
55
StyleSchema,
66
} from "@blocknote/core";
@@ -41,7 +41,7 @@ export const FileReplaceButton = () => {
4141

4242
if (
4343
block === undefined ||
44-
!blockHasProps(block, { url: { default: "" } }) ||
44+
!blockHasType(block, editor, ["url"]) ||
4545
!editor.isEditable
4646
) {
4747
return null;

packages/core/src/blocks/defaultBlockTypeGuards.ts

Lines changed: 104 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -14,156 +14,167 @@ import {
1414
} from "./defaultBlocks.js";
1515
import { Selection } from "prosemirror-state";
1616

17-
export function editorHasBlockWithType<BType extends string>(
18-
editor: BlockNoteEditor<any, any, any>,
19-
blockType: BType,
20-
): editor is BlockNoteEditor<
21-
{
22-
[BT in BType]: BlockConfig<
23-
BT,
24-
{
25-
[PN in string]: PropSpec<boolean | number | string>;
26-
}
27-
>;
28-
},
29-
any,
30-
any
31-
> {
32-
if (!(blockType in editor.schema.blockSpecs)) {
33-
return false;
34-
}
35-
36-
if (editor.schema.blockSpecs[blockType].config.type !== blockType) {
37-
return false;
38-
}
39-
40-
return true;
41-
}
42-
43-
export function editorHasBlockWithTypeAndProps<
17+
export function editorHasBlockWithType<
4418
BType extends string,
45-
PSchema extends PropSchema,
19+
Props extends
20+
| PropSchema
21+
| Record<string, "boolean" | "number" | "string">
22+
| undefined = undefined,
4623
>(
4724
editor: BlockNoteEditor<any, any, any>,
4825
blockType: BType,
49-
propSchema: PSchema,
26+
props?: Props,
5027
): editor is BlockNoteEditor<
5128
{
52-
[BT in BType]: BlockConfig<BT, PSchema>;
29+
[BT in BType]: Props extends PropSchema
30+
? BlockConfig<BT, Props>
31+
: Props extends Record<string, "boolean" | "number" | "string">
32+
? BlockConfig<
33+
BT,
34+
{
35+
[PN in keyof Props]: {
36+
default: undefined;
37+
type: Props[PN];
38+
};
39+
}
40+
>
41+
: BlockConfig<BT, PropSchema>;
5342
},
5443
any,
5544
any
5645
> {
5746
if (!editorHasBlockWithType(editor, blockType)) {
5847
return false;
5948
}
49+
if (!props) {
50+
return true;
51+
}
6052

61-
for (const [propName, propSpec] of Object.entries(propSchema)) {
53+
for (const [propName, propSpec] of Object.entries(props)) {
6254
if (!(propName in editor.schema.blockSpecs[blockType].config.propSchema)) {
6355
return false;
6456
}
6557

66-
if (
67-
editor.schema.blockSpecs[blockType].config.propSchema[propName]
68-
.default !== propSpec.default
69-
) {
70-
return false;
71-
}
72-
73-
if (
74-
typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
75-
.values !== typeof propSpec.values
76-
) {
77-
return false;
78-
}
58+
if (typeof propSpec === "string") {
59+
if (
60+
editor.schema.blockSpecs[blockType].config.propSchema[propName]
61+
.default &&
62+
typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
63+
.default !== propSpec
64+
) {
65+
return false;
66+
}
7967

80-
if (
81-
typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
82-
.values === "object" &&
83-
typeof propSpec.values === "object"
84-
) {
8568
if (
86-
editor.schema.blockSpecs[blockType].config.propSchema[propName].values
87-
.length !== propSpec.values.length
69+
editor.schema.blockSpecs[blockType].config.propSchema[propName].type &&
70+
editor.schema.blockSpecs[blockType].config.propSchema[propName].type !==
71+
propSpec
72+
) {
73+
return false;
74+
}
75+
} else {
76+
if (
77+
editor.schema.blockSpecs[blockType].config.propSchema[propName]
78+
.default !== propSpec.default
8879
) {
8980
return false;
9081
}
9182

92-
for (
93-
let i = 0;
94-
i <
95-
editor.schema.blockSpecs[blockType].config.propSchema[propName].values
96-
.length;
97-
i++
83+
if (
84+
editor.schema.blockSpecs[blockType].config.propSchema[propName]
85+
.default === undefined &&
86+
propSpec.default === undefined
9887
) {
9988
if (
10089
editor.schema.blockSpecs[blockType].config.propSchema[propName]
101-
.values[i] !== propSpec.values[i]
90+
.type !== propSpec.type
10291
) {
10392
return false;
10493
}
10594
}
106-
}
10795

108-
if (
109-
editor.schema.blockSpecs[blockType].config.propSchema[propName]
110-
.default === undefined &&
111-
propSpec.default === undefined
112-
) {
11396
if (
114-
editor.schema.blockSpecs[blockType].config.propSchema[propName].type !==
115-
propSpec.type
97+
typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
98+
.values !== typeof propSpec.values
11699
) {
117100
return false;
118101
}
102+
103+
if (
104+
typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
105+
.values === "object" &&
106+
typeof propSpec.values === "object"
107+
) {
108+
if (
109+
editor.schema.blockSpecs[blockType].config.propSchema[propName].values
110+
.length !== propSpec.values.length
111+
) {
112+
return false;
113+
}
114+
115+
for (
116+
let i = 0;
117+
i <
118+
editor.schema.blockSpecs[blockType].config.propSchema[propName].values
119+
.length;
120+
i++
121+
) {
122+
if (
123+
editor.schema.blockSpecs[blockType].config.propSchema[propName]
124+
.values[i] !== propSpec.values[i]
125+
) {
126+
return false;
127+
}
128+
}
129+
}
119130
}
120131
}
121132

122133
return true;
123134
}
124135

125-
export function blockHasType<BType extends string>(
126-
block: Block<any, any, any>,
127-
editor: BlockNoteEditor<any, any, any>,
128-
blockType: BType,
129-
): block is Block<
130-
{
131-
[BT in string]: BlockConfig<
132-
BT,
133-
{
134-
[PN in string]: PropSpec<boolean | number | string>;
135-
}
136-
>;
137-
},
138-
any,
139-
any
140-
> {
141-
return editorHasBlockWithType(editor, blockType) && block.type === blockType;
142-
}
143-
144-
export function blockHasTypeAndProps<
136+
export function blockHasType<
145137
BType extends string,
146-
PSchema extends PropSchema,
138+
Props extends
139+
| PropSchema
140+
| Record<string, "boolean" | "number" | "string">
141+
| undefined = undefined,
147142
>(
148143
block: Block<any, any, any>,
149144
editor: BlockNoteEditor<any, any, any>,
150145
blockType: BType,
151-
propSchema: PSchema,
146+
props?: Props,
152147
): block is Block<
153148
{
154-
[BT in string]: BlockConfig<BT, PSchema>;
149+
[BT in BType]: Props extends PropSchema
150+
? BlockConfig<BT, Props>
151+
: Props extends Record<string, "boolean" | "number" | "string">
152+
? BlockConfig<
153+
BT,
154+
{
155+
[PN in keyof Props]: PropSpec<
156+
Props[PN] extends "boolean"
157+
? boolean
158+
: Props[PN] extends "number"
159+
? number
160+
: Props[PN] extends "string"
161+
? string
162+
: never
163+
>;
164+
}
165+
>
166+
: BlockConfig<BT, PropSchema>;
155167
},
156168
any,
157169
any
158170
> {
159171
return (
160-
editorHasBlockWithTypeAndProps(editor, blockType, propSchema) &&
161-
block.type === blockType
172+
editorHasBlockWithType(editor, blockType, props) && block.type === blockType
162173
);
163174
}
164175

165-
// TODO: Only used in the emoji picker - is it even needed? If so, needs to be
166-
// changed to be like the block type guards.
176+
// TODO: Only used once in the emoji picker - is it even needed? If so, should
177+
// be changed to be like the block type guards.
167178
export function checkDefaultInlineContentTypeInSchema<
168179
InlineContentType extends keyof DefaultInlineContentSchema,
169180
B extends BlockSchema,

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

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import {
55
} from "../../blocks/defaultBlocks.js";
66
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
77

8-
import {
9-
editorHasBlockWithType,
10-
editorHasBlockWithTypeAndProps,
11-
} from "../../blocks/defaultBlockTypeGuards.js";
8+
import { editorHasBlockWithType } from "../../blocks/defaultBlockTypeGuards.js";
129
import {
1310
BlockSchema,
1411
InlineContentSchema,
@@ -94,11 +91,7 @@ export function getDefaultSlashMenuItems<
9491
>(editor: BlockNoteEditor<BSchema, I, S>) {
9592
const items: DefaultSuggestionItem[] = [];
9693

97-
if (
98-
editorHasBlockWithTypeAndProps(editor, "heading", {
99-
level: defaultBlockSpecs["heading"].config.propSchema.level,
100-
})
101-
) {
94+
if (editorHasBlockWithType(editor, "heading", { level: "number" })) {
10295
items.push(
10396
{
10497
onItemClick: () => {
@@ -250,7 +243,7 @@ export function getDefaultSlashMenuItems<
250243
});
251244
}
252245

253-
if (editorHasBlockWithType(editor, "image")) {
246+
if (editorHasBlockWithType(editor, "image", { url: "string" })) {
254247
items.push({
255248
onItemClick: () => {
256249
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -269,7 +262,7 @@ export function getDefaultSlashMenuItems<
269262
});
270263
}
271264

272-
if (editorHasBlockWithType(editor, "video")) {
265+
if (editorHasBlockWithType(editor, "video", { url: "string" })) {
273266
items.push({
274267
onItemClick: () => {
275268
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -288,7 +281,7 @@ export function getDefaultSlashMenuItems<
288281
});
289282
}
290283

291-
if (editorHasBlockWithType(editor, "audio")) {
284+
if (editorHasBlockWithType(editor, "audio", { url: "string" })) {
292285
items.push({
293286
onItemClick: () => {
294287
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -307,7 +300,7 @@ export function getDefaultSlashMenuItems<
307300
});
308301
}
309302

310-
if (editorHasBlockWithType(editor, "file")) {
303+
if (editorHasBlockWithType(editor, "file", { url: "string" })) {
311304
items.push({
312305
onItemClick: () => {
313306
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -327,11 +320,9 @@ export function getDefaultSlashMenuItems<
327320
}
328321

329322
if (
330-
editorHasBlockWithTypeAndProps(editor, "heading", {
331-
level: defaultBlockSpecs["heading"].config.propSchema.level,
332-
isToggleable: {
333-
default: true,
334-
},
323+
editorHasBlockWithType(editor, "heading", {
324+
level: "number",
325+
isToggleable: "boolean",
335326
})
336327
) {
337328
items.push(

packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
2-
blockHasTypeAndProps,
2+
blockHasType,
33
BlockSchema,
4+
editorHasBlockWithType,
45
InlineContentSchema,
56
StyleSchema,
67
} from "@blocknote/core";
@@ -41,9 +42,9 @@ export const FileCaptionButton = () => {
4142
const block = selectedBlocks[0];
4243

4344
if (
44-
blockHasTypeAndProps(block, editor, block.type, {
45-
url: { default: "" },
46-
caption: { default: "" },
45+
blockHasType(block, editor, block.type, {
46+
url: "string",
47+
caption: "string",
4748
})
4849
) {
4950
setCurrentEditingCaption(block.props.caption);
@@ -55,11 +56,17 @@ export const FileCaptionButton = () => {
5556

5657
const handleEnter = useCallback(
5758
(event: KeyboardEvent) => {
58-
if (fileBlock && event.key === "Enter") {
59+
if (
60+
fileBlock &&
61+
editorHasBlockWithType(editor, fileBlock.type, {
62+
caption: "string",
63+
}) &&
64+
event.key === "Enter"
65+
) {
5966
event.preventDefault();
6067
editor.updateBlock(fileBlock, {
6168
props: {
62-
caption: currentEditingCaption as any, // TODO
69+
caption: currentEditingCaption,
6370
},
6471
});
6572
}

0 commit comments

Comments
 (0)