import { MarkSpec, NodeSpec, Schema } from '@tiptap/pm/model' import { MarkConfig, NodeConfig } from '..' import { AnyConfig, Extensions } from '../types' import { callOrReturn } from '../utilities/callOrReturn' import { isEmptyObject } from '../utilities/isEmptyObject' import { getAttributesFromExtensions } from './getAttributesFromExtensions' import { getExtensionField } from './getExtensionField' import { getRenderedAttributes } from './getRenderedAttributes' import { injectExtensionAttributesToParseRule } from './injectExtensionAttributesToParseRule' import { splitExtensions } from './splitExtensions' function cleanUpSchemaItem(data: T) { return Object.fromEntries( Object.entries(data).filter(([key, value]) => { if (key === 'attrs' && isEmptyObject(value)) { return false } return value !== null && value !== undefined }), ) as T } export function getSchemaByResolvedExtensions(extensions: Extensions): Schema { const allAttributes = getAttributesFromExtensions(extensions) const { nodeExtensions, markExtensions } = splitExtensions(extensions) const topNode = nodeExtensions.find(extension => getExtensionField(extension, 'topNode'))?.name const nodes = Object.fromEntries( nodeExtensions.map(extension => { const extensionAttributes = allAttributes.filter( attribute => attribute.type === extension.name, ) const context = { name: extension.name, options: extension.options, storage: extension.storage, } const extraNodeFields = extensions.reduce((fields, e) => { const extendNodeSchema = getExtensionField( e, 'extendNodeSchema', context, ) return { ...fields, ...(extendNodeSchema ? extendNodeSchema(extension) : {}), } }, {}) const schema: NodeSpec = cleanUpSchemaItem({ ...extraNodeFields, content: callOrReturn( getExtensionField(extension, 'content', context), ), marks: callOrReturn(getExtensionField(extension, 'marks', context)), group: callOrReturn(getExtensionField(extension, 'group', context)), inline: callOrReturn(getExtensionField(extension, 'inline', context)), atom: callOrReturn(getExtensionField(extension, 'atom', context)), selectable: callOrReturn( getExtensionField(extension, 'selectable', context), ), draggable: callOrReturn( getExtensionField(extension, 'draggable', context), ), code: callOrReturn(getExtensionField(extension, 'code', context)), defining: callOrReturn( getExtensionField(extension, 'defining', context), ), isolating: callOrReturn( getExtensionField(extension, 'isolating', context), ), attrs: Object.fromEntries( extensionAttributes.map(extensionAttribute => { return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }] }), ), }) const parseHTML = callOrReturn( getExtensionField(extension, 'parseHTML', context), ) if (parseHTML) { schema.parseDOM = parseHTML.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes)) } const renderHTML = getExtensionField( extension, 'renderHTML', context, ) if (renderHTML) { schema.toDOM = node => renderHTML({ node, HTMLAttributes: getRenderedAttributes(node, extensionAttributes), }) } const renderText = getExtensionField( extension, 'renderText', context, ) if (renderText) { schema.toText = renderText } return [extension.name, schema] }), ) const marks = Object.fromEntries( markExtensions.map(extension => { const extensionAttributes = allAttributes.filter( attribute => attribute.type === extension.name, ) const context = { name: extension.name, options: extension.options, storage: extension.storage, } const extraMarkFields = extensions.reduce((fields, e) => { const extendMarkSchema = getExtensionField( e, 'extendMarkSchema', context, ) return { ...fields, ...(extendMarkSchema ? extendMarkSchema(extension) : {}), } }, {}) const schema: MarkSpec = cleanUpSchemaItem({ ...extraMarkFields, inclusive: callOrReturn( getExtensionField(extension, 'inclusive', context), ), excludes: callOrReturn( getExtensionField(extension, 'excludes', context), ), group: callOrReturn(getExtensionField(extension, 'group', context)), spanning: callOrReturn( getExtensionField(extension, 'spanning', context), ), code: callOrReturn(getExtensionField(extension, 'code', context)), attrs: Object.fromEntries( extensionAttributes.map(extensionAttribute => { return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }] }), ), }) const parseHTML = callOrReturn( getExtensionField(extension, 'parseHTML', context), ) if (parseHTML) { schema.parseDOM = parseHTML.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes)) } const renderHTML = getExtensionField( extension, 'renderHTML', context, ) if (renderHTML) { schema.toDOM = mark => renderHTML({ mark, HTMLAttributes: getRenderedAttributes(mark, extensionAttributes), }) } return [extension.name, schema] }), ) return new Schema({ topNode, nodes, marks, }) }