@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
text/typescript
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>;
}