diff --git a/docs/src/docPages/api/commands.md b/docs/src/docPages/api/commands.md index c218b8a2..48939905 100644 --- a/docs/src/docPages/api/commands.md +++ b/docs/src/docPages/api/commands.md @@ -179,7 +179,7 @@ Have a look at all of the core commands listed below. They should give you a goo | .undoInputRule() | Undo an input rule. | | .unsetAllMarks() | Remove all marks in the current selection. | | .unsetMark() | Remove a mark in the current selection. | -| .updateNodeAttributes() | Update attributes of a node. | +| .updateAttributes() | Update attributes of a node or mark. | ### Lists | Command | Description | diff --git a/packages/core/src/commands/updateAttributes.ts b/packages/core/src/commands/updateAttributes.ts new file mode 100644 index 00000000..931f7bb5 --- /dev/null +++ b/packages/core/src/commands/updateAttributes.ts @@ -0,0 +1,66 @@ +import { NodeType, MarkType } from 'prosemirror-model' +import getNodeType from '../helpers/getNodeType' +import getMarkType from '../helpers/getMarkType' +import getSchemaTypeNameByName from '../helpers/getSchemaTypeNameByName' +import { AnyObject, Command, RawCommands } from '../types' + +declare module '@tiptap/core' { + interface Commands { + updateAttributes: { + /** + * Update attributes of a node or mark. + */ + updateAttributes: (typeOrName: string | NodeType | MarkType, attributes: AnyObject) => Command, + } + } +} + +export const updateAttributes: RawCommands['updateAttributes'] = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => { + let nodeType: NodeType | null = null + let markType: MarkType | null = null + + const schemaType = getSchemaTypeNameByName( + typeof typeOrName === 'string' + ? typeOrName + : typeOrName.name, + state.schema, + ) + + if (!schemaType) { + return false + } + + if (schemaType === 'node') { + nodeType = getNodeType(typeOrName as NodeType, state.schema) + } + + if (schemaType === 'mark') { + markType = getMarkType(typeOrName as MarkType, state.schema) + } + + if (dispatch) { + tr.selection.ranges.forEach(range => { + state.doc.nodesBetween(range.$from.pos, range.$to.pos, (node, pos) => { + if (nodeType && nodeType === node.type) { + tr.setNodeMarkup(pos, undefined, { + ...node.attrs, + ...attributes, + }) + } + + if (markType && node.marks.length) { + node.marks.forEach(mark => { + if (markType === mark.type) { + tr.addMark(pos, pos + node.nodeSize, markType.create({ + ...mark.attrs, + ...attributes, + })) + } + }) + } + }) + }) + } + + return true +} diff --git a/packages/core/src/commands/updateNodeAttributes.ts b/packages/core/src/commands/updateNodeAttributes.ts index fd16f1e5..29ede6f4 100644 --- a/packages/core/src/commands/updateNodeAttributes.ts +++ b/packages/core/src/commands/updateNodeAttributes.ts @@ -14,6 +14,8 @@ declare module '@tiptap/core' { } export const updateNodeAttributes: RawCommands['updateNodeAttributes'] = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => { + console.warn('[tiptap warn]: updateNodeAttributes() is deprecated. please use updateAttributes() instead.') + const type = getNodeType(typeOrName, state.schema) const { selection } = tr const { ranges } = selection diff --git a/packages/core/src/extensions/commands.ts b/packages/core/src/extensions/commands.ts index ca992cea..a7262130 100644 --- a/packages/core/src/extensions/commands.ts +++ b/packages/core/src/extensions/commands.ts @@ -43,6 +43,7 @@ import * as toggleWrap from '../commands/toggleWrap' import * as undoInputRule from '../commands/undoInputRule' import * as unsetAllMarks from '../commands/unsetAllMarks' import * as unsetMark from '../commands/unsetMark' +import * as updateAttributes from '../commands/updateAttributes' import * as updateNodeAttributes from '../commands/updateNodeAttributes' import * as wrapIn from '../commands/wrapIn' import * as wrapInList from '../commands/wrapInList' @@ -91,6 +92,7 @@ export { toggleWrap } export { undoInputRule } export { unsetAllMarks } export { unsetMark } +export { updateAttributes } export { updateNodeAttributes } export { wrapIn } export { wrapInList } @@ -144,6 +146,7 @@ export const Commands = Extension.create({ ...undoInputRule, ...unsetAllMarks, ...unsetMark, + ...updateAttributes, ...updateNodeAttributes, ...wrapIn, ...wrapInList, diff --git a/packages/extension-text-align/src/text-align.ts b/packages/extension-text-align/src/text-align.ts index cef5ad27..c69763b5 100644 --- a/packages/extension-text-align/src/text-align.ts +++ b/packages/extension-text-align/src/text-align.ts @@ -56,7 +56,7 @@ export const TextAlign = Extension.create({ return false } - return this.options.types.every(type => commands.updateNodeAttributes(type, { textAlign: alignment })) + return this.options.types.every(type => commands.updateAttributes(type, { textAlign: alignment })) }, unsetTextAlign: () => ({ commands }) => { return this.options.types.every(type => commands.resetNodeAttributes(type, 'textAlign'))