@eeacms/volto-cca-policy 0.3.42 → 0.3.44

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,41 @@ 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.44](https://github.com/eea/volto-cca-policy/compare/0.3.43...0.3.44) - 16 May 2025
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix(mission): fix actions fallback [kreafox - [`4868c71`](https://github.com/eea/volto-cca-policy/commit/4868c71eb3658b0eaac7946274fb8b4d5cd3c698)]
12
+ - fix(mission): default result to empty object [kreafox - [`1a4adbd`](https://github.com/eea/volto-cca-policy/commit/1a4adbd8203c36fb51be6060403e5ae683f1a47c)]
13
+
14
+ #### :nail_care: Enhancements
15
+
16
+ - change(mission): small update [kreafox - [`8314492`](https://github.com/eea/volto-cca-policy/commit/8314492b334a1ab523b1bd10497da1806460282d)]
17
+ - change(mission): update data for Assessment section - refs #286757 [kreafox - [`eeede94`](https://github.com/eea/volto-cca-policy/commit/eeede943d1f87e66d8688acabb4be051e37cea10)]
18
+ - change(mission): add fallback when results are empty [kreafox - [`4316e12`](https://github.com/eea/volto-cca-policy/commit/4316e122fb1f89582a70e8c5f33b57583d275001)]
19
+
20
+ #### :hammer_and_wrench: Others
21
+
22
+ - Show different message [Tiberiu Ichim - [`714bda0`](https://github.com/eea/volto-cca-policy/commit/714bda05dc7e8b5738a6bbd4a9b493e637e8b142)]
23
+ ### [0.3.43](https://github.com/eea/volto-cca-policy/compare/0.3.42...0.3.43) - 16 May 2025
24
+
25
+ #### :bug: Bug Fixes
26
+
27
+ - fix: update formatTextToHTML - refs #287671 [kreafox - [`79e5faf`](https://github.com/eea/volto-cca-policy/commit/79e5fafa7b2ba45405284b5f61865cb841c06e25)]
28
+
29
+ #### :nail_care: Enhancements
30
+
31
+ - change(mission): add fallback when action is empty - refs #286863 [kreafox - [`c99d5ea`](https://github.com/eea/volto-cca-policy/commit/c99d5ea1cf73796639585dca5fb2c524ac9bf91f)]
32
+ - change: update templates & tests [kreafox - [`57b9083`](https://github.com/eea/volto-cca-policy/commit/57b90839efb13377b47c53127596d88cb96b3246)]
33
+ - change(mission): add country subtitle - refs #287773 [kreafox - [`a8d07b7`](https://github.com/eea/volto-cca-policy/commit/a8d07b72cf71194bbbe5ea0559a3e8cf72ca221d)]
34
+
35
+ #### :house: Internal changes
36
+
37
+ - style(mission): adjust font size - refs #287671 [kreafox - [`50645b7`](https://github.com/eea/volto-cca-policy/commit/50645b75ae7d204a1cd47a78c3caa10bd3f63499)]
38
+
39
+ #### :hammer_and_wrench: Others
40
+
41
+ - No console.log [Tiberiu Ichim - [`6479dcc`](https://github.com/eea/volto-cca-policy/commit/6479dcc3a0f134d81fd4c3407692fa1d839a7093)]
7
42
  ### [0.3.42](https://github.com/eea/volto-cca-policy/compare/0.3.41...0.3.42) - 15 May 2025
8
43
 
9
44
  #### :rocket: Dependency updates
@@ -2352,13 +2387,10 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2352
2387
  - Refs #260715 rast-block wip [Tripon Eugen - [`f19d54e`](https://github.com/eea/volto-cca-policy/commit/f19d54e0b9a6a86bf344eb85b6a1cda7f3de91bf)]
2353
2388
  - Refs #260715 rast-block wip [Tripon Eugen - [`2828537`](https://github.com/eea/volto-cca-policy/commit/2828537b6c084cd1a82162d552fb4ef025b71f9f)]
2354
2389
  - Refs #260715 rast-block updates [Tripon Eugen - [`1e803e5`](https://github.com/eea/volto-cca-policy/commit/1e803e5bd3d3fb7558f261c76c68866be7beb8b5)]
2355
- - test: [JENKINS] Use java17 for sonarqube scanner [valentinab25 - [`0a15e1b`](https://github.com/eea/volto-cca-policy/commit/0a15e1b2ad081233685e80d5b3c60a8663f6b896)]
2356
- - test: [JENKINS] Run cypress in started frontend container [valentinab25 - [`9554e44`](https://github.com/eea/volto-cca-policy/commit/9554e44c92a621a52b2adb5a4830fb084ee5734b)]
2357
2390
  ### [0.1.49](https://github.com/eea/volto-cca-policy/compare/0.1.48...0.1.49) - 15 November 2023
2358
2391
 
2359
2392
  #### :house: Internal changes
2360
2393
 
2361
- - chore: [JENKINS] Refactor automated testing [valentinab25 - [`7b820a6`](https://github.com/eea/volto-cca-policy/commit/7b820a6369c2ddd5203b1a4abe352cb4bb43db7a)]
2362
2394
  - chore: husky, lint-staged use fixed versions [valentinab25 - [`f0a8061`](https://github.com/eea/volto-cca-policy/commit/f0a8061c275c236deb00087c23fac9860a073106)]
2363
2395
 
2364
2396
  #### :hammer_and_wrench: Others
@@ -2375,9 +2407,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2375
2407
  - Refs #259267 - jenkins test [Tripon Eugen - [`cacd31e`](https://github.com/eea/volto-cca-policy/commit/cacd31e7b1afe0983674ed5c7632d2e1d7fa752e)]
2376
2408
  - Refs #259267 - jenkins [Tripon Eugen - [`5b3affe`](https://github.com/eea/volto-cca-policy/commit/5b3affee8401239de10097884c1b7f2349d15ec0)]
2377
2409
  - Refs #259267 - add When, lead image and title to files [Tripon Eugen - [`2cedb23`](https://github.com/eea/volto-cca-policy/commit/2cedb237f898af9057e13fba94b615ef71077204)]
2378
- - test: [JENKINS] Add cpu limit on cypress docker [valentinab25 - [`4d607a5`](https://github.com/eea/volto-cca-policy/commit/4d607a576e9d0a5c34e48c41b409e7df616ee3d6)]
2379
- - test: [JENKINS] Increase shm-size to cypress docker [valentinab25 - [`b7f74d5`](https://github.com/eea/volto-cca-policy/commit/b7f74d53513a6edbfbca5cb6d19687929bb1e5db)]
2380
- - test: [JENKINS] Improve cypress time [valentinab25 - [`db65617`](https://github.com/eea/volto-cca-policy/commit/db656173391f65157098d95d388c25f6429753d8)]
2381
2410
  - Refs #259267 - cca event blocks attachments and check not mandatoty fields [Tripon Eugen - [`3138e5a`](https://github.com/eea/volto-cca-policy/commit/3138e5afb5bfbdbed14e27ed457b16867b7fa414)]
2382
2411
  - 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)]
2383
2412
  - Refs #161485 - Fix ECDE name conflict. [GhitaB - [`8bfd99f`](https://github.com/eea/volto-cca-policy/commit/8bfd99ff68bb82a04d1c0ed625fa514fcf46289e)]
@@ -2594,7 +2623,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2594
2623
 
2595
2624
  #### :house: Internal changes
2596
2625
 
2597
- - chore: [JENKINS] Remove alpha testing version [valentinab25 - [`ad1ced0`](https://github.com/eea/volto-cca-policy/commit/ad1ced0971ba116c13a3b5fcc039172cc915c919)]
2598
2626
 
2599
2627
  #### :hammer_and_wrench: Others
2600
2628
 
@@ -3075,7 +3103,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
3075
3103
  #### :hammer_and_wrench: Others
3076
3104
 
3077
3105
  - Refs #158294 - Update supported languages list. [GhitaB - [`0a4f91f`](https://github.com/eea/volto-cca-policy/commit/0a4f91f39b7edc367bd4c127d6a8f273c7788361)]
3078
- - Add Sonarqube tag using cca-frontend addons list [EEA Jenkins - [`8f1f9ce`](https://github.com/eea/volto-cca-policy/commit/8f1f9ce6c22805670cc0800d3c779b6d619d0f31)]
3079
3106
  ### [0.1.1](https://github.com/eea/volto-cca-policy/compare/0.1.0...0.1.1) - 13 December 2022
3080
3107
 
3081
3108
  #### :hammer_and_wrench: Others
package/out.diff ADDED
@@ -0,0 +1,55 @@
1
+ diff --git a/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.js b/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.js
2
+ index f1f256d8..85d0490e 100644
3
+ --- a/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.js
4
+ +++ b/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.js
5
+ @@ -72,7 +72,8 @@ export default function CaseStudyExplorerView(props) {
6
+ setActiveItems(activeItems);
7
+ }, [activeFilters, cases]);
8
+
9
+ - if (__SERVER__) return '';
10
+ + if (__SERVER__)
11
+ + return <div className="casestudy-explorer-map">CaseStudyExplorer</div>;
12
+
13
+ return (
14
+ <div className="casestudy-explorer-map">
15
+ diff --git a/src/components/theme/BannerTitle/BannerTitle.jsx b/src/components/theme/BannerTitle/BannerTitle.jsx
16
+ index 386eb9e1..22f1b255 100644
17
+ --- a/src/components/theme/BannerTitle/BannerTitle.jsx
18
+ +++ b/src/components/theme/BannerTitle/BannerTitle.jsx
19
+ @@ -21,8 +21,10 @@ const BannerTitle = (props) => {
20
+ const hasHomePageClass = bodyClasses.includes('homepage');
21
+ setHasBodyClass(hasHomePageClass);
22
+ }, []);
23
+ + const isChromeless =
24
+ + __CLIENT__ && window?.location?.search?.indexOf('chromeless=1') > -1;
25
+
26
+ - return isHomePage ? null : (
27
+ + return isHomePage || isChromeless ? null : (
28
+ <>
29
+ {!hasTitleBlock && !hasCountryFlagBlock ? (
30
+ <>
31
+ diff --git a/src/customizations/volto/components/theme/View/DefaultView.jsx b/src/customizations/volto/components/theme/View/DefaultView.jsx
32
+ index 90de6436..48ec77b6 100644
33
+ --- a/src/customizations/volto/components/theme/View/DefaultView.jsx
34
+ +++ b/src/customizations/volto/components/theme/View/DefaultView.jsx
35
+ @@ -85,7 +85,8 @@ const DefaultView = (props) => {
36
+ : 3;
37
+ const currentLang = useSelector((state) => state.intl.locale);
38
+
39
+ - // If the content is not yet loaded, then do not show anything
40
+ + const isChromeless = location.search?.indexOf('chromeless=1') > -1;
41
+ +
42
+ return contentLoaded ? (
43
+ hasBlocksData(content) ? (
44
+ <>
45
+ @@ -123,8 +124,8 @@ const DefaultView = (props) => {
46
+ </Grid>
47
+ </Container>
48
+ ) : (
49
+ - <Container id="page-document">
50
+ - <BannerTitle {...props} />
51
+ + <Container id="page-document" className="here">
52
+ + {!isChromeless && <BannerTitle {...props} />}
53
+ <RenderBlocks {...props} path={path} />
54
+ </Container>
55
+ )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "0.3.42",
3
+ "version": "0.3.44",
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",
@@ -72,7 +72,8 @@ export default function CaseStudyExplorerView(props) {
72
72
  setActiveItems(activeItems);
73
73
  }, [activeFilters, cases]);
74
74
 
75
- if (__SERVER__) return '';
75
+ if (__SERVER__)
76
+ return <div className="casestudy-explorer-map">Loading...</div>;
76
77
 
77
78
  return (
78
79
  <div className="casestudy-explorer-map">
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import '@testing-library/jest-dom';
3
2
  import { render, fireEvent } from '@testing-library/react';
4
3
  import AccordionList from './AccordionList';
@@ -1,47 +1,50 @@
1
1
  import React from 'react';
2
2
  import { Tab, Container, Divider } from 'semantic-ui-react';
3
- import { BannerTitle } from '@eeacms/volto-cca-policy/helpers';
3
+ import { formatTextToHTML } from '@eeacms/volto-cca-policy/utils';
4
+ import { BannerTitle, HTMLField } from '@eeacms/volto-cca-policy/helpers';
5
+
4
6
  import GovernanceTab from './TabSections/GovernanceTab';
5
7
  import AssessmentTab from './TabSections/AssessmentTab';
6
8
  import PlanningTab from './TabSections/PlanningTab';
7
9
  import ActionPagesTab from './TabSections/ActionPagesTab';
8
10
 
9
11
  const tabRenderers = {
10
- Governance_Label: (data) => <GovernanceTab result={data} />,
11
- Assessment_Label: (data) => <AssessmentTab result={data} />,
12
- Planning_Label: (data) => <PlanningTab result={data} />,
13
- Action_Label: (data) => <ActionPagesTab result={data} />,
12
+ Governance_Label: (props) => <GovernanceTab {...props} />,
13
+ Assessment_Label: (props) => <AssessmentTab {...props} />,
14
+ Planning_Label: (props) => <PlanningTab {...props} />,
15
+ Action_Label: (props) => <ActionPagesTab {...props} />,
14
16
  };
15
17
 
16
- const MissionSignatoryProfileView = (props) => {
17
- const { content } = props || {};
18
- const dataJson =
19
- props?.content?.['@components']?.missionsignatoryprofile || {};
18
+ const MissionSignatoryProfileView = ({ content }) => {
19
+ const signatoryData =
20
+ content?.['@components']?.missionsignatoryprofile?.result || {};
20
21
 
21
- const result = dataJson?.result || {};
22
- const governance = result?.governance?.[0] || {};
23
- const planning = result?.planning || {};
24
- const assessment = result?.assessment || {};
25
- const action = result?.action || {};
26
- const footer_text = result?.footer_text || {};
27
- const tab_labels = result?.tab_labels || [];
22
+ const {
23
+ governance = [{}],
24
+ planning = {},
25
+ assessment = {},
26
+ action = {},
27
+ footer_text = {},
28
+ tab_labels = [],
29
+ general_text = [{}],
30
+ } = signatoryData;
28
31
 
29
32
  const [activeIndex, setActiveIndex] = React.useState(0);
30
33
 
34
+ const tabData = {
35
+ Governance_Label: { result: governance[0], general_text: general_text[0] },
36
+ Assessment_Label: { result: assessment, general_text: general_text[0] },
37
+ Planning_Label: { result: planning, general_text: general_text[0] },
38
+ Action_Label: { result: action, general_text: general_text[0] },
39
+ };
40
+
31
41
  const panes = tab_labels
32
42
  .filter(({ key }) => key !== 'Language')
33
43
  .map(({ key, value }) => {
34
- const renderTab = tabRenderers[key];
35
- const dataMap = {
36
- Governance_Label: governance,
37
- Assessment_Label: assessment,
38
- Planning_Label: planning,
39
- Action_Label: action,
40
- };
41
-
44
+ const Renderer = tabRenderers[key];
42
45
  return {
43
46
  menuItem: value,
44
- render: () => (renderTab ? renderTab(dataMap[key]) : null),
47
+ render: () => (Renderer ? Renderer(tabData[key]) : null),
45
48
  };
46
49
  });
47
50
 
@@ -57,11 +60,12 @@ const MissionSignatoryProfileView = (props) => {
57
60
  hidePublishingDate: true,
58
61
  hideDownloadButton: false,
59
62
  hideShareButton: false,
63
+ subtitle: general_text?.[0]?.Country_Or_Area,
60
64
  }}
61
65
  />
66
+
62
67
  <div className="signatory-profile">
63
68
  <br />
64
-
65
69
  <Tab
66
70
  menu={{
67
71
  fluid: true,
@@ -75,11 +79,13 @@ const MissionSignatoryProfileView = (props) => {
75
79
  panes={panes}
76
80
  />
77
81
 
78
- {footer_text.Disclaimer && (
82
+ {footer_text?.Disclaimer && (
79
83
  <div className="footer-text">
80
84
  <Divider />
81
- <strong>{footer_text.Disclaimer_Title}</strong>
82
- <p>{footer_text.Disclaimer}</p>
85
+ <strong>{footer_text?.Disclaimer_Title}</strong>
86
+ <HTMLField
87
+ value={{ data: formatTextToHTML(footer_text.Disclaimer) }}
88
+ />
83
89
  </div>
84
90
  )}
85
91
  </div>
@@ -1,9 +1,7 @@
1
- import React from 'react';
2
1
  import { render, fireEvent, screen } from '@testing-library/react';
3
2
  import '@testing-library/jest-dom';
4
3
  import MissionSignatoryProfileView from './MissionSignatoryProfileView';
5
4
 
6
- // Mock the tab components with minimal placeholders
7
5
  jest.mock('./TabSections/GovernanceTab', () => () => (
8
6
  <div>Mocked Governance</div>
9
7
  ));
@@ -12,8 +10,12 @@ jest.mock('./TabSections/AssessmentTab', () => () => (
12
10
  ));
13
11
  jest.mock('./TabSections/PlanningTab', () => () => <div>Mocked Planning</div>);
14
12
  jest.mock('./TabSections/ActionPagesTab', () => () => <div>Mocked Action</div>);
13
+
15
14
  jest.mock('@eeacms/volto-cca-policy/helpers', () => ({
16
15
  BannerTitle: ({ children }) => <div>{children}</div>,
16
+ HTMLField: ({ value }) => (
17
+ <div dangerouslySetInnerHTML={{ __html: value?.data }} />
18
+ ),
17
19
  }));
18
20
 
19
21
  describe('MissionSignatoryProfileView', () => {
@@ -34,7 +36,7 @@ describe('MissionSignatoryProfileView', () => {
34
36
  { key: 'Assessment_Label', value: 'Assessment' },
35
37
  { key: 'Planning_Label', value: 'Planning & Target' },
36
38
  { key: 'Action_Label', value: 'Action' },
37
- { key: 'Language', value: 'en' }, // will be filtered out
39
+ { key: 'Language', value: 'en' },
38
40
  ],
39
41
  },
40
42
  },
@@ -70,6 +72,8 @@ describe('MissionSignatoryProfileView', () => {
70
72
  it('renders footer disclaimer text if present', () => {
71
73
  render(<MissionSignatoryProfileView content={content} />);
72
74
  expect(screen.getByText('Disclaimer Title')).toBeInTheDocument();
73
- expect(screen.getByText('This is a disclaimer.')).toBeInTheDocument();
75
+ expect(
76
+ screen.getByText((content) => content.includes('This is a disclaimer.')),
77
+ ).toBeInTheDocument();
74
78
  });
75
79
  });
@@ -4,6 +4,8 @@ import { HTMLField } from '@eeacms/volto-cca-policy/helpers';
4
4
  import { formatTextToHTML } from '@eeacms/volto-cca-policy/utils';
5
5
  import AccordionList from '../AccordionList';
6
6
 
7
+ const isEmpty = (arr) => !Array.isArray(arr) || arr.length === 0;
8
+
7
9
  const ActionsTabContent = ({ action }) => {
8
10
  const hasHazards = action?.Climate_Hazards?.length > 0;
9
11
  const hasSectors = action?.Sectors.length > 0;
@@ -51,21 +53,36 @@ const ActionsTabContent = ({ action }) => {
51
53
  {action.Funding_Sources && (
52
54
  <>
53
55
  <br />
54
- <p>
56
+ <div className="funding-sources">
55
57
  <span>{action.Funding_Sources_Label} </span>
56
- <strong>{action.Funding_Sources}</strong>
57
- </p>
58
+ <strong>
59
+ <HTMLField
60
+ value={{ data: formatTextToHTML(action.Funding_Sources) }}
61
+ />
62
+ </strong>
63
+ </div>
58
64
  </>
59
65
  )}
60
66
  </>
61
67
  );
62
68
  };
63
69
 
64
- const ActionPagesTab = ({ result }) => {
65
- const { Title, Abstract, Abstract_Line } = result.action_text?.[0] || [];
66
- const actions = result.actions || [];
70
+ const ActionPagesTab = ({ result, general_text }) => {
71
+ const { action_text, actions } = result || {};
72
+ const { No_Data_Reported_Label } = general_text || {};
73
+ const { Title, Abstract, Abstract_Line } = action_text?.[0] || {};
74
+
75
+ const sortedActions = [...(actions || [])].sort((a, b) => a.Order - b.Order);
76
+
77
+ const NoResults = isEmpty(action_text) && isEmpty(actions);
67
78
 
68
- const sortedActions = [...actions].sort((a, b) => a.Order - b.Order);
79
+ if (NoResults) {
80
+ return (
81
+ <Tab.Pane>
82
+ <p>{No_Data_Reported_Label}</p>
83
+ </Tab.Pane>
84
+ );
85
+ }
69
86
 
70
87
  return (
71
88
  <Tab.Pane>
@@ -77,11 +94,11 @@ const ActionPagesTab = ({ result }) => {
77
94
  </Callout>
78
95
  )}
79
96
 
80
- {sortedActions.map((action, index) => {
97
+ {sortedActions?.map((action, index) => {
81
98
  return (
82
99
  <div key={index} className="section-wrapper">
83
100
  <h5 className="section-title">
84
- <span className="section-number">{action.Order}. </span>
101
+ <span className="section-number">{action?.Order}. </span>
85
102
  <HTMLField value={{ data: formatTextToHTML(action?.Action) }} />
86
103
  </h5>
87
104
 
@@ -89,7 +106,7 @@ const ActionPagesTab = ({ result }) => {
89
106
  variation="secondary"
90
107
  accordions={[
91
108
  {
92
- title: action?.More_Details_Label || 'More details',
109
+ title: action?.More_Details_Label,
93
110
  content: <ActionsTabContent action={action} />,
94
111
  },
95
112
  ]}
@@ -1,9 +1,7 @@
1
- import React from 'react';
2
- import { render, within } from '@testing-library/react';
3
1
  import '@testing-library/jest-dom';
2
+ import { render, within } from '@testing-library/react';
4
3
  import ActionPagesTab from './ActionPagesTab';
5
4
 
6
- // Mocking components used inside
7
5
  jest.mock('@eeacms/volto-eea-design-system/ui', () => ({
8
6
  Callout: ({ children }) => <div>{children}</div>,
9
7
  }));
@@ -6,6 +6,8 @@ import AccordionList from '../AccordionList';
6
6
 
7
7
  import image from '@eeacms/volto-cca-policy/../theme//assets/images/image-narrow.svg';
8
8
 
9
+ const isEmpty = (arr) => !Array.isArray(arr) || arr.length === 0;
10
+
9
11
  const ItemsSection = ({ items }) => {
10
12
  if (!items?.length) return null;
11
13
 
@@ -45,7 +47,7 @@ const AssessmentAccordionContent = ({ result }) => {
45
47
  );
46
48
  };
47
49
 
48
- const AssessmentTab = ({ result }) => {
50
+ const AssessmentTab = ({ result, general_text }) => {
49
51
  const {
50
52
  Title,
51
53
  Subheading,
@@ -55,12 +57,26 @@ const AssessmentTab = ({ result }) => {
55
57
  Attachments,
56
58
  Hazards_Title,
57
59
  Hazards_Abstract,
58
- } = result.assessment_text?.[0] || [];
60
+ } = result.assessment_text?.[0] || {};
59
61
  const assessment_risks = result.assessment_risks || [];
60
- const assessment_sectors = result.assessment_sectors || [];
61
-
62
+ const assessment_hazards_sectors = result.assessment_hazards_sectors || [];
63
+ const { No_Data_Reported_Label } = general_text || {};
62
64
  // const [activeIndex, setActiveIndex] = React.useState(0);
63
65
 
66
+ const NoResults =
67
+ isEmpty(result.assessment_text) &&
68
+ isEmpty(result.assessment_factors) &&
69
+ isEmpty(result.assessment_risks) &&
70
+ isEmpty(result.assessment_hazards_sectors);
71
+
72
+ if (NoResults) {
73
+ return (
74
+ <Tab.Pane>
75
+ <p>{No_Data_Reported_Label}</p>
76
+ </Tab.Pane>
77
+ );
78
+ }
79
+
64
80
  return (
65
81
  <Tab.Pane>
66
82
  {Title && <h2>{Title}</h2>}
@@ -110,10 +126,10 @@ const AssessmentTab = ({ result }) => {
110
126
 
111
127
  <br />
112
128
 
113
- {assessment_sectors && (
129
+ {assessment_hazards_sectors && (
114
130
  <AccordionList
115
- accordions={assessment_sectors.map((category) => ({
116
- title: category.Category_Name,
131
+ accordions={assessment_hazards_sectors.map((category) => ({
132
+ title: category.Hazard,
117
133
  content: (
118
134
  <ul>
119
135
  {category.Sectors.map((sector, idx) => (
@@ -1,6 +1,5 @@
1
- import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
1
  import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
4
3
  import AssessmentTab from './AssessmentTab';
5
4
 
6
5
  jest.mock('@eeacms/volto-eea-design-system/ui', () => ({
@@ -5,7 +5,7 @@ import { formatTextToHTML } from '@eeacms/volto-cca-policy/utils';
5
5
  import AccordionList from '../AccordionList';
6
6
  import StatisticSection from '../StatisticSection';
7
7
 
8
- const GovernanceTab = ({ result }) => {
8
+ const GovernanceTab = ({ result, general_text }) => {
9
9
  const {
10
10
  Title,
11
11
  Introduction,
@@ -21,7 +21,8 @@ const GovernanceTab = ({ result }) => {
21
21
  Statistic_Population_Size_Label,
22
22
  Statistic_Population_Year,
23
23
  Statistic_Population_Year_Label,
24
- } = result;
24
+ } = result || {};
25
+ const { No_Data_Reported_Label } = general_text || {};
25
26
 
26
27
  const statisticData = [
27
28
  {
@@ -42,6 +43,14 @@ const GovernanceTab = ({ result }) => {
42
43
  },
43
44
  ].filter((stat) => stat.value && stat.label);
44
45
 
46
+ if (!result) {
47
+ return (
48
+ <Tab.Pane>
49
+ <p>{No_Data_Reported_Label}</p>
50
+ </Tab.Pane>
51
+ );
52
+ }
53
+
45
54
  return (
46
55
  <Tab.Pane>
47
56
  {Title && <h2>{Title}</h2>}
@@ -1,6 +1,5 @@
1
- import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
1
  import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
4
3
  import GovernanceTab from './GovernanceTab';
5
4
 
6
5
  describe('GovernanceTab', () => {
@@ -9,6 +9,8 @@ import {
9
9
  import AccordionList from '../AccordionList';
10
10
  import image from '@eeacms/volto-cca-policy/../theme/assets/images/image-narrow.svg';
11
11
 
12
+ const isEmpty = (arr) => !Array.isArray(arr) || arr.length === 0;
13
+
12
14
  const ItemsSection = ({ items }) => {
13
15
  if (!items?.length) return null;
14
16
 
@@ -69,12 +71,13 @@ const PlanningGoalContent = ({ goal }) => {
69
71
  );
70
72
  };
71
73
 
72
- const PlanningTab = ({ result }) => {
74
+ const PlanningTab = ({ result, general_text }) => {
73
75
  const {
74
76
  planning_goals = [],
75
77
  planning_titles = [],
76
78
  planning_climate_action = [],
77
79
  } = result || {};
80
+ const { No_Data_Reported_Label } = general_text || {};
78
81
 
79
82
  const titleData = planning_titles?.[0];
80
83
  const goalData = planning_goals?.[0];
@@ -85,6 +88,19 @@ const PlanningTab = ({ result }) => {
85
88
  return aNum - bNum;
86
89
  });
87
90
 
91
+ const NoResults =
92
+ isEmpty(planning_goals) &&
93
+ isEmpty(planning_titles) &&
94
+ isEmpty(planning_climate_action);
95
+
96
+ if (NoResults) {
97
+ return (
98
+ <Tab.Pane>
99
+ <p>{No_Data_Reported_Label}</p>
100
+ </Tab.Pane>
101
+ );
102
+ }
103
+
88
104
  return (
89
105
  <Tab.Pane>
90
106
  {titleData?.Title && <h2>{titleData.Title}</h2>}
@@ -1,6 +1,5 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
1
  import '@testing-library/jest-dom';
2
+ import { render } from '@testing-library/react';
4
3
  import PlanningTab from './PlanningTab';
5
4
 
6
5
  jest.mock('@eeacms/volto-cca-policy/helpers', () => ({
@@ -0,0 +1,342 @@
1
+ /**
2
+ * App container.
3
+ * @module components/theme/App/App
4
+ */
5
+
6
+ import React, { Component } from 'react';
7
+ import jwtDecode from 'jwt-decode';
8
+ import PropTypes from 'prop-types';
9
+ import { connect } from 'react-redux';
10
+ import { compose } from 'redux';
11
+ import { asyncConnect, Helmet } from '@plone/volto/helpers';
12
+ import { Segment } from 'semantic-ui-react';
13
+ import { renderRoutes } from 'react-router-config';
14
+ import { Slide, ToastContainer, toast } from 'react-toastify';
15
+ import split from 'lodash/split';
16
+ import join from 'lodash/join';
17
+ import trim from 'lodash/trim';
18
+ import cx from 'classnames';
19
+ import config from '@plone/volto/registry';
20
+ import { PluggablesProvider } from '@plone/volto/components/manage/Pluggable';
21
+ import { visitBlocks } from '@plone/volto/helpers/Blocks/Blocks';
22
+ import { injectIntl } from 'react-intl';
23
+
24
+ import Error from '@plone/volto/error';
25
+
26
+ import {
27
+ Breadcrumbs,
28
+ Footer,
29
+ Header,
30
+ Icon,
31
+ OutdatedBrowser,
32
+ AppExtras,
33
+ SkipLinks,
34
+ } from '@plone/volto/components';
35
+ import {
36
+ BodyClass,
37
+ getBaseUrl,
38
+ getView,
39
+ hasApiExpander,
40
+ isCmsUi,
41
+ } from '@plone/volto/helpers';
42
+ import {
43
+ getBreadcrumbs,
44
+ getContent,
45
+ getNavigation,
46
+ getTypes,
47
+ getWorkflow,
48
+ } from '@plone/volto/actions';
49
+
50
+ import clearSVG from '@plone/volto/icons/clear.svg';
51
+ import MultilingualRedirector from '@plone/volto/components/theme/MultilingualRedirector/MultilingualRedirector';
52
+ import WorkingCopyToastsFactory from '@plone/volto/components/manage/WorkingCopyToastsFactory/WorkingCopyToastsFactory';
53
+ import LockingToastsFactory from '@plone/volto/components/manage/LockingToastsFactory/LockingToastsFactory';
54
+
55
+ /**
56
+ * @export
57
+ * @class App
58
+ * @extends {Component}
59
+ */
60
+ export class App extends Component {
61
+ /**
62
+ * Property types.
63
+ * @property {Object} propTypes Property types.
64
+ * @static
65
+ */
66
+ static propTypes = {
67
+ pathname: PropTypes.string.isRequired,
68
+ };
69
+
70
+ state = {
71
+ hasError: false,
72
+ error: null,
73
+ errorInfo: null,
74
+ };
75
+
76
+ constructor(props) {
77
+ super(props);
78
+ this.mainRef = React.createRef();
79
+ }
80
+
81
+ /**
82
+ * @method componentWillReceiveProps
83
+ * @param {Object} nextProps Next properties
84
+ * @returns {undefined}
85
+ */
86
+ UNSAFE_componentWillReceiveProps(nextProps) {
87
+ if (nextProps.pathname !== this.props.pathname) {
88
+ if (this.state.hasError) {
89
+ this.setState({ hasError: false });
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * ComponentDidCatch
96
+ * @method ComponentDidCatch
97
+ * @param {string} error The error
98
+ * @param {string} info The info
99
+ * @returns {undefined}
100
+ */
101
+ componentDidCatch(error, info) {
102
+ this.setState({ hasError: true, error, errorInfo: info });
103
+ config.settings.errorHandlers.forEach((handler) => handler(error));
104
+ }
105
+
106
+ dispatchContentClick = (event) => {
107
+ if (event.target === event.currentTarget) {
108
+ const rect = this.mainRef.current.getBoundingClientRect();
109
+ if (event.clientY > rect.bottom) {
110
+ document.dispatchEvent(new Event('voltoClickBelowContent'));
111
+ }
112
+ }
113
+ };
114
+
115
+ /**
116
+ * Render method.
117
+ * @method render
118
+ * @returns {string} Markup for the component.
119
+ */
120
+ render() {
121
+ const { views } = config;
122
+ const path = getBaseUrl(this.props.pathname);
123
+ const action = getView(this.props.pathname);
124
+ const isCmsUI = isCmsUi(this.props.pathname);
125
+ const ConnectionRefusedView = views.errorViews.ECONNREFUSED;
126
+
127
+ const language =
128
+ this.props.content?.language?.token ?? this.props.intl?.locale;
129
+ const isChromeless =
130
+ this.props.isChromelessSSR ||
131
+ (__CLIENT__ && window?.location?.search?.indexOf('chromeless=1') > -1);
132
+
133
+ return (
134
+ <PluggablesProvider>
135
+ {language && (
136
+ <Helmet>
137
+ <html lang={language} />
138
+ </Helmet>
139
+ )}
140
+ <BodyClass className={`view-${action}view`} />
141
+
142
+ {/* Body class depending on content type */}
143
+ {this.props.content && this.props.content['@type'] && (
144
+ <BodyClass
145
+ className={`contenttype-${this.props.content['@type']
146
+ .replace(' ', '-')
147
+ .toLowerCase()}`}
148
+ />
149
+ )}
150
+
151
+ {/* Body class depending on sections */}
152
+ <BodyClass
153
+ className={cx({
154
+ [trim(join(split(this.props.pathname, '/'), ' section-'))]:
155
+ this.props.pathname !== '/',
156
+ siteroot: this.props.pathname === '/',
157
+ 'is-authenticated': !!this.props.token,
158
+ 'is-anonymous': !this.props.token,
159
+ 'cms-ui': isCmsUI,
160
+ 'public-ui': !isCmsUI,
161
+ })}
162
+ />
163
+ {!isChromeless && (
164
+ <>
165
+ <SkipLinks />
166
+ <Header pathname={path} />
167
+ <Breadcrumbs pathname={path} />
168
+ </>
169
+ )}
170
+ <MultilingualRedirector
171
+ pathname={this.props.pathname}
172
+ contentLanguage={this.props.content?.language?.token}
173
+ >
174
+ <Segment
175
+ basic
176
+ className="content-area"
177
+ onClick={this.dispatchContentClick}
178
+ >
179
+ <main ref={this.mainRef}>
180
+ <OutdatedBrowser />
181
+ {this.props.connectionRefused ? (
182
+ <ConnectionRefusedView />
183
+ ) : this.state.hasError ? (
184
+ <Error
185
+ message={this.state.error.message}
186
+ stackTrace={this.state.errorInfo.componentStack}
187
+ />
188
+ ) : (
189
+ renderRoutes(this.props.route.routes, {
190
+ staticContext: this.props.staticContext,
191
+ })
192
+ )}
193
+ </main>
194
+ </Segment>
195
+ </MultilingualRedirector>
196
+ {!isChromeless && <Footer />}
197
+ <LockingToastsFactory
198
+ content={this.props.content}
199
+ user={this.props.userId}
200
+ />
201
+ <WorkingCopyToastsFactory content={this.props.content} />
202
+ <ToastContainer
203
+ position={toast.POSITION.BOTTOM_CENTER}
204
+ hideProgressBar
205
+ transition={Slide}
206
+ autoClose={5000}
207
+ closeButton={
208
+ <Icon
209
+ className="toast-dismiss-action"
210
+ name={clearSVG}
211
+ size="18px"
212
+ />
213
+ }
214
+ />
215
+ <AppExtras {...this.props} />
216
+ </PluggablesProvider>
217
+ );
218
+ }
219
+ }
220
+
221
+ export const __test__ = connect(
222
+ (state, props) => ({
223
+ pathname: props.location.pathname,
224
+ token: state.userSession.token,
225
+ content: state.content.data,
226
+ apiError: state.apierror.error,
227
+ connectionRefused: state.apierror.connectionRefused,
228
+ }),
229
+ {},
230
+ )(App);
231
+
232
+ export const fetchContent = async ({ store, location }) => {
233
+ const content = await store.dispatch(
234
+ getContent(getBaseUrl(location.pathname)),
235
+ );
236
+
237
+ const promises = [];
238
+ const { blocksConfig } = config.blocks;
239
+
240
+ const visitor = ([id, data]) => {
241
+ const blockType = data['@type'];
242
+ const { getAsyncData } = blocksConfig[blockType];
243
+ if (getAsyncData) {
244
+ const p = getAsyncData({
245
+ store,
246
+ dispatch: store.dispatch,
247
+ path: location.pathname,
248
+ location,
249
+ id,
250
+ data,
251
+ blocksConfig,
252
+ content,
253
+ });
254
+ if (!p?.length) {
255
+ throw new Error(
256
+ 'You should return a list of promises from getAsyncData',
257
+ );
258
+ }
259
+ promises.push(...p);
260
+ }
261
+ };
262
+
263
+ visitBlocks(content, visitor);
264
+
265
+ await Promise.allSettled(promises);
266
+
267
+ return content;
268
+ };
269
+
270
+ export function connectAppComponent(AppComponent) {
271
+ return compose(
272
+ asyncConnect([
273
+ {
274
+ key: 'breadcrumbs',
275
+ promise: ({ location, store: { dispatch } }) => {
276
+ // Do not trigger the breadcrumbs action if the expander is present
277
+ if (
278
+ __SERVER__ &&
279
+ !hasApiExpander('breadcrumbs', getBaseUrl(location.pathname))
280
+ ) {
281
+ return dispatch(getBreadcrumbs(getBaseUrl(location.pathname)));
282
+ }
283
+ },
284
+ },
285
+ {
286
+ key: 'content',
287
+ promise: ({ location, store }) =>
288
+ __SERVER__ && fetchContent({ store, location }),
289
+ },
290
+ {
291
+ key: 'navigation',
292
+ promise: ({ location, store: { dispatch } }) => {
293
+ // Do not trigger the navigation action if the expander is present
294
+ if (
295
+ __SERVER__ &&
296
+ !hasApiExpander('navigation', getBaseUrl(location.pathname))
297
+ ) {
298
+ return dispatch(
299
+ getNavigation(
300
+ getBaseUrl(location.pathname),
301
+ config.settings.navDepth,
302
+ ),
303
+ );
304
+ }
305
+ },
306
+ },
307
+ {
308
+ key: 'types',
309
+ promise: ({ location, store: { dispatch } }) => {
310
+ // Do not trigger the types action if the expander is present
311
+ if (
312
+ __SERVER__ &&
313
+ !hasApiExpander('types', getBaseUrl(location.pathname))
314
+ ) {
315
+ return dispatch(getTypes(getBaseUrl(location.pathname)));
316
+ }
317
+ },
318
+ },
319
+ {
320
+ key: 'workflow',
321
+ promise: ({ location, store: { dispatch } }) =>
322
+ __SERVER__ && dispatch(getWorkflow(getBaseUrl(location.pathname))),
323
+ },
324
+ ]),
325
+ injectIntl,
326
+ connect((state, props) => {
327
+ return {
328
+ isChromelessSSR: props.location.search?.indexOf('chromeless') > -1,
329
+ pathname: props.location.pathname,
330
+ token: state.userSession.token,
331
+ userId: state.userSession.token
332
+ ? jwtDecode(state.userSession.token).sub
333
+ : '',
334
+ content: state.content.data,
335
+ apiError: state.apierror.error,
336
+ connectionRefused: state.apierror.connectionRefused,
337
+ };
338
+ }, null),
339
+ )(AppComponent);
340
+ }
341
+
342
+ export default connectAppComponent(App);
@@ -0,0 +1 @@
1
+ override to support the chromeless parameter
@@ -85,7 +85,8 @@ const DefaultView = (props) => {
85
85
  : 3;
86
86
  const currentLang = useSelector((state) => state.intl.locale);
87
87
 
88
- // If the content is not yet loaded, then do not show anything
88
+ const isChromeless = location.search?.indexOf('chromeless=1') > -1;
89
+
89
90
  return contentLoaded ? (
90
91
  hasBlocksData(content) ? (
91
92
  <>
@@ -123,8 +124,8 @@ const DefaultView = (props) => {
123
124
  </Grid>
124
125
  </Container>
125
126
  ) : (
126
- <Container id="page-document">
127
- <BannerTitle {...props} />
127
+ <Container id="page-document" className="here">
128
+ {!isChromeless && <BannerTitle {...props} />}
128
129
  <RenderBlocks {...props} path={path} />
129
130
  </Container>
130
131
  )}
package/src/utils.js CHANGED
@@ -92,11 +92,13 @@ export const formatTextToHTML = (text) => {
92
92
 
93
93
  // Convert URLs to clickable links
94
94
  formattedText = formattedText.replace(
95
- /((https?:\/\/[^\s<>"]+))/g,
95
+ /(?<!["'>])((https?:\/\/[^\s<>"]+))/g,
96
96
  '<a href="$1" target="_blank" rel="noreferrer">$1</a>',
97
97
  );
98
98
 
99
- return `<p>${formattedText}</p>`;
99
+ return formattedText.includes('<p>') || formattedText.includes('<a>')
100
+ ? formattedText
101
+ : `<p>${formattedText}</p>`;
100
102
  };
101
103
 
102
104
  export const extractPlanNameAndURL = (text) => {
@@ -127,6 +127,11 @@ body.subsite-mkh {
127
127
  background-color: #fff !important;
128
128
  }
129
129
 
130
+ .funding-sources {
131
+ display: flex;
132
+ gap: 0.5em;
133
+ }
134
+
130
135
  .section-wrapper-info {
131
136
  display: flex;
132
137
  justify-content: space-between;
@@ -211,6 +216,7 @@ body.subsite-mkh {
211
216
 
212
217
  .footer-text {
213
218
  margin-top: 3em;
219
+ font-size: 15px;
214
220
  }
215
221
  }
216
222
  }