diff --git a/packages/editor/src/components/post-revisions-preview/block-diff.js b/packages/editor/src/components/post-revisions-preview/block-diff.js index 95ed7197ca0d53..ce3be7a5e89a87 100644 --- a/packages/editor/src/components/post-revisions-preview/block-diff.js +++ b/packages/editor/src/components/post-revisions-preview/block-diff.js @@ -520,6 +520,67 @@ function describeFormatChange( }; } +/** + * Merge adjacent removed/added diff parts into fewer del/ins pairs. + * + * `diffWordsWithSpace` can emit alternating removed/added tokens for a run of + * changed words, which produces one del/ins pair per word, making the output + * more verbose for screen readers. This merges such runs into a single removed part and a + * single added part. + * + * @param {Array<{ added?: boolean, removed?: boolean, value: string }>} parts Output from `diffWordsWithSpace`. + * @return {Array<{ added?: boolean, removed?: boolean, value: string }>} Merged parts. + */ +function mergeTextDiffParts( parts ) { + const result = []; + let index = 0; + + while ( index < parts.length ) { + const part = parts[ index ]; + + if ( ! part.removed && ! part.added ) { + result.push( part ); + index++; + continue; + } + + let removed = ''; + let added = ''; + + while ( index < parts.length ) { + const current = parts[ index ]; + + if ( current.removed ) { + removed += current.value; + index++; + } else if ( current.added ) { + added += current.value; + index++; + } else if ( + current.value.trim() === '' && + index + 1 < parts.length && + ( parts[ index + 1 ].removed || parts[ index + 1 ].added ) + ) { + // Whitespace between changed tokens — include in both sides. + removed += current.value; + added += current.value; + index++; + } else { + break; + } + } + + if ( removed ) { + result.push( { removed: true, value: removed } ); + } + if ( added ) { + result.push( { added: true, value: added } ); + } + } + + return result; +} + /** * Apply inline diff formatting comparing two RichTextData values. * - Text changes: apply revision/diff-removed and revision/diff-added formats @@ -534,7 +595,9 @@ function applyRichTextDiff( currentRichText, previousRichText ) { const previousText = previousRichText.toPlainText(); // Diff the plain text (words for cleaner output). - const textDiff = diffWordsWithSpace( previousText, currentText ); + const textDiff = mergeTextDiffParts( + diffWordsWithSpace( previousText, currentText ) + ); let result = create( { text: '' } ); let currentIdx = 0; diff --git a/packages/editor/src/components/post-revisions-preview/test/block-diff.jsdom.test.js b/packages/editor/src/components/post-revisions-preview/test/block-diff.jsdom.test.js index dd797b5e632856..e4e6a7f04774ae 100644 --- a/packages/editor/src/components/post-revisions-preview/test/block-diff.jsdom.test.js +++ b/packages/editor/src/components/post-revisions-preview/test/block-diff.jsdom.test.js @@ -1139,6 +1139,33 @@ describe( 'diffRevisionContent', () => { ] ); } ); + it( 'merges adjacent changed words into single del/ins pairs', () => { + const previous = serialize( [ + createBlock( 'core/paragraph', { + content: 'one two three', + } ), + ] ); + const current = serialize( [ + createBlock( 'core/paragraph', { + content: 'alpha beta gamma', + } ), + ] ); + const blocks = diffRevisionContent( current, previous ); + + expect( normalizeBlockTree( blocks ) ).toMatchObject( [ + { + name: 'core/paragraph', + attributes: { + content: + 'one two threealpha beta gamma', + __revisionDiffStatus: { + status: 'modified', + }, + }, + }, + ] ); + } ); + it( 'detects changed link text as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { @@ -1155,12 +1182,13 @@ describe( 'diffRevisionContent', () => { const blocks = diffRevisionContent( current, previous ); // Word-level diff: "our site" changed to "the website" within link. + // Adjacent changed words are merged into a single del/ins pair. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: - 'Visit ourthe sitewebsite today', + 'Visit our sitethe website today', __revisionDiffStatus: { status: 'modified', }, @@ -1323,7 +1351,7 @@ describe( 'diffRevisionContent', () => { name: 'core/paragraph', attributes: { content: - 'HelloGoodbye worldeveryone', + 'Hello worldGoodbye everyone', __revisionDiffStatus: { status: 'modified', },