@eeacms/volto-clms-theme 1.1.44 → 1.1.46

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,11 +4,26 @@ 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
- ### [1.1.44](https://github.com/eea/volto-clms-theme/compare/1.1.43...1.1.44) - 19 September 2023
7
+ ### [1.1.46](https://github.com/eea/volto-clms-theme/compare/1.1.45...1.1.46) - 20 September 2023
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: search block facets [Ion Lizarazu - [`61f8a64`](https://github.com/eea/volto-clms-theme/commit/61f8a640c4fcec06a08245e5ed74caa8b2011922)]
12
+
13
+ ### [1.1.45](https://github.com/eea/volto-clms-theme/compare/1.1.44...1.1.45) - 20 September 2023
8
14
 
9
15
  #### :bug: Bug Fixes
10
16
 
11
- - fix: useCase card image label z-index [Ion Lizarazu - [`6e78c6d`](https://github.com/eea/volto-clms-theme/commit/6e78c6d1d7559c9e21dd64e97727512c67f3c334)]
17
+ - fix: RelatedNews test params [Ion Lizarazu - [`2980a78`](https://github.com/eea/volto-clms-theme/commit/2980a78eb94c3295bbf38f40e27bff4188c6cac2)]
18
+ - fix: sonarqube [Ion Lizarazu - [`9e84298`](https://github.com/eea/volto-clms-theme/commit/9e84298ca5128e33c8d54adab719965a49515042)]
19
+
20
+ #### :hammer_and_wrench: Others
21
+
22
+ - remove console statement [Ion Lizarazu - [`4e67ca2`](https://github.com/eea/volto-clms-theme/commit/4e67ca27a605c944c37674d30d1fb3adc73cb3df)]
23
+ - jest tests [Ion Lizarazu - [`29f9d35`](https://github.com/eea/volto-clms-theme/commit/29f9d351e6b9ebdcb4d81a2d654a0782cd8af855)]
24
+ - unused code removed and add tests [Ion Lizarazu - [`51f6856`](https://github.com/eea/volto-clms-theme/commit/51f685623c2148138ee3780e07b369a9a8b2c09a)]
25
+ - add jest test [Ion Lizarazu - [`159792c`](https://github.com/eea/volto-clms-theme/commit/159792c28b3d8bc903111708b1664704ec0fed5e)]
26
+ ### [1.1.44](https://github.com/eea/volto-clms-theme/compare/1.1.43...1.1.44) - 19 September 2023
12
27
 
13
28
  ### [1.1.43](https://github.com/eea/volto-clms-theme/compare/1.1.42...1.1.43) - 19 September 2023
14
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.44",
3
+ "version": "1.1.46",
4
4
  "description": "volto-clms-theme: Volto theme for CLMS site",
5
5
  "main": "src/index.js",
6
6
  "author": "CodeSyntax for the European Environment Agency",
@@ -141,7 +141,7 @@ const DataSetInfoContent = (props) => {
141
141
  <Accordion.Title
142
142
  as={'h2'}
143
143
  onClick={() => handleClick({ index: 99 })}
144
- active={activeIndex === 99}
144
+ active={activeIndex.includes(99)}
145
145
  index={99}
146
146
  className={'accordion-title align-arrow-right'}
147
147
  >
@@ -19,41 +19,31 @@ describe('DataSetInfoContent', () => {
19
19
  messages: {},
20
20
  },
21
21
  search: {
22
- subrequest: {
23
- '123': { items: [] },
22
+ subrequests: {
23
+ datasetinfocontentid: { items: [] },
24
24
  },
25
25
  },
26
26
  });
27
27
 
28
- const data = {
29
- dataResourceAbstract: {
30
- title: 'example title',
31
- description: 'example description',
32
- tooltip: 'example tooltip',
33
- data: 'Resource Abstract example',
34
- },
35
- dataSources: {
36
- title: 'example title',
37
- description: 'example description',
38
- tooltip: 'example tooltip',
39
- data: 'Source data',
40
- },
41
- dataResourceLocator: 'Resource locator',
42
- dataResourceTitle: 'example dataResourceTitle',
28
+ const props = {
29
+ UID: 'gfdjgf8edgtrh789',
30
+ id: 'datasetinfocontentid',
43
31
  validation: {
44
32
  title: 'example title',
45
33
  description: 'example description',
46
34
  tooltip: 'example tooltip',
47
35
  data: 'Validation',
48
36
  },
37
+ geonetwork_identifiers: {
38
+ items: [],
39
+ },
40
+ data: { right_arrows: true },
49
41
  };
50
42
  const datasetInfo = renderer
51
43
  .create(
52
44
  <Provider store={store}>
53
45
  <MemoryRouter>
54
- <DataSetInfoContent props={data}>
55
- <p>Dataset info view test</p>
56
- </DataSetInfoContent>
46
+ <DataSetInfoContent {...props} />
57
47
  </MemoryRouter>
58
48
  </Provider>,
59
49
  )
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import renderer from 'react-test-renderer';
3
+ import { MetadataPaginatedListing } from './MetadataPaginatedListing';
4
+ import { MemoryRouter } from 'react-router-dom';
5
+ import configureStore from 'redux-mock-store';
6
+ import { Provider } from 'react-redux';
7
+
8
+ describe('MetadataPaginatedListing', () => {
9
+ it('Check MetadataPaginatedListing view', () => {
10
+ const mockStore = configureStore();
11
+
12
+ const store = mockStore({
13
+ intl: {
14
+ locale: 'en',
15
+ messages: {},
16
+ },
17
+ search: {
18
+ subrequests: {
19
+ datasetinfocontentid: { items: [] },
20
+ },
21
+ },
22
+ });
23
+
24
+ const props = {
25
+ id: 'metadata-paginated-listing',
26
+ geonetwork_identifiers_items: [
27
+ { id: 'geo-eea-id', title: 'Geo title', type: 'EEA' },
28
+ { id: 'geo-vito-id', title: 'Geo title 2', type: 'VITO' },
29
+ ],
30
+ };
31
+ const datasetInfo = renderer
32
+ .create(
33
+ <Provider store={store}>
34
+ <MemoryRouter>
35
+ <MetadataPaginatedListing {...props} />
36
+ </MemoryRouter>
37
+ </Provider>,
38
+ )
39
+ .toJSON();
40
+ expect(datasetInfo).toBeDefined();
41
+ });
42
+ });
@@ -6,21 +6,27 @@ import React from 'react';
6
6
  import { searchContent } from '@plone/volto/actions';
7
7
  import { useLocation } from 'react-router-dom';
8
8
 
9
- const RelatedNews = (props) => {
9
+ const RelatedItems = (props) => {
10
10
  const dispatch = useDispatch();
11
- const { UID, id } = props;
11
+ const { UID, id, type } = props;
12
+ let hash = '';
13
+ if (type === 'News Item') {
14
+ hash = '#News';
15
+ } else if (type === 'UseCase') {
16
+ hash = '#Use-cases';
17
+ }
12
18
  const searchSubrequests = useSelector((state) => state.search.subrequests);
13
19
  let libraries = searchSubrequests?.[id]?.items || [];
14
20
  let librariesPending = searchSubrequests?.[id]?.loading;
15
21
  const location = useLocation();
16
22
  React.useEffect(() => {
17
- if (location.hash === '#News' && UID) {
23
+ if (location.hash === hash && UID && hash) {
18
24
  dispatch(
19
25
  searchContent(
20
26
  '',
21
27
  {
22
28
  fullobjects: 1,
23
- portal_type: 'News Item',
29
+ portal_type: type,
24
30
  path: '/',
25
31
  associated_datasets: UID,
26
32
  },
@@ -28,6 +34,7 @@ const RelatedNews = (props) => {
28
34
  ),
29
35
  );
30
36
  }
37
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
38
  }, [id, UID, dispatch, location]);
32
39
 
33
40
  return (
@@ -48,4 +55,4 @@ const RelatedNews = (props) => {
48
55
  );
49
56
  };
50
57
 
51
- export default RelatedNews;
58
+ export default RelatedItems;
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import renderer from 'react-test-renderer';
3
+ import RelatedItems from './RelatedItems';
4
+ import { MemoryRouter } from 'react-router-dom';
5
+ import configureStore from 'redux-mock-store';
6
+ import { Provider } from 'react-redux';
7
+ import { card } from '../CclCard/CclCard.test';
8
+ describe('RelatedItems', () => {
9
+ it('Check RelatedItems News view', () => {
10
+ const mockStore = configureStore();
11
+
12
+ const store = mockStore({
13
+ userSession: {
14
+ token:
15
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTY0NDM4MzA0NCwiZnVsbG5hbWUiOm51bGx9.cB_q3Q0Jhu8h2m_SDmmknodpDxDLfb4o-qY6Y2plE04',
16
+ },
17
+ intl: {
18
+ locale: 'en',
19
+ messages: {},
20
+ },
21
+ search: {
22
+ subrequests: {
23
+ 'related-news': { items: [card] },
24
+ },
25
+ },
26
+ });
27
+
28
+ const props = {
29
+ id: 'related-news',
30
+ UID: 'uid3894032nhhlgtrjekl',
31
+ type: 'News Item',
32
+ };
33
+
34
+ const datasetInfo = renderer
35
+ .create(
36
+ <Provider store={store}>
37
+ <MemoryRouter>
38
+ <RelatedItems {...props} />
39
+ </MemoryRouter>
40
+ </Provider>,
41
+ )
42
+ .toJSON();
43
+ expect(datasetInfo).toBeDefined();
44
+ });
45
+
46
+ it('Check RelatedItems UseCase view', () => {
47
+ const mockStore = configureStore();
48
+
49
+ const store = mockStore({
50
+ userSession: {
51
+ token:
52
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTY0NDM4MzA0NCwiZnVsbG5hbWUiOm51bGx9.cB_q3Q0Jhu8h2m_SDmmknodpDxDLfb4o-qY6Y2plE04',
53
+ },
54
+ intl: {
55
+ locale: 'en',
56
+ messages: {},
57
+ },
58
+ search: {
59
+ subrequests: {
60
+ 'related-news': { items: [card] },
61
+ },
62
+ },
63
+ });
64
+
65
+ const props = {
66
+ id: 'related-news',
67
+ UID: 'uid3894032nhhlgtrjekl',
68
+ type: 'UseCase',
69
+ };
70
+
71
+ const datasetInfo = renderer
72
+ .create(
73
+ <Provider store={store}>
74
+ <MemoryRouter>
75
+ <RelatedItems {...props} />
76
+ </MemoryRouter>
77
+ </Provider>,
78
+ )
79
+ .toJSON();
80
+ expect(datasetInfo).toBeDefined();
81
+ });
82
+ });
@@ -1,4 +1,3 @@
1
1
  export DataSetInfoContent from './DataSetInfoContent';
2
2
  export DownloadDataSetContent from './DownloadDataSetContent';
3
- export RelatedNews from './RelatedNews';
4
- export RelatedUseCases from './RelatedUseCases';
3
+ export RelatedItems from './RelatedItems';
@@ -6,6 +6,39 @@ import configureStore from 'redux-mock-store';
6
6
  import { Provider } from 'react-intl-redux';
7
7
 
8
8
  const mockStore = configureStore();
9
+ export const card = {
10
+ title: 'title example',
11
+ description: 'description example',
12
+ '@type': 'News Item',
13
+ start: 'Wed May 19 2021 12:49:04 GMT+0200 (hora de verano de Europa central)',
14
+ end: 'Wed May 26 2024 12:49:04 GMT+0200 (hora de verano de Europa central)',
15
+ whole_day: false,
16
+ image: {
17
+ src:
18
+ 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
19
+ scales: {
20
+ mini: {
21
+ download:
22
+ 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
23
+ },
24
+ preview: {
25
+ download:
26
+ 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
27
+ },
28
+ },
29
+ download: 'false',
30
+ alt: 'Placeholder',
31
+ },
32
+ taxonomy_technical_library_categorization: ['1', '2', '3'],
33
+ file: {
34
+ 'content-type': 'text/html',
35
+ src:
36
+ 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
37
+ download: 'true',
38
+ },
39
+ effective:
40
+ 'Wed May 19 2021 12:49:04 GMT+0200 (hora de verano de Europa central)',
41
+ };
9
42
 
10
43
  describe('CclCard', () => {
11
44
  const store = mockStore({
@@ -31,40 +64,7 @@ describe('CclCard', () => {
31
64
  },
32
65
  taxonomy_technical_library_categorization: ['1', '2', '3'],
33
66
  });
34
- const card = {
35
- title: 'title example',
36
- description: 'description example',
37
- '@type': 'News Item',
38
- start:
39
- 'Wed May 19 2021 12:49:04 GMT+0200 (hora de verano de Europa central)',
40
- end: 'Wed May 26 2024 12:49:04 GMT+0200 (hora de verano de Europa central)',
41
- whole_day: false,
42
- image: {
43
- src:
44
- 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
45
- scales: {
46
- mini: {
47
- download:
48
- 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
49
- },
50
- preview: {
51
- download:
52
- 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
53
- },
54
- },
55
- download: 'false',
56
- alt: 'Placeholder',
57
- },
58
- taxonomy_technical_library_categorization: ['1', '2', '3'],
59
- file: {
60
- 'content-type': 'text/html',
61
- src:
62
- 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg',
63
- download: 'true',
64
- },
65
- effective:
66
- 'Wed May 19 2021 12:49:04 GMT+0200 (hora de verano de Europa central)',
67
- };
67
+
68
68
  it('Check doc card', () => {
69
69
  const cardtest = renderer
70
70
  .create(
@@ -0,0 +1,414 @@
1
+ import React from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import qs from 'query-string';
4
+ import { useLocation, useHistory } from 'react-router-dom';
5
+
6
+ import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions';
7
+ import config from '@plone/volto/registry';
8
+ import { usePrevious } from '@plone/volto/helpers';
9
+ import { isEqual } from 'lodash';
10
+
11
+ function getDisplayName(WrappedComponent) {
12
+ return WrappedComponent.displayName || WrappedComponent.name || 'Component';
13
+ }
14
+
15
+ const SEARCH_ENDPOINT_FIELDS = [
16
+ 'SearchableText',
17
+ 'b_size',
18
+ 'limit',
19
+ 'sort_on',
20
+ 'sort_order',
21
+ ];
22
+
23
+ const PAQO = 'plone.app.querystring.operation';
24
+
25
+ /**
26
+ * Based on URL state, gets an initial internal state for the search
27
+ *
28
+ * @function getInitialState
29
+ *
30
+ */
31
+ function getInitialState(data, facets, urlSearchText, id) {
32
+ const {
33
+ types: facetWidgetTypes,
34
+ } = config.blocks.blocksConfig.search.extensions.facetWidgets;
35
+ const facetSettings = data?.facets || [];
36
+
37
+ return {
38
+ query: [
39
+ ...(data.query?.query || []),
40
+ ...(facetSettings || [])
41
+ .map((facet) => {
42
+ if (!facet?.field) return null;
43
+
44
+ const { valueToQuery } = resolveExtension(
45
+ 'type',
46
+ facetWidgetTypes,
47
+ facet,
48
+ );
49
+
50
+ const name = facet.field.value;
51
+ const value = facets[name];
52
+
53
+ return valueToQuery({ value, facet });
54
+ })
55
+ .filter((f) => !!f),
56
+ ...(urlSearchText
57
+ ? [
58
+ {
59
+ i: 'SearchableText',
60
+ o: 'plone.app.querystring.operation.string.contains',
61
+ v: urlSearchText,
62
+ },
63
+ ]
64
+ : []),
65
+ ],
66
+ sort_on: data.query?.sort_on,
67
+ sort_order: data.query?.sort_order,
68
+ b_size: data.query?.b_size,
69
+ limit: data.query?.limit,
70
+ block: id,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * "Normalizes" the search state to something that's serializable
76
+ * (for querying) and used to compute data for the ListingBody
77
+ *
78
+ * @function normalizeState
79
+ *
80
+ */
81
+ function normalizeState({
82
+ query, // base query
83
+ facets, // facet values
84
+ id, // block id
85
+ searchText, // SearchableText
86
+ sortOn,
87
+ sortOrder,
88
+ facetSettings, // data.facets extracted from block data
89
+ }) {
90
+ const {
91
+ types: facetWidgetTypes,
92
+ } = config.blocks.blocksConfig.search.extensions.facetWidgets;
93
+
94
+ const params = {
95
+ query: [
96
+ ...(query.query || []),
97
+ ...(facetSettings || []).map((facet) => {
98
+ if (!facet?.field) return null;
99
+
100
+ const { valueToQuery } = resolveExtension(
101
+ 'type',
102
+ facetWidgetTypes,
103
+ facet,
104
+ );
105
+
106
+ const name = facet.field.value;
107
+ const value = facets[name];
108
+
109
+ return valueToQuery({ value, facet });
110
+ }),
111
+ ].filter((o) => !!o),
112
+ sort_on: sortOn || query.sort_on,
113
+ sort_order: sortOrder || query.sort_order,
114
+ b_size: query.b_size,
115
+ limit: query.limit,
116
+ block: id,
117
+ };
118
+
119
+ // Note Ideally the searchtext functionality should be restructured as being just
120
+ // another facet. But right now it's the same. This means that if a searchText
121
+ // is provided, it will override the SearchableText facet.
122
+ // If there is no searchText, the SearchableText in the query remains in effect.
123
+ // TODO eventually the searchText should be a distinct facet from SearchableText, and
124
+ // the two conditions could be combined, in comparison to the current state, when
125
+ // one overrides the other.
126
+ if (searchText) {
127
+ params.query = params.query.reduce(
128
+ // Remove SearchableText from query
129
+ (acc, kvp) => (kvp.i === 'SearchableText' ? acc : [...acc, kvp]),
130
+ [],
131
+ );
132
+ params.query.push({
133
+ i: 'SearchableText',
134
+ o: 'plone.app.querystring.operation.string.contains',
135
+ v: searchText,
136
+ });
137
+ }
138
+
139
+ return params;
140
+ }
141
+
142
+ const getSearchFields = (searchData) => {
143
+ return Object.assign(
144
+ {},
145
+ ...SEARCH_ENDPOINT_FIELDS.map((k) => {
146
+ return searchData[k] ? { [k]: searchData[k] } : {};
147
+ }),
148
+ searchData.query ? { query: serializeQuery(searchData['query']) } : {},
149
+ );
150
+ };
151
+
152
+ /**
153
+ * A hook that will mirror the search block state to a hash location
154
+ */
155
+ const useHashState = () => {
156
+ const location = useLocation();
157
+ const history = useHistory();
158
+
159
+ /**
160
+ * Required to maintain parameter compatibility.
161
+ With this we will maintain support for receiving hash (#) and search (?) type parameters.
162
+ */
163
+ const oldState = React.useMemo(() => {
164
+ return {
165
+ ...qs.parse(location.search),
166
+ ...qs.parse(location.hash),
167
+ };
168
+ }, [location.hash, location.search]);
169
+
170
+ // This creates a shallow copy. Why is this needed?
171
+ const current = Object.assign(
172
+ {},
173
+ ...Array.from(Object.keys(oldState)).map((k) => ({ [k]: oldState[k] })),
174
+ );
175
+
176
+ const setSearchData = React.useCallback(
177
+ (searchData) => {
178
+ const newParams = qs.parse(location.search);
179
+
180
+ let changed = false;
181
+
182
+ Object.keys(searchData)
183
+ .sort()
184
+ .forEach((k) => {
185
+ if (searchData[k]) {
186
+ newParams[k] = searchData[k];
187
+ if (oldState[k] !== searchData[k]) {
188
+ changed = true;
189
+ }
190
+ }
191
+ });
192
+
193
+ if (changed) {
194
+ history.push({
195
+ search: qs.stringify(newParams),
196
+ });
197
+ }
198
+ },
199
+ [history, oldState, location.search],
200
+ );
201
+
202
+ return [current, setSearchData];
203
+ };
204
+
205
+ /**
206
+ * A hook to make it possible to switch disable mirroring the search block
207
+ * state to the window location. When using the internal state we "start from
208
+ * scratch", as it's intended to be used in the edit page.
209
+ */
210
+ const useSearchBlockState = (uniqueId, isEditMode) => {
211
+ const [hashState, setHashState] = useHashState();
212
+ const [internalState, setInternalState] = React.useState({});
213
+
214
+ return isEditMode
215
+ ? [internalState, setInternalState]
216
+ : [hashState, setHashState];
217
+ };
218
+
219
+ // Simple compress/decompress the state in URL by replacing the lengthy string
220
+ const deserializeQuery = (q) => {
221
+ return JSON.parse(q)?.map((kvp) => ({
222
+ ...kvp,
223
+ o: kvp.o.replace(/^paqo/, PAQO),
224
+ }));
225
+ };
226
+ const serializeQuery = (q) => {
227
+ return JSON.stringify(
228
+ q?.map((kvp) => ({ ...kvp, o: kvp.o.replace(PAQO, 'paqo') })),
229
+ );
230
+ };
231
+
232
+ const withSearch = (options) => (WrappedComponent) => {
233
+ const { inputDelay = 1000 } = options || {};
234
+
235
+ function WithSearch(props) {
236
+ const { data, id, editable = false } = props;
237
+
238
+ const [locationSearchData, setLocationSearchData] = useSearchBlockState(
239
+ id,
240
+ editable,
241
+ );
242
+
243
+ const urlQuery = locationSearchData.query
244
+ ? deserializeQuery(locationSearchData.query)
245
+ : [];
246
+ const urlSearchText =
247
+ locationSearchData.SearchableText ||
248
+ urlQuery.find(({ i }) => i === 'SearchableText')?.v ||
249
+ '';
250
+
251
+ // TODO: refactor, should use only useLocationStateManager()!!!
252
+ const [searchText, setSearchText] = React.useState(urlSearchText);
253
+ const configuredFacets =
254
+ data.facets?.map((facet) => facet?.field?.value) || [];
255
+ const multiFacets = data.facets
256
+ ?.filter((facet) => facet?.multiple)
257
+ .map((facet) => facet?.field?.value);
258
+ const [facets, setFacets] = React.useState(
259
+ Object.assign(
260
+ {},
261
+ ...urlQuery.map(({ i, v }) => ({ [i]: v })), // TODO: the 'o' should be kept. This would be a major refactoring of the facets
262
+
263
+ // support for simple filters like ?Subject=something
264
+ // TODO: since the move to hash params this is no longer working.
265
+ // We'd have to treat the location.search and manage it just like the
266
+ // hash, to support it. We can read it, but we'd have to reset it as
267
+ // well, so at that point what's the difference to the hash?
268
+ ...configuredFacets.map((f) =>
269
+ locationSearchData[f]
270
+ ? {
271
+ [f]:
272
+ multiFacets.indexOf(f) > -1
273
+ ? [locationSearchData[f]]
274
+ : locationSearchData[f],
275
+ }
276
+ : {},
277
+ ),
278
+ ),
279
+ );
280
+ const previousUrlQuery = usePrevious(urlQuery);
281
+
282
+ React.useEffect(() => {
283
+ if (!isEqual(urlQuery, previousUrlQuery)) {
284
+ setFacets(
285
+ Object.assign(
286
+ {},
287
+ ...urlQuery.map(({ i, v }) => ({ [i]: v })), // TODO: the 'o' should be kept. This would be a major refactoring of the facets
288
+
289
+ // support for simple filters like ?Subject=something
290
+ // TODO: since the move to hash params this is no longer working.
291
+ // We'd have to treat the location.search and manage it just like the
292
+ // hash, to support it. We can read it, but we'd have to reset it as
293
+ // well, so at that point what's the difference to the hash?
294
+ ...configuredFacets.map((f) =>
295
+ locationSearchData[f]
296
+ ? {
297
+ [f]:
298
+ multiFacets.indexOf(f) > -1
299
+ ? [locationSearchData[f]]
300
+ : locationSearchData[f],
301
+ }
302
+ : {},
303
+ ),
304
+ ),
305
+ );
306
+ }
307
+ }, [
308
+ urlQuery,
309
+ configuredFacets,
310
+ locationSearchData,
311
+ multiFacets,
312
+ previousUrlQuery,
313
+ ]);
314
+
315
+ const [sortOn, setSortOn] = React.useState(data?.query?.sort_on);
316
+ const [sortOrder, setSortOrder] = React.useState(data?.query?.sort_order);
317
+
318
+ const [searchData, setSearchData] = React.useState(
319
+ getInitialState(data, facets, urlSearchText, id),
320
+ );
321
+
322
+ const deepFacets = JSON.stringify(facets);
323
+ React.useEffect(() => {
324
+ setSearchData(getInitialState(data, facets, urlSearchText, id));
325
+ }, [deepFacets, facets, data, urlSearchText, id]);
326
+
327
+ const timeoutRef = React.useRef();
328
+ const facetSettings = data?.facets;
329
+
330
+ const deepQuery = JSON.stringify(data.query);
331
+ const onTriggerSearch = React.useCallback(
332
+ (
333
+ toSearchText = undefined,
334
+ toSearchFacets = undefined,
335
+ toSortOn = undefined,
336
+ toSortOrder = undefined,
337
+ ) => {
338
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
339
+ timeoutRef.current = setTimeout(
340
+ () => {
341
+ const newSearchData = normalizeState({
342
+ id,
343
+ query: data.query || {},
344
+ facets: toSearchFacets || facets,
345
+ searchText: toSearchText ? toSearchText.trim() : '',
346
+ sortOn: toSortOn || sortOn,
347
+ sortOrder: toSortOrder || sortOrder,
348
+ facetSettings,
349
+ });
350
+ if (toSearchFacets) setFacets(toSearchFacets);
351
+ if (toSortOn) setSortOn(toSortOn);
352
+ if (toSortOrder) setSortOrder(toSortOrder);
353
+ setSearchData(newSearchData);
354
+ setLocationSearchData(getSearchFields(newSearchData));
355
+ },
356
+ toSearchFacets ? inputDelay / 3 : inputDelay,
357
+ );
358
+ },
359
+ // eslint-disable-next-line react-hooks/exhaustive-deps
360
+ [
361
+ // Use deep comparison of data.query
362
+ deepQuery,
363
+ facets,
364
+ id,
365
+ setLocationSearchData,
366
+ searchText,
367
+ sortOn,
368
+ sortOrder,
369
+ facetSettings,
370
+ ],
371
+ );
372
+
373
+ const removeSearchQuery = () => {
374
+ let newSearchData = { ...searchData };
375
+ newSearchData.query = searchData.query.reduce(
376
+ // Remove SearchableText from query
377
+ (acc, kvp) => (kvp.i === 'SearchableText' ? acc : [...acc, kvp]),
378
+ [],
379
+ );
380
+ setSearchData(newSearchData);
381
+ setLocationSearchData(getSearchFields(newSearchData));
382
+ };
383
+
384
+ const querystringResults = useSelector(
385
+ (state) => state.querystringsearch.subrequests,
386
+ );
387
+ const totalItems =
388
+ querystringResults[id]?.total || querystringResults[id]?.items?.length;
389
+
390
+ return (
391
+ <WrappedComponent
392
+ {...props}
393
+ searchData={searchData}
394
+ facets={facets}
395
+ setFacets={setFacets}
396
+ setSortOn={setSortOn}
397
+ setSortOrder={setSortOrder}
398
+ sortOn={sortOn}
399
+ sortOrder={sortOrder}
400
+ searchedText={urlSearchText}
401
+ searchText={searchText}
402
+ removeSearchQuery={removeSearchQuery}
403
+ setSearchText={setSearchText}
404
+ onTriggerSearch={onTriggerSearch}
405
+ totalItems={totalItems}
406
+ />
407
+ );
408
+ }
409
+ WithSearch.displayName = `WithSearch(${getDisplayName(WrappedComponent)})`;
410
+
411
+ return WithSearch;
412
+ };
413
+
414
+ export default withSearch;
@@ -1,51 +0,0 @@
1
- import { Loader } from 'semantic-ui-react';
2
- import { useDispatch, useSelector } from 'react-redux';
3
-
4
- import CclCard from '@eeacms/volto-clms-theme/components/CclCard/CclCard';
5
- import React from 'react';
6
- import { searchContent } from '@plone/volto/actions';
7
- import { useLocation } from 'react-router-dom';
8
-
9
- const RelatedUseCases = (props) => {
10
- const dispatch = useDispatch();
11
- const { UID, id } = props;
12
- const searchSubrequests = useSelector((state) => state.search.subrequests);
13
- let libraries = searchSubrequests?.[id]?.items || [];
14
- let librariesPending = searchSubrequests?.[id]?.loading;
15
- const location = useLocation();
16
- React.useEffect(() => {
17
- if (location.hash === '#Use-cases' && UID) {
18
- dispatch(
19
- searchContent(
20
- '',
21
- {
22
- fullobjects: 1,
23
- portal_type: 'UseCase',
24
- path: '/',
25
- associated_datasets: UID,
26
- },
27
- id,
28
- ),
29
- );
30
- }
31
- }, [id, UID, dispatch, location]);
32
-
33
- return (
34
- <div>
35
- {librariesPending && <Loader active inline="centered" />}
36
- {libraries.length > 0 ? (
37
- libraries.map((item, index) => (
38
- <CclCard
39
- key={index}
40
- type={null}
41
- card={{ Type: item['@type'], ...item }}
42
- />
43
- ))
44
- ) : (
45
- <p>There are no related items.</p>
46
- )}
47
- </div>
48
- );
49
- };
50
-
51
- export default RelatedUseCases;