@eeacms/volto-cca-policy 0.3.44 → 0.3.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,6 +4,37 @@ 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
+ ### [0.3.46](https://github.com/eea/volto-cca-policy/compare/0.3.45...0.3.46) - 21 May 2025
8
+
9
+ #### :house: Internal changes
10
+
11
+ - style: Automated code fix [eea-jenkins - [`ea4b8be`](https://github.com/eea/volto-cca-policy/commit/ea4b8beed2cb7426c3125b6edb85d3b1fadf66a1)]
12
+
13
+ #### :hammer_and_wrench: Others
14
+
15
+ - Dont' need this now [Tiberiu Ichim - [`eff27fb`](https://github.com/eea/volto-cca-policy/commit/eff27fb4022b445c6c298a1c59e36ecab661e91e)]
16
+ - Add actions customization [Tiberiu Ichim - [`69f9b00`](https://github.com/eea/volto-cca-policy/commit/69f9b00cb45149e9b4d06085f4688800cb4d1d9f)]
17
+ - Remove etag from server [Tiberiu Ichim - [`7d94455`](https://github.com/eea/volto-cca-policy/commit/7d94455440b69ebd97c0ef5cc3dd839687b13e54)]
18
+ ### [0.3.45](https://github.com/eea/volto-cca-policy/compare/0.3.44...0.3.45) - 21 May 2025
19
+
20
+ #### :rocket: New Features
21
+
22
+ - feat: add event accordion listing view - refs #286702 [kreafox - [`47b26e1`](https://github.com/eea/volto-cca-policy/commit/47b26e14d80e8a7bb0e067d9ffda87b7c028601a)]
23
+
24
+ #### :nail_care: Enhancements
25
+
26
+ - change: add Footer customization - refs #287013 [kreafox - [`875f5ab`](https://github.com/eea/volto-cca-policy/commit/875f5ab3bdf16fd2446757fd6949f9bf65eeaa96)]
27
+
28
+ #### :house: Internal changes
29
+
30
+ - style: Automated code fix [eea-jenkins - [`dc669ad`](https://github.com/eea/volto-cca-policy/commit/dc669ad88e0568b83821e99c3bc31066c3624261)]
31
+ - style: small css adjustments [kreafox - [`b93f7bb`](https://github.com/eea/volto-cca-policy/commit/b93f7bb521fbd2ecbb4245d5c07141ec8f4a74fd)]
32
+
33
+ #### :hammer_and_wrench: Others
34
+
35
+ - test: add tests to increase code coverage [kreafox - [`68fb5dc`](https://github.com/eea/volto-cca-policy/commit/68fb5dc83c58a77efa4f87cae4822758c93242e4)]
36
+ - Refs #284995 - add description [Tripon Eugen - [`9a2aa38`](https://github.com/eea/volto-cca-policy/commit/9a2aa38e4d0879acbcdedfdb95b08b1bf8ae6650)]
37
+ - Refs #284995 - add description [Tripon Eugen - [`2378952`](https://github.com/eea/volto-cca-policy/commit/2378952ca23bc4e4eae74445bec9a08869f6b8d7)]
7
38
  ### [0.3.44](https://github.com/eea/volto-cca-policy/compare/0.3.43...0.3.44) - 16 May 2025
8
39
 
9
40
  #### :bug: Bug Fixes
@@ -2387,10 +2418,13 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2387
2418
  - Refs #260715 rast-block wip [Tripon Eugen - [`f19d54e`](https://github.com/eea/volto-cca-policy/commit/f19d54e0b9a6a86bf344eb85b6a1cda7f3de91bf)]
2388
2419
  - Refs #260715 rast-block wip [Tripon Eugen - [`2828537`](https://github.com/eea/volto-cca-policy/commit/2828537b6c084cd1a82162d552fb4ef025b71f9f)]
2389
2420
  - Refs #260715 rast-block updates [Tripon Eugen - [`1e803e5`](https://github.com/eea/volto-cca-policy/commit/1e803e5bd3d3fb7558f261c76c68866be7beb8b5)]
2421
+ - test: [JENKINS] Use java17 for sonarqube scanner [valentinab25 - [`0a15e1b`](https://github.com/eea/volto-cca-policy/commit/0a15e1b2ad081233685e80d5b3c60a8663f6b896)]
2422
+ - test: [JENKINS] Run cypress in started frontend container [valentinab25 - [`9554e44`](https://github.com/eea/volto-cca-policy/commit/9554e44c92a621a52b2adb5a4830fb084ee5734b)]
2390
2423
  ### [0.1.49](https://github.com/eea/volto-cca-policy/compare/0.1.48...0.1.49) - 15 November 2023
2391
2424
 
2392
2425
  #### :house: Internal changes
2393
2426
 
2427
+ - chore: [JENKINS] Refactor automated testing [valentinab25 - [`7b820a6`](https://github.com/eea/volto-cca-policy/commit/7b820a6369c2ddd5203b1a4abe352cb4bb43db7a)]
2394
2428
  - chore: husky, lint-staged use fixed versions [valentinab25 - [`f0a8061`](https://github.com/eea/volto-cca-policy/commit/f0a8061c275c236deb00087c23fac9860a073106)]
2395
2429
 
2396
2430
  #### :hammer_and_wrench: Others
@@ -2407,6 +2441,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2407
2441
  - Refs #259267 - jenkins test [Tripon Eugen - [`cacd31e`](https://github.com/eea/volto-cca-policy/commit/cacd31e7b1afe0983674ed5c7632d2e1d7fa752e)]
2408
2442
  - Refs #259267 - jenkins [Tripon Eugen - [`5b3affe`](https://github.com/eea/volto-cca-policy/commit/5b3affee8401239de10097884c1b7f2349d15ec0)]
2409
2443
  - Refs #259267 - add When, lead image and title to files [Tripon Eugen - [`2cedb23`](https://github.com/eea/volto-cca-policy/commit/2cedb237f898af9057e13fba94b615ef71077204)]
2444
+ - test: [JENKINS] Add cpu limit on cypress docker [valentinab25 - [`4d607a5`](https://github.com/eea/volto-cca-policy/commit/4d607a576e9d0a5c34e48c41b409e7df616ee3d6)]
2445
+ - test: [JENKINS] Increase shm-size to cypress docker [valentinab25 - [`b7f74d5`](https://github.com/eea/volto-cca-policy/commit/b7f74d53513a6edbfbca5cb6d19687929bb1e5db)]
2446
+ - test: [JENKINS] Improve cypress time [valentinab25 - [`db65617`](https://github.com/eea/volto-cca-policy/commit/db656173391f65157098d95d388c25f6429753d8)]
2410
2447
  - Refs #259267 - cca event blocks attachments and check not mandatoty fields [Tripon Eugen - [`3138e5a`](https://github.com/eea/volto-cca-policy/commit/3138e5afb5bfbdbed14e27ed457b16867b7fa414)]
2411
2448
  - Refs #256681 - Fix error in CCA Event view menu. ([React Intl] An id must be provided to format a message.) [GhitaB - [`517eeb8`](https://github.com/eea/volto-cca-policy/commit/517eeb817264a47bbfd6b9b7d22aaf22d44ed224)]
2412
2449
  - Refs #161485 - Fix ECDE name conflict. [GhitaB - [`8bfd99f`](https://github.com/eea/volto-cca-policy/commit/8bfd99ff68bb82a04d1c0ed625fa514fcf46289e)]
@@ -2623,6 +2660,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2623
2660
 
2624
2661
  #### :house: Internal changes
2625
2662
 
2663
+ - chore: [JENKINS] Remove alpha testing version [valentinab25 - [`ad1ced0`](https://github.com/eea/volto-cca-policy/commit/ad1ced0971ba116c13a3b5fcc039172cc915c919)]
2626
2664
 
2627
2665
  #### :hammer_and_wrench: Others
2628
2666
 
@@ -3103,6 +3141,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
3103
3141
  #### :hammer_and_wrench: Others
3104
3142
 
3105
3143
  - Refs #158294 - Update supported languages list. [GhitaB - [`0a4f91f`](https://github.com/eea/volto-cca-policy/commit/0a4f91f39b7edc367bd4c127d6a8f273c7788361)]
3144
+ - Add Sonarqube tag using cca-frontend addons list [EEA Jenkins - [`8f1f9ce`](https://github.com/eea/volto-cca-policy/commit/8f1f9ce6c22805670cc0800d3c779b6d619d0f31)]
3106
3145
  ### [0.1.1](https://github.com/eea/volto-cca-policy/compare/0.1.0...0.1.1) - 13 December 2022
3107
3146
 
3108
3147
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1,35 +1,14 @@
1
- import React from 'react';
2
1
  import { MemoryRouter } from 'react-router-dom';
3
2
  import configureStore from 'redux-mock-store';
4
- import renderer from 'react-test-renderer';
5
- import '@testing-library/jest-dom/extend-expect';
3
+ import { render, screen } from '@testing-library/react';
4
+ import '@testing-library/jest-dom';
6
5
  import { Provider } from 'react-intl-redux';
7
6
  import DropdownListingView from './DropdownListingView';
8
- import config from '@plone/volto/registry';
9
-
10
- config.blocks = {
11
- blocksConfig: {
12
- contentLinks: {
13
- variations: [
14
- {
15
- id: 'default',
16
- title: 'Simple list (default)',
17
- isDefault: true,
18
- },
19
- {
20
- id: 'navigationList',
21
- title: 'Navigation list',
22
- isDefault: false,
23
- },
24
- ],
25
- },
26
- },
27
- };
28
7
 
29
8
  const mockStore = configureStore();
30
9
 
31
10
  describe('DropdownListingView', () => {
32
- it('should render the component', () => {
11
+ it('renders dropdown with items', () => {
33
12
  const data = {
34
13
  '@type': 'listing',
35
14
  items: [
@@ -54,14 +33,18 @@ describe('DropdownListingView', () => {
54
33
  },
55
34
  });
56
35
 
57
- const component = renderer.create(
36
+ render(
58
37
  <Provider store={store}>
59
38
  <MemoryRouter>
60
39
  <DropdownListingView {...data} />
61
40
  </MemoryRouter>
62
41
  </Provider>,
63
42
  );
64
- const json = component.toJSON();
65
- expect(json).toMatchSnapshot();
43
+
44
+ // Check the placeholder text is present
45
+ expect(screen.getByText('Select')).toBeInTheDocument();
46
+
47
+ expect(screen.getAllByText('Item 1').length).toBeGreaterThan(0);
48
+ expect(screen.getAllByText('Item 2').length).toBeGreaterThan(0);
66
49
  });
67
50
  });
@@ -0,0 +1,123 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useSelector } from 'react-redux';
5
+ import { Icon, Accordion, Button } from 'semantic-ui-react';
6
+ import { ConditionalLink } from '@plone/volto/components';
7
+ import { When } from '@plone/volto/components/theme/View/EventDatesInfo';
8
+
9
+ import './styles.less';
10
+
11
+ function formatDateRange(startStr, endStr, locale = 'en') {
12
+ if (!startStr || !endStr) return null;
13
+
14
+ const startDate = new Date(startStr);
15
+ const endDate = new Date(endStr);
16
+
17
+ if (isNaN(startDate) || isNaN(endDate)) return null;
18
+
19
+ const sameDay = startDate.toDateString() === endDate.toDateString();
20
+ const sameMonth = startDate.getMonth() === endDate.getMonth();
21
+ const sameYear = startDate.getFullYear() === endDate.getFullYear();
22
+
23
+ const day = (date) => date.toLocaleDateString(locale, { day: 'numeric' });
24
+
25
+ const month = (date) => date.toLocaleDateString(locale, { month: 'long' });
26
+
27
+ const year = (date) => date.toLocaleDateString(locale, { year: 'numeric' });
28
+
29
+ if (sameDay) {
30
+ return `${day(startDate)} ${month(startDate)} ${year(startDate)}`;
31
+ }
32
+
33
+ if (sameMonth && sameYear) {
34
+ return `${day(startDate)} - ${day(endDate)} ${month(endDate)} ${year(
35
+ endDate,
36
+ )}`;
37
+ }
38
+
39
+ if (sameYear) {
40
+ return `${day(startDate)} ${month(startDate)} - ${day(endDate)} ${month(
41
+ endDate,
42
+ )} ${year(endDate)}`;
43
+ }
44
+
45
+ // Different years
46
+ return `${day(startDate)} ${month(startDate)} ${year(startDate)} – ${day(
47
+ endDate,
48
+ )} ${month(endDate)} ${year(endDate)}`;
49
+ }
50
+
51
+ const EventAccordionListingView = ({ items, isEditMode }) => {
52
+ const currentLang = useSelector((state) => state.intl.locale);
53
+ const [activeIndex, setActiveIndex] = React.useState(-1);
54
+ const handleAccordionClick = (index) => {
55
+ setActiveIndex(activeIndex === index ? -1 : index);
56
+ };
57
+
58
+ return (
59
+ <div className="ui fluid event-accordion-view">
60
+ <Accordion className="primary">
61
+ {items.map((item, index) => {
62
+ const formattedDate = formatDateRange(
63
+ item.start,
64
+ item.end,
65
+ currentLang,
66
+ );
67
+ return (
68
+ <React.Fragment key={item.UID}>
69
+ <Accordion.Title
70
+ active={activeIndex === index}
71
+ onClick={() => handleAccordionClick(index)}
72
+ >
73
+ <Icon
74
+ className={
75
+ activeIndex === index
76
+ ? 'ri-arrow-up-s-line'
77
+ : 'ri-arrow-down-s-line'
78
+ }
79
+ />
80
+ <div>
81
+ <div className="listing-header">
82
+ {item?.start && <span>{formattedDate}</span>}
83
+ {item.location && <> - {item.location}</>}
84
+ </div>
85
+ <div>{item.title}</div>
86
+ </div>
87
+ </Accordion.Title>
88
+ <Accordion.Content active={activeIndex === index}>
89
+ {item?.description && (
90
+ <p className="listing-description">{item.description}</p>
91
+ )}
92
+
93
+ <div className="bottom-info">
94
+ <ConditionalLink item={item} condition={!isEditMode}>
95
+ <Button inverted primary>
96
+ <FormattedMessage
97
+ id="Learn more"
98
+ defaultMessage="Learn more"
99
+ />
100
+ </Button>
101
+ </ConditionalLink>
102
+ <When
103
+ start={item.start}
104
+ end={item.end}
105
+ whole_day={item.whole_day}
106
+ open_end={item.open_end}
107
+ />
108
+ </div>
109
+ </Accordion.Content>
110
+ </React.Fragment>
111
+ );
112
+ })}
113
+ </Accordion>
114
+ </div>
115
+ );
116
+ };
117
+
118
+ EventAccordionListingView.propTypes = {
119
+ items: PropTypes.arrayOf(PropTypes.any).isRequired,
120
+ isEditMode: PropTypes.bool,
121
+ };
122
+
123
+ export default EventAccordionListingView;
@@ -0,0 +1,101 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import configureStore from 'redux-mock-store';
5
+ import { Provider } from 'react-intl-redux';
6
+ import EventAccordionListingView from './EventAccordionListingView';
7
+
8
+ const mockStore = configureStore();
9
+
10
+ jest.mock('@plone/volto/components', () => ({
11
+ ConditionalLink: ({ children }) => <div>{children}</div>,
12
+ }));
13
+
14
+ jest.mock('@plone/volto/components/theme/View/EventDatesInfo', () => ({
15
+ When: () => <div>Event Date Info</div>,
16
+ }));
17
+
18
+ describe('EventAccordionListingView', () => {
19
+ let store;
20
+ const items = [
21
+ {
22
+ UID: 'event-123',
23
+ title: 'Test Event One',
24
+ description: 'Details about Event One',
25
+ start: '2025-05-20T10:00:00',
26
+ end: '2025-05-20T12:00:00',
27
+ location: 'Hall A',
28
+ whole_day: false,
29
+ open_end: false,
30
+ '@id': '/event-1',
31
+ },
32
+ {
33
+ UID: 'event-456',
34
+ title: 'Test Event Two',
35
+ description: 'Details about Event Two',
36
+ start: '2025-06-10T09:00:00',
37
+ end: '2025-06-10T11:00:00',
38
+ location: 'Room B',
39
+ whole_day: false,
40
+ open_end: false,
41
+ '@id': '/event-2',
42
+ },
43
+ ];
44
+
45
+ beforeEach(() => {
46
+ store = mockStore({
47
+ intl: {
48
+ locale: 'en',
49
+ messages: {},
50
+ },
51
+ });
52
+ });
53
+
54
+ it('renders all event titles', () => {
55
+ render(
56
+ <Provider store={store}>
57
+ <MemoryRouter>
58
+ <EventAccordionListingView items={items} isEditMode={false} />
59
+ </MemoryRouter>
60
+ </Provider>,
61
+ );
62
+
63
+ expect(screen.getByText('Test Event One')).toBeInTheDocument();
64
+ expect(screen.getByText('Test Event Two')).toBeInTheDocument();
65
+ });
66
+
67
+ it('toggles accordion content on title click', () => {
68
+ render(
69
+ <Provider store={store}>
70
+ <MemoryRouter>
71
+ <EventAccordionListingView items={items} isEditMode={false} />
72
+ </MemoryRouter>
73
+ </Provider>,
74
+ );
75
+
76
+ const title = screen.getByText('Test Event One');
77
+ expect(title.closest('.title')).toHaveClass('title');
78
+ expect(title.closest('.title')).not.toHaveClass('active');
79
+
80
+ fireEvent.click(title);
81
+
82
+ expect(title.closest('.title')).toHaveClass('active');
83
+
84
+ expect(screen.getByText('Details about Event One')).toBeInTheDocument();
85
+
86
+ fireEvent.click(title);
87
+ expect(title.closest('.title')).not.toHaveClass('active');
88
+ });
89
+
90
+ it('renders Learn more buttons', () => {
91
+ render(
92
+ <Provider store={store}>
93
+ <MemoryRouter>
94
+ <EventAccordionListingView items={items} isEditMode={false} />
95
+ </MemoryRouter>
96
+ </Provider>,
97
+ );
98
+
99
+ expect(screen.getAllByText('Learn more')).toHaveLength(2);
100
+ });
101
+ });
@@ -2,6 +2,7 @@ import OrganisationCardsListingView from './OrganisationCardsListingView';
2
2
  import IndicatorCardsListingView from './IndicatorCardsListingView';
3
3
  import EventCardsListingView from './EventCardsListingView';
4
4
  import DropdownListingView from './DropdownListingView';
5
+ import EventAccordionListingView from './EventAccordionListingView';
5
6
 
6
7
  export default function installListing(config) {
7
8
  config.blocks.blocksConfig.listing = {
@@ -43,6 +44,13 @@ export default function installListing(config) {
43
44
  isDefault: false,
44
45
  fullobjects: true,
45
46
  },
47
+ {
48
+ id: 'eventAccordion',
49
+ title: 'Event Accordion',
50
+ template: EventAccordionListingView,
51
+ isDefault: false,
52
+ fullobjects: true,
53
+ },
46
54
  ],
47
55
  };
48
56
 
@@ -142,3 +142,22 @@ div.indicatorCards {
142
142
  }
143
143
  }
144
144
  }
145
+
146
+ .event-accordion-view {
147
+ .listing-header {
148
+ font-weight: bold;
149
+ }
150
+
151
+ .bottom-info {
152
+ display: flex;
153
+ flex-wrap: wrap;
154
+ align-items: center;
155
+ justify-content: flex-start;
156
+ margin-top: 2em;
157
+ gap: 1em;
158
+
159
+ p {
160
+ margin: 0;
161
+ }
162
+ }
163
+ }
package/src/constants.js CHANGED
@@ -6,6 +6,7 @@ export const download_fields = [
6
6
  { field: 'issued', name: 'Publication Date' },
7
7
  { field: 'creators', name: 'Creator' },
8
8
  { field: 'objectProvides', name: 'Content type' },
9
+ { field: 'fulltext', name: 'Description' },
9
10
  { field: 'cca_keywords', name: 'Keywords' },
10
11
  { field: 'cca_adaptation_sectors', name: 'Sectors' },
11
12
  { field: 'cca_climate_impacts', name: 'Climate impact' },
@@ -17,7 +18,6 @@ export const download_fields = [
17
18
  { field: 'cca_origin_websites', name: 'Origin website' },
18
19
  { field: 'cca_health_impacts', name: 'Health impacts' },
19
20
  { field: 'cca_partner_contributors', name: 'Observatory impacts' },
20
- // { field: 'fulltext', name: 'Description' },
21
21
  ];
22
22
 
23
23
  export const eea_languages = [
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Footer component.
3
+ * @module components/theme/Footer/Footer
4
+ */
5
+
6
+ import React from 'react';
7
+ import { useSelector, shallowEqual } from 'react-redux';
8
+ import { flattenToAppURL } from '@plone/volto/helpers';
9
+ import EEAFooter from '@eeacms/volto-eea-design-system/ui/Footer/Footer';
10
+ import config from '@plone/volto/registry';
11
+ import isArray from 'lodash/isArray';
12
+
13
+ const Footer = () => {
14
+ const { eea } = config.settings;
15
+ const {
16
+ footerActions,
17
+ copyrightActions,
18
+ socialActions,
19
+ contactActions,
20
+ contactExtraActions,
21
+ } = useSelector(
22
+ (state) => ({
23
+ footerActions: state.actions?.actions?.footer_actions,
24
+ copyrightActions: state.actions?.actions?.copyright_actions,
25
+ socialActions: state.actions?.actions?.social_actions,
26
+ contactActions: state.actions?.actions?.contact_actions,
27
+ contactExtraActions: state.actions?.actions?.contact_extra_actions,
28
+ }),
29
+ shallowEqual,
30
+ );
31
+ // ZMI > portal_actions > footer_actions
32
+ const actions = isArray(footerActions)
33
+ ? footerActions.map((action) => ({
34
+ title: action.title,
35
+ url: flattenToAppURL(action.url),
36
+ }))
37
+ : eea.footerOpts.actions;
38
+
39
+ // ZMI > portal_actions > copyright_actions
40
+ const copyright = isArray(copyrightActions)
41
+ ? copyrightActions.map((action) => ({
42
+ title: action.title,
43
+ site: action.title,
44
+ url: flattenToAppURL(action.url),
45
+ }))
46
+ : eea.footerOpts.copyright;
47
+
48
+ // ZMI > portal_actions > social_actions
49
+ const social = isArray(socialActions)
50
+ ? socialActions.map((action) => ({
51
+ name: action.id,
52
+ icon: action.icon,
53
+ url: action.url,
54
+ }))
55
+ : eea.footerOpts.social;
56
+
57
+ // ZMI > portal_actions > contact_actions
58
+ const contacts = isArray(contactActions)
59
+ ? contactActions.map((action, idx) => ({
60
+ text: action.title,
61
+ icon: action.icon,
62
+ url: flattenToAppURL(action.url),
63
+ children:
64
+ idx === 0
65
+ ? (contactExtraActions || []).map((child) => ({
66
+ text: child.title,
67
+ icon: child.icon,
68
+ url: flattenToAppURL(child.url),
69
+ }))
70
+ : [],
71
+ }))
72
+ : eea.footerOpts.contacts;
73
+
74
+ // Update options with actions from backend
75
+ const options = {
76
+ ...eea.footerOpts,
77
+ social,
78
+ contacts,
79
+ };
80
+
81
+ return (
82
+ <EEAFooter>
83
+ <EEAFooter.Header>{eea.footerOpts.logosHeader}</EEAFooter.Header>
84
+ <EEAFooter.SubFooter {...options} />
85
+ <EEAFooter.Header>{eea.footerOpts.header}</EEAFooter.Header>
86
+ <EEAFooter.SitesButton
87
+ buttonName={eea.footerOpts.buttonName}
88
+ hrefButton={eea.footerOpts.hrefButton}
89
+ />
90
+ <EEAFooter.Social {...options} />
91
+ <EEAFooter.Actions actions={actions} copyright={copyright} />
92
+ </EEAFooter>
93
+ );
94
+ };
95
+
96
+ export default Footer;
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Html helper.
3
+ * @module helpers/Html
4
+ */
5
+
6
+ import React, { Component } from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
9
+ import serialize from 'serialize-javascript';
10
+ import { join } from 'lodash';
11
+ import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
12
+ import { runtimeConfig } from '@plone/volto/runtime_config';
13
+ import config from '@plone/volto/registry';
14
+
15
+ const CRITICAL_CSS_TEMPLATE = `function alter() {
16
+ document.querySelectorAll("head link[rel='prefetch']").forEach(function(el) { el.rel = 'stylesheet'});
17
+ }
18
+ if (window.addEventListener) {
19
+ window.addEventListener('DOMContentLoaded', alter, false)
20
+ } else {
21
+ window.onload=alter
22
+ }`;
23
+
24
+ export const loadReducers = (state = {}) => {
25
+ const { settings } = config;
26
+ return Object.assign(
27
+ {},
28
+ ...Object.keys(state).map((name) =>
29
+ settings.initialReducersBlacklist.includes(name)
30
+ ? {}
31
+ : { [name]: state[name] },
32
+ ),
33
+ );
34
+ };
35
+
36
+ /**
37
+ * Html class.
38
+ * Wrapper component containing HTML metadata and boilerplate tags.
39
+ * Used in server-side code only to wrap the string output of the
40
+ * rendered route component.
41
+ *
42
+ * The only thing this component doesn't (and can't) include is the
43
+ * HTML doctype declaration, which is added to the rendered output
44
+ * by the server.js file.
45
+ *
46
+ * Critical.css behaviour: when a file `public/critical.css` is present, the
47
+ * loading of stylesheets is changed. The styles in critical.css are inlined in
48
+ * the generated HTML, and the whole story needs to change completely: instead
49
+ * of treating stylesheets as priority for rendering, we want to defer their
50
+ * loading as much as possible. So we change the stylesheets to be prefetched
51
+ * and we switch their rel back to stylesheets at document ready event.
52
+ *
53
+ * @function Html
54
+ * @param {Object} props Component properties.
55
+ * @param {Object} props.assets Assets to be rendered.
56
+ * @param {Object} props.component Content to be rendered as child node.
57
+ * @param {Object} props.store Store object.
58
+ * @returns {string} Markup of the not found page.
59
+ */
60
+
61
+ /**
62
+ * Html class.
63
+ * @class Html
64
+ * @extends Component
65
+ */
66
+ class Html extends Component {
67
+ /**
68
+ * Property types.
69
+ * @property {Object} propTypes Property types.
70
+ * @static
71
+ */
72
+ static propTypes = {
73
+ extractor: PropTypes.shape({
74
+ getLinkElements: PropTypes.func.isRequired,
75
+ getScriptElements: PropTypes.func.isRequired,
76
+ getStyleElements: PropTypes.func.isRequired,
77
+ }).isRequired,
78
+ markup: PropTypes.string.isRequired,
79
+ store: PropTypes.shape({
80
+ getState: PropTypes.func,
81
+ }).isRequired,
82
+ nonce: PropTypes.string,
83
+ };
84
+
85
+ /**
86
+ * Render method.
87
+ * @method render
88
+ * @returns {string} Markup for the component.
89
+ */
90
+ render() {
91
+ const {
92
+ extractor,
93
+ markup,
94
+ store,
95
+ criticalCss,
96
+ apiPath,
97
+ publicURL,
98
+ nonce,
99
+ } = this.props;
100
+ const head = Helmet.rewind();
101
+ const bodyClass = join(BodyClass.rewind(), ' ');
102
+ const htmlAttributes = head.htmlAttributes.toComponent();
103
+
104
+ return (
105
+ <html lang={htmlAttributes.lang}>
106
+ <head>
107
+ <meta charSet="utf-8" />
108
+ {head.base.toComponent()}
109
+ {head.title.toComponent()}
110
+ {head.meta.toComponent()}
111
+ {head.link.toComponent()}
112
+ {head.script.toComponent()}
113
+
114
+ {React.createElement('script', {
115
+ nonce: nonce,
116
+ dangerouslySetInnerHTML: {
117
+ __html: `window.env = ${serialize(
118
+ {
119
+ ...runtimeConfig,
120
+ // Seamless mode requirement, the client need to know where the API is located
121
+ // if not set in the API_PATH
122
+ ...(apiPath && {
123
+ apiPath,
124
+ }),
125
+ ...(publicURL && {
126
+ publicURL,
127
+ }),
128
+ },
129
+ { space: 2 },
130
+ )};`,
131
+ },
132
+ })}
133
+
134
+ <link rel="icon" href="/favicon.ico" sizes="any" />
135
+ <link rel="icon" href="/icon.svg" type="image/svg+xml" />
136
+ <link
137
+ rel="apple-touch-icon"
138
+ sizes="180x180"
139
+ href="/apple-touch-icon.png"
140
+ />
141
+ <link rel="manifest" href="/site.webmanifest" />
142
+ <meta name="generator" content="Plone 6 - https://plone.org" />
143
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
144
+ <meta name="apple-mobile-web-app-capable" content="yes" />
145
+ {process.env.NODE_ENV === 'production' && criticalCss && (
146
+ <style
147
+ dangerouslySetInnerHTML={{ __html: this.props.criticalCss }}
148
+ />
149
+ )}
150
+ {/* Add the crossorigin while in development */}
151
+ {extractor.getLinkElements().map((elem) =>
152
+ React.cloneElement(elem, {
153
+ crossOrigin:
154
+ process.env.NODE_ENV === 'production' ? undefined : 'true',
155
+ rel: !criticalCss
156
+ ? elem.props.rel
157
+ : elem.props.as === 'style'
158
+ ? 'prefetch'
159
+ : elem.props.rel,
160
+ }),
161
+ )}
162
+ {/* Styles in development are loaded with Webpack's style-loader, in production,
163
+ they need to be static*/}
164
+ {process.env.NODE_ENV === 'production' ? (
165
+ criticalCss ? (
166
+ <>
167
+ <script
168
+ dangerouslySetInnerHTML={{
169
+ __html: CRITICAL_CSS_TEMPLATE,
170
+ }}
171
+ ></script>
172
+ {extractor.getStyleElements().map((elem) => (
173
+ <noscript>
174
+ {React.cloneElement(elem, {
175
+ rel: 'stylesheet',
176
+ crossOrigin:
177
+ process.env.NODE_ENV === 'production'
178
+ ? undefined
179
+ : 'true',
180
+ })}
181
+ </noscript>
182
+ ))}
183
+ </>
184
+ ) : (
185
+ extractor.getStyleElements()
186
+ )
187
+ ) : undefined}
188
+ </head>
189
+ <body className={bodyClass}>
190
+ <div role="navigation" aria-label="Toolbar" id="toolbar" />
191
+ <div id="main" dangerouslySetInnerHTML={{ __html: markup }} />
192
+ <div role="complementary" aria-label="Sidebar" id="sidebar" />
193
+ {React.createElement('script', {
194
+ nonce: nonce,
195
+ dangerouslySetInnerHTML: {
196
+ __html: `window.__data=${serialize(
197
+ loadReducers(store.getState()),
198
+ { space: 2 },
199
+ )};`,
200
+ },
201
+ charSet: 'UTF-8',
202
+ })}
203
+ {/* Add the crossorigin while in development */}
204
+ {this.props.extractScripts !== false
205
+ ? extractor.getScriptElements().map((elem) =>
206
+ React.cloneElement(elem, {
207
+ nonce: nonce,
208
+ crossOrigin:
209
+ process.env.NODE_ENV === 'production' ? undefined : 'true',
210
+ }),
211
+ )
212
+ : ''}
213
+ </body>
214
+ </html>
215
+ );
216
+ }
217
+ }
218
+
219
+ export default Html;
@@ -0,0 +1,3 @@
1
+ Customized for CSP support. Copy of https://github.com/plone/volto/blob/8b0f2af98ce0362f6975ce9c50137f6bd7cc3bb8/src/helpers/Html/Html.jsx Volto 17.x.x branch
2
+
3
+ Added formatting of json serialized strings
@@ -20,6 +20,7 @@ import { resetServerContext } from 'react-beautiful-dnd';
20
20
  import { CookiesProvider } from 'react-cookie';
21
21
  import cookiesMiddleware from 'universal-cookie-express';
22
22
  import debug from 'debug';
23
+ // import crypto from 'crypto';
23
24
 
24
25
  import routes from '@root/routes';
25
26
  import config from '@plone/volto/registry';
@@ -64,6 +65,18 @@ if (config.settings) {
64
65
  });
65
66
  }
66
67
 
68
+ // function buildCSPHeader(opts, nonce) {
69
+ // return Object.keys(opts)
70
+ // .sort()
71
+ // .reduce((acc, key) => {
72
+ // return [
73
+ // ...acc,
74
+ // `${key} ${opts[key].replaceAll('{nonce}', `'nonce-${nonce}'`)}`,
75
+ // ];
76
+ // }, [])
77
+ // .join('; ');
78
+ // }
79
+
67
80
  function reactIntlErrorHandler(error) {
68
81
  debug('i18n')(error);
69
82
  }
@@ -71,6 +84,7 @@ function reactIntlErrorHandler(error) {
71
84
  const supported = new locale.Locales(keys(languages), 'en');
72
85
 
73
86
  const server = express()
87
+ .set('etag', false)
74
88
  .disable('x-powered-by')
75
89
  .head('/*', function (req, res) {
76
90
  // Support for HEAD requests. Required by start-test utility in CI.
package/src/index.js CHANGED
@@ -158,6 +158,10 @@ const applyConfig = (config) => {
158
158
  ],
159
159
  footerOpts: {
160
160
  ...(config.settings.eea?.footerOpts || {}),
161
+ buttonName: 'Explore our environmental information systems',
162
+ hrefButton: 'https://www.eea.europa.eu/en/information-systems#',
163
+ logosHeader: 'Managed by',
164
+ header: '',
161
165
  description:
162
166
  'The European Climate Adaptation Platform Climate-ADAPT is a partnership between the European Commission and the European Environment Agency.',
163
167
  managedBy: [
@@ -551,6 +555,13 @@ const applyConfig = (config) => {
551
555
  ...config.settings.storeExtenders,
552
556
  ];
553
557
 
558
+ config.settings.initialReducersBlacklist = [
559
+ ...config.settings.initialReducersBlacklist,
560
+ 'intl',
561
+ // 'router',
562
+ // 'content',
563
+ ];
564
+
554
565
  config.widgets.vocabulary[
555
566
  'plone.app.vocabularies.Users'
556
567
  ] = SelectAutoCompleteWidget;
@@ -63,7 +63,6 @@ cca_build_runtime_mappings['op_cluster'] = {
63
63
 
64
64
  export default function installMainSearch(config) {
65
65
  const envConfig = ccaConfig;
66
-
67
66
  const pjson = require('@eeacms/volto-cca-policy/../package.json');
68
67
 
69
68
  envConfig.app_name = pjson.name;
@@ -221,7 +220,17 @@ export default function installMainSearch(config) {
221
220
  config.searchui.ccaSearch.host =
222
221
  process.env.RAZZLE_ES_PROXY_ADDR || getClientProxyAddress();
223
222
  }
223
+ let fieldsMatchAll = [
224
+ 'cca_climate_impacts.keyword',
225
+ 'cca_adaptation_sectors.keyword',
226
+ 'cca_adaptation_elements.keyword',
227
+ 'cca_key_type_measure.keyword',
228
+ ];
229
+ config.searchui.ccaSearch.facets.forEach((facet) => {
230
+ if (fieldsMatchAll.includes(facet.field)) {
231
+ facet.filterType = 'all';
232
+ }
233
+ });
224
234
 
225
- // console.log(config);
226
235
  return config;
227
236
  }
@@ -62,7 +62,7 @@ export const cca_adaptation_sectors = {
62
62
  factory: 'MultiTermFacet',
63
63
  label: messages.adaptationSectors,
64
64
  showInFacetsList: true,
65
- filterType: 'any',
65
+ filterType: 'all',
66
66
  isFilterable: false,
67
67
  show: 10000,
68
68
  isMulti: true,
@@ -128,8 +128,10 @@ body.subsite-mkh {
128
128
  }
129
129
 
130
130
  .funding-sources {
131
- display: flex;
132
- gap: 0.5em;
131
+ span {
132
+ float: left;
133
+ margin-right: 5px;
134
+ }
133
135
  }
134
136
 
135
137
  .section-wrapper-info {
@@ -251,6 +251,16 @@ iframe {
251
251
  }
252
252
  }
253
253
 
254
+ #footer {
255
+ .theme-sites {
256
+ padding: 3rem 0 ;
257
+ }
258
+
259
+ .footer-header {
260
+ margin-bottom: 1.5em;
261
+ }
262
+ }
263
+
254
264
  #mega-menu {
255
265
  .ui.list.has--4--columns {
256
266
  a.item {
@@ -303,10 +313,6 @@ iframe {
303
313
  padding-left: 0.8em;
304
314
  min-width: 10em;
305
315
  }
306
-
307
- .noneu-langs-label {
308
- // border-bottom: 1px solid black;
309
- }
310
316
  }
311
317
  }
312
318
  }