diff --git a/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx b/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx index 48e32a2b01..dc718437b1 100644 --- a/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx +++ b/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx @@ -1,6 +1,6 @@ import { BlockSchema, - checkBlockIsFileBlock, + blockHasType, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -41,7 +41,7 @@ export const FileReplaceButton = () => { if ( block === undefined || - !checkBlockIsFileBlock(block, editor) || + !blockHasType(block, editor, { url: "string" }) || !editor.isEditable ) { return null; diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts index d164dcb762..ce49383b86 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts @@ -2,7 +2,6 @@ import { Block, PartialBlock } from "../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; import { BlockSchema, - FileBlockConfig, InlineContentSchema, StyleSchema, } from "../../../schema/index.js"; @@ -106,15 +105,11 @@ export async function handleFileInsertion< event.preventDefault(); - const fileBlockConfigs = Object.values(editor.schema.blockSchema).filter( - (blockConfig) => blockConfig.isFileBlock, - ) as FileBlockConfig[]; - for (let i = 0; i < items.length; i++) { // Gets file block corresponding to MIME type. let fileBlockType = "file"; - for (const fileBlockConfig of fileBlockConfigs) { - for (const mimeType of fileBlockConfig.fileBlockAccept || []) { + for (const fileBlockConfig of Object.values(editor.schema.blockSchema)) { + for (const mimeType of fileBlockConfig.meta?.fileBlockAccept || []) { const isFileExtension = mimeType.startsWith("."); const file = items[i].getAsFile(); diff --git a/packages/core/src/blks/Audio/definition.ts b/packages/core/src/blks/Audio/definition.ts index eb36220e3a..c58b547671 100644 --- a/packages/core/src/blks/Audio/definition.ts +++ b/packages/core/src/blks/Audio/definition.ts @@ -72,7 +72,7 @@ export const definition = createBlockDefinition(config).implementation( }, render: (block, editor) => { const icon = document.createElement("div"); - icon.innerHTML = config.icon ?? FILE_AUDIO_ICON_SVG; + icon.innerHTML = config?.icon ?? FILE_AUDIO_ICON_SVG; const audio = document.createElement("audio"); audio.className = "bn-audio"; diff --git a/packages/core/src/blks/Heading/definition.ts b/packages/core/src/blks/Heading/definition.ts index 443c5672ea..0a365b7c51 100644 --- a/packages/core/src/blks/Heading/definition.ts +++ b/packages/core/src/blks/Heading/definition.ts @@ -1,5 +1,6 @@ import { updateBlockTr } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getBlockInfoFromTransaction } from "../../api/getBlockInfoFromPos.js"; +import { defaultProps } from "../../blocks/defaultProps.js"; import { createToggleWrapper } from "../../blocks/ToggleWrapper/createToggleWrapper.js"; import { createBlockConfig, @@ -24,6 +25,7 @@ const config = createBlockConfig( }: HeadingOptions = {}) => ({ type: "heading" as const, propSchema: { + ...defaultProps, level: { default: defaultLevel, values: levels }, ...(allowToggleHeadings ? { isToggleable: { default: false } } : {}), }, diff --git a/packages/core/src/blks/Image/definition.ts b/packages/core/src/blks/Image/definition.ts index af8c34e025..7e5d72400c 100644 --- a/packages/core/src/blks/Image/definition.ts +++ b/packages/core/src/blks/Image/definition.ts @@ -41,7 +41,7 @@ const config = createBlockConfig((_ctx: ImageOptions = {}) => ({ default: undefined, type: "number", }, - }, + } as const, content: "none" as const, meta: { fileBlockAccept: ["image/*"], @@ -78,7 +78,7 @@ export const definition = createBlockDefinition(config).implementation( }, render: (block, editor) => { const icon = document.createElement("div"); - icon.innerHTML = config.icon ?? FILE_IMAGE_ICON_SVG; + icon.innerHTML = config?.icon ?? FILE_IMAGE_ICON_SVG; const imageWrapper = document.createElement("div"); imageWrapper.className = "bn-visual-media-wrapper"; diff --git a/packages/core/src/blks/NumberedListItem/definition.ts b/packages/core/src/blks/NumberedListItem/definition.ts index 6e6f35476a..43962612fa 100644 --- a/packages/core/src/blks/NumberedListItem/definition.ts +++ b/packages/core/src/blks/NumberedListItem/definition.ts @@ -15,7 +15,7 @@ const config = createBlockConfig(() => ({ propSchema: { ...defaultProps, start: { default: undefined, type: "number" }, - }, + } as const, content: "inline", })); diff --git a/packages/core/src/blks/Video/definition.ts b/packages/core/src/blks/Video/definition.ts index fe681a58cd..d8aec2fcc2 100644 --- a/packages/core/src/blks/Video/definition.ts +++ b/packages/core/src/blks/Video/definition.ts @@ -62,7 +62,7 @@ export const definition = createBlockDefinition(config).implementation( }, render: (block, editor) => { const icon = document.createElement("div"); - icon.innerHTML = config.icon ?? FILE_VIDEO_ICON_SVG; + icon.innerHTML = config?.icon ?? FILE_VIDEO_ICON_SVG; const videoWrapper = document.createElement("div"); videoWrapper.className = "bn-visual-media-wrapper"; diff --git a/packages/core/src/blks/index.ts b/packages/core/src/blks/index.ts index a501019f03..97473296ef 100644 --- a/packages/core/src/blks/index.ts +++ b/packages/core/src/blks/index.ts @@ -6,7 +6,7 @@ export * as heading from "./Heading/definition.js"; export * as numberedListItem from "./NumberedListItem/definition.js"; export * as pageBreak from "./PageBreak/definition.js"; export * as paragraph from "./Paragraph/definition.js"; -export * as quoteBlock from "./Quote/definition.js"; +export * as quote from "./Quote/definition.js"; export * as toggleListItem from "./ToggleListItem/definition.js"; export * as file from "./File/definition.js"; diff --git a/packages/core/src/blocks/defaultBlockTypeGuards.ts b/packages/core/src/blocks/defaultBlockTypeGuards.ts index 4fbdc2e99b..c3db69fdaf 100644 --- a/packages/core/src/blocks/defaultBlockTypeGuards.ts +++ b/packages/core/src/blocks/defaultBlockTypeGuards.ts @@ -1,199 +1,169 @@ import { CellSelection } from "prosemirror-tables"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; -import { - BlockConfig, - BlockFromConfig, - BlockSchema, - InlineContentConfig, - InlineContentSchema, - StyleSchema, -} from "../schema/index.js"; -import { - Block, - DefaultBlockSchema, - DefaultInlineContentSchema, - defaultBlockSpecs, - defaultInlineContentSchema, -} from "./defaultBlocks.js"; -import { defaultProps } from "./defaultProps.js"; +import { BlockConfig, PropSchema, PropSpec } from "../schema/index.js"; +import { Block } from "./defaultBlocks.js"; import { Selection } from "prosemirror-state"; -export function checkDefaultBlockTypeInSchema< - BlockType extends keyof DefaultBlockSchema, - I extends InlineContentSchema, - S extends StyleSchema, +export function editorHasBlockWithType< + BType extends string, + Props extends + | PropSchema + | Record + | undefined = undefined, >( - blockType: BlockType, - editor: BlockNoteEditor, + editor: BlockNoteEditor, + blockType: BType, + props?: Props, ): editor is BlockNoteEditor< - { [K in BlockType]: DefaultBlockSchema[BlockType] }, - I, - S + { + [BT in BType]: Props extends PropSchema + ? BlockConfig + : Props extends Record + ? BlockConfig< + BT, + { + [PN in keyof Props]: { + default: undefined; + type: Props[PN]; + }; + } + > + : BlockConfig; + }, + any, + any > { - return ( - blockType in editor.schema.blockSchema && blockType in defaultBlockSpecs - ); -} + if (!(blockType in editor.schema.blockSpecs)) { + return false; + } -export function checkBlockTypeInSchema< - BlockType extends string, - Config extends BlockConfig, ->( - blockType: BlockType, - blockConfig: Config, - editor: BlockNoteEditor, -): editor is BlockNoteEditor<{ [T in BlockType]: Config }, any, any> { - return ( - blockType in editor.schema.blockSchema && - editor.schema.blockSchema[blockType] === blockConfig - ); -} + if (!props) { + return true; + } -export function checkDefaultInlineContentTypeInSchema< - InlineContentType extends keyof DefaultInlineContentSchema, - B extends BlockSchema, - S extends StyleSchema, ->( - inlineContentType: InlineContentType, - editor: BlockNoteEditor, -): editor is BlockNoteEditor< - B, - { [K in InlineContentType]: DefaultInlineContentSchema[InlineContentType] }, - S -> { - return ( - inlineContentType in editor.schema.inlineContentSchema && - editor.schema.inlineContentSchema[inlineContentType] === - defaultInlineContentSchema[inlineContentType] - ); -} + for (const [propName, propSpec] of Object.entries(props)) { + if (!(propName in editor.schema.blockSpecs[blockType].config.propSchema)) { + return false; + } -export function checkInlineContentTypeInSchema< - InlineContentType extends string, - Config extends InlineContentConfig, ->( - inlineContentType: InlineContentType, - inlineContentConfig: Config, - editor: BlockNoteEditor, -): editor is BlockNoteEditor { - return ( - inlineContentType in editor.schema.inlineContentSchema && - editor.schema.inlineContentSchema[inlineContentType] === inlineContentConfig - ); -} + if (typeof propSpec === "string") { + if ( + editor.schema.blockSpecs[blockType].config.propSchema[propName] + .default && + typeof editor.schema.blockSpecs[blockType].config.propSchema[propName] + .default !== propSpec + ) { + return false; + } -export function checkBlockIsDefaultType< - BlockType extends keyof DefaultBlockSchema, - I extends InlineContentSchema, - S extends StyleSchema, ->( - blockType: BlockType, - block: Block, - editor: BlockNoteEditor, -): block is BlockFromConfig { - return ( - block.type === blockType && - block.type in editor.schema.blockSchema && - checkDefaultBlockTypeInSchema(block.type, editor) - ); -} + if ( + editor.schema.blockSpecs[blockType].config.propSchema[propName].type && + editor.schema.blockSpecs[blockType].config.propSchema[propName].type !== + propSpec + ) { + return false; + } + } else { + if ( + editor.schema.blockSpecs[blockType].config.propSchema[propName] + .default !== propSpec.default + ) { + return false; + } -export function checkBlockIsFileBlock< - B extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema, ->( - block: Block, - editor: BlockNoteEditor, -): block is BlockFromConfig { - return ( - (block.type in editor.schema.blockSchema && - editor.schema.blockSchema[block.type].isFileBlock) || - false - ); -} + if ( + editor.schema.blockSpecs[blockType].config.propSchema[propName] + .default === undefined && + propSpec.default === undefined + ) { + if ( + editor.schema.blockSpecs[blockType].config.propSchema[propName] + .type !== propSpec.type + ) { + return false; + } + } -export function checkBlockIsFileBlockWithPreview< - B extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema, ->( - block: Block, - editor: BlockNoteEditor, -): block is BlockFromConfig< - FileBlockConfig & { - propSchema: Required; - }, - I, - S -> { - return ( - (block.type in editor.schema.blockSchema && - editor.schema.blockSchema[block.type].isFileBlock && - "showPreview" in editor.schema.blockSchema[block.type].propSchema) || - false - ); -} + if ( + typeof editor.schema.blockSpecs[blockType].config.propSchema[propName] + .values !== typeof propSpec.values + ) { + return false; + } + + if ( + typeof editor.schema.blockSpecs[blockType].config.propSchema[propName] + .values === "object" && + typeof propSpec.values === "object" + ) { + if ( + editor.schema.blockSpecs[blockType].config.propSchema[propName].values + .length !== propSpec.values.length + ) { + return false; + } + + for ( + let i = 0; + i < + editor.schema.blockSpecs[blockType].config.propSchema[propName].values + .length; + i++ + ) { + if ( + editor.schema.blockSpecs[blockType].config.propSchema[propName] + .values[i] !== propSpec.values[i] + ) { + return false; + } + } + } + } + } -export function checkBlockIsFileBlockWithPlaceholder< - B extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema, ->(block: Block, editor: BlockNoteEditor) { - const config = editor.schema.blockSchema[block.type]; - return config.isFileBlock && !block.props.url; + return true; } -export function checkBlockTypeHasDefaultProp< - Prop extends keyof typeof defaultProps, - I extends InlineContentSchema, - S extends StyleSchema, +export function blockHasType< + BType extends string, + Props extends + | PropSchema + | Record + | undefined = undefined, >( - prop: Prop, - blockType: string, - editor: BlockNoteEditor, -): editor is BlockNoteEditor< + block: Block, + editor: BlockNoteEditor, + blockType: BType, + props?: Props, +): block is Block< { - [BT in string]: { - type: BT; - propSchema: { - [P in Prop]: (typeof defaultProps)[P]; - }; - content: "table" | "inline" | "none"; - }; + [BT in BType]: Props extends PropSchema + ? BlockConfig + : Props extends Record + ? BlockConfig< + BT, + { + [PN in keyof Props]: PropSpec< + Props[PN] extends "boolean" + ? boolean + : Props[PN] extends "number" + ? number + : Props[PN] extends "string" + ? string + : never + >; + } + > + : BlockConfig; }, - I, - S + any, + any > { return ( - blockType in editor.schema.blockSchema && - prop in editor.schema.blockSchema[blockType].propSchema && - editor.schema.blockSchema[blockType].propSchema[prop] === defaultProps[prop] + editorHasBlockWithType(editor, blockType, props) && block.type === blockType ); } -export function checkBlockHasDefaultProp< - Prop extends keyof typeof defaultProps, - I extends InlineContentSchema, - S extends StyleSchema, ->( - prop: Prop, - block: Block, - editor: BlockNoteEditor, -): block is BlockFromConfig< - { - type: string; - propSchema: { - [P in Prop]: (typeof defaultProps)[P]; - }; - content: "table" | "inline" | "none"; - }, - I, - S -> { - return checkBlockTypeHasDefaultProp(prop, block.type, editor); -} - export function isTableCellSelection( selection: Selection, ): selection is CellSelection { diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index 81e32dd5ce..bc43c0c7a4 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -14,7 +14,7 @@ import { numberedListItem, pageBreak, paragraph, - quoteBlock, + quote, toggleListItem, video, } from "../blks/index.js"; @@ -45,7 +45,7 @@ export const defaultBlockSpecs = { heading: heading.definition(), numberedListItem: numberedListItem.definition(), pageBreak: pageBreak.definition(), - quoteBlock: quoteBlock.definition(), + quote: quote.definition(), toggleListItem: toggleListItem.definition(), file: file.definition(), image: image.definition(), diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 096f15edee..ae989e1e09 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -88,7 +88,7 @@ import { TextCursorPosition } from "./cursorPositionTypes.js"; import { Selection } from "./selectionTypes.js"; import { transformPasted } from "./transformPasted.js"; -import { checkDefaultBlockTypeInSchema } from "../blocks/defaultBlockTypeGuards.js"; +import { editorHasBlockWithType } from "../blocks/defaultBlockTypeGuards.js"; import { BlockNoteSchema } from "./BlockNoteSchema.js"; import { BlockNoteTipTapEditor, @@ -682,7 +682,7 @@ export class BlockNoteEditor< disableExtensions: newOptions.disableExtensions, setIdAttribute: newOptions.setIdAttribute, animations: newOptions.animations ?? true, - tableHandles: checkDefaultBlockTypeInSchema("table", this), + tableHandles: editorHasBlockWithType(this, "table"), dropCursor: this.options.dropCursor ?? dropCursor, placeholders: newOptions.placeholders, tabBehavior: newOptions.tabBehavior, diff --git a/packages/core/src/editor/playground.ts b/packages/core/src/editor/playground.ts index ce358a44c7..86ffa125c4 100644 --- a/packages/core/src/editor/playground.ts +++ b/packages/core/src/editor/playground.ts @@ -9,7 +9,7 @@ import { numberedListItem, pageBreak, paragraph, - quoteBlock, + quote, toggleListItem, video, } from "../blks/index.js"; @@ -38,7 +38,7 @@ const defaultBlockSpecs = { heading: heading.definition, numberedListItem: numberedListItem.definition, pageBreak: pageBreak.definition, - quoteBlock: quoteBlock.definition, + quote: quote.definition, toggleListItem: toggleListItem.definition, file: file.definition, image: image.definition, diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts index 4f02c94625..85ab84ded2 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts @@ -1,6 +1,6 @@ import type { Emoji, EmojiMartData } from "@emoji-mart/data"; -import { checkDefaultInlineContentTypeInSchema } from "../../blocks/defaultBlockTypeGuards.js"; +import { defaultInlineContentSchema } from "../../blocks/defaultBlocks.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { BlockSchema, @@ -54,7 +54,11 @@ export async function getDefaultEmojiPickerItems< editor: BlockNoteEditor, query: string, ): Promise { - if (!checkDefaultInlineContentTypeInSchema("text", editor)) { + if ( + !("text" in editor.schema.inlineContentSchema) || + editor.schema.inlineContentSchema["text"] !== + defaultInlineContentSchema["text"] + ) { return []; } diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index bcc95c83a7..b37d4be1da 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -1,7 +1,11 @@ -import { Block, PartialBlock } from "../../blocks/defaultBlocks.js"; +import { + Block, + defaultBlockSpecs, + PartialBlock, +} from "../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import { checkDefaultBlockTypeInSchema } from "../../blocks/defaultBlockTypeGuards.js"; +import { editorHasBlockWithType } from "../../blocks/defaultBlockTypeGuards.js"; import { BlockSchema, InlineContentSchema, @@ -87,7 +91,7 @@ export function getDefaultSlashMenuItems< >(editor: BlockNoteEditor) { const items: DefaultSuggestionItem[] = []; - if (checkDefaultBlockTypeInSchema("heading", editor)) { + if (editorHasBlockWithType(editor, "heading", { level: "number" })) { items.push( { onItemClick: () => { @@ -125,7 +129,7 @@ export function getDefaultSlashMenuItems< ); } - if (checkDefaultBlockTypeInSchema("quote", editor)) { + if (editorHasBlockWithType(editor, "quote")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -137,7 +141,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("toggleListItem", editor)) { + if (editorHasBlockWithType(editor, "toggleListItem")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -150,7 +154,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("numberedListItem", editor)) { + if (editorHasBlockWithType(editor, "numberedListItem")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -163,7 +167,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("bulletListItem", editor)) { + if (editorHasBlockWithType(editor, "bulletListItem")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -176,7 +180,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("checkListItem", editor)) { + if (editorHasBlockWithType(editor, "checkListItem")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -189,7 +193,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("paragraph", editor)) { + if (editorHasBlockWithType(editor, "paragraph")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -202,7 +206,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("codeBlock", editor)) { + if (editorHasBlockWithType(editor, "codeBlock")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -215,7 +219,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("table", editor)) { + if (editorHasBlockWithType(editor, "table")) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -230,7 +234,7 @@ export function getDefaultSlashMenuItems< cells: ["", "", ""], }, ], - }, + } as any, }); }, badge: undefined, @@ -239,7 +243,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("image", editor)) { + if (editorHasBlockWithType(editor, "image", { url: "string" })) { items.push({ onItemClick: () => { const insertedBlock = insertOrUpdateBlock(editor, { @@ -258,7 +262,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("video", editor)) { + if (editorHasBlockWithType(editor, "video", { url: "string" })) { items.push({ onItemClick: () => { const insertedBlock = insertOrUpdateBlock(editor, { @@ -277,7 +281,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("audio", editor)) { + if (editorHasBlockWithType(editor, "audio", { url: "string" })) { items.push({ onItemClick: () => { const insertedBlock = insertOrUpdateBlock(editor, { @@ -296,7 +300,7 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("file", editor)) { + if (editorHasBlockWithType(editor, "file", { url: "string" })) { items.push({ onItemClick: () => { const insertedBlock = insertOrUpdateBlock(editor, { @@ -315,7 +319,12 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("heading", editor)) { + if ( + editorHasBlockWithType(editor, "heading", { + level: "number", + isToggleable: "boolean", + }) + ) { items.push( { onItemClick: () => { diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 388cb47f36..a8e444db71 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -27,7 +27,7 @@ import { import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../api/nodeUtil.js"; import { - checkBlockIsDefaultType, + editorHasBlockWithType, isTableCellSelection, } from "../../blocks/defaultBlockTypeGuards.js"; import { DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; @@ -278,7 +278,7 @@ export class TableHandlesView< this.editor.schema.styleSchema, ); - if (checkBlockIsDefaultType("table", block, this.editor)) { + if (editorHasBlockWithType(this.editor, "table")) { this.tablePos = pmNodeInfo.posBeforeNode + 1; tableBlock = block; } diff --git a/packages/react/src/components/Comments/schema.ts b/packages/react/src/components/Comments/schema.ts index 576aa2c3ef..38b65ce88f 100644 --- a/packages/react/src/components/Comments/schema.ts +++ b/packages/react/src/components/Comments/schema.ts @@ -1,20 +1,8 @@ -import { - BlockNoteSchema, - createBlockSpecFromStronglyTypedTiptapNode, - createStronglyTypedTiptapNode, - defaultBlockSpecs, - defaultStyleSpecs, -} from "@blocknote/core"; +import { BlockNoteSchema, defaultStyleSpecs } from "@blocknote/core"; +import { paragraph } from "../../../../core/src/blks"; // this is quite convoluted. we'll clean this up when we make // it easier to extend / customize the default blocks -const paragraph = createBlockSpecFromStronglyTypedTiptapNode( - createStronglyTypedTiptapNode<"paragraph", "inline*">( - defaultBlockSpecs.paragraph.implementation.node.config as any, - ), - // disable default props on paragraph (such as textalignment and colors) - {}, -); // remove textColor, backgroundColor from styleSpecs const { textColor, backgroundColor, ...styleSpecs } = defaultStyleSpecs; @@ -22,7 +10,7 @@ const { textColor, backgroundColor, ...styleSpecs } = defaultStyleSpecs; // the schema to use for comments export const schema = BlockNoteSchema.create({ blockSpecs: { - paragraph, + paragraph: paragraph.definition(), }, styleSpecs, }); diff --git a/packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx b/packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx index e650875720..f1ca112f0a 100644 --- a/packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx +++ b/packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx @@ -75,10 +75,9 @@ export const UploadTab = < ); const config = editor.schema.blockSchema[block.type]; - const accept = - config.isFileBlock && config.fileBlockAccept?.length - ? config.fileBlockAccept.join(",") - : "*/*"; + const accept = config.meta?.fileBlockAccept?.length + ? config.meta.fileBlockAccept.join(",") + : "*/*"; return ( diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx index 204e5bc5dc..38b1fef90c 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx @@ -1,7 +1,7 @@ import { + blockHasType, BlockSchema, - checkBlockIsFileBlock, - checkBlockIsFileBlockWithPlaceholder, + editorHasBlockWithType, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -41,7 +41,12 @@ export const FileCaptionButton = () => { const block = selectedBlocks[0]; - if (checkBlockIsFileBlock(block, editor)) { + if ( + blockHasType(block, editor, block.type, { + url: "string", + caption: "string", + }) + ) { setCurrentEditingCaption(block.props.caption); return block; } @@ -51,11 +56,17 @@ export const FileCaptionButton = () => { const handleEnter = useCallback( (event: KeyboardEvent) => { - if (fileBlock && event.key === "Enter") { + if ( + fileBlock && + editorHasBlockWithType(editor, fileBlock.type, { + caption: "string", + }) && + event.key === "Enter" + ) { event.preventDefault(); editor.updateBlock(fileBlock, { props: { - caption: currentEditingCaption as any, // TODO + caption: currentEditingCaption, }, }); } @@ -69,11 +80,7 @@ export const FileCaptionButton = () => { [], ); - if ( - !fileBlock || - checkBlockIsFileBlockWithPlaceholder(fileBlock, editor) || - !editor.isEditable - ) { + if (!fileBlock || fileBlock.props.url === "" || !editor.isEditable) { return null; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx index 747e5942a0..2ea096de36 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx @@ -1,7 +1,6 @@ import { + blockHasType, BlockSchema, - checkBlockIsFileBlock, - checkBlockIsFileBlockWithPlaceholder, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -33,7 +32,7 @@ export const FileDeleteButton = () => { const block = selectedBlocks[0]; - if (checkBlockIsFileBlock(block, editor)) { + if (blockHasType(block, editor, block.type, { url: "string" })) { return block; } @@ -45,11 +44,7 @@ export const FileDeleteButton = () => { editor.removeBlocks([fileBlock!]); }, [editor, fileBlock]); - if ( - !fileBlock || - checkBlockIsFileBlockWithPlaceholder(fileBlock, editor) || - !editor.isEditable - ) { + if (!fileBlock || fileBlock.props.url === "" || !editor.isEditable) { return null; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx index a80eb5e50e..d19e202564 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx @@ -1,7 +1,6 @@ import { + blockHasType, BlockSchema, - checkBlockIsFileBlock, - checkBlockIsFileBlockWithPlaceholder, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -34,7 +33,7 @@ export const FileDownloadButton = () => { const block = selectedBlocks[0]; - if (checkBlockIsFileBlock(block, editor)) { + if (blockHasType(block, editor, block.type, { url: "string" })) { return block; } @@ -57,7 +56,7 @@ export const FileDownloadButton = () => { } }, [editor, fileBlock]); - if (!fileBlock || checkBlockIsFileBlockWithPlaceholder(fileBlock, editor)) { + if (!fileBlock || fileBlock.props.url === "") { return null; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx index c52159c3d7..9e8fcb0425 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx @@ -1,7 +1,7 @@ import { + blockHasType, BlockSchema, - checkBlockIsFileBlockWithPlaceholder, - checkBlockIsFileBlockWithPreview, + editorHasBlockWithType, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -33,7 +33,12 @@ export const FilePreviewButton = () => { const block = selectedBlocks[0]; - if (checkBlockIsFileBlockWithPreview(block, editor)) { + if ( + blockHasType(block, editor, block.type, { + url: "string", + showPreview: "boolean", + }) + ) { return block; } @@ -41,20 +46,21 @@ export const FilePreviewButton = () => { }, [editor, selectedBlocks]); const onClick = useCallback(() => { - if (fileBlock) { + if ( + fileBlock && + editorHasBlockWithType(editor, fileBlock.type, { + showPreview: "boolean", + }) + ) { editor.updateBlock(fileBlock, { props: { - showPreview: !fileBlock.props.showPreview as any, // TODO + showPreview: !fileBlock.props.showPreview, }, }); } }, [editor, fileBlock]); - if ( - !fileBlock || - checkBlockIsFileBlockWithPlaceholder(fileBlock, editor) || - !editor.isEditable - ) { + if (!fileBlock || fileBlock.props.url === "" || !editor.isEditable) { return null; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx index 595b9c5271..583494917f 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx @@ -1,7 +1,7 @@ import { + blockHasType, BlockSchema, - checkBlockIsFileBlock, - checkBlockIsFileBlockWithPlaceholder, + editorHasBlockWithType, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -41,7 +41,12 @@ export const FileRenameButton = () => { const block = selectedBlocks[0]; - if (checkBlockIsFileBlock(block, editor)) { + if ( + blockHasType(block, editor, block.type, { + url: "string", + name: "string", + }) + ) { setCurrentEditingName(block.props.name); return block; } @@ -51,11 +56,17 @@ export const FileRenameButton = () => { const handleEnter = useCallback( (event: KeyboardEvent) => { - if (fileBlock && event.key === "Enter") { + if ( + fileBlock && + editorHasBlockWithType(editor, fileBlock.type, { + name: "string", + }) && + event.key === "Enter" + ) { event.preventDefault(); editor.updateBlock(fileBlock, { props: { - name: currentEditingName as any, // TODO + name: currentEditingName, }, }); } @@ -69,11 +80,7 @@ export const FileRenameButton = () => { [], ); - if ( - !fileBlock || - checkBlockIsFileBlockWithPlaceholder(fileBlock, editor) || - !editor.isEditable - ) { + if (!fileBlock || fileBlock.props.name === "" || !editor.isEditable) { return null; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx index 7770cd4fd3..ed68593186 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx @@ -1,6 +1,6 @@ import { + blockHasType, BlockSchema, - checkBlockIsFileBlock, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -35,7 +35,9 @@ export const FileReplaceButton = () => { if ( block === undefined || - !checkBlockIsFileBlock(block, editor) || + !blockHasType(block, editor, block.type, { + url: "string", + }) || !editor.isEditable ) { return null; diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index 934fc3a32a..05b38f6a7a 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -1,8 +1,9 @@ import { + blockHasType, BlockSchema, - checkBlockHasDefaultProp, - checkBlockTypeHasDefaultProp, + defaultProps, DefaultProps, + editorHasBlockWithType, InlineContentSchema, mapTableCell, StyleSchema, @@ -46,7 +47,11 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { const textAlignment = useMemo(() => { const block = selectedBlocks[0]; - if (checkBlockHasDefaultProp("textAlignment", block, editor)) { + if ( + blockHasType(block, editor, block.type, { + textAlignment: defaultProps.textAlignment, + }) + ) { return block.props.textAlignment; } if (block.type === "table") { @@ -75,7 +80,14 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { editor.focus(); for (const block of selectedBlocks) { - if (checkBlockTypeHasDefaultProp("textAlignment", block.type, editor)) { + if ( + blockHasType(block, editor, block.type, { + textAlignment: defaultProps.textAlignment, + }) && + editorHasBlockWithType(editor, block.type, { + textAlignment: defaultProps.textAlignment, + }) + ) { editor.updateBlock(block, { props: { textAlignment: textAlignment }, }); @@ -122,10 +134,12 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { const show = useMemo(() => { return !!selectedBlocks.find( (block) => - "textAlignment" in block.props || + blockHasType(block, editor, block.type, { + textAlignment: defaultProps.textAlignment, + }) || (block.type === "table" && block.children), ); - }, [selectedBlocks]); + }, [editor, selectedBlocks]); if (!show || !editor.isEditable) { return null; diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx index 5e27e1eb21..c8733458d6 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx @@ -1,10 +1,11 @@ import { + Block, + blockHasType, BlockSchema, - checkBlockHasDefaultProp, - checkBlockTypeHasDefaultProp, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, + editorHasBlockWithType, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -28,9 +29,18 @@ export const BlockColorsItem = < const editor = useBlockNoteEditor(); + // We cast the block to a generic one, as the base type causes type errors + // with runtime type checking using `blockHasType`. Runtime type checking is + // more valuable than static checks, so better to do it like this. + const block = props.block as Block; + if ( - !checkBlockTypeHasDefaultProp("textColor", props.block.type, editor) && - !checkBlockTypeHasDefaultProp("backgroundColor", props.block.type, editor) + !blockHasType(block, editor, block.type, { + textColor: "string", + }) || + !blockHasType(block, editor, block.type, { + backgroundColor: "string", + }) ) { return null; } @@ -53,32 +63,33 @@ export const BlockColorsItem = < - editor.updateBlock(props.block, { - type: props.block.type, + editor.updateBlock(block, { + type: block.type, props: { textColor: color }, }), } : undefined } background={ - checkBlockTypeHasDefaultProp( - "backgroundColor", - props.block.type, - editor, - ) && - checkBlockHasDefaultProp("backgroundColor", props.block, editor) + blockHasType(block, editor, block.type, { + backgroundColor: "string", + }) && + editorHasBlockWithType(editor, block.type, { + backgroundColor: "string", + }) ? { - color: props.block.props.backgroundColor, + color: block.props.backgroundColor, setColor: (color) => - editor.updateBlock(props.block, { + editor.updateBlock(block, { props: { backgroundColor: color }, }), }