diff --git a/.changeset/dirty-clocks-hide.md b/.changeset/dirty-clocks-hide.md new file mode 100644 index 0000000000..599ad987e0 --- /dev/null +++ b/.changeset/dirty-clocks-hide.md @@ -0,0 +1,18 @@ +--- +"@udecode/plate-diff": minor +--- + +- Remove `shouldDiffDescendants` option in favour of `elementsAreRelated`. +- The `elementsAreRelated` option controls whether `computeDiff` treats a given pair of elements as "related" and thus tries to diff them. By default, elements are related if they have the same `children` OR they differ only in their `children`. Return null to use the default logic for a pair of elements. + + - Use case: In addition to supporting the same use case as the deprecated `shouldDiffDescendants`, `elementsAreRelated` can be used to ensure that `computeDiff` compares the correct pair of paragraphs. + + For example, by default, `computeDiff` would compare `My slightly modified paragraph.` with `New paragraph` in the following diff. + + ```diff + - My slightly modified paragraph. + + New paragraph + + My slightly modified paragraph! + ``` + + If a custom `elementsAreRelated` function is provided that returns true for mostly similar paragraphs, `computeDiff` would instead compare `My slightly modified paragraph.` with `My slightly modified paragraph!`. diff --git a/packages/diff/src/computeDiff.spec.ts b/packages/diff/src/computeDiff.spec.ts index 0e6d3f56db..61df486314 100644 --- a/packages/diff/src/computeDiff.spec.ts +++ b/packages/diff/src/computeDiff.spec.ts @@ -3,14 +3,14 @@ * contributors. See /packages/diff/LICENSE for more information. */ -import { isText, Value } from '@udecode/plate-common'; +import { getNodeString, TElement, Value } from '@udecode/plate-common'; import { computeDiff, ComputeDiffOptions } from './computeDiff'; const ELEMENT_INLINE_VOID = 'inline-void'; interface ComputeDiffFixture - extends Pick { + extends Pick { it?: typeof it; input1: Value; input2: Value; @@ -1449,11 +1449,9 @@ const fixtures: Record = { ], }, - shouldNotDiffDescendants: { - shouldDiffDescendants: ([firstNode]) => - !firstNode || - !isText(firstNode) || - !firstNode.text.startsWith('NO_DIFF_INLINE'), + unrelatedTexts: { + elementsAreRelated: (element) => + !getNodeString(element).startsWith('NO_DIFF_INLINE'), input1: [ { type: 'paragraph', @@ -1545,6 +1543,69 @@ const fixtures: Record = { }, ], }, + + customRelatedFunction: { + elementsAreRelated: (element, nextElement) => { + const getId = (e: TElement) => getNodeString(e).split('/')[0]; + return getId(element) === getId(nextElement); + }, + input1: [ + { + type: 'paragraph', + children: [{ text: '1/First paragraph' }], + }, + { + type: 'paragraph', + children: [{ text: '2/Second paragraph' }], + }, + ], + input2: [ + { + type: 'paragraph', + children: [{ text: '3/Added paragraph 1' }], + }, + { + type: 'paragraph', + children: [{ text: '1/First paragraph modified' }], + }, + { + type: 'paragraph', + children: [{ text: '4/Added paragraph 2' }], + }, + { + type: 'paragraph', + children: [{ text: '2/Second paragraph modified' }], + }, + ], + expected: [ + { + type: 'paragraph', + children: [{ text: '3/Added paragraph 1' }], + diff: true, + diffOperation: { type: 'insert' }, + }, + { + type: 'paragraph', + children: [ + { text: '1/First paragraph' }, + { text: ' modified', diff: true, diffOperation: { type: 'insert' } }, + ], + }, + { + type: 'paragraph', + children: [{ text: '4/Added paragraph 2' }], + diff: true, + diffOperation: { type: 'insert' }, + }, + { + type: 'paragraph', + children: [ + { text: '2/Second paragraph' }, + { text: ' modified', diff: true, diffOperation: { type: 'insert' } }, + ], + }, + ], + }, }; describe('computeDiff', () => { diff --git a/packages/diff/src/computeDiff.ts b/packages/diff/src/computeDiff.ts index db5670e3fa..fb3281d22b 100644 --- a/packages/diff/src/computeDiff.ts +++ b/packages/diff/src/computeDiff.ts @@ -3,7 +3,7 @@ * contributors. See /packages/diff/LICENSE for more information. */ -import { PlateEditor, TDescendant } from '@udecode/plate-common'; +import { PlateEditor, TDescendant, TElement } from '@udecode/plate-common'; import { transformDiffDescendants } from './internal/transforms/transformDiffDescendants'; import { dmp } from './internal/utils/dmp'; @@ -14,10 +14,10 @@ export interface ComputeDiffOptions { isInline: PlateEditor['isInline']; ignoreProps?: string[]; lineBreakChar?: string; - shouldDiffDescendants?: ( - nodes: TDescendant[], - nextNodes: TDescendant[] - ) => boolean; + elementsAreRelated?: ( + element: TElement, + nextElement: TElement + ) => boolean | null; getInsertProps: (node: TDescendant) => any; getDeleteProps: (node: TDescendant) => any; getUpdateProps: ( diff --git a/packages/diff/src/internal/transforms/transformDiffDescendants.ts b/packages/diff/src/internal/transforms/transformDiffDescendants.ts index b8752914f2..f0383e18c2 100644 --- a/packages/diff/src/internal/transforms/transformDiffDescendants.ts +++ b/packages/diff/src/internal/transforms/transformDiffDescendants.ts @@ -118,7 +118,7 @@ export function transformDiffDescendants( } // If not all nodes are text nodes, use diffNodes to generate operations - const diffResult = diffNodes(nodes, nextNodes); + const diffResult = diffNodes(nodes, nextNodes, options); diffResult.forEach((item: NodeRelatedItem) => { if (item.delete) { deleteNode(item.originNode); diff --git a/packages/diff/src/internal/transforms/transformDiffNodes.ts b/packages/diff/src/internal/transforms/transformDiffNodes.ts index 49cc20664f..77877ec530 100644 --- a/packages/diff/src/internal/transforms/transformDiffNodes.ts +++ b/packages/diff/src/internal/transforms/transformDiffNodes.ts @@ -28,18 +28,12 @@ type Handler = ( * algorithm on the children. */ const childrenOnlyStrategy: Handler = (node, nextNode, options) => { - const { shouldDiffDescendants = () => true } = options; - if ( node['children'] != null && nextNode['children'] != null && isEqual( copyWithout(node, ['children']), copyWithout(nextNode, ['children']) - ) && - shouldDiffDescendants( - node['children'] as TDescendant[], - nextNode['children'] as TDescendant[] ) ) { const children = computeDiff( diff --git a/packages/diff/src/internal/utils/diff-nodes.ts b/packages/diff/src/internal/utils/diff-nodes.ts index 78f5aa7023..dbc54298a2 100644 --- a/packages/diff/src/internal/utils/diff-nodes.ts +++ b/packages/diff/src/internal/utils/diff-nodes.ts @@ -6,11 +6,13 @@ import { isElement, isText, TDescendant } from '@udecode/plate-common'; import isEqual from 'lodash/isEqual.js'; +import { ComputeDiffOptions } from '../../computeDiff'; import { copyWithout } from './copy-without'; export function diffNodes( originNodes: TDescendant[], - targetNodes: TDescendant[] + targetNodes: TDescendant[], + { elementsAreRelated }: ComputeDiffOptions ) { const result: NodeRelatedItem[] = []; let relatedNode: TDescendant | undefined; @@ -20,6 +22,11 @@ export function diffNodes( let childrenUpdated = false; let nodeUpdated = false; relatedNode = leftTargetNodes.find((targetNode: TDescendant) => { + if (isElement(originNode) && isElement(targetNode)) { + const relatedResult = + elementsAreRelated?.(originNode, targetNode) ?? null; + if (relatedResult !== null) return relatedResult; + } if (isEqualNode(originNode, targetNode)) { childrenUpdated = true; }