@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 9.74 kB
Source Map (JSON)
{"version":3,"file":"BlockNoteExtension-BWw0r8Gy.cjs","sources":["../src/editor/managers/ExtensionManager/symbol.ts","../src/editor/BlockNoteExtension.ts"],"sourcesContent":["/**\n * Symbol used to track the original factory function for extensions.\n * This allows us to retrieve the original factory for comparison and other operations.\n */\nexport const originalFactorySymbol = Symbol(\"originalFactory\");\n\n","import { Store, StoreOptions } from \"@tanstack/store\";\nimport { type AnyExtension } from \"@tiptap/core\";\nimport type { Plugin as ProsemirrorPlugin } from \"prosemirror-state\";\nimport type { PartialBlockNoDefaults } from \"../schema/index.js\";\nimport type { BlockNoteEditor } from \"./BlockNoteEditor.js\";\nimport { originalFactorySymbol } from \"./managers/ExtensionManager/symbol.js\";\n\n/**\n * This function is called when the extension is destroyed.\n */\ntype OnDestroy = () => void;\n\n/**\n * Describes a BlockNote extension.\n */\nexport interface Extension<State = any, Key extends string = string> {\n /**\n * The unique identifier for the extension.\n */\n readonly key: Key;\n\n /**\n * Triggered when the extension is mounted to the editor.\n */\n readonly mount?: (ctx: {\n /**\n * The DOM element that the editor is mounted to.\n */\n dom: HTMLElement;\n /**\n * The root document of the {@link document} that the editor is mounted to.\n */\n root: Document | ShadowRoot;\n /**\n * An {@link AbortSignal} that will be aborted when the extension is destroyed.\n */\n signal: AbortSignal;\n }) => void | OnDestroy;\n\n /**\n * The store for the extension.\n */\n readonly store?: Store<State>;\n\n /**\n * Declares what {@link Extension}s that this extension depends on.\n */\n readonly runsBefore?: ReadonlyArray<string>;\n\n /**\n * Input rules for a block: An input rule is what is used to replace text in a block when a regular expression match is found.\n * As an example, typing `#` in a paragraph block will trigger an input rule to replace the text with a heading block.\n */\n readonly inputRules?: ReadonlyArray<InputRule>;\n\n /**\n * A mapping of a keyboard shortcut to a function that will be called when the shortcut is pressed\n *\n * The keys are in the format:\n * - Key names may be strings like `Shift-Ctrl-Enter`—a key identifier prefixed with zero or more modifiers\n * - Key identifiers are based on the strings that can appear in KeyEvent.key\n * - Use lowercase letters to refer to letter keys (or uppercase letters if you want shift to be held)\n * - You may use `Space` as an alias for the \" \" name\n * - Modifiers can be given in any order: `Shift-` (or `s-`), `Alt-` (or `a-`), `Ctrl-` (or `c-` or `Control-`) and `Cmd-` (or `m-` or `Meta-`)\n * - For characters that are created by holding shift, the Shift- prefix is implied, and should not be added explicitly\n * - You can use Mod- as a shorthand for Cmd- on Mac and Ctrl- on other platforms\n *\n * @example\n * ```typescript\n * keyboardShortcuts: {\n * \"Mod-Enter\": (ctx) => { return true; },\n * \"Shift-Ctrl-Space\": (ctx) => { return true; },\n * \"a\": (ctx) => { return true; },\n * \"Space\": (ctx) => { return true; }\n * }\n * ```\n */\n readonly keyboardShortcuts?: Record<\n string,\n (ctx: { editor: BlockNoteEditor<any, any, any> }) => boolean\n >;\n\n /**\n * Add additional prosemirror plugins to the editor.\n */\n readonly prosemirrorPlugins?: ReadonlyArray<ProsemirrorPlugin>;\n\n /**\n * Add additional tiptap extensions to the editor.\n */\n readonly tiptapExtensions?: ReadonlyArray<AnyExtension>;\n\n /**\n * Add additional BlockNote extensions to the editor.\n */\n readonly blockNoteExtensions?: ReadonlyArray<ExtensionFactoryInstance>;\n}\n\n/**\n * An input rule is what is used to replace text in a block when a regular expression match is found.\n * As an example, typing `#` in a paragraph block will trigger an input rule to replace the text with a heading block.\n */\ntype InputRule = {\n /**\n * The regex to match when to trigger the input rule\n */\n find: RegExp;\n /**\n * The function to call when the input rule is matched\n * @returns undefined if the input rule should not be triggered, or an object with the type and props to update the block\n */\n replace: (props: {\n /**\n * The result of the regex match\n */\n match: RegExpMatchArray;\n // TODO this will be a Point, when we have the Location API\n /**\n * The range of the text that was matched\n */\n range: { from: number; to: number };\n /**\n * The editor instance\n */\n editor: BlockNoteEditor<any, any, any>;\n }) => undefined | PartialBlockNoDefaults<any, any, any>;\n};\n\n/**\n * These are the arguments that are passed to an {@link ExtensionFactoryInstance}.\n */\nexport interface ExtensionOptions<\n Options extends Record<string, any> | undefined =\n | Record<string, any>\n | undefined,\n> {\n options: Options;\n editor: BlockNoteEditor<any, any, any>;\n}\n\n// a type that maps the extension key to the return type of the extension factory\nexport type ExtensionMap<T extends ReadonlyArray<ExtensionFactoryInstance>> = {\n [K in T[number] extends ExtensionFactoryInstance<infer Ext>\n ? Ext[\"key\"]\n : never]: T[number] extends ExtensionFactoryInstance<infer Ext>\n ? Ext\n : never;\n};\n\n/**\n * This is a type that represents the function which will actually create the extension.\n * It requires the editor instance to be passed in, but will already have the options applied automatically.\n *\n * @note Only the BlockNoteEditor should instantiate this function, not the user. Look at {@link createExtension} for user-facing functions.\n */\nexport type ExtensionFactoryInstance<\n Ext extends Extension<any, any> = Extension<any, any>,\n> = (ctx: Omit<ExtensionOptions<any>, \"options\">) => Ext;\n\n/**\n * This is the return type of the {@link createExtension} function.\n * It is a function that can be invoked with the extension's options to create a new extension factory.\n */\nexport type ExtensionFactory<\n State = any,\n Key extends string = string,\n Factory extends (ctx: any) => Extension<State, Key> = (\n ctx: ExtensionOptions<any>,\n ) => Extension<State, Key>,\n> =\n Parameters<Factory>[0] extends ExtensionOptions<infer Options>\n ? undefined extends Options\n ? (\n options?: Exclude<Options, undefined>,\n ) => ExtensionFactoryInstance<ReturnType<Factory>>\n : (options: Options) => ExtensionFactoryInstance<ReturnType<Factory>>\n : () => ExtensionFactoryInstance<ReturnType<Factory>>;\n\n/**\n * Constructs a BlockNote {@link ExtensionFactory} from a factory function or object\n */\n// This overload is for `createExtension({ key: \"test\", ... })`\nexport function createExtension<\n const State = any,\n const Key extends string = string,\n const Ext extends Extension<State, Key> = Extension<State, Key>,\n>(factory: Ext): ExtensionFactoryInstance<Ext>;\n// This overload is for `createExtension(({editor, options}) => ({ key: \"test\", ... }))`\nexport function createExtension<\n const State = any,\n const Options extends Record<string, any> | undefined = any,\n const Key extends string = string,\n const Factory extends (ctx: any) => Extension<State, Key> = (\n ctx: ExtensionOptions<Options>,\n ) => Extension<State, Key>,\n>(factory: Factory): ExtensionFactory<State, Key, Factory>;\n// This overload is for both of the above overloads as it is the implementation of the function\nexport function createExtension<\n const State = any,\n const Options extends Record<string, any> | undefined = any,\n const Key extends string = string,\n const Factory extends\n | Extension<State, Key>\n | ((ctx: any) => Extension<State, Key>) = (\n ctx: ExtensionOptions<Options>,\n ) => Extension<State, Key>,\n>(\n factory: Factory,\n): Factory extends Extension<State, Key>\n ? ExtensionFactoryInstance<Factory>\n : Factory extends (ctx: any) => Extension<State, Key>\n ? ExtensionFactory<State, Key, Factory>\n : never {\n if (typeof factory === \"object\" && \"key\" in factory) {\n return function factoryFn() {\n (factory as any)[originalFactorySymbol] = factoryFn;\n return factory;\n } as any;\n }\n\n if (typeof factory !== \"function\") {\n throw new Error(\"factory must be a function\");\n }\n\n return function factoryFn(options: Options) {\n return (ctx: { editor: BlockNoteEditor<any, any, any> }) => {\n const extension = factory({ editor: ctx.editor, options });\n // We stick a symbol onto the extension to allow us to retrieve the original factory for comparison later.\n // This enables us to do things like: `editor.getExtension(YSync).prosemirrorPlugins`\n (extension as any)[originalFactorySymbol] = factoryFn;\n return extension;\n };\n } as any;\n}\n\nexport function createStore<T = any>(\n initialState: T,\n options?: StoreOptions<T>,\n): Store<T> {\n return new Store(initialState, options);\n}\n"],"names":["originalFactorySymbol","createExtension","factory","factoryFn","options","ctx","extension","createStore","initialState","Store"],"mappings":"gDAIaA,EAAwB,OAAO,iBAAiB,ECiMtD,SAASC,EAUdC,EAKU,CACV,GAAI,OAAOA,GAAY,UAAY,QAASA,EAC1C,OAAO,SAASC,GAAY,CACzB,OAAAD,EAAgBF,CAAqB,EAAIG,EACnCD,CACT,EAGF,GAAI,OAAOA,GAAY,WACrB,MAAM,IAAI,MAAM,4BAA4B,EAG9C,OAAO,SAASC,EAAUC,EAAkB,CAC1C,OAAQC,GAAoD,CAC1D,MAAMC,EAAYJ,EAAQ,CAAE,OAAQG,EAAI,OAAQ,QAAAD,EAAS,EAGxD,OAAAE,EAAkBN,CAAqB,EAAIG,EACrCG,CACT,CACF,CACF,CAEO,SAASC,EACdC,EACAJ,EACU,CACV,OAAO,IAAIK,EAAAA,MAAMD,EAAcJ,CAAO,CACxC"}