From 6f9557294e2cd365c665998af9c8c232a851e413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Fri, 19 Feb 2021 09:54:39 +0100 Subject: [PATCH] add basic schema extender --- packages/core/src/Extension.ts | 27 ++++++++++++++++ packages/core/src/Mark.ts | 2 ++ packages/core/src/Node.ts | 10 ++---- packages/core/src/helpers/getSchema.ts | 36 ++++++++++++++++++++- packages/core/src/index.ts | 3 +- packages/core/src/utilities/callOrReturn.ts | 7 ++-- packages/extension-table/src/table.ts | 16 +++++++++ 7 files changed, 88 insertions(+), 13 deletions(-) diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts index 6f3594c5..7ad45967 100644 --- a/packages/core/src/Extension.ts +++ b/packages/core/src/Extension.ts @@ -2,6 +2,7 @@ import { Plugin, Transaction } from 'prosemirror-state' import { Command as ProseMirrorCommand } from 'prosemirror-commands' import { InputRule } from 'prosemirror-inputrules' import { Editor } from './Editor' +import { Node } from './Node' import mergeDeep from './utilities/mergeDeep' import { GlobalAttributes, RawCommands } from './types' @@ -65,6 +66,30 @@ export interface ExtensionConfig { editor: Editor, }) => Plugin[], + /** + * Extend Node Schema + */ + extendNodeSchema?: (( + this: { + options: Options, + }, + extension: Node, + ) => { + [key: string]: any, + }) | null, + + /** + * Extend Mark Schema + */ + extendMarkSchema?: (( + this: { + options: Options, + }, + extension: Node, + ) => { + [key: string]: any, + }) | null, + /** * The editor is ready. */ @@ -149,6 +174,8 @@ export class Extension { addInputRules: () => [], addPasteRules: () => [], addProseMirrorPlugins: () => [], + extendNodeSchema: null, + extendMarkSchema: null, onCreate: null, onUpdate: null, onSelection: null, diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts index 04e19a81..83a4ac0f 100644 --- a/packages/core/src/Mark.ts +++ b/packages/core/src/Mark.ts @@ -209,6 +209,8 @@ export class Mark { parseHTML: () => null, renderHTML: null, addAttributes: () => ({}), + extendNodeSchema: null, + extendMarkSchema: null, onCreate: null, onUpdate: null, onSelection: null, diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts index 08c1d256..221e9ce4 100644 --- a/packages/core/src/Node.ts +++ b/packages/core/src/Node.ts @@ -70,12 +70,6 @@ export interface NodeConfig extends Overwrite NodeSpec['isolating']), - // TODO: extend via extension-table - /** - * Table Role - */ - tableRole?: NodeSpec['tableRole'] | ((this: { options: Options }) => NodeSpec['tableRole']), - /** * Parse HTML */ @@ -284,6 +278,8 @@ export class Node { renderText: null, addAttributes: () => ({}), addNodeView: null, + extendNodeSchema: null, + extendMarkSchema: null, onCreate: null, onUpdate: null, onSelection: null, @@ -291,8 +287,6 @@ export class Node { onFocus: null, onBlur: null, onDestroy: null, - // TODO: remove, - tableRole: null, } options!: Options diff --git a/packages/core/src/helpers/getSchema.ts b/packages/core/src/helpers/getSchema.ts index 2f65659a..82c6fcd3 100644 --- a/packages/core/src/helpers/getSchema.ts +++ b/packages/core/src/helpers/getSchema.ts @@ -1,5 +1,6 @@ import { NodeSpec, MarkSpec, Schema } from 'prosemirror-model' import { Extensions } from '../types' +import { ExtensionConfig } from '../Extension' import splitExtensions from './splitExtensions' import getAttributesFromExtensions from './getAttributesFromExtensions' import getRenderedAttributes from './getRenderedAttributes' @@ -21,11 +22,34 @@ export default function getSchema(extensions: Extensions): Schema { const allAttributes = getAttributesFromExtensions(extensions) const { nodeExtensions, markExtensions } = splitExtensions(extensions) const topNode = nodeExtensions.find(extension => extension.config.topNode)?.config.name + const nodeSchemaExtenders: ExtensionConfig['extendNodeSchema'][] = [] + const markSchemaExtenders: ExtensionConfig['extendMarkSchema'][] = [] + + extensions.forEach(extension => { + if (typeof extension.config.extendNodeSchema === 'function') { + nodeSchemaExtenders.push(extension.config.extendNodeSchema) + } + + if (typeof extension.config.extendMarkSchema === 'function') { + markSchemaExtenders.push(extension.config.extendMarkSchema) + } + }) const nodes = Object.fromEntries(nodeExtensions.map(extension => { const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name) const context = { options: extension.options } + + const extraNodeFields = nodeSchemaExtenders.reduce((fields, nodeSchemaExtender) => { + const extraFields = callOrReturn(nodeSchemaExtender, context, extension) + + return { + ...fields, + ...extraFields, + } + }, {}) + const schema: NodeSpec = cleanUpSchemaItem({ + ...extraNodeFields, content: callOrReturn(extension.config.content, context), marks: callOrReturn(extension.config.marks, context), group: callOrReturn(extension.config.group, context), @@ -36,7 +60,6 @@ export default function getSchema(extensions: Extensions): Schema { code: callOrReturn(extension.config.code, context), defining: callOrReturn(extension.config.defining, context), isolating: callOrReturn(extension.config.isolating, context), - tableRole: callOrReturn(extension.config.tableRole, context), attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => { return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }] })), @@ -61,7 +84,18 @@ export default function getSchema(extensions: Extensions): Schema { const marks = Object.fromEntries(markExtensions.map(extension => { const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.config.name) const context = { options: extension.options } + + const extraMarkFields = markSchemaExtenders.reduce((fields, markSchemaExtender) => { + const extraFields = callOrReturn(markSchemaExtender, context, extension) + + return { + ...fields, + ...extraFields, + } + }, {}) + const schema: MarkSpec = cleanUpSchemaItem({ + ...extraMarkFields, inclusive: callOrReturn(extension.config.inclusive, context), excludes: callOrReturn(extension.config.excludes, context), group: callOrReturn(extension.config.group, context), diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a3e610e7..ba953275 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,11 +8,12 @@ export { default as nodeInputRule } from './inputRules/nodeInputRule' export { default as markInputRule } from './inputRules/markInputRule' export { default as markPasteRule } from './pasteRules/markPasteRule' +export { default as callOrReturn } from './utilities/callOrReturn' +export { default as mergeAttributes } from './utilities/mergeAttributes' export { default as generateHTML } from './helpers/generateHTML' export { default as getSchema } from './helpers/getSchema' export { default as getHTMLFromFragment } from './helpers/getHTMLFromFragment' export { default as getMarkAttributes } from './helpers/getMarkAttributes' -export { default as mergeAttributes } from './utilities/mergeAttributes' export { default as isActive } from './helpers/isActive' export { default as isMarkActive } from './helpers/isMarkActive' export { default as isNodeActive } from './helpers/isNodeActive' diff --git a/packages/core/src/utilities/callOrReturn.ts b/packages/core/src/utilities/callOrReturn.ts index 17f8adba..abbefd9c 100644 --- a/packages/core/src/utilities/callOrReturn.ts +++ b/packages/core/src/utilities/callOrReturn.ts @@ -3,14 +3,15 @@ * Otherwise it is returned directly. * @param value Function or any value. * @param context Optional context to bind to function. + * @param props Optional props to pass to function. */ -export default function callOrReturn(value: any, context?: any): any { +export default function callOrReturn(value: any, context: any = undefined, ...props: any[]): any { if (typeof value === 'function') { if (context) { - return value.bind(context)() + return value.bind(context)(...props) } - return value() + return value(...props) } return value diff --git a/packages/extension-table/src/table.ts b/packages/extension-table/src/table.ts index 789b4c27..7a36c822 100644 --- a/packages/extension-table/src/table.ts +++ b/packages/extension-table/src/table.ts @@ -4,6 +4,7 @@ import { mergeAttributes, isCellSelection, findParentNodeClosestToPos, + callOrReturn, } from '@tiptap/core' import { tableEditing, @@ -64,6 +65,13 @@ declare module '@tiptap/core' { fixTables: () => Command, } } + + interface NodeConfig { + /** + * Table Role + */ + tableRole?: string | ((this: { options: Options }) => string), + } } export const Table = Node.create({ @@ -81,6 +89,14 @@ export const Table = Node.create({ allowTableNodeSelection: false, }, + extendNodeSchema(extension) { + const context = { options: extension.options } + + return { + tableRole: callOrReturn(extension.config.tableRole, context), + } + }, + content: 'tableRow+', tableRole: 'table',