UNPKG

@blocknote/core

Version:

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

109 lines (99 loc) 2.95 kB
import { KeyboardShortcutCommand, Node } from "@tiptap/core"; import { camelToDataKebab } from "../../util/string.js"; import { PropSchema, Props } from "../propTypes.js"; import { CustomInlineContentConfig, InlineContentConfig, InlineContentImplementation, InlineContentSchemaFromSpecs, InlineContentSpec, InlineContentSpecs, } from "./types.js"; // Function that adds necessary classes and attributes to the `dom` element // returned from a custom inline content's 'render' function, to ensure no data // is lost on internal copy & paste. export function addInlineContentAttributes< IType extends string, PSchema extends PropSchema, >( element: { dom: HTMLElement; contentDOM?: HTMLElement; }, inlineContentType: IType, inlineContentProps: Props<PSchema>, propSchema: PSchema, ): { dom: HTMLElement; contentDOM?: HTMLElement; } { // Sets content type attribute element.dom.setAttribute("data-inline-content-type", inlineContentType); // Adds props as HTML attributes in kebab-case with "data-" prefix. Skips props // set to their default values. Object.entries(inlineContentProps) .filter(([prop, value]) => { const spec = propSchema[prop]; return value !== spec.default; }) .map(([prop, value]) => { return [camelToDataKebab(prop), value]; }) .forEach(([prop, value]) => element.dom.setAttribute(prop, value)); if (element.contentDOM !== undefined) { element.contentDOM.setAttribute("data-editable", ""); } return element; } // see https://github.com/TypeCellOS/BlockNote/pull/435 export function addInlineContentKeyboardShortcuts< T extends CustomInlineContentConfig, >( config: T, ): { [p: string]: KeyboardShortcutCommand; } { return { Backspace: ({ editor }) => { const resolvedPos = editor.state.selection.$from; return ( editor.state.selection.empty && resolvedPos.node().type.name === config.type && resolvedPos.parentOffset === 0 ); }, }; } // This helper function helps to instantiate a InlineContentSpec with a // config and implementation that conform to the type of Config export function createInternalInlineContentSpec<T extends InlineContentConfig>( config: T, implementation: InlineContentImplementation<T>, ) { return { config, implementation, } satisfies InlineContentSpec<T>; } export function createInlineContentSpecFromTipTapNode< T extends Node, P extends PropSchema, >(node: T, propSchema: P) { return createInternalInlineContentSpec( { type: node.name as T["name"], propSchema, content: node.config.content === "inline*" ? "styled" : "none", }, { node, }, ); } export function getInlineContentSchemaFromSpecs<T extends InlineContentSpecs>( specs: T, ) { return Object.fromEntries( Object.entries(specs).map(([key, value]) => [key, value.config]), ) as InlineContentSchemaFromSpecs<T>; }