Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve DiffField.jsx with better support for displaying HTML elements such as images #6309

Merged
merged 34 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
210800a
improve DiffField.jsx to work with complex pages
dobri1408 Sep 24, 2024
3ecc68a
Merge branch 'main' into improve-history-diff
dobri1408 Sep 24, 2024
378a10f
Create 6309.bugfix
dobri1408 Sep 24, 2024
a3634bc
Update DiffField.test.jsx.snap
dobri1408 Sep 24, 2024
74ff22e
Update Diff.test.jsx.snap
dobri1408 Sep 24, 2024
4a2c41c
refactor DiffField.jsx
dobri1408 Sep 24, 2024
cc2c963
Update DiffField.jsx
dobri1408 Sep 25, 2024
2be55dd
Update DiffField.jsx
dobri1408 Sep 25, 2024
8b88779
Update DiffField.test.jsx.snap
dobri1408 Sep 25, 2024
59d8e1e
Update Diff.test.jsx.snap
dobri1408 Sep 25, 2024
2ae90ed
Update DiffField.jsx
dobri1408 Sep 25, 2024
830f537
Update DiffField.jsx
dobri1408 Sep 25, 2024
0cb5303
Update DiffField.test.jsx.snap
dobri1408 Sep 25, 2024
1976762
Update DiffField.jsx to handle arrays
dobri1408 Sep 25, 2024
fffb242
Update Diff.test.jsx.snap
dobri1408 Sep 25, 2024
1c7d72e
Update DiffField.jsx
dobri1408 Sep 25, 2024
c970ba2
Update DiffField.test.jsx.snap
dobri1408 Sep 25, 2024
1e9ac97
Update Diff.test.jsx.snap
dobri1408 Sep 25, 2024
bc57a7d
Update Diff.test.jsx.snap
dobri1408 Sep 25, 2024
5f4fca5
Update DiffField.test.jsx.snap
dobri1408 Sep 25, 2024
cab4a52
Update DiffField.jsx to work with textarea
dobri1408 Sep 25, 2024
3816ef2
Update DiffField.test.jsx.snap
dobri1408 Sep 25, 2024
ec41787
fix eslint
dobri1408 Sep 25, 2024
6fdf75c
Merge branch 'main' into improve-history-diff
dobri1408 Sep 25, 2024
2176113
Merge branch 'main' into improve-history-diff
dobri1408 Sep 26, 2024
2f3fe15
Merge branch 'main' into improve-history-diff
dobri1408 Sep 27, 2024
b17d375
Merge branch 'main' into improve-history-diff
dobri1408 Sep 30, 2024
ff8c4db
Merge branch 'main' into improve-history-diff
dobri1408 Oct 2, 2024
5959458
Merge branch 'main' into improve-history-diff
dobri1408 Oct 3, 2024
52725cc
Merge branch 'main' into improve-history-diff
ichim-david Oct 3, 2024
2883f39
Update packages/volto/news/6309.bugfix
ichim-david Oct 3, 2024
496e7fc
Delete packages/volto/news/6309.bugfix
dobri1408 Oct 3, 2024
6635c23
Create 6309.feature
dobri1408 Oct 3, 2024
4fc6d7c
Merge branch 'main' into improve-history-diff
dobri1408 Oct 3, 2024
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
1 change: 1 addition & 0 deletions packages/volto/news/6309.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve DiffField.jsx to render only the blocks on the page with better support for displaying HTML elements such as images. @dobri1408
206 changes: 167 additions & 39 deletions packages/volto/src/components/manage/Diff/DiffField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import React from 'react';
// import { diffWords as dWords } from 'diff';
import { join, map } from 'lodash';
import PropTypes from 'prop-types';
import { Grid } from 'semantic-ui-react';
Expand All @@ -13,20 +12,128 @@ import { Provider } from 'react-intl-redux';
import { createBrowserHistory } from 'history';
import { ConnectedRouter } from 'connected-react-router';
import { useSelector } from 'react-redux';

