diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts index 1956478a..4458edbd 100644 --- a/packages/core/src/Node.ts +++ b/packages/core/src/Node.ts @@ -71,6 +71,7 @@ import { DOMOutputSpec, NodeSpec, Node } from 'prosemirror-model' import { ExtensionSpec } from './Extension' +import { Attributes } from './types' export interface NodeExtensionSpec extends ExtensionSpec { topNode?: boolean, @@ -100,6 +101,11 @@ export interface NodeExtensionSpec extends Extensio }, } ) => DOMOutputSpec, + createAttributes?: ( + this: { + options: Options, + }, + ) => Attributes, } export type NodeExtension = Required & { @@ -127,6 +133,7 @@ const defaultNode: NodeExtension = { createCommands: () => ({}), parseHTML: () => null, renderHTML: () => null, + createAttributes: () => ({}), } export function createNode(config: NodeExtensionSpec) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index bc33c59c..f2edacc4 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,5 +1,24 @@ -import Extension from './Extension' -import Node from './Node' -import Mark from './Mark' +import { Extension } from './Extension' +import { NodeExtension } from './Node' +import { MarkExtension } from './Mark' -export type Extensions = (Extension | Node | Mark)[] +export type Extensions = (Extension | NodeExtension | MarkExtension)[] + +export type Attribute = { + default: any, + rendered?: boolean, + renderHTML?: (attributes: { + [key: string]: any, + }) => any, + parseHTML?: () => any, +} + +export type Attributes = { + [key: string]: Attribute +} + +export type ExtensionAttribute = { + type: string, + name: string, + attribute: Attribute +} diff --git a/packages/core/src/utils/getAttributesFromExtensions.ts b/packages/core/src/utils/getAttributesFromExtensions.ts new file mode 100644 index 00000000..6eae27be --- /dev/null +++ b/packages/core/src/utils/getAttributesFromExtensions.ts @@ -0,0 +1,38 @@ +import { + Extensions, Attributes, Attribute, ExtensionAttribute, +} from '../types' +import splitExtensions from './splitExtensions' + +export default function getAttributesFromExtensions(extensions: Extensions) { + const allAttributes: ExtensionAttribute[] = [] + + const { nodeExtensions } = splitExtensions(extensions) + + const defaultAttribute: Required = { + default: null, + rendered: true, + renderHTML: () => ({}), + parseHTML: () => null, + } + + nodeExtensions.forEach(extension => { + const context = { + options: extension.options, + } + + const attributes = extension.createAttributes.bind(context)() as Attributes + + Object.entries(attributes).forEach(([name, attribute]) => { + allAttributes.push({ + type: extension.name, + name, + attribute: { + ...defaultAttribute, + ...attribute, + }, + }) + }) + }) + + return allAttributes +} diff --git a/packages/core/src/utils/getRenderedAttributes.ts b/packages/core/src/utils/getRenderedAttributes.ts new file mode 100644 index 00000000..db559c26 --- /dev/null +++ b/packages/core/src/utils/getRenderedAttributes.ts @@ -0,0 +1,17 @@ +import { Node } from 'prosemirror-model' +import { ExtensionAttribute } from '../types' + +export default function getRenderedAttributes(node: Node, attributes: ExtensionAttribute[]) { + return attributes + .map(attribute => { + // TODO: fallback if renderHTML doesn’t exist + return attribute.attribute.renderHTML(node.attrs) + }) + .reduce((accumulator, value) => { + // TODO: add support for "class" merge + return { + ...accumulator, + ...value, + } + }, {}) +} diff --git a/packages/core/src/utils/getSchema.ts b/packages/core/src/utils/getSchema.ts index 3d1f0c9b..0000471c 100644 --- a/packages/core/src/utils/getSchema.ts +++ b/packages/core/src/utils/getSchema.ts @@ -5,8 +5,12 @@ import { Extensions } from '../types' // import getMarksFromExtensions from './getMarksFromExtensions' // import resolveExtensionConfig from './resolveExtensionConfig' import splitExtensions from './splitExtensions' +import getAttributesFromExtensions from './getAttributesFromExtensions' +import getRenderedAttributes from './getRenderedAttributes' export default function getSchema(extensions: Extensions): Schema { + const allAttributes = getAttributesFromExtensions(extensions) + const { nodeExtensions, markExtensions } = splitExtensions(extensions) const topNode = nodeExtensions.find(extension => extension.topNode)?.name @@ -16,9 +20,7 @@ export default function getSchema(extensions: Extensions): Schema { options: extension.options, } - const attributes = { - class: 'test', - } + const attributes = allAttributes.filter(attribute => attribute.type === extension.name) const schema: NodeSpec = { content: extension.content, @@ -32,20 +34,30 @@ export default function getSchema(extensions: Extensions): Schema { defining: extension.defining, isolating: extension.isolating, parseDOM: extension.parseHTML.bind(context)(), - toDOM: node => extension.renderHTML.bind(context)({ node, attributes }), + toDOM: node => { + return extension.renderHTML.bind(context)({ + node, + attributes: getRenderedAttributes(node, attributes), + }) + }, + attrs: Object.fromEntries(attributes.map(attribute => { + return [attribute.name, { default: attribute?.attribute?.default }] + })), } return [extension.name, schema] })) + // console.log({ nodes }) + const marks = Object.fromEntries(markExtensions.map(extension => { const context = { options: extension.options, } - const attributes = { - class: 'test', - } + // const attributes = { + // class: 'test', + // } const schema: MarkSpec = { inclusive: extension.inclusive, @@ -53,7 +65,7 @@ export default function getSchema(extensions: Extensions): Schema { group: extension.group, spanning: extension.spanning, parseDOM: extension.parseHTML.bind(context)(), - toDOM: node => extension.renderHTML.bind(context)({ node, attributes }), + toDOM: node => extension.renderHTML.bind(context)({ node, attributes: {} }), } return [extension.name, schema] diff --git a/packages/extension-paragraph/index.ts b/packages/extension-paragraph/index.ts index 6dea2e79..63c6a87e 100644 --- a/packages/extension-paragraph/index.ts +++ b/packages/extension-paragraph/index.ts @@ -65,6 +65,15 @@ export default createNode({ content: 'inline*', + createAttributes() { + return { + id: { + default: '123', + renderHTML: attributes => ({ class: `foo-${attributes.id}`, id: 'foo' }), + }, + } + }, + parseHTML() { return [ { tag: 'p' },