UNPKG

@blocknote/core

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

228 lines (211 loc) 6.71 kB
import { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; import { sortByDependencies } from "../util/topo-sort.js"; import { BlockNoDefaults, BlockSchema, BlockSpecs, InlineContentConfig, InlineContentSchema, InlineContentSpec, InlineContentSpecs, LooseBlockSpec, PartialBlockNoDefaults, StyleSchema, StyleSpecs, addNodeAndExtensionsToSpec, getInlineContentSchemaFromSpecs, getStyleSchemaFromSpecs, } from "./index.js"; function removeUndefined<T extends Record<string, any> | undefined>(obj: T): T { if (!obj) { return obj; } return Object.fromEntries( Object.entries(obj).filter(([, value]) => value !== undefined), ) as T; } export class CustomBlockNoteSchema< BSchema extends BlockSchema, ISchema extends InlineContentSchema, SSchema extends StyleSchema, > { // Helper so that you can use typeof schema.BlockNoteEditor public readonly BlockNoteEditor: BlockNoteEditor<BSchema, ISchema, SSchema> = "only for types" as any; public readonly Block: BlockNoDefaults<BSchema, ISchema, SSchema> = "only for types" as any; public readonly PartialBlock: PartialBlockNoDefaults< BSchema, ISchema, SSchema > = "only for types" as any; public inlineContentSpecs: InlineContentSpecs; public styleSpecs: StyleSpecs; public blockSpecs: { [K in keyof BSchema]: K extends string ? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]> : never; }; public blockSchema: BSchema; public inlineContentSchema: ISchema; public styleSchema: SSchema; constructor( private opts: { blockSpecs: BlockSpecs; inlineContentSpecs: InlineContentSpecs; styleSpecs: StyleSpecs; }, ) { const { blockSpecs, inlineContentSpecs, styleSpecs, blockSchema, inlineContentSchema, styleSchema, } = this.init(); this.blockSpecs = blockSpecs; this.styleSpecs = styleSpecs; this.styleSchema = styleSchema; this.inlineContentSpecs = inlineContentSpecs; this.blockSchema = blockSchema; this.inlineContentSchema = inlineContentSchema; } private init() { const getPriority = sortByDependencies( Object.entries({ ...this.opts.blockSpecs, ...this.opts.inlineContentSpecs, ...this.opts.styleSpecs, }).map(([key, val]) => ({ key: key, runsBefore: val.implementation?.runsBefore ?? [], })), ); const blockSpecs = Object.fromEntries( Object.entries(this.opts.blockSpecs).map(([key, blockSpec]) => { return [ key, addNodeAndExtensionsToSpec( blockSpec.config, blockSpec.implementation, blockSpec.extensions, getPriority(key), ), ]; }), ) as { [K in keyof BSchema]: K extends string ? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]> : never; }; const inlineContentSpecs = Object.fromEntries( Object.entries(this.opts.inlineContentSpecs).map( ([key, inlineContentSpec]) => { // Case for text and links. if (typeof inlineContentSpec.config !== "object") { return [key, inlineContentSpec]; } return [ key, { ...inlineContentSpec, implementation: { ...inlineContentSpec.implementation, node: inlineContentSpec.implementation?.node.extend({ priority: getPriority(key), }), }, }, ]; }, ), ) as InlineContentSpecs; const styleSpecs = Object.fromEntries( Object.entries(this.opts.styleSpecs).map(([key, styleSpec]) => [ key, { ...styleSpec, implementation: { ...styleSpec.implementation, mark: styleSpec.implementation?.mark.extend({ priority: getPriority(key), }), }, }, ]), ) as StyleSpecs; return { blockSpecs, blockSchema: Object.fromEntries( Object.entries(blockSpecs).map(([key, blockDef]) => { return [key, blockDef.config]; }), ) as any, inlineContentSpecs: removeUndefined(inlineContentSpecs), styleSpecs: removeUndefined(styleSpecs), inlineContentSchema: getInlineContentSchemaFromSpecs( inlineContentSpecs, ) as any, styleSchema: getStyleSchemaFromSpecs(styleSpecs) as any, }; } /** * Adds additional block specs to the current schema in a builder pattern. * This method allows extending the schema after it has been created. * * @param additionalBlockSpecs - Additional block specs to add to the schema * @returns The current schema instance for chaining */ public extend< AdditionalBlockSpecs extends BlockSpecs = Record<string, never>, AdditionalInlineContentSpecs extends Record< string, InlineContentSpec<InlineContentConfig> > = Record<string, never>, AdditionalStyleSpecs extends StyleSpecs = Record<string, never>, >(opts: { blockSpecs?: AdditionalBlockSpecs; inlineContentSpecs?: AdditionalInlineContentSpecs; styleSpecs?: AdditionalStyleSpecs; }): CustomBlockNoteSchema< AdditionalBlockSpecs extends undefined | Record<string, never> ? BSchema : BSchema & { [K in keyof AdditionalBlockSpecs]: K extends string ? AdditionalBlockSpecs[K]["config"] : never; }, AdditionalInlineContentSpecs extends undefined | Record<string, never> ? ISchema : ISchema & { [K in keyof AdditionalInlineContentSpecs]: AdditionalInlineContentSpecs[K]["config"]; }, AdditionalStyleSpecs extends undefined | Record<string, never> ? SSchema : SSchema & { [K in keyof AdditionalStyleSpecs]: AdditionalStyleSpecs[K]["config"]; } > { // Merge the new specs with existing ones Object.assign(this.opts.blockSpecs, opts.blockSpecs); Object.assign(this.opts.inlineContentSpecs, opts.inlineContentSpecs); Object.assign(this.opts.styleSpecs, opts.styleSpecs); // Reinitialize the block specs with the merged specs const { blockSpecs, inlineContentSpecs, styleSpecs, blockSchema, inlineContentSchema, styleSchema, } = this.init(); this.blockSpecs = blockSpecs; this.styleSpecs = styleSpecs; this.styleSchema = styleSchema; this.inlineContentSpecs = inlineContentSpecs; this.blockSchema = blockSchema; this.inlineContentSchema = inlineContentSchema; return this as any; } }