diff --git a/packages/core/src/helpers/isMarkActive.ts b/packages/core/src/helpers/isMarkActive.ts index d8a18240..728aed53 100644 --- a/packages/core/src/helpers/isMarkActive.ts +++ b/packages/core/src/helpers/isMarkActive.ts @@ -23,7 +23,7 @@ export default function isMarkActive( return type.name === mark.type.name }) - .find(mark => objectIncludes(mark.attrs, attributes)) + .find(mark => objectIncludes(mark.attrs, attributes, { strict: false })) } let selectionRange = 0 @@ -58,7 +58,7 @@ export default function isMarkActive( return type.name === markRange.mark.type.name }) - .filter(markRange => objectIncludes(markRange.mark.attrs, attributes)) + .filter(markRange => objectIncludes(markRange.mark.attrs, attributes, { strict: false })) .reduce((sum, markRange) => { const size = markRange.to - markRange.from diff --git a/packages/core/src/helpers/isNodeActive.ts b/packages/core/src/helpers/isNodeActive.ts index 4ea838a0..2314e862 100644 --- a/packages/core/src/helpers/isNodeActive.ts +++ b/packages/core/src/helpers/isNodeActive.ts @@ -38,7 +38,7 @@ export default function isNodeActive( return type.name === nodeRange.node.type.name }) - .find(nodeRange => objectIncludes(nodeRange.node.attrs, attributes)) + .find(nodeRange => objectIncludes(nodeRange.node.attrs, attributes, { strict: false })) } const selectionRange = to - from @@ -51,7 +51,7 @@ export default function isNodeActive( return type.name === nodeRange.node.type.name }) - .filter(nodeRange => objectIncludes(nodeRange.node.attrs, attributes)) + .filter(nodeRange => objectIncludes(nodeRange.node.attrs, attributes, { strict: false })) .reduce((sum, nodeRange) => { const size = nodeRange.to - nodeRange.from return sum + size diff --git a/packages/core/src/utilities/isRegExp.ts b/packages/core/src/utilities/isRegExp.ts new file mode 100644 index 00000000..a18be7d9 --- /dev/null +++ b/packages/core/src/utilities/isRegExp.ts @@ -0,0 +1,3 @@ +export default function isRegExp(value: any): boolean { + return Object.prototype.toString.call(value) === '[object RegExp]' +} diff --git a/packages/core/src/utilities/objectIncludes.ts b/packages/core/src/utilities/objectIncludes.ts index 0581c063..c9e01a3f 100644 --- a/packages/core/src/utilities/objectIncludes.ts +++ b/packages/core/src/utilities/objectIncludes.ts @@ -1,16 +1,30 @@ +import isRegExp from './isRegExp' + /** * Check if object1 includes object2 * @param object1 Object * @param object2 Object */ -export default function objectIncludes(object1: Record, object2: Record): boolean { +export default function objectIncludes( + object1: Record, + object2: Record, + options: { strict: boolean } = { strict: true }, +): boolean { const keys = Object.keys(object2) if (!keys.length) { return true } - return !!keys - .filter(key => object2[key] === object1[key]) - .length + return keys.every(key => { + if (options.strict) { + return object2[key] === object1[key] + } + + if (isRegExp(object2[key])) { + return object2[key].test(object1[key]) + } + + return object2[key] === object1[key] + }) } diff --git a/tests/cypress/integration/core/isActive.spec.ts b/tests/cypress/integration/core/isActive.spec.ts new file mode 100644 index 00000000..042056f2 --- /dev/null +++ b/tests/cypress/integration/core/isActive.spec.ts @@ -0,0 +1,124 @@ +/// + +import { Editor } from '@tiptap/core' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' +import TextStyle from '@tiptap/extension-text-style' +import FontFamily from '@tiptap/extension-font-family' +import Color from '@tiptap/extension-color' + +describe('isActive', () => { + it('should check the current node', () => { + const editor = new Editor({ + extensions: [ + Document, + Paragraph, + Text, + ], + }) + + expect(editor.isActive('paragraph')).to.eq(true) + }) + + it('should check non-existent nodes', () => { + const editor = new Editor({ + extensions: [ + Document, + Paragraph, + Text, + ], + }) + + expect(editor.isActive('doesNotExist')).to.eq(false) + }) + + it('should check the current mark for correct values', () => { + const editor = new Editor({ + extensions: [ + Document, + Paragraph, + Text, + TextStyle, + FontFamily, + Color, + ], + content: ` +

text

+ `, + }) + + expect(editor.isActive('textStyle', { fontFamily: 'Inter' })).to.eq(true) + }) + + it('should check the current mark for false values', () => { + const editor = new Editor({ + extensions: [ + Document, + Paragraph, + Text, + TextStyle, + FontFamily, + Color, + ], + content: ` +

text

+ `, + }) + + expect(editor.isActive('textStyle', { fontFamily: 'Comic Sans' })).to.eq(false) + }) + + it('should check the current mark for any values', () => { + const editor = new Editor({ + extensions: [ + Document, + Paragraph, + Text, + TextStyle, + FontFamily, + ], + content: ` +

text

+ `, + }) + + expect(editor.isActive('textStyle', { fontFamily: /.*/ })).to.eq(true) + }) + + it('should check the current mark for correct values (multiple)', () => { + const editor = new Editor({ + extensions: [ + Document, + Paragraph, + Text, + TextStyle, + FontFamily, + Color, + ], + content: ` +

text

+ `, + }) + + expect(editor.isActive('textStyle', { fontFamily: 'Inter', color: 'red' })).to.eq(true) + }) + + it('should check the current mark for false values (multiple)', () => { + const editor = new Editor({ + extensions: [ + Document, + Paragraph, + Text, + TextStyle, + FontFamily, + Color, + ], + content: ` +

text

+ `, + }) + + expect(editor.isActive('textStyle', { fontFamily: 'Inter', color: 'green' })).to.eq(false) + }) +})