import config from '@plone/volto/registry';
import { Api } from '@plone/volto/helpers';
import configureStore from '@plone/volto/store';
import { DefaultView } from '@plone/volto/components/';
import { RenderBlocks } from '@plone/volto/components';
import { serializeNodes } from '@plone/volto-slate/editor/render';

import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';

/**
* Enhanced diff words utility
* @function diffWords
* @param oneStr Field one
* @param twoStr Field two
*/
const isHtmlTag = (str) => {
// Match complete HTML tags, including:
// 1. Opening tags like <div>, <img src="example" />, <svg>...</svg>
// 2. Self-closing tags like <img />, <br />
// 3. Closing tags like </div>
return /^<([a-zA-Z]+[0-9]*)\b[^>]*>|^<\/([a-zA-Z]+[0-9]*)\b[^>]*>$|^<([a-zA-Z]+[0-9]*)\b[^>]*\/>$/.test(
str,
);
};

const splitWords = (str) => {
if (typeof str !== 'string') return str;
if (!str) return [];

const result = [];
let currentWord = '';
let insideTag = false;
let insideSpecialTag = false;
let tagBuffer = '';

// Special tags that should not be split (e.g., <img />, <svg> ... </svg>)
const specialTags = ['img', 'svg'];

for (let i = 0; i < str.length; i++) {
const char = str[i];

// Start of an HTML tag
if (char === '<') {
if (currentWord) {
result.push(currentWord); // Push text before the tag
currentWord = '';
}
insideTag = true;
tagBuffer += char;
}
// End of an HTML tag
else if (char === '>') {
tagBuffer += char;
insideTag = false;

// Check if the tagBuffer contains a special tag
const tagNameMatch = tagBuffer.match(/^<\/?([a-zA-Z]+[0-9]*)\b/);
if (tagNameMatch && specialTags.includes(tagNameMatch[1])) {
insideSpecialTag =
tagNameMatch[0].startsWith('<') && !tagNameMatch[0].startsWith('</');
result.push(tagBuffer); // Push the complete special tag as one unit
tagBuffer = '';
continue;
}

result.push(tagBuffer); // Push the complete tag
tagBuffer = '';
}
// Inside the tag or special tag
else if (insideTag || insideSpecialTag) {
tagBuffer += char;
}
// Space outside of tags - push current word
else if (char === ' ' && !insideTag && !insideSpecialTag) {
if (currentWord) {
result.push(currentWord);
currentWord = '';
}
result.push(' ');
} else if (
char === ',' &&
i < str.length - 1 &&
str[i + 1] !== ' ' &&
!insideTag &&
!insideSpecialTag
) {
if (currentWord) {
result.push(currentWord + char);
currentWord = '';
}
result.push(' ');
}
// Accumulate characters outside of tags
else {
currentWord += char;
}
}

// Push any remaining text
if (currentWord) {
result.push(currentWord);
}
if (tagBuffer) {
result.push(tagBuffer); // Push remaining tagBuffer
}

return result;
};

const formatDiffPart = (part, value, side) => {
if (!isHtmlTag(value)) {
if (part.removed && (side === 'left' || side === 'unified')) {
return `<span class="deletion">${value}</span>`;
} else if (part.removed) return '';
else if (part.added && (side === 'right' || side === 'unified')) {
return `<span class="addition">${value}</span>`;
} else if (part.added) return '';
return value;
} else {
if (side === 'unified' && part.added) return value;
else if (side === 'unified' && part.removed) return '';
if (part.removed && side === 'left') {
return value;
} else if (part.removed) return '';
else if (part.added && side === 'right') {
return value;
} else if (part.added) return '';
return value;
}
};

/**
* Diff field component.
Expand All @@ -36,6 +143,7 @@ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
* @param {Object} schema Field schema
* @returns {string} Markup of the component.
*/

