@eeacms/volto-eea-website-theme 3.5.4 → 3.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [3.5.5](https://github.com/eea/volto-eea-website-theme/compare/3.5.4...3.5.5) - 15 April 2025
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: History compare not working with plotly chart field - refs #281229 [Teodor Voicu - [`d76a35b`](https://github.com/eea/volto-eea-website-theme/commit/d76a35bb3cbf11971e541a96c1ff49bbc30a87aa)]
12
+
13
+ #### :hammer_and_wrench: Others
14
+
15
+ - Add Sonarqube tag using fise-frontend addons list [EEA Jenkins - [`3d50874`](https://github.com/eea/volto-eea-website-theme/commit/3d50874281ebcb91a1ae3727248f656a6f26c603)]
16
+ - Add Sonarqube tag using ied-frontend addons list [EEA Jenkins - [`a3f7676`](https://github.com/eea/volto-eea-website-theme/commit/a3f76767d99cb6dd93cc7fa0eafdea57e9000b2c)]
7
17
  ### [3.5.4](https://github.com/eea/volto-eea-website-theme/compare/3.5.3...3.5.4) - 30 January 2025
8
18
 
9
19
  #### :bug: Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-website-theme",
3
- "version": "3.5.4",
3
+ "version": "3.5.5",
4
4
  "description": "@eeacms/volto-eea-website-theme: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -0,0 +1,3 @@
1
+ export default function ImageViewWidget({ value }) {
2
+ return <img src={value.download} alt={value.filename} />;
3
+ }
@@ -0,0 +1,150 @@
1
+ diff --git a/src/customizations/volto/components/manage/Diff/DiffField.jsx b/src/customizations/volto/components/manage/Diff/DiffField.jsx
2
+ index a9bad2a..5d306e5 100644
3
+ --- a/src/customizations/volto/components/manage/Diff/DiffField.jsx
4
+ +++ b/src/customizations/volto/components/manage/Diff/DiffField.jsx
5
+ @@ -135,6 +135,17 @@ const formatDiffPart = (part, value, side) => {
6
+ }
7
+ };
8
+
9
+ +const getWidgetByBehavior = (behavior) =>
10
+ + config.widgets.views.behavior?.[behavior] || null;
11
+ +
12
+ +const getWidgetByFactory = (factory) =>
13
+ + config.widgets.views.factory?.[factory] || null;
14
+ +
15
+ +const getDefaultWidget =
16
+ + () =>
17
+ + ({ diff }) =>
18
+ + diff;
19
+ +
20
+ /**
21
+ * Diff field component.
22
+ * @function DiffField
23
+ @@ -164,8 +175,11 @@ const DiffField = ({
24
+ splitWords(String(twoStr)),
25
+ );
26
+ };
27
+ -
28
+ let parts, oneArray, twoArray;
29
+ + const Widget =
30
+ + getWidgetByBehavior(schema.behavior) ||
31
+ + getWidgetByFactory(schema.factory) ||
32
+ + getDefaultWidget();
33
+ if (schema.widget) {
34
+ switch (schema.widget) {
35
+ case 'richtext':
36
+ @@ -272,33 +286,49 @@ const DiffField = ({
37
+ {view === 'split' && (
38
+ <Grid.Row>
39
+ <Grid.Column width={6} verticalAlign="top">
40
+ - <span
41
+ - dangerouslySetInnerHTML={{
42
+ - __html: join(
43
+ - map(parts, (part) => {
44
+ - let combined = (part.value || []).reduce((acc, value) => {
45
+ - return acc + formatDiffPart(part, value, 'left');
46
+ - }, '');
47
+ - return combined;
48
+ - }),
49
+ - '',
50
+ - ),
51
+ - }}
52
+ + <Widget
53
+ + value={one}
54
+ + diff={
55
+ + <span
56
+ + dangerouslySetInnerHTML={{
57
+ + __html: join(
58
+ + map(parts, (part) => {
59
+ + let combined = (part.value || []).reduce(
60
+ + (acc, value) => {
61
+ + return acc + formatDiffPart(part, value, 'left');
62
+ + },
63
+ + '',
64
+ + );
65
+ + return combined;
66
+ + }),
67
+ + '',
68
+ + ),
69
+ + }}
70
+ + />
71
+ + }
72
+ />
73
+ </Grid.Column>
74
+ <Grid.Column width={6} verticalAlign="top">
75
+ - <span
76
+ - dangerouslySetInnerHTML={{
77
+ - __html: join(
78
+ - map(parts, (part) => {
79
+ - let combined = (part.value || []).reduce((acc, value) => {
80
+ - return acc + formatDiffPart(part, value, 'right');
81
+ - }, '');
82
+ - return combined;
83
+ - }),
84
+ - '',
85
+ - ),
86
+ - }}
87
+ + <Widget
88
+ + value={two}
89
+ + diff={
90
+ + <span
91
+ + dangerouslySetInnerHTML={{
92
+ + __html: join(
93
+ + map(parts, (part) => {
94
+ + let combined = (part.value || []).reduce(
95
+ + (acc, value) => {
96
+ + return acc + formatDiffPart(part, value, 'right');
97
+ + },
98
+ + '',
99
+ + );
100
+ + return combined;
101
+ + }),
102
+ + '',
103
+ + ),
104
+ + }}
105
+ + />
106
+ + }
107
+ />
108
+ </Grid.Column>
109
+ </Grid.Row>
110
+ @@ -306,18 +336,28 @@ const DiffField = ({
111
+ {view === 'unified' && (
112
+ <Grid.Row>
113
+ <Grid.Column width={16} verticalAlign="top">
114
+ - <span
115
+ - dangerouslySetInnerHTML={{
116
+ - __html: join(
117
+ - map(parts, (part) => {
118
+ - let combined = (part.value || []).reduce((acc, value) => {
119
+ - return acc + formatDiffPart(part, value, 'unified');
120
+ - }, '');
121
+ - return combined;
122
+ - }),
123
+ - '',
124
+ - ),
125
+ - }}
126
+ + <Widget
127
+ + value={two}
128
+ + one={one}
129
+ + two={two}
130
+ + diff={
131
+ + <span
132
+ + dangerouslySetInnerHTML={{
133
+ + __html: join(
134
+ + map(parts, (part) => {
135
+ + let combined = (part.value || []).reduce(
136
+ + (acc, value) => {
137
+ + return acc + formatDiffPart(part, value, 'unified');
138
+ + },
139
+ + '',
140
+ + );
141
+ + return combined;
142
+ + }),
143
+ + '',
144
+ + ),
145
+ + }}
146
+ + />
147
+ + }
148
+ />
149
+ </Grid.Column>
150
+ </Grid.Row>
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Diff field component.
3
+ * @module components/manage/Diff/DiffField
4
+ */
5
+
6
+ import React from 'react';
7
+ import { join, map } from 'lodash';
8
+ import PropTypes from 'prop-types';
9
+ import { Grid } from 'semantic-ui-react';
10
+ import ReactDOMServer from 'react-dom/server';
11
+ import { Provider } from 'react-intl-redux';
12
+ import { createBrowserHistory } from 'history';
13
+ import { ConnectedRouter } from 'connected-react-router';
14
+ import { useSelector } from 'react-redux';
15
+ import config from '@plone/volto/registry';
16
+ import { Api } from '@plone/volto/helpers';
17
+ import configureStore from '@plone/volto/store';
18
+ import { RenderBlocks } from '@plone/volto/components';
19
+ import { serializeNodes } from '@plone/volto-slate/editor/render';
20
+ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
21
+
22
+ const isHtmlTag = (str) => {
23
+ // Match complete HTML tags, including:
24
+ // 1. Opening tags like <div>, <img src="example" />, <svg>...</svg>
25
+ // 2. Self-closing tags like <img />, <br />
26
+ // 3. Closing tags like </div>
27
+ return /^<([a-zA-Z]+[0-9]*)\b[^>]*>|^<\/([a-zA-Z]+[0-9]*)\b[^>]*>$|^<([a-zA-Z]+[0-9]*)\b[^>]*\/>$/.test(
28
+ str,
29
+ );
30
+ };
31
+
32
+ const splitWords = (str) => {
33
+ if (typeof str !== 'string') return str;
34
+ if (!str) return [];
35
+
36
+ const result = [];
37
+ let currentWord = '';
38
+ let insideTag = false;
39
+ let insideSpecialTag = false;
40
+ let tagBuffer = '';
41
+
42
+ // Special tags that should not be split (e.g., <img />, <svg> ... </svg>)
43
+ const specialTags = ['img', 'svg'];
44
+
45
+ for (let i = 0; i < str.length; i++) {
46
+ const char = str[i];
47
+
48
+ // Start of an HTML tag
49
+ if (char === '<') {
50
+ if (currentWord) {
51
+ result.push(currentWord); // Push text before the tag
52
+ currentWord = '';
53
+ }
54
+ insideTag = true;
55
+ tagBuffer += char;
56
+ }
57
+ // End of an HTML tag
58
+ else if (char === '>') {
59
+ tagBuffer += char;
60
+ insideTag = false;
61
+
62
+ // Check if the tagBuffer contains a special tag
63
+ const tagNameMatch = tagBuffer.match(/^<\/?([a-zA-Z]+[0-9]*)\b/);
64
+ if (tagNameMatch && specialTags.includes(tagNameMatch[1])) {
65
+ insideSpecialTag =
66
+ tagNameMatch[0].startsWith('<') && !tagNameMatch[0].startsWith('</');
67
+ result.push(tagBuffer); // Push the complete special tag as one unit
68
+ tagBuffer = '';
69
+ continue;
70
+ }
71
+
72
+ result.push(tagBuffer); // Push the complete tag
73
+ tagBuffer = '';
74
+ }
75
+ // Inside the tag or special tag
76
+ else if (insideTag || insideSpecialTag) {
77
+ tagBuffer += char;
78
+ }
79
+ // Space outside of tags - push current word
80
+ else if (char === ' ' && !insideTag && !insideSpecialTag) {
81
+ if (currentWord) {
82
+ result.push(currentWord);
83
+ currentWord = '';
84
+ }
85
+ result.push(' ');
86
+ } else if (
87
+ char === ',' &&
88
+ i < str.length - 1 &&
89
+ str[i + 1] !== ' ' &&
90
+ !insideTag &&
91
+ !insideSpecialTag
92
+ ) {
93
+ if (currentWord) {
94
+ result.push(currentWord + char);
95
+ currentWord = '';
96
+ }
97
+ result.push(' ');
98
+ }
99
+ // Accumulate characters outside of tags
100
+ else {
101
+ currentWord += char;
102
+ }
103
+ }
104
+
105
+ // Push any remaining text
106
+ if (currentWord) {
107
+ result.push(currentWord);
108
+ }
109
+ if (tagBuffer) {
110
+ result.push(tagBuffer); // Push remaining tagBuffer
111
+ }
112
+
113
+ return result;
114
+ };
115
+
116
+ const formatDiffPart = (part, value, side) => {
117
+ if (!isHtmlTag(value)) {
118
+ if (part.removed && (side === 'left' || side === 'unified')) {
119
+ return `<span class="deletion">${value}</span>`;
120
+ } else if (part.removed) return '';
121
+ else if (part.added && (side === 'right' || side === 'unified')) {
122
+ return `<span class="addition">${value}</span>`;
123
+ } else if (part.added) return '';
124
+ return value;
125
+ } else {
126
+ if (side === 'unified' && part.added) return value;
127
+ else if (side === 'unified' && part.removed) return '';
128
+ if (part.removed && side === 'left') {
129
+ return value;
130
+ } else if (part.removed) return '';
131
+ else if (part.added && side === 'right') {
132
+ return value;
133
+ } else if (part.added) return '';
134
+ return value;
135
+ }
136
+ };
137
+
138
+ const getWidgetByBehavior = (behavior) =>
139
+ config.widgets.views.behavior?.[behavior] || null;
140
+
141
+ const getWidgetByFactory = (factory) =>
142
+ config.widgets.views.factory?.[factory] || null;
143
+
144
+ const getDefaultWidget =
145
+ () =>
146
+ ({ diff }) =>
147
+ diff;
148
+
149
+ /**
150
+ * Diff field component.
151
+ * @function DiffField
152
+ * @param {*} one Field one
153
+ * @param {*} two Field two
154
+ * @param {Object} schema Field schema
155
+ * @returns {string} Markup of the component.
156
+ */
157
+
158
+ const DiffField = ({
159
+ one,
160
+ two,
161
+ contentOne,
162
+ contentTwo,
163
+ view,
164
+ schema,
165
+ diffLib,
166
+ }) => {
167
+ const language = useSelector((state) => state.intl.locale);
168
+ const readable_date_format = {
169
+ dateStyle: 'full',
170
+ timeStyle: 'short',
171
+ };
172
+ const diffWords = (oneStr, twoStr) => {
173
+ return diffLib.diffArrays(
174
+ splitWords(String(oneStr)),
175
+ splitWords(String(twoStr)),
176
+ );
177
+ };
178
+ let parts, oneArray, twoArray;
179
+ const Widget =
180
+ getWidgetByBehavior(schema.behavior) ||
181
+ getWidgetByFactory(schema.factory) ||
182
+ getDefaultWidget();
183
+ if (schema.widget) {
184
+ switch (schema.widget) {
185
+ case 'richtext':
186
+ parts = diffWords(one?.data, two?.data);
187
+ break;
188
+ case 'datetime':
189
+ parts = diffWords(
190
+ new Intl.DateTimeFormat(language, readable_date_format)
191
+ .format(new Date(one))
192
+ .replace('\u202F', ' '),
193
+ new Intl.DateTimeFormat(language, readable_date_format)
194
+ .format(new Date(two))
195
+ .replace('\u202F', ' '),
196
+ );
197
+ break;
198
+ case 'json': {
199
+ const api = new Api();
200
+ const history = createBrowserHistory();
201
+ const store = configureStore(window.__data, history, api);
202
+ parts = diffWords(
203
+ ReactDOMServer.renderToStaticMarkup(
204
+ <Provider store={store}>
205
+ <ConnectedRouter history={history}>
206
+ <RenderBlocks content={contentOne} />
207
+ </ConnectedRouter>
208
+ </Provider>,
209
+ ),
210
+ ReactDOMServer.renderToStaticMarkup(
211
+ <Provider store={store}>
212
+ <ConnectedRouter history={history}>
213
+ <RenderBlocks content={contentTwo} />
214
+ </ConnectedRouter>
215
+ </Provider>,
216
+ ),
217
+ );
218
+ break;
219
+ }
220
+ case 'slate': {
221
+ const api = new Api();
222
+ const history = createBrowserHistory();
223
+ const store = configureStore(window.__data, history, api);
224
+ parts = diffWords(
225
+ ReactDOMServer.renderToStaticMarkup(
226
+ <Provider store={store}>
227
+ <ConnectedRouter history={history}>
228
+ {serializeNodes(one)}
229
+ </ConnectedRouter>
230
+ </Provider>,
231
+ ),
232
+ ReactDOMServer.renderToStaticMarkup(
233
+ <Provider store={store}>
234
+ <ConnectedRouter history={history}>
235
+ {serializeNodes(two)}
236
+ </ConnectedRouter>
237
+ </Provider>,
238
+ ),
239
+ );
240
+ break;
241
+ }
242
+ case 'textarea':
243
+ default:
244
+ const Widget = config.widgets?.views?.widget?.[schema.widget];
245
+
246
+ if (Widget) {
247
+ const api = new Api();
248
+ const history = createBrowserHistory();
249
+ const store = configureStore(window.__data, history, api);
250
+ parts = diffWords(
251
+ ReactDOMServer.renderToStaticMarkup(
252
+ <Provider store={store}>
253
+ <ConnectedRouter history={history}>
254
+ <Widget value={one} />
255
+ </ConnectedRouter>
256
+ </Provider>,
257
+ ),
258
+ ReactDOMServer.renderToStaticMarkup(
259
+ <Provider store={store}>
260
+ <ConnectedRouter history={history}>
261
+ <Widget value={two} />
262
+ </ConnectedRouter>
263
+ </Provider>,
264
+ ),
265
+ );
266
+ } else parts = diffWords(one, two);
267
+
268
+ break;
269
+ }
270
+ } else if (schema.type === 'object') {
271
+ parts = diffWords(one?.filename || one, two?.filename || two);
272
+ } else if (schema.type === 'array') {
273
+ oneArray = (one || []).map((i) => i?.title || i);
274
+ twoArray = (two || []).map((j) => j?.title || j);
275
+ parts = diffWords(oneArray, twoArray);
276
+ } else {
277
+ parts = diffWords(one?.title || one, two?.title || two);
278
+ }
279
+
280
+ return (
281
+ <Grid data-testid="DiffField">
282
+ <Grid.Row>
283
+ <Grid.Column width={12}>{schema.title}</Grid.Column>
284
+ </Grid.Row>
285
+
286
+ {view === 'split' && (
287
+ <Grid.Row>
288
+ <Grid.Column width={6} verticalAlign="top">
289
+ <Widget
290
+ value={one}
291
+ diff={
292
+ <span
293
+ dangerouslySetInnerHTML={{
294
+ __html: join(
295
+ map(parts, (part) => {
296
+ let combined = (part.value || []).reduce(
297
+ (acc, value) => {
298
+ return acc + formatDiffPart(part, value, 'left');
299
+ },
300
+ '',
301
+ );
302
+ return combined;
303
+ }),
304
+ '',
305
+ ),
306
+ }}
307
+ />
308
+ }
309
+ />
310
+ </Grid.Column>
311
+ <Grid.Column width={6} verticalAlign="top">
312
+ <Widget
313
+ value={two}
314
+ diff={
315
+ <span
316
+ dangerouslySetInnerHTML={{
317
+ __html: join(
318
+ map(parts, (part) => {
319
+ let combined = (part.value || []).reduce(
320
+ (acc, value) => {
321
+ return acc + formatDiffPart(part, value, 'right');
322
+ },
323
+ '',
324
+ );
325
+ return combined;
326
+ }),
327
+ '',
328
+ ),
329
+ }}
330
+ />
331
+ }
332
+ />
333
+ </Grid.Column>
334
+ </Grid.Row>
335
+ )}
336
+ {view === 'unified' && (
337
+ <Grid.Row>
338
+ <Grid.Column width={16} verticalAlign="top">
339
+ <Widget
340
+ value={two}
341
+ one={one}
342
+ two={two}
343
+ diff={
344
+ <span
345
+ dangerouslySetInnerHTML={{
346
+ __html: join(
347
+ map(parts, (part) => {
348
+ let combined = (part.value || []).reduce(
349
+ (acc, value) => {
350
+ return acc + formatDiffPart(part, value, 'unified');
351
+ },
352
+ '',
353
+ );
354
+ return combined;
355
+ }),
356
+ '',
357
+ ),
358
+ }}
359
+ />
360
+ }
361
+ />
362
+ </Grid.Column>
363
+ </Grid.Row>
364
+ )}
365
+ </Grid>
366
+ );
367
+ };
368
+
369
+ /**
370
+ * Property types.
371
+ * @property {Object} propTypes Property types.
372
+ * @static
373
+ */
374
+ DiffField.propTypes = {
375
+ one: PropTypes.any.isRequired,
376
+ two: PropTypes.any.isRequired,
377
+ contentOne: PropTypes.any,
378
+ contentTwo: PropTypes.any,
379
+ view: PropTypes.string.isRequired,
380
+ schema: PropTypes.shape({
381
+ widget: PropTypes.string,
382
+ type: PropTypes.string,
383
+ title: PropTypes.string,
384
+ }).isRequired,
385
+ };
386
+
387
+ export default injectLazyLibs('diffLib')(DiffField);
@@ -0,0 +1 @@
1
+ Customize DiffField.jsx from @plone/volto 17.21.0. See https://taskman.eionet.europa.eu/issues/281229
package/src/index.js CHANGED
@@ -20,6 +20,7 @@ import { TopicsWidget } from '@eeacms/volto-eea-website-theme/components/theme/W
20
20
  import { DateWidget } from '@eeacms/volto-eea-website-theme/components/theme/Widgets/DateWidget';
21
21
  import { DatetimeWidget } from '@eeacms/volto-eea-website-theme/components/theme/Widgets/DatetimeWidget';
22
22
  import CreatableSelectWidget from '@eeacms/volto-eea-website-theme/components/theme/Widgets/CreatableSelectWidget';
23
+ import ImageViewWidget from '@eeacms/volto-eea-website-theme/components/theme/Widgets/ImageViewWidget';
23
24
 
24
25
  import Tag from '@eeacms/volto-eea-design-system/ui/Tag/Tag';
25
26
 
@@ -374,6 +375,11 @@ const applyConfig = (config) => {
374
375
  config.widgets.vocabulary['plone.app.vocabularies.Users'] =
375
376
  SelectAutoCompleteWidget;
376
377
 
378
+ config.widgets.views.factory = {
379
+ ...(config.widgets.views.factory || {}),
380
+ Image: ImageViewWidget,
381
+ };
382
+
377
383
  // /voltoCustom.css express-middleware
378
384
  // /ok express-middleware - see also: https://github.com/plone/volto/pull/4432
379
385
  if (__SERVER__) {