From 129ad83167900526d65c5c25da8249d0d65bcef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Fri, 7 May 2021 10:25:55 +0200 Subject: [PATCH] fix: add support for priority and nested extension for getSchema --- packages/core/src/ExtensionManager.ts | 15 +- packages/core/src/helpers/getSchema.ts | 140 +----------------- .../helpers/getSchemaByResolvedExtensions.ts | 138 +++++++++++++++++ 3 files changed, 153 insertions(+), 140 deletions(-) create mode 100644 packages/core/src/helpers/getSchemaByResolvedExtensions.ts diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index 5b3a4bfa..aa0062cc 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -6,7 +6,7 @@ import { Plugin } from 'prosemirror-state' import { Editor } from './Editor' import { Extensions, RawCommands, AnyConfig } from './types' import getExtensionField from './helpers/getExtensionField' -import getSchema from './helpers/getSchema' +import getSchemaByResolvedExtensions from './helpers/getSchemaByResolvedExtensions' import getSchemaTypeByName from './helpers/getSchemaTypeByName' import getNodeType from './helpers/getNodeType' import splitExtensions from './helpers/splitExtensions' @@ -27,8 +27,8 @@ export default class ExtensionManager { constructor(extensions: Extensions, editor: Editor) { this.editor = editor - this.extensions = this.sort(this.flatten(extensions)) - this.schema = getSchema(this.extensions) + this.extensions = ExtensionManager.resolve(extensions) + this.schema = getSchemaByResolvedExtensions(this.extensions) this.extensions.forEach(extension => { const context = { @@ -128,13 +128,16 @@ export default class ExtensionManager { }) } - private flatten(extensions: Extensions): Extensions { + static resolve(extensions: Extensions): Extensions { + return ExtensionManager.sort(ExtensionManager.flatten(extensions)) + } + + static flatten(extensions: Extensions): Extensions { return extensions .map(extension => { const context = { name: extension.name, options: extension.options, - editor: this.editor, } const addExtensions = getExtensionField( @@ -153,7 +156,7 @@ export default class ExtensionManager { .flat(10) } - private sort(extensions: Extensions): Extensions { + static sort(extensions: Extensions): Extensions { const defaultPriority = 100 return extensions.sort((a, b) => { diff --git a/packages/core/src/helpers/getSchema.ts b/packages/core/src/helpers/getSchema.ts index cc05f4c9..34095798 100644 --- a/packages/core/src/helpers/getSchema.ts +++ b/packages/core/src/helpers/getSchema.ts @@ -1,138 +1,10 @@ -import { NodeSpec, MarkSpec, Schema } from 'prosemirror-model' -import { AnyConfig, Extensions } from '../types' -import { NodeConfig, MarkConfig } from '..' -import splitExtensions from './splitExtensions' -import getAttributesFromExtensions from './getAttributesFromExtensions' -import getRenderedAttributes from './getRenderedAttributes' -import isEmptyObject from '../utilities/isEmptyObject' -import injectExtensionAttributesToParseRule from './injectExtensionAttributesToParseRule' -import callOrReturn from '../utilities/callOrReturn' -import getExtensionField from './getExtensionField' - -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 -} +import { Schema } from 'prosemirror-model' +import getSchemaByResolvedExtensions from './getSchemaByResolvedExtensions' +import ExtensionManager from '../ExtensionManager' +import { Extensions } from '../types' export default function getSchema(extensions: Extensions): Schema { - const allAttributes = getAttributesFromExtensions(extensions) - const { nodeExtensions, markExtensions } = splitExtensions(extensions) - const topNode = nodeExtensions.find(extension => getExtensionField(extension, 'topNode'))?.name + const resolvedExtensions = ExtensionManager.resolve(extensions) - const nodes = Object.fromEntries(nodeExtensions.map(extension => { - const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name) - const context = { - name: extension.name, - options: extension.options, - } - - 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), - }) - } - - 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, - } - - 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)), - 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, - }) + return getSchemaByResolvedExtensions(resolvedExtensions) } diff --git a/packages/core/src/helpers/getSchemaByResolvedExtensions.ts b/packages/core/src/helpers/getSchemaByResolvedExtensions.ts new file mode 100644 index 00000000..62c017db --- /dev/null +++ b/packages/core/src/helpers/getSchemaByResolvedExtensions.ts @@ -0,0 +1,138 @@ +import { NodeSpec, MarkSpec, Schema } from 'prosemirror-model' +import { AnyConfig, Extensions } from '../types' +import { NodeConfig, MarkConfig } from '..' +import splitExtensions from './splitExtensions' +import getAttributesFromExtensions from './getAttributesFromExtensions' +import getRenderedAttributes from './getRenderedAttributes' +import isEmptyObject from '../utilities/isEmptyObject' +import injectExtensionAttributesToParseRule from './injectExtensionAttributesToParseRule' +import callOrReturn from '../utilities/callOrReturn' +import getExtensionField from './getExtensionField' + +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 default 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, + } + + 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), + }) + } + + 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, + } + + 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)), + 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, + }) +}