@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 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
@@ -0,0 +1 @@
1
+ fix the search block for search via url - @ionlizarazu
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "17.1.0",
12
+ "version": "17.2.0",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plone/volto-slate",
3
- "version": "17.1.0",
3
+ "version": "17.2.0",
4
4
  "description": "Slate.js integration with Volto",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -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/image-400.png',
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(data, facets, urlSearchText, id) {
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(getInitialState(data, facets, urlSearchText, id));
303
- }, [facets, data, urlSearchText, id]);
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 searchData = normalizeState({
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(searchData);
332
- setLocationSearchData(getSearchFields(searchData));
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
- searchData.query = searchData.query.reduce(
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(searchData);
358
- setLocationSearchData(getSearchFields(searchData));
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) => v['i'] === field[0],
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
- : item.image_scales[imageFieldWithDefault]?.[0];
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
- const baseUrl = isFromRealObject ? '' : flattenToAppURL(item['@id'] + '/');
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
- `${baseUrl}${flattenToAppURL(scale.download)} ${scale.width}w`,
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/image.png',
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/image.png',
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/image.png',
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/image.png',
94
+ download: '@@images/image.png',
95
95
  width: 400,
96
96
  height: 400,
97
97
  scales: {
98
98
  preview: {
99
- download: '@@images/image/image-400.png',
99
+ download: '@@images/image-400.png',
100
100
  width: 400,
101
101
  height: 400,
102
102
  },
@@ -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
  });
@@ -29,6 +29,7 @@ export {
29
29
  normalizeUrl,
30
30
  removeProtocol,
31
31
  URLUtils,
32
+ flattenScales,
32
33
  } from '@plone/volto/helpers/Url/Url';
33
34
  export { generateRobots } from '@plone/volto/helpers/Robots/Robots';
34
35
  export {