From 4819540ff5cf7e69209ecd983e536b4ae74cdbb5 Mon Sep 17 00:00:00 2001 From: Dennis Hackethal Date: Tue, 23 Sep 2025 12:14:45 -0500 Subject: [PATCH 1/2] Allow multiple class names in class strings --- README.md | 6 +++--- src/config.ts | 9 ++++++--- src/diff.test.ts | 17 ++++++++++++++--- src/util.ts | 6 +++--- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1eb355e..6eab30d 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ Changes to attributes of structural elements are treated as modifications (`vdd- #### Options -- `addedClass: string = 'vdd-added'` The class used for annotating content additions. -- `modifiedClass: string = 'vdd-modified'` The class used for annotating content modifications. -- `removedClass: string = 'vdd-removed'` The class used for annotating content removals. +- `addedClass: string = 'vdd-added'` The class used for annotating content additions. May contain multiple classes separated by a space. +- `modifiedClass: string = 'vdd-modified'` The class used for annotating content modifications. May contain multiple classes separated by a space. +- `removedClass: string = 'vdd-removed'` The class used for annotating content removals. May contain multiple classes separated by a space. - `skipModified: boolean = false` If `true`, then formatting changes are NOT wrapped in `` and modified structural elements are NOT annotated with the `vdd-modified` class. - `skipChildren: (node: Node): boolean | undefined` Indicates if the child nodes of the specified `node` should be ignored. It is useful for ignoring child nodes of an element representing some embedded content, which should not be compared. Return `undefined` for the default behaviour. - `skipSelf: (node: Node): boolean | undefined` Indicates if the specified `node` should be ignored. Even if the `node` is ignored, its child nodes will still be processed, unless `skipChildNodes` says they should also be ignored. Ignored elements whose child nodes are processed are treated as formatting elements. Return `undefined` for the default behaviour. diff --git a/src/config.ts b/src/config.ts index 98db085..c6132e6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,17 +15,20 @@ import { */ export interface Options { /** - * The class name to use to mark up inserted content. + * The class name to use to mark up inserted content. May contain multiple classes separated + * by a space. * Default is `'vdd-added'`. */ addedClass?: string /** - * The class name to use to mark up modified content. + * The class name to use to mark up modified content. May contain multiple classes separated + * by a space. * Default is `'vdd-modified'`. */ modifiedClass?: string /** - * The class name to use to mark up removed content. + * The class name to use to mark up removed content. May contain multiple classes separated + * by a space. * Default is `'vdd-removed'`. */ removedClass?: string diff --git a/src/diff.test.ts b/src/diff.test.ts index 1610cbf..a700d98 100644 --- a/src/diff.test.ts +++ b/src/diff.test.ts @@ -439,15 +439,26 @@ test.each<[string, Node, Node, string, Options | undefined]>([ ], [ 'custom class names', - htmlToFragment('Modified Removed'), - htmlToFragment('Modified Added'), - 'Modified RemovAdded', + htmlToFragment('Modified Removed

heading

'), + htmlToFragment('Modified Added

heading 2

'), + 'Modified RemovAdded

heading 2

', { addedClass: 'ADDED', modifiedClass: 'MODIFIED', removedClass: 'REMOVED', }, ], + [ + 'multiple custom class names', + htmlToFragment('Modified Removed

heading

'), + htmlToFragment('Modified Added

heading 2

'), + 'Modified RemovAdded

heading 2

', + { + addedClass: 'ADDED ADDED-2', + modifiedClass: 'MODIFIED MODIFIED-2', + removedClass: 'REMOVED REMOVED-2', + }, + ], [ 'change letter case', htmlToFragment('Lowercase Removed'), diff --git a/src/util.ts b/src/util.ts index 9100500..0a9a094 100644 --- a/src/util.ts +++ b/src/util.ts @@ -311,16 +311,16 @@ export function markUpNode( const previousSibling = node.previousSibling if (isElement(node)) { - node.classList.add(className) + node.className = className } else if ( previousSibling && previousSibling.nodeName === elementName && - (previousSibling as Element).classList.contains(className) + [...(className.trim().split(/\s+/))].every(c => (previousSibling as Element).classList.contains(c)) ) { previousSibling.appendChild(node) } else { const wrapper = document.createElement(elementName) - wrapper.classList.add(className) + wrapper.className = className parentNode.insertBefore(wrapper, node) wrapper.appendChild(node) } From 7e5f52a5a652b74c2790bbe3793ce1a65ff43a9a Mon Sep 17 00:00:00 2001 From: Dennis Hackethal Date: Tue, 23 Sep 2025 12:57:33 -0500 Subject: [PATCH 2/2] Fix pretty --- src/diff.test.ts | 8 ++++++-- src/util.ts | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/diff.test.ts b/src/diff.test.ts index a700d98..a55155d 100644 --- a/src/diff.test.ts +++ b/src/diff.test.ts @@ -439,7 +439,9 @@ test.each<[string, Node, Node, string, Options | undefined]>([ ], [ 'custom class names', - htmlToFragment('Modified Removed

heading

'), + htmlToFragment( + 'Modified Removed

heading

', + ), htmlToFragment('Modified Added

heading 2

'), 'Modified RemovAdded

heading 2

', { @@ -450,7 +452,9 @@ test.each<[string, Node, Node, string, Options | undefined]>([ ], [ 'multiple custom class names', - htmlToFragment('Modified Removed

heading

'), + htmlToFragment( + 'Modified Removed

heading

', + ), htmlToFragment('Modified Added

heading 2

'), 'Modified RemovAdded

heading 2

', { diff --git a/src/util.ts b/src/util.ts index 0a9a094..ced873f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -315,7 +315,9 @@ export function markUpNode( } else if ( previousSibling && previousSibling.nodeName === elementName && - [...(className.trim().split(/\s+/))].every(c => (previousSibling as Element).classList.contains(c)) + [...className.trim().split(/\s+/)].every(c => + (previousSibling as Element).classList.contains(c), + ) ) { previousSibling.appendChild(node) } else {