const DiffField = ({
one,
two,
Expand All @@ -51,7 +159,10 @@ const DiffField = ({
timeStyle: 'short',
};
const diffWords = (oneStr, twoStr) => {
return diffLib.diffWords(String(oneStr), String(twoStr));
return diffLib.diffArrays(
splitWords(String(oneStr)),
splitWords(String(twoStr)),
);
};

let parts, oneArray, twoArray;
Expand All @@ -78,14 +189,14 @@ const DiffField = ({
ReactDOMServer.renderToStaticMarkup(
<Provider store={store}>
<ConnectedRouter history={history}>
<DefaultView content={contentOne} />
<RenderBlocks content={contentOne} />
</ConnectedRouter>
</Provider>,
),
ReactDOMServer.renderToStaticMarkup(
<Provider store={store}>
<ConnectedRouter history={history}>
<DefaultView content={contentTwo} />
<RenderBlocks content={contentTwo} />
</ConnectedRouter>
</Provider>,
),
Expand Down Expand Up @@ -116,7 +227,30 @@ const DiffField = ({
}
case 'textarea':
default:
parts = diffWords(one, two);
const Widget = config.widgets?.views?.widget?.[schema.widget];

if (Widget) {
const api = new Api();
const history = createBrowserHistory();
const store = configureStore(window.__data, history, api);
parts = diffWords(
ReactDOMServer.renderToStaticMarkup(
<Provider store={store}>
<ConnectedRouter history={history}>
<Widget value={one} />
</ConnectedRouter>
</Provider>,
),
ReactDOMServer.renderToStaticMarkup(
<Provider store={store}>
<ConnectedRouter history={history}>
<Widget value={two} />
</ConnectedRouter>
</Provider>,
),
);
} else parts = diffWords(one, two);

break;
}
} else if (schema.type === 'object') {
Expand All @@ -128,6 +262,7 @@ const DiffField = ({
} else {
parts = diffWords(one?.title || one, two?.title || two);
}

return (
<Grid data-testid="DiffField">
<Grid.Row>
Expand All @@ -140,14 +275,12 @@ const DiffField = ({
<span
dangerouslySetInnerHTML={{
__html: join(
map(
parts,
(part) =>
(part.removed &&
`<span class="deletion">${part.value}</span>`) ||
(!part.added && `<span>${part.value}</span>`) ||
'',
),
map(parts, (part) => {
let combined = (part.value || []).reduce((acc, value) => {
return acc + formatDiffPart(part, value, 'left');
}, '');
return combined;
}),
'',
),
}}
Expand All @@ -157,14 +290,12 @@ const DiffField = ({
<span
dangerouslySetInnerHTML={{
__html: join(
map(
parts,
(part) =>
(part.added &&
`<span class="addition">${part.value}</span>`) ||
(!part.removed && `<span>${part.value}</span>`) ||
'',
),
map(parts, (part) => {
let combined = (part.value || []).reduce((acc, value) => {
return acc + formatDiffPart(part, value, 'right');
}, '');
return combined;
}),
'',
),
}}
Expand All @@ -178,15 +309,12 @@ const DiffField = ({
<span
dangerouslySetInnerHTML={{
__html: join(
map(
parts,
(part) =>
(part.removed &&
`<span class="deletion">${part.value}</span>`) ||
(part.added &&
`<span class="addition">${part.value}</span>`) ||
(!part.added && `<span>${part.value}</span>`),
),
map(parts, (part) => {
let combined = (part.value || []).reduce((acc, value) => {
return acc + formatDiffPart(part, value, 'unified');
}, '');
return combined;
}),
'',
),
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,38 +232,35 @@ exports[`Diff renders a diff component 1`] = `
class="top aligned six wide column"
>
<span>
<span>
My
</span>
My
<span
class="deletion"
>
old
</span>
<span>
title

<span
class="deletion"
>
title
</span>
</span>
</div>
<div
class="top aligned six wide column"
>
<span>
<span>
My
</span>
My
<span
class="addition"
>
new
</span>
<span>
title
</span>

<span
class="addition"
>
,
title,
</span>
</span>
</div>
Expand Down
Loading
Loading