Skip to content

Commit 81c8d20

Browse files
committed
fix: better typing & support for hardBreakShortcut
1 parent fda7be0 commit 81c8d20

File tree

4 files changed

+83
-12
lines changed

4 files changed

+83
-12
lines changed

packages/core/src/blocks/BlockNoteSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { CustomBlockNoteSchema } from "../schema/index.js";
21
import {
32
BlockSchema,
43
BlockSchemaFromSpecs,
54
BlockSpecs,
5+
CustomBlockNoteSchema,
66
InlineContentSchema,
77
InlineContentSchemaFromSpecs,
88
InlineContentSpecs,

packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js";
1515
import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
1616

1717
export const KeyboardShortcutsExtension = Extension.create<{
18-
editor: BlockNoteEditor<any, any, any>;
18+
editor: BlockNoteEditor;
1919
tabBehavior: "prefer-navigate-ui" | "prefer-indent";
2020
}>({
2121
priority: 50,
@@ -470,9 +470,10 @@ export const KeyboardShortcutsExtension = Extension.create<{
470470
commands.command(({ state }) => {
471471
const blockInfo = getBlockInfoFromSelection(state);
472472

473-
const blockHardBreakShortcut: "shift+enter" | "enter" | "none" =
474-
this.options.editor.schema.blockSchema[blockInfo.blockNoteType]
475-
.hardBreakShortcut ?? "shift+enter";
473+
const blockHardBreakShortcut =
474+
this.options.editor.schema.blockSchema[
475+
blockInfo.blockNoteType as keyof typeof this.options.editor.schema.blockSchema
476+
].meta?.hardBreakShortcut ?? "shift+enter";
476477

477478
if (blockHardBreakShortcut === "none") {
478479
return false;

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export type BlockNoteDOMAttributes = Partial<{
2525
}>;
2626

2727
export interface BlockConfigMeta {
28+
/**
29+
* Defines which keyboard shortcut should be used to insert a hard break into the block's inline content.
30+
* @default "shift+enter"
31+
*/
32+
hardBreakShortcut?: "shift+enter" | "enter" | "none";
33+
2834
/**
2935
* Whether the block is selectable
3036
*/
@@ -94,6 +100,51 @@ export type BlockSpec<
94100
extensions?: BlockNoteExtension<any>[];
95101
};
96102

103+
/**
104+
* This allows de-coupling the types that we display to users versus the types we expose internally.
105+
*
106+
* This prevents issues with type-inference across parameters that Typescript cannot handle.
107+
* Specifically, the blocks shape cannot be properly inferred to a specific type like we expose to the user.
108+
*/
109+
export type LooseBlockSpec<
110+
T extends string = string,
111+
PS extends PropSchema = PropSchema,
112+
C extends "inline" | "none" | "table" = "inline" | "none" | "table",
113+
> = {
114+
config: BlockConfig<T, PS, C>;
115+
implementation: Omit<
116+
BlockImplementation<T, PS, C>,
117+
"render" | "toExternalHTML"
118+
> & {
119+
// purposefully stub the types for render and toExternalHTML since they reference the block
120+
render: (
121+
/**
122+
* The custom block to render
123+
*/
124+
block: any,
125+
/**
126+
* The BlockNote editor instance
127+
*/
128+
editor: BlockNoteEditor<any>,
129+
) => {
130+
dom: HTMLElement | DocumentFragment;
131+
contentDOM?: HTMLElement;
132+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
133+
destroy?: () => void;
134+
};
135+
toExternalHTML?: (
136+
block: any,
137+
editor: BlockNoteEditor<any>,
138+
) =>
139+
| {
140+
dom: HTMLElement | DocumentFragment;
141+
contentDOM?: HTMLElement;
142+
}
143+
| undefined;
144+
};
145+
extensions?: BlockNoteExtension<any>[];
146+
};
147+
97148
// Utility type. For a given object block schema, ensures that the key of each
98149
// block spec matches the name of the TipTap node in it.
99150
type NamesMatch<Blocks extends Record<string, BlockConfig>> = Blocks extends {

packages/core/src/schema/schema.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1+
import { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
2+
import { createDependencyGraph, toposortReverse } from "../util/topo-sort.js";
13
import {
24
BlockNoDefaults,
35
BlockSchema,
46
BlockSpecs,
57
InlineContentSchema,
68
InlineContentSpecs,
9+
LooseBlockSpec,
710
PartialBlockNoDefaults,
811
StyleSchema,
912
StyleSpecs,
1013
addNodeAndExtensionsToSpec,
1114
getInlineContentSchemaFromSpecs,
1215
getStyleSchemaFromSpecs,
1316
} from "./index.js";
14-
import { createDependencyGraph, toposortReverse } from "../util/topo-sort.js";
15-
import { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
1617

1718
function removeUndefined<T extends Record<string, any> | undefined>(obj: T): T {
1819
if (!obj) {
@@ -43,7 +44,11 @@ export class CustomBlockNoteSchema<
4344

4445
public inlineContentSpecs: InlineContentSpecs;
4546
public styleSpecs: StyleSpecs;
46-
public blockSpecs: BlockSpecs;
47+
public blockSpecs: {
48+
[K in keyof BSchema]: K extends string
49+
? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]>
50+
: never;
51+
};
4752

4853
public blockSchema: BSchema;
4954
public inlineContentSchema: ISchema;
@@ -117,7 +122,11 @@ export class CustomBlockNoteSchema<
117122
),
118123
];
119124
}),
120-
) as BlockSpecs;
125+
) as {
126+
[K in keyof BSchema]: K extends string
127+
? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]>
128+
: never;
129+
};
121130

122131
return {
123132
blockSpecs,
@@ -151,9 +160,19 @@ export class CustomBlockNoteSchema<
151160
inlineContentSpecs?: AdditionalInlineContentSpecs;
152161
styleSpecs?: AdditionalStyleSpecs;
153162
}): CustomBlockNoteSchema<
154-
AdditionalBlockSpecs extends undefined ? BSchema : BSchema,
155-
AdditionalInlineContentSpecs extends undefined ? ISchema : ISchema,
156-
AdditionalStyleSpecs extends undefined ? SSchema : SSchema
163+
AdditionalBlockSpecs extends undefined
164+
? BSchema & {
165+
[K in keyof AdditionalBlockSpecs]: K extends string
166+
? AdditionalBlockSpecs[K]["config"]
167+
: never;
168+
}
169+
: BSchema,
170+
AdditionalInlineContentSpecs extends undefined
171+
? ISchema & AdditionalInlineContentSpecs
172+
: ISchema,
173+
AdditionalStyleSpecs extends undefined
174+
? SSchema & AdditionalStyleSpecs
175+
: SSchema
157176
> {
158177
// Merge the new specs with existing ones
159178
Object.assign(this.opts.blockSpecs, opts.blockSpecs);

0 commit comments

Comments
 (0)