@eeacms/volto-clms-theme 1.1.68 → 1.1.70

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,25 @@ 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.70](https://github.com/eea/volto-clms-theme/compare/1.1.69...1.1.70) - 26 October 2023
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - Jenkins alert [Ion Lizarazu - [`52b0d70`](https://github.com/eea/volto-clms-theme/commit/52b0d7061c6e33ba15dddc821dd398bb1aea04e0)]
12
+ - deep compare adaptedQuery to avoid multimple querystring-search requests on listing and search blocks [Ion Lizarazu - [`ad10528`](https://github.com/eea/volto-clms-theme/commit/ad10528ef526979fa2f3c9e8377051912e6e5c53)]
13
+ - rename correctly withQuerystringResults.jsx [Ion Lizarazu - [`3c5304a`](https://github.com/eea/volto-clms-theme/commit/3c5304afc48baedee570e48b37e2bc5ad53f2b8e)]
14
+ - add original withQueryStringResults.jsx file to override [Ion Lizarazu - [`dab1086`](https://github.com/eea/volto-clms-theme/commit/dab1086ea5e6f09e82c030a75a4180a8c68dafc0)]
15
+ ### [1.1.69](https://github.com/eea/volto-clms-theme/compare/1.1.68...1.1.69) - 25 October 2023
16
+
17
+ #### :rocket: New Features
18
+
19
+ - feat: optimization for search block. sort_on and sort_order default values [Ion Lizarazu - [`96d0b66`](https://github.com/eea/volto-clms-theme/commit/96d0b6624238ffc114633f7ae21a25515cba9167)]
20
+ - feat: add titles to be accessible [Mikel Larreategi - [`82e2b97`](https://github.com/eea/volto-clms-theme/commit/82e2b97a0c0655c2322302e6b167f64430dff52c)]
21
+
22
+ #### :hammer_and_wrench: Others
23
+
24
+ - add original file to override SearchBlockView.jsx [Ion Lizarazu - [`7334fdc`](https://github.com/eea/volto-clms-theme/commit/7334fdc1c6c9023329490bfc086daf09934bce1b)]
25
+ - do not use duplicate names for tab controlling [Mikel Larreategi - [`ad6a648`](https://github.com/eea/volto-clms-theme/commit/ad6a648c91c5011fce2499b5c1bb72a8aa388d1c)]
7
26
  ### [1.1.68](https://github.com/eea/volto-clms-theme/compare/1.1.67...1.1.68) - 24 October 2023
8
27
 
9
28
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.68",
3
+ "version": "1.1.70",
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",
@@ -9,29 +9,36 @@ import cx from 'classnames';
9
9
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
10
10
 
11
11
  const CclTabsView = (props) => {
12
- const { metadata = {}, tabsList = [], setActiveTab } = props;
12
+ const {
13
+ metadata = {},
14
+ tabsList = [],
15
+ setActiveTab,
16
+ activeTab = null,
17
+ tabs = {},
18
+ } = props;
13
19
 
14
20
  const PanelsComponent = () => {
15
21
  return (
16
22
  <div className="panels">
17
- {tabsList.map((tab, index) => {
18
- const { activeTab = null, tabs = {} } = props;
19
- return (
20
- <div
21
- key={index}
22
- className={cx('panel', tab === activeTab && 'panel-selected')}
23
- id="news_panel"
24
- role="tabpanel"
25
- aria-hidden="false"
26
- >
27
- <RenderBlocks
28
- {...props}
29
- metadata={metadata}
30
- content={tabs[tab]}
31
- />
32
- </div>
33
- );
34
- })}
23
+ {tabsList
24
+ .filter((tab) => tab === activeTab)
25
+ .map((tab, index) => {
26
+ return (
27
+ <div
28
+ key={index}
29
+ className={cx('panel', tab === activeTab && 'panel-selected')}
30
+ id={`tab-${index}`}
31
+ role="tabpanel"
32
+ aria-hidden="false"
33
+ >
34
+ <RenderBlocks
35
+ {...props}
36
+ metadata={metadata}
37
+ content={tabs[tab]}
38
+ />
39
+ </div>
40
+ );
41
+ })}
35
42
  </div>
36
43
  );
37
44
  };
@@ -56,7 +63,7 @@ const CclTabsView = (props) => {
56
63
  key={index}
57
64
  id={tabIndex}
58
65
  role="tab"
59
- aria-controls={title || defaultTitle}
66
+ aria-controls={`tab-${index}`}
60
67
  aria-selected={tab === activeTab}
61
68
  active={(tab === activeTab).toString()}
62
69
  /* classname hontan estiloa aldatu behar bada "===" "!==" gatik aldatuz nahikoa da */
@@ -98,25 +98,27 @@ const PanelsComponent = (props) => {
98
98
  const { metadata = {}, tabsList = [], activeTab = null, tabs = {} } = props;
99
99
  return (
100
100
  <div className="right-content cont-w-75">
101
- {tabsList.map((tab, index) => {
102
- const title = tabs[tab].title;
103
- const tabHash = `tab=${slugify(title)}`;
104
- return (
105
- <Route key={index} to={'#' + tabHash}>
106
- <div
107
- className={cx('panel', tab === activeTab && 'panel-selected')}
108
- role="tabpanel"
109
- aria-hidden="false"
110
- >
111
- <RenderBlocks
112
- {...props}
113
- metadata={metadata}
114
- content={tabs[tab]}
115
- />
116
- </div>
117
- </Route>
118
- );
119
- })}
101
+ {tabsList
102
+ .filter((tab) => tab === activeTab)
103
+ .map((tab, index) => {
104
+ const title = tabs[tab].title;
105
+ const tabHash = `tab=${slugify(title)}`;
106
+ return (
107
+ <Route key={index} to={'#' + tabHash}>
108
+ <div
109
+ className={cx('panel', tab === activeTab && 'panel-selected')}
110
+ role="tabpanel"
111
+ aria-hidden="false"
112
+ >
113
+ <RenderBlocks
114
+ {...props}
115
+ metadata={metadata}
116
+ content={tabs[tab]}
117
+ />
118
+ </div>
119
+ </Route>
120
+ );
121
+ })}
120
122
  </div>
121
123
  );
122
124
  };
@@ -30,9 +30,9 @@ const RoutingHOC = (TabView) =>
30
30
  const hashMatch = window.location.search
31
31
  .match(/.*([&|?|#]tab=.*)/)[1]
32
32
  .replace(/[&|?|#]tab=/, '');
33
- const result = tabsDict.filter((t) => slugify(t.title) === hashMatch);
34
- if (result.length > 0) {
35
- return result[0].id;
33
+ const tab = tabsDict.find((t) => slugify(t.title) === hashMatch);
34
+ if (tab) {
35
+ return tab.id;
36
36
  }
37
37
  }
38
38
  return decideTabSubtab(rTabs, rTabsList[0], rTabsList[1]);
@@ -50,7 +50,6 @@ const RoutingHOC = (TabView) =>
50
50
  }
51
51
  // eslint-disable-next-line react-hooks/exhaustive-deps
52
52
  }, [activeTabIndex, location]);
53
-
54
53
  return <TabView {...props} />;
55
54
  };
56
55
 
@@ -0,0 +1,156 @@
1
+ import React, { useRef } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import hoistNonReactStatics from 'hoist-non-react-statics';
4
+ import useDeepCompareEffect from 'use-deep-compare-effect';
5
+
6
+ import { getContent, getQueryStringResults } from '@plone/volto/actions';
7
+ import { usePagination, getBaseUrl } from '@plone/volto/helpers';
8
+
9
+ import config from '@plone/volto/registry';
10
+
11
+ function getDisplayName(WrappedComponent) {
12
+ return WrappedComponent.displayName || WrappedComponent.name || 'Component';
13
+ }
14
+
15
+ export default function withQuerystringResults(WrappedComponent) {
16
+ function WithQuerystringResults(props) {
17
+ const {
18
+ data = {},
19
+ id = data.block,
20
+ properties: content,
21
+ path,
22
+ variation,
23
+ } = props;
24
+ const { settings } = config;
25
+ const querystring = data.querystring || data; // For backwards compat with data saved before Blocks schema. Note, this is also how the Search block passes data to ListingBody
26
+ const subrequestID = content?.UID ? `${content?.UID}-${id}` : id;
27
+ const { b_size = settings.defaultPageSize } = querystring; // batchsize
28
+
29
+ // save the path so it won't trigger dispatch on eager router location change
30
+ const [initialPath] = React.useState(getBaseUrl(path));
31
+
32
+ const copyFields = ['limit', 'query', 'sort_on', 'sort_order', 'depth'];
33
+ const { currentPage, setCurrentPage } = usePagination(id, 1);
34
+ const adaptedQuery = Object.assign(
35
+ variation?.fullobjects ? { fullobjects: 1 } : { metadata_fields: '_all' },
36
+ {
37
+ b_size: b_size,
38
+ },
39
+ ...copyFields.map((name) =>
40
+ Object.keys(querystring).includes(name)
41
+ ? { [name]: querystring[name] }
42
+ : {},
43
+ ),
44
+ );
45
+ const adaptedQueryRef = useRef(adaptedQuery);
46
+ const currentPageRef = useRef(currentPage);
47
+
48
+ const querystringResults = useSelector(
49
+ (state) => state.querystringsearch.subrequests,
50
+ );
51
+ const dispatch = useDispatch();
52
+
53
+ const folderItems = content?.is_folderish ? content.items : [];
54
+ const hasQuery = querystring?.query?.length > 0;
55
+ const hasLoaded = hasQuery
56
+ ? querystringResults?.[subrequestID]?.loaded
57
+ : true;
58
+
59
+ const listingItems = hasQuery
60
+ ? querystringResults?.[subrequestID]?.items || []
61
+ : folderItems;
62
+
63
+ const showAsFolderListing = !hasQuery && content?.items_total > b_size;
64
+ const showAsQueryListing =
65
+ hasQuery && querystringResults?.[subrequestID]?.total > b_size;
66
+
67
+ const totalPages = showAsFolderListing
68
+ ? Math.ceil(content.items_total / b_size)
69
+ : showAsQueryListing
70
+ ? Math.ceil(querystringResults[subrequestID].total / b_size)
71
+ : 0;
72
+
73
+ const prevBatch = showAsFolderListing
74
+ ? content.batching?.prev
75
+ : showAsQueryListing
76
+ ? querystringResults[subrequestID].batching?.prev
77
+ : null;
78
+ const nextBatch = showAsFolderListing
79
+ ? content.batching?.next
80
+ : showAsQueryListing
81
+ ? querystringResults[subrequestID].batching?.next
82
+ : null;
83
+
84
+ const isImageGallery =
85
+ (!data.variation && data.template === 'imageGallery') ||
86
+ data.variation === 'imageGallery';
87
+
88
+ const deepAdaptedQuery = JSON.stringify(adaptedQuery);
89
+ useDeepCompareEffect(() => {
90
+ if (hasQuery) {
91
+ dispatch(
92
+ getQueryStringResults(
93
+ initialPath,
94
+ adaptedQuery,
95
+ subrequestID,
96
+ currentPage,
97
+ ),
98
+ );
99
+ } else if (isImageGallery && !hasQuery) {
100
+ // when used as image gallery, it doesn't need a query to list children
101
+ dispatch(
102
+ getQueryStringResults(
103
+ initialPath,
104
+ {
105
+ ...adaptedQuery,
106
+ b_size: 10000000000,
107
+ query: [
108
+ {
109
+ i: 'path',
110
+ o: 'plone.app.querystring.operation.string.relativePath',
111
+ v: '',
112
+ },
113
+ ],
114
+ },
115
+ subrequestID,
116
+ ),
117
+ );
118
+ } else {
119
+ dispatch(getContent(initialPath, null, null, currentPage));
120
+ }
121
+ adaptedQueryRef.current = adaptedQuery;
122
+ currentPageRef.current = currentPage;
123
+ // eslint-disable-next-line react-hooks/exhaustive-deps
124
+ }, [
125
+ subrequestID,
126
+ isImageGallery,
127
+ deepAdaptedQuery,
128
+ hasQuery,
129
+ initialPath,
130
+ dispatch,
131
+ currentPage,
132
+ ]);
133
+
134
+ return (
135
+ <WrappedComponent
136
+ {...props}
137
+ onPaginationChange={(e, { activePage }) => setCurrentPage(activePage)}
138
+ total={querystringResults?.[subrequestID]?.total}
139
+ batch_size={b_size}
140
+ currentPage={currentPage}
141
+ totalPages={totalPages}
142
+ prevBatch={prevBatch}
143
+ nextBatch={nextBatch}
144
+ listingItems={listingItems}
145
+ hasLoaded={hasLoaded}
146
+ isFolderContentsListing={showAsFolderListing}
147
+ />
148
+ );
149
+ }
150
+
151
+ WithQuerystringResults.displayName = `WithQuerystringResults(${getDisplayName(
152
+ WrappedComponent,
153
+ )})`;
154
+
155
+ return hoistNonReactStatics(WithQuerystringResults, WrappedComponent);
156
+ }
@@ -0,0 +1,115 @@
1
+ import React from 'react';
2
+
3
+ import ListingBody from '@plone/volto/components/manage/Blocks/Listing/ListingBody';
4
+ import { withBlockExtensions } from '@plone/volto/helpers';
5
+
6
+ import config from '@plone/volto/registry';
7
+
8
+ import {
9
+ withSearch,
10
+ withQueryString,
11
+ } from '@plone/volto/components/manage/Blocks/Search/hocs';
12
+ import { compose } from 'redux';
13
+ import { useSelector } from 'react-redux';
14
+ import { isEqual, isFunction } from 'lodash';
15
+
16
+ const getListingBodyVariation = (data) => {
17
+ const { variations } = config.blocks.blocksConfig.listing;
18
+
19
+ let variation = data.listingBodyTemplate
20
+ ? variations.find(({ id }) => id === data.listingBodyTemplate)
21
+ : variations.find(({ isDefault }) => isDefault);
22
+
23
+ if (!variation) variation = variations[0];
24
+
25
+ return variation;
26
+ };
27
+
28
+ const isfunc = (obj) => isFunction(obj) || typeof obj === 'function';
29
+
30
+ const _filtered = (obj) =>
31
+ Object.assign(
32
+ {},
33
+ ...Object.keys(obj).map((k) => {
34
+ const reject = k !== 'properties' && !isfunc(obj[k]);
35
+ return reject ? { [k]: obj[k] } : {};
36
+ }),
37
+ );
38
+
39
+ const blockPropsAreChanged = (prevProps, nextProps) => {
40
+ const prev = _filtered(prevProps);
41
+ const next = _filtered(nextProps);
42
+
43
+ return isEqual(prev, next);
44
+ };
45
+
46
+ const applyDefaults = (data, root) => {
47
+ const defaultQuery = [
48
+ {
49
+ i: 'path',
50
+ o: 'plone.app.querystring.operation.string.absolutePath',
51
+ v: root || '/',
52
+ },
53
+ ];
54
+ const sort_on = data?.sort_on ? { sort_on: data?.sort_on } : {};
55
+ const sort_order = data?.sort_order ? { sort_order: data?.sort_order } : {};
56
+
57
+ return {
58
+ ...data,
59
+ ...sort_on,
60
+ ...sort_order,
61
+ query: data?.query?.length ? data.query : defaultQuery,
62
+ };
63
+ };
64
+
65
+ const SearchBlockView = (props) => {
66
+ const { data, searchData, mode = 'view', variation } = props;
67
+
68
+ const Layout = variation.view;
69
+
70
+ const dataListingBodyVariation = getListingBodyVariation(data).id;
71
+ const [selectedView, setSelectedView] = React.useState(
72
+ dataListingBodyVariation,
73
+ );
74
+
75
+ // in the block edit you can change the used listing block variation,
76
+ // but it's cached here in the state. So we reset it.
77
+ React.useEffect(() => {
78
+ if (mode !== 'view') {
79
+ setSelectedView(dataListingBodyVariation);
80
+ }
81
+ }, [dataListingBodyVariation, mode]);
82
+
83
+ const root = useSelector((state) => state.breadcrumbs.root);
84
+ const listingBodyData = applyDefaults(searchData, root);
85
+
86
+ const { variations } = config.blocks.blocksConfig.listing;
87
+ const listingBodyVariation = variations.find(({ id }) => id === selectedView);
88
+
89
+ return (
90
+ <div className="block search">
91
+ <Layout
92
+ {...props}
93
+ isEditMode={mode === 'edit'}
94
+ selectedView={selectedView}
95
+ setSelectedView={setSelectedView}
96
+ >
97
+ <ListingBody
98
+ variation={{ ...data, ...listingBodyVariation }}
99
+ data={listingBodyData}
100
+ path={props.path}
101
+ isEditMode={mode === 'edit'}
102
+ />
103
+ </Layout>
104
+ </div>
105
+ );
106
+ };
107
+
108
+ export const SearchBlockViewComponent = compose(
109
+ withBlockExtensions,
110
+ (Component) => React.memo(Component, blockPropsAreChanged),
111
+ )(SearchBlockView);
112
+
113
+ export default compose(withSearch())(
114
+ compose(withQueryString)(SearchBlockViewComponent),
115
+ );
@@ -478,6 +478,7 @@ class Footer extends Component {
478
478
  href="https://www.eea.europa.eu/"
479
479
  target="_blank"
480
480
  rel="noreferrer"
481
+ title="European Environment Agency"
481
482
  >
482
483
  <ReactSVG
483
484
  src={ECImage}
@@ -495,6 +496,7 @@ class Footer extends Component {
495
496
  <a
496
497
  href="https://joint-research-centre.ec.europa.eu/index_en"
497
498
  target="_blank"
499
+ title="Joint Research Centre"
498
500
  rel="noreferrer"
499
501
  >
500
502
  <ReactSVG
@@ -515,10 +517,6 @@ class Footer extends Component {
515
517
  </span>
516
518
  </a>
517
519
  </div>
518
- {/* <div className="ccl-footer-col-title">
519
- {intl.formatMessage(messages.expertSupportProvidedBy)}
520
- </div>
521
- <p>{intl.formatMessage(messages.EIONETActionGroup)}</p> */}
522
520
  </CclFooterColumn>
523
521
  </div>
524
522
  </div>
@@ -165,6 +165,7 @@ class Header extends Component {
165
165
  <div className="ccl-container">
166
166
  <div
167
167
  className="ccl-main-menu-collapse-button"
168
+ aria-label="Toggle main menu"
168
169
  onClick={() =>
169
170
  this.setState({
170
171
  mobileMenuOpen: !this.state.mobileMenuOpen,
@@ -188,6 +189,7 @@ class Header extends Component {
188
189
  </div>
189
190
  <div
190
191
  className="ccl-search-collapse-button"
192
+ aria-label="Toggle search menu"
191
193
  onClick={() =>
192
194
  this.setState({
193
195
  mobileSearchBoxOpen: !this.state.mobileSearchBoxOpen,