diff --git a/docs/concepts/extra-attributes.md b/docs/concepts/extra-attributes.md index 9c430d7cc5..9217cf178b 100644 --- a/docs/concepts/extra-attributes.md +++ b/docs/concepts/extra-attributes.md @@ -52,6 +52,24 @@ const paragraphExtension = new ParagraphExtension({ This example accomplishes the same things as the previous example and remirror is smart enough to automatically parse the dom and write to the dom the required values. +### Render as data attributes + +You can return an array of a key value pair, to determine how your extra attribute is rendered in the DOM. + +```ts +import { ParagraphExtension } from 'remirror/extension/paragraph'; + +const paragraphExtension = new ParagraphExtension({ + extraAttributes: { + custom: { + default: 'my default', + parseDOM: (dom) => dom.getAttribute('data-custom'), + toDOM: (attrs) => ['data-custom', attrs.custom], + }, + }, +}); +``` + ## RemirrorManager Extra attributes can also be added via the `RemirrorManager`. This can set attributes for a collection of nodes, marks and tags. This is very useful when adding attributes to multiple places in one sweep. diff --git a/packages/@remirror/core-utils/src/core-utils.ts b/packages/@remirror/core-utils/src/core-utils.ts index 1eb4b84d1e..2e6abf5f6f 100644 --- a/packages/@remirror/core-utils/src/core-utils.ts +++ b/packages/@remirror/core-utils/src/core-utils.ts @@ -10,17 +10,21 @@ import { isObject, isString, keys, + omit, sort, unset, } from '@remirror/core-helpers'; import type { AnchorHeadParameter, AnyConstructor, + ApplySchemaAttributes, + DOMCompatibleAttributes, EditorSchema, EditorState, FromToParameter, MarkAttributes, MarkTypeParameter, + NodeAttributes, PrimitiveSelection, ProsemirrorNode, ProsemirrorNodeParameter, @@ -1062,6 +1066,20 @@ export function areSchemasCompatible(schemaA: EditorSchema, schemaB: EditorSchem return true; } +/** + * Returns attributes for a node excluding those that were provided as extra attributes + * + * @param attrs - The source attributes + * @param extra - The extra attribute schema for this node + */ +export function omitExtraAttributes( + attrs: NodeAttributes, + extra: ApplySchemaAttributes, +): DOMCompatibleAttributes { + const extraAttributeNames = keys(extra.defaults()); + return omit({ ...attrs }, extraAttributeNames) as DOMCompatibleAttributes; +} + /** * A description of an invalid content block (representing a node or a mark). */ diff --git a/packages/@remirror/core-utils/src/index.ts b/packages/@remirror/core-utils/src/index.ts index f004edaac6..45c238ed41 100644 --- a/packages/@remirror/core-utils/src/index.ts +++ b/packages/@remirror/core-utils/src/index.ts @@ -65,6 +65,7 @@ export { isTransaction, shouldUseDomEnvironment, startPositionOfParent, + omitExtraAttributes, toDom, toHtml, getChangedRanges, diff --git a/packages/@remirror/core/src/builtins/schema-extension.ts b/packages/@remirror/core/src/builtins/schema-extension.ts index e0f8643c95..d22cd8c61a 100644 --- a/packages/@remirror/core/src/builtins/schema-extension.ts +++ b/packages/@remirror/core/src/builtins/schema-extension.ts @@ -76,7 +76,7 @@ import type { CombinedTags } from './tags-extension'; * awesome: { * default: 'awesome', * parseDOM: (domNode) => domNode.getAttribute('data-awesome'), - * toDOM: (node) => ({ 'data-awesome': node.attrs.awesome }) + * toDOM: (attrs) => ([ 'data-awesome', attrs.awesome ]) * }, * }, * }, @@ -857,7 +857,8 @@ function createToDOM(extraAttributes: SchemaAttributes, shouldIgnore: boolean) { } if (isArray(value)) { - domAttributes[value[0]] = value[1] ?? (item.attrs[name] as string); + const [attr, val] = value; + domAttributes[attr] = val ?? (item.attrs[name] as string); } return; diff --git a/packages/@remirror/extension-auto-link/src/auto-link-extension.ts b/packages/@remirror/extension-auto-link/src/auto-link-extension.ts index dfc9f7c866..752b4a13b7 100644 --- a/packages/@remirror/extension-auto-link/src/auto-link-extension.ts +++ b/packages/@remirror/extension-auto-link/src/auto-link-extension.ts @@ -19,6 +19,7 @@ import { markPasteRule, MarkType, MarkTypeParameter, + omitExtraAttributes, ProsemirrorPlugin, Static, TransactionParameter, @@ -68,11 +69,12 @@ export class AutoLinkExtension extends MarkExtension { }, ], toDOM: (node) => { + const attrs = omitExtraAttributes(node.attrs, extra); return [ 'a', { ...extra.dom(node), - ...node.attrs, + ...attrs, role: 'presentation', }, 0, diff --git a/packages/@remirror/extension-callout/src/callout-extension.ts b/packages/@remirror/extension-callout/src/callout-extension.ts index be268c62d7..74e6305866 100644 --- a/packages/@remirror/extension-callout/src/callout-extension.ts +++ b/packages/@remirror/extension-callout/src/callout-extension.ts @@ -8,6 +8,7 @@ import { KeyBindings, NodeExtension, NodeExtensionSpec, + omitExtraAttributes, toggleWrap, } from '@remirror/core'; import { TextSelection } from '@remirror/pm/state'; @@ -54,7 +55,7 @@ export class CalloutExtension extends NodeExtension { }, ], toDOM: (node) => { - const { type, ...rest } = node.attrs as CalloutAttributes; + const { type, ...rest } = omitExtraAttributes(node.attrs, extra) as CalloutAttributes; const attributes = { ...extra.dom(node), ...rest, [dataAttributeType]: type }; return ['div', attributes, 0]; diff --git a/packages/@remirror/extension-image/src/image-extension.ts b/packages/@remirror/extension-image/src/image-extension.ts index 1ae7ad500a..86e2802872 100644 --- a/packages/@remirror/extension-image/src/image-extension.ts +++ b/packages/@remirror/extension-image/src/image-extension.ts @@ -10,6 +10,7 @@ import { NodeAttributes, NodeExtension, NodeExtensionSpec, + omitExtraAttributes, } from '@remirror/core'; import type { ResolvedPos } from '@remirror/pm/model'; @@ -57,7 +58,8 @@ export class ImageExtension extends NodeExtension { }, ], toDOM: (node) => { - return ['img', { ...extra.dom(node), ...node.attrs }]; + const attrs = omitExtraAttributes(node.attrs, extra); + return ['img', { ...extra.dom(node), ...attrs }]; }, }; } diff --git a/packages/@remirror/extension-link/src/link-extension.ts b/packages/@remirror/extension-link/src/link-extension.ts index 29ca54c6da..83bfcfa4dc 100644 --- a/packages/@remirror/extension-link/src/link-extension.ts +++ b/packages/@remirror/extension-link/src/link-extension.ts @@ -22,6 +22,7 @@ import { MarkExtension, MarkExtensionSpec, markPasteRule, + omitExtraAttributes, OnSetOptionsParameter, preserveSelection, ProsemirrorNode, @@ -152,7 +153,7 @@ export class LinkExtension extends MarkExtension { }, ], toDOM: (node) => { - const { auto: _, ...rest } = node.attrs; + const { auto: _, ...rest } = omitExtraAttributes(node.attrs, extra); const auto = node.attrs.auto ? { [AUTO_ATTRIBUTE]: '' } : {}; const rel = 'noopener noreferrer nofollow'; const attrs = { ...extra.dom(node), ...rest, rel, ...auto }; diff --git a/packages/@remirror/extension-mention-atom/src/mention-atom-extension.ts b/packages/@remirror/extension-mention-atom/src/mention-atom-extension.ts index d3c880ee19..4a4b152108 100644 --- a/packages/@remirror/extension-mention-atom/src/mention-atom-extension.ts +++ b/packages/@remirror/extension-mention-atom/src/mention-atom-extension.ts @@ -12,6 +12,7 @@ import { NodeAttributes, NodeExtension, NodeExtensionSpec, + omitExtraAttributes, pick, replaceText, Static, @@ -150,7 +151,7 @@ export class MentionAtomExtension extends NodeExtension { name, range, ...rest - } = node.attrs as NamedMentionAtomNodeAttributes; + } = omitExtraAttributes(node.attrs, extra) as NamedMentionAtomNodeAttributes; const matcher = this.options.matchers.find((matcher) => matcher.name === name); const mentionClassName = matcher diff --git a/packages/@remirror/preset-embed/src/iframe-extension.ts b/packages/@remirror/preset-embed/src/iframe-extension.ts index bab7d46683..3eca0f3138 100644 --- a/packages/@remirror/preset-embed/src/iframe-extension.ts +++ b/packages/@remirror/preset-embed/src/iframe-extension.ts @@ -10,6 +10,7 @@ import { NodeExtension, NodeExtensionSpec, object, + omitExtraAttributes, ProsemirrorAttributes, Shape, Static, @@ -88,7 +89,10 @@ export class IframeExtension extends NodeExtension { }, ], toDOM: (node) => { - const { frameBorder, allowFullScreen, src, type, ...rest } = node.attrs; + const { frameBorder, allowFullScreen, src, type, ...rest } = omitExtraAttributes( + node.attrs, + extra, + ); const { class: className } = this.options; return [ @@ -100,7 +104,7 @@ export class IframeExtension extends NodeExtension { src, 'data-embed-type': type, allowfullscreen: allowFullScreen ? 'true' : 'false', - frameBorder: frameBorder.toString(), + frameBorder: frameBorder?.toString(), }, ]; },