Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
'<del aria-describedby="revision-diff-removed-desc" class="revision-diff-removed">one two three</del><ins aria-describedby="revision-diff-added-desc" class="revision-diff-added">alpha beta gamma</ins>',
__revisionDiffStatus: {
status: 'modified',
},
},
},
] );
} );

it( 'detects changed link text as modification', () => {
const previous = serialize( [
createBlock( 'core/paragraph', {
Expand All @@ -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 <a href="https://example.com"><del aria-describedby="revision-diff-removed-desc" class="revision-diff-removed">our</del><ins aria-describedby="revision-diff-added-desc" class="revision-diff-added">the</ins> <del aria-describedby="revision-diff-removed-desc" class="revision-diff-removed">site</del><ins aria-describedby="revision-diff-added-desc" class="revision-diff-added">website</ins></a> today',
'Visit <a href="https://example.com"><del aria-describedby="revision-diff-removed-desc" class="revision-diff-removed">our site</del><ins aria-describedby="revision-diff-added-desc" class="revision-diff-added">the website</ins></a> today',
__revisionDiffStatus: {
status: 'modified',
},
Expand Down Expand Up @@ -1323,7 +1351,7 @@ describe( 'diffRevisionContent', () => {
name: 'core/paragraph',
attributes: {
content:
'<del aria-describedby="revision-diff-removed-desc" class="revision-diff-removed">Hello</del><ins aria-describedby="revision-diff-added-desc" class="revision-diff-added">Goodbye</ins> <strong><del aria-describedby="revision-diff-removed-desc" class="revision-diff-removed">world</del><ins aria-describedby="revision-diff-added-desc" class="revision-diff-added">everyone</ins></strong>',
'<del aria-describedby="revision-diff-removed-desc" class="revision-diff-removed">Hello <strong>world</strong></del><ins aria-describedby="revision-diff-added-desc" class="revision-diff-added">Goodbye <strong>everyone</strong></ins>',
__revisionDiffStatus: {
status: 'modified',
},
Expand Down
Loading