@plone/volto 17.1.0 → 17.2.0
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 +19 -0
- package/news/5298 bugfix +1 -0
- package/package.json +1 -1
- package/packages/volto-slate/package.json +1 -1
- package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +3 -2
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +47 -13
- package/src/components/manage/Widgets/QueryWidget.jsx +6 -2
- package/src/components/theme/Image/Image.jsx +8 -7
- package/src/components/theme/Image/Image.test.jsx +11 -11
- package/src/helpers/Url/Url.js +29 -0
- package/src/helpers/Url/Url.test.js +104 -0
- package/src/helpers/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,25 @@
|
|
|
8
8
|
|
|
9
9
|
<!-- towncrier release notes start -->
|
|
10
10
|
|
|
11
|
+
## 17.2.0 (2023-10-16)
|
|
12
|
+
|
|
13
|
+
### Feature
|
|
14
|
+
|
|
15
|
+
- add cypress test for search block via url - @ionlizarazu [#5298](https://github.com/plone/volto/issues/5298)
|
|
16
|
+
|
|
17
|
+
### Bugfix
|
|
18
|
+
|
|
19
|
+
- Fix adding multiple path criteria in search and listing blocks. @davisagli [#5317](https://github.com/plone/volto/issues/5317)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## 17.1.1 (2023-10-13)
|
|
23
|
+
|
|
24
|
+
### Bugfix
|
|
25
|
+
|
|
26
|
+
- Normalize the shape of the image component item prop if it contains the serialized object after creation to match the one in the catalog. @sneridagh [#5266](https://github.com/plone/volto/issues/5266)
|
|
27
|
+
- Added guard in `flattenScales` in edge case image is undefined @sneridagh [#5318](https://github.com/plone/volto/issues/5318)
|
|
28
|
+
|
|
29
|
+
|
|
11
30
|
## 17.1.0 (2023-10-11)
|
|
12
31
|
|
|
13
32
|
### Feature
|
package/news/5298 bugfix
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fix the search block for search via url - @ionlizarazu
|
package/package.json
CHANGED
|
@@ -19,14 +19,15 @@ test('renders a Lead Image block Sidebar component', () => {
|
|
|
19
19
|
<LeadImageSidebar
|
|
20
20
|
data={{}}
|
|
21
21
|
properties={{
|
|
22
|
+
'@id': 'http://localhost:3000/image.png',
|
|
22
23
|
image: {
|
|
23
|
-
download: 'http://localhost:3000/image.png',
|
|
24
|
+
download: 'http://localhost:3000/image.png/@@images/image-1200.png',
|
|
24
25
|
width: 400,
|
|
25
26
|
height: 400,
|
|
26
27
|
scales: {
|
|
27
28
|
preview: {
|
|
28
29
|
download:
|
|
29
|
-
'http://localhost:3000/image.png/@@images/image
|
|
30
|
+
'http://localhost:3000/image.png/@@images/image-400.png',
|
|
30
31
|
width: 400,
|
|
31
32
|
height: 400,
|
|
32
33
|
},
|
|
@@ -28,7 +28,14 @@ const PAQO = 'plone.app.querystring.operation';
|
|
|
28
28
|
* @function getInitialState
|
|
29
29
|
*
|
|
30
30
|
*/
|
|
31
|
-
function getInitialState(
|
|
31
|
+
function getInitialState(
|
|
32
|
+
data,
|
|
33
|
+
facets,
|
|
34
|
+
urlSearchText,
|
|
35
|
+
id,
|
|
36
|
+
sortOnParam,
|
|
37
|
+
sortOrderParam,
|
|
38
|
+
) {
|
|
32
39
|
const { types: facetWidgetTypes } =
|
|
33
40
|
config.blocks.blocksConfig.search.extensions.facetWidgets;
|
|
34
41
|
const facetSettings = data?.facets || [];
|
|
@@ -62,8 +69,8 @@ function getInitialState(data, facets, urlSearchText, id) {
|
|
|
62
69
|
]
|
|
63
70
|
: []),
|
|
64
71
|
],
|
|
65
|
-
sort_on: data.query?.sort_on,
|
|
66
|
-
sort_order: data.query?.sort_order,
|
|
72
|
+
sort_on: sortOnParam || data.query?.sort_on,
|
|
73
|
+
sort_order: sortOrderParam || data.query?.sort_order,
|
|
67
74
|
b_size: data.query?.b_size,
|
|
68
75
|
limit: data.query?.limit,
|
|
69
76
|
block: id,
|
|
@@ -257,7 +264,28 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
257
264
|
const multiFacets = data.facets
|
|
258
265
|
?.filter((facet) => facet?.multiple)
|
|
259
266
|
.map((facet) => facet?.field?.value);
|
|
260
|
-
const [facets, setFacets] = React.useState(
|
|
267
|
+
const [facets, setFacets] = React.useState(
|
|
268
|
+
Object.assign(
|
|
269
|
+
{},
|
|
270
|
+
...urlQuery.map(({ i, v }) => ({ [i]: v })), // TODO: the 'o' should be kept. This would be a major refactoring of the facets
|
|
271
|
+
|
|
272
|
+
// support for simple filters like ?Subject=something
|
|
273
|
+
// TODO: since the move to hash params this is no longer working.
|
|
274
|
+
// We'd have to treat the location.search and manage it just like the
|
|
275
|
+
// hash, to support it. We can read it, but we'd have to reset it as
|
|
276
|
+
// well, so at that point what's the difference to the hash?
|
|
277
|
+
...configuredFacets.map((f) =>
|
|
278
|
+
locationSearchData[f]
|
|
279
|
+
? {
|
|
280
|
+
[f]:
|
|
281
|
+
multiFacets.indexOf(f) > -1
|
|
282
|
+
? [locationSearchData[f]]
|
|
283
|
+
: locationSearchData[f],
|
|
284
|
+
}
|
|
285
|
+
: {},
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
261
289
|
const previousUrlQuery = usePrevious(urlQuery);
|
|
262
290
|
|
|
263
291
|
React.useEffect(() => {
|
|
@@ -296,11 +324,16 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
296
324
|
const [sortOn, setSortOn] = React.useState(data?.query?.sort_on);
|
|
297
325
|
const [sortOrder, setSortOrder] = React.useState(data?.query?.sort_order);
|
|
298
326
|
|
|
299
|
-
const [searchData, setSearchData] = React.useState(
|
|
327
|
+
const [searchData, setSearchData] = React.useState(
|
|
328
|
+
getInitialState(data, facets, urlSearchText, id),
|
|
329
|
+
);
|
|
300
330
|
|
|
331
|
+
const deepFacets = JSON.stringify(facets);
|
|
301
332
|
React.useEffect(() => {
|
|
302
|
-
setSearchData(
|
|
303
|
-
|
|
333
|
+
setSearchData(
|
|
334
|
+
getInitialState(data, facets, urlSearchText, id, sortOn, sortOrder),
|
|
335
|
+
);
|
|
336
|
+
}, [deepFacets, facets, data, urlSearchText, id, sortOn, sortOrder]);
|
|
304
337
|
|
|
305
338
|
const timeoutRef = React.useRef();
|
|
306
339
|
const facetSettings = data?.facets;
|
|
@@ -316,7 +349,7 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
316
349
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
317
350
|
timeoutRef.current = setTimeout(
|
|
318
351
|
() => {
|
|
319
|
-
const
|
|
352
|
+
const newSearchData = normalizeState({
|
|
320
353
|
id,
|
|
321
354
|
query: data.query || {},
|
|
322
355
|
facets: toSearchFacets || facets,
|
|
@@ -328,8 +361,8 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
328
361
|
if (toSearchFacets) setFacets(toSearchFacets);
|
|
329
362
|
if (toSortOn) setSortOn(toSortOn);
|
|
330
363
|
if (toSortOrder) setSortOrder(toSortOrder);
|
|
331
|
-
setSearchData(
|
|
332
|
-
setLocationSearchData(getSearchFields(
|
|
364
|
+
setSearchData(newSearchData);
|
|
365
|
+
setLocationSearchData(getSearchFields(newSearchData));
|
|
333
366
|
},
|
|
334
367
|
toSearchFacets ? inputDelay / 3 : inputDelay,
|
|
335
368
|
);
|
|
@@ -349,13 +382,14 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
349
382
|
);
|
|
350
383
|
|
|
351
384
|
const removeSearchQuery = () => {
|
|
352
|
-
|
|
385
|
+
let newSearchData = { ...searchData };
|
|
386
|
+
newSearchData.query = searchData.query.reduce(
|
|
353
387
|
// Remove SearchableText from query
|
|
354
388
|
(acc, kvp) => (kvp.i === 'SearchableText' ? acc : [...acc, kvp]),
|
|
355
389
|
[],
|
|
356
390
|
);
|
|
357
|
-
setSearchData(
|
|
358
|
-
setLocationSearchData(getSearchFields(
|
|
391
|
+
setSearchData(newSearchData);
|
|
392
|
+
setLocationSearchData(getSearchFields(newSearchData));
|
|
359
393
|
};
|
|
360
394
|
|
|
361
395
|
const querystringResults = useSelector(
|
|
@@ -314,7 +314,8 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
314
314
|
label: field[1].title,
|
|
315
315
|
value: field[0],
|
|
316
316
|
isDisabled: (value || []).some(
|
|
317
|
-
(v) =>
|
|
317
|
+
(v) =>
|
|
318
|
+
v['i'] !== 'path' && v['i'] === field[0],
|
|
318
319
|
),
|
|
319
320
|
}),
|
|
320
321
|
),
|
|
@@ -444,8 +445,11 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
444
445
|
(field) => ({
|
|
445
446
|
label: field[1].title,
|
|
446
447
|
value: field[0],
|
|
448
|
+
// disable selecting indexes that are already used,
|
|
449
|
+
// except for path, which has explicit support
|
|
450
|
+
// in the backend for multipath queries
|
|
447
451
|
isDisabled: (value || []).some(
|
|
448
|
-
(v) => v['i'] === field[0],
|
|
452
|
+
(v) => v['i'] !== 'path' && v['i'] === field[0],
|
|
449
453
|
),
|
|
450
454
|
}),
|
|
451
455
|
),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import cx from 'classnames';
|
|
3
|
-
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
3
|
+
import { flattenToAppURL, flattenScales } from '@plone/volto/helpers';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Image component
|
|
@@ -36,16 +36,17 @@ export default function Image({
|
|
|
36
36
|
const imageFieldWithDefault = imageField || item.image_field || 'image';
|
|
37
37
|
|
|
38
38
|
const image = isFromRealObject
|
|
39
|
-
? item[imageFieldWithDefault]
|
|
40
|
-
:
|
|
39
|
+
? flattenScales(item['@id'], item[imageFieldWithDefault])
|
|
40
|
+
: flattenScales(
|
|
41
|
+
item['@id'],
|
|
42
|
+
item.image_scales[imageFieldWithDefault]?.[0],
|
|
43
|
+
);
|
|
41
44
|
|
|
42
45
|
if (!image) return null;
|
|
43
46
|
|
|
44
47
|
const isSvg = image['content-type'] === 'image/svg+xml';
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
attrs.src = `${baseUrl}${flattenToAppURL(image.download)}`;
|
|
49
|
+
attrs.src = `${flattenToAppURL(item['@id'])}/${image.download}`;
|
|
49
50
|
attrs.width = image.width;
|
|
50
51
|
attrs.height = image.height;
|
|
51
52
|
attrs.className = cx(className, { responsive });
|
|
@@ -60,7 +61,7 @@ export default function Image({
|
|
|
60
61
|
attrs.srcSet = sortedScales
|
|
61
62
|
.map(
|
|
62
63
|
(scale) =>
|
|
63
|
-
`${
|
|
64
|
+
`${flattenToAppURL(item['@id'])}/${scale.download} ${scale.width}w`,
|
|
64
65
|
)
|
|
65
66
|
.join(', ');
|
|
66
67
|
}
|
|
@@ -6,14 +6,14 @@ test('renders an image component with fetchpriority high', () => {
|
|
|
6
6
|
const component = renderer.create(
|
|
7
7
|
<Image
|
|
8
8
|
item={{
|
|
9
|
+
'@id': 'http://localhost:3000/image',
|
|
9
10
|
image: {
|
|
10
|
-
download: 'http://localhost:3000/image/@@images/image
|
|
11
|
+
download: 'http://localhost:3000/image/@@images/image.png',
|
|
11
12
|
width: 400,
|
|
12
13
|
height: 400,
|
|
13
14
|
scales: {
|
|
14
15
|
preview: {
|
|
15
|
-
download:
|
|
16
|
-
'http://localhost:3000/image/@@images/image/image-400.png',
|
|
16
|
+
download: 'http://localhost:3000/image/@@images/image-400.png',
|
|
17
17
|
width: 400,
|
|
18
18
|
height: 400,
|
|
19
19
|
},
|
|
@@ -32,14 +32,14 @@ test('renders an image component with lazy loading', () => {
|
|
|
32
32
|
const component = renderer.create(
|
|
33
33
|
<Image
|
|
34
34
|
item={{
|
|
35
|
+
'@id': 'http://localhost:3000/image',
|
|
35
36
|
image: {
|
|
36
|
-
download: 'http://localhost:3000/image/@@images/image
|
|
37
|
+
download: 'http://localhost:3000/image/@@images/image.png',
|
|
37
38
|
width: 400,
|
|
38
39
|
height: 400,
|
|
39
40
|
scales: {
|
|
40
41
|
preview: {
|
|
41
|
-
download:
|
|
42
|
-
'http://localhost:3000/image/@@images/image/image-400.png',
|
|
42
|
+
download: 'http://localhost:3000/image/@@images/image-400.png',
|
|
43
43
|
width: 400,
|
|
44
44
|
height: 400,
|
|
45
45
|
},
|
|
@@ -59,14 +59,14 @@ test('renders an image component with responsive class', () => {
|
|
|
59
59
|
const component = renderer.create(
|
|
60
60
|
<Image
|
|
61
61
|
item={{
|
|
62
|
+
'@id': 'http://localhost:3000/image',
|
|
62
63
|
image: {
|
|
63
|
-
download: 'http://localhost:3000/image/@@images/image
|
|
64
|
+
download: 'http://localhost:3000/image/@@images/image-1200.png',
|
|
64
65
|
width: 400,
|
|
65
66
|
height: 400,
|
|
66
67
|
scales: {
|
|
67
68
|
preview: {
|
|
68
|
-
download:
|
|
69
|
-
'http://localhost:3000/image/@@images/image/image-400.png',
|
|
69
|
+
download: 'http://localhost:3000/image/@@images/image-400.png',
|
|
70
70
|
width: 400,
|
|
71
71
|
height: 400,
|
|
72
72
|
},
|
|
@@ -91,12 +91,12 @@ test('renders an image component from a catalog brain', () => {
|
|
|
91
91
|
image_scales: {
|
|
92
92
|
image: [
|
|
93
93
|
{
|
|
94
|
-
download: '@@images/image
|
|
94
|
+
download: '@@images/image.png',
|
|
95
95
|
width: 400,
|
|
96
96
|
height: 400,
|
|
97
97
|
scales: {
|
|
98
98
|
preview: {
|
|
99
|
-
download: '@@images/image
|
|
99
|
+
download: '@@images/image-400.png',
|
|
100
100
|
width: 400,
|
|
101
101
|
height: 400,
|
|
102
102
|
},
|
package/src/helpers/Url/Url.js
CHANGED
|
@@ -355,3 +355,32 @@ export const URLUtils = {
|
|
|
355
355
|
isUrl,
|
|
356
356
|
checkAndNormalizeUrl,
|
|
357
357
|
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Given an image info object, it does flatten all the scales information to
|
|
361
|
+
* match the ones stored in the catalog
|
|
362
|
+
* 'http://localhost:3000/{path}/@@images/{scalefile}' -> '@images/{scalefile}'
|
|
363
|
+
* @function flattenScales
|
|
364
|
+
* @param {string} path path of the content object
|
|
365
|
+
* @param {object} image image information object
|
|
366
|
+
* @returns {object} New object with the flattened scale URLs
|
|
367
|
+
*/
|
|
368
|
+
export function flattenScales(path, image) {
|
|
369
|
+
function removeObjectIdFromURL(path, scale) {
|
|
370
|
+
return scale.replace(`${path}/`, '');
|
|
371
|
+
}
|
|
372
|
+
if (!image) return;
|
|
373
|
+
|
|
374
|
+
const imageInfo = {
|
|
375
|
+
...image,
|
|
376
|
+
download: flattenToAppURL(removeObjectIdFromURL(path, image.download)),
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
Object.keys(imageInfo.scales).forEach((key) => {
|
|
380
|
+
imageInfo.scales[key].download = flattenToAppURL(
|
|
381
|
+
removeObjectIdFromURL(path, image.scales[key].download),
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return imageInfo;
|
|
386
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
checkAndNormalizeUrl,
|
|
19
19
|
normaliseMail,
|
|
20
20
|
normalizeTelephone,
|
|
21
|
+
flattenScales,
|
|
21
22
|
} from './Url';
|
|
22
23
|
|
|
23
24
|
beforeEach(() => {
|
|
@@ -350,4 +351,107 @@ describe('Url', () => {
|
|
|
350
351
|
);
|
|
351
352
|
});
|
|
352
353
|
});
|
|
354
|
+
describe('flattenScales', () => {
|
|
355
|
+
it('flattenScales image is not set', () => {
|
|
356
|
+
const id = '/halfdome2022-2.jpg';
|
|
357
|
+
const image = undefined;
|
|
358
|
+
expect(flattenScales(id, image)).toBe(undefined);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('flattenScales test from the catalog', () => {
|
|
362
|
+
const id = '/halfdome2022-2.jpg';
|
|
363
|
+
const image = {
|
|
364
|
+
'content-type': 'image/jpeg',
|
|
365
|
+
download: '@@images/image-1182-cf763ae23c52340d8a17a7afdb26c8cb.jpeg',
|
|
366
|
+
filename: 'halfdome2022.jpg',
|
|
367
|
+
height: 665,
|
|
368
|
+
scales: {
|
|
369
|
+
great: {
|
|
370
|
+
download:
|
|
371
|
+
'@@images/image-1200-539ab119ebadc7d011798980a4a5e8d4.jpeg',
|
|
372
|
+
height: 665,
|
|
373
|
+
width: 1182,
|
|
374
|
+
},
|
|
375
|
+
huge: {
|
|
376
|
+
download:
|
|
377
|
+
'@@images/image-1600-188968febc677890c1b99d5339f9bef1.jpeg',
|
|
378
|
+
height: 665,
|
|
379
|
+
width: 1182,
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
size: 319364,
|
|
383
|
+
width: 1182,
|
|
384
|
+
};
|
|
385
|
+
expect(flattenScales(id, image)).toStrictEqual({
|
|
386
|
+
'content-type': 'image/jpeg',
|
|
387
|
+
download: '@@images/image-1182-cf763ae23c52340d8a17a7afdb26c8cb.jpeg',
|
|
388
|
+
filename: 'halfdome2022.jpg',
|
|
389
|
+
height: 665,
|
|
390
|
+
scales: {
|
|
391
|
+
great: {
|
|
392
|
+
download:
|
|
393
|
+
'@@images/image-1200-539ab119ebadc7d011798980a4a5e8d4.jpeg',
|
|
394
|
+
height: 665,
|
|
395
|
+
width: 1182,
|
|
396
|
+
},
|
|
397
|
+
huge: {
|
|
398
|
+
download:
|
|
399
|
+
'@@images/image-1600-188968febc677890c1b99d5339f9bef1.jpeg',
|
|
400
|
+
height: 665,
|
|
401
|
+
width: 1182,
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
size: 319364,
|
|
405
|
+
width: 1182,
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
it('flattenScales test from serialization', () => {
|
|
409
|
+
const id = 'http://localhost:3000/halfdome2022-2.jpg';
|
|
410
|
+
const image = {
|
|
411
|
+
'content-type': 'image/jpeg',
|
|
412
|
+
download:
|
|
413
|
+
'http://localhost:3000/halfdome2022-2.jpg/@@images/image-1182-cf763ae23c52340d8a17a7afdb26c8cb.jpeg',
|
|
414
|
+
filename: 'halfdome2022.jpg',
|
|
415
|
+
height: 665,
|
|
416
|
+
scales: {
|
|
417
|
+
great: {
|
|
418
|
+
download:
|
|
419
|
+
'http://localhost:3000/halfdome2022-2.jpg/@@images/image-1200-539ab119ebadc7d011798980a4a5e8d4.jpeg',
|
|
420
|
+
height: 665,
|
|
421
|
+
width: 1182,
|
|
422
|
+
},
|
|
423
|
+
huge: {
|
|
424
|
+
download:
|
|
425
|
+
'http://localhost:3000/halfdome2022-2.jpg/@@images/image-1600-188968febc677890c1b99d5339f9bef1.jpeg',
|
|
426
|
+
height: 665,
|
|
427
|
+
width: 1182,
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
size: 319364,
|
|
431
|
+
width: 1182,
|
|
432
|
+
};
|
|
433
|
+
expect(flattenScales(id, image)).toStrictEqual({
|
|
434
|
+
'content-type': 'image/jpeg',
|
|
435
|
+
download: '@@images/image-1182-cf763ae23c52340d8a17a7afdb26c8cb.jpeg',
|
|
436
|
+
filename: 'halfdome2022.jpg',
|
|
437
|
+
height: 665,
|
|
438
|
+
scales: {
|
|
439
|
+
great: {
|
|
440
|
+
download:
|
|
441
|
+
'@@images/image-1200-539ab119ebadc7d011798980a4a5e8d4.jpeg',
|
|
442
|
+
height: 665,
|
|
443
|
+
width: 1182,
|
|
444
|
+
},
|
|
445
|
+
huge: {
|
|
446
|
+
download:
|
|
447
|
+
'@@images/image-1600-188968febc677890c1b99d5339f9bef1.jpeg',
|
|
448
|
+
height: 665,
|
|
449
|
+
width: 1182,
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
size: 319364,
|
|
453
|
+
width: 1182,
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
});
|
|
353
457
|
});
|