@eeacms/volto-cca-policy 0.3.42 → 0.3.43

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
+ ### [0.3.43](https://github.com/eea/volto-cca-policy/compare/0.3.42...0.3.43) - 16 May 2025
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: update formatTextToHTML - refs #287671 [kreafox - [`79e5faf`](https://github.com/eea/volto-cca-policy/commit/79e5fafa7b2ba45405284b5f61865cb841c06e25)]
12
+
13
+ #### :nail_care: Enhancements
14
+
15
+ - change(mission): add fallback when action is empty - refs #286863 [kreafox - [`c99d5ea`](https://github.com/eea/volto-cca-policy/commit/c99d5ea1cf73796639585dca5fb2c524ac9bf91f)]
16
+ - change: update templates & tests [kreafox - [`57b9083`](https://github.com/eea/volto-cca-policy/commit/57b90839efb13377b47c53127596d88cb96b3246)]
17
+ - change(mission): add country subtitle - refs #287773 [kreafox - [`a8d07b7`](https://github.com/eea/volto-cca-policy/commit/a8d07b72cf71194bbbe5ea0559a3e8cf72ca221d)]
18
+
19
+ #### :house: Internal changes
20
+
21
+ - style(mission): adjust font size - refs #287671 [kreafox - [`50645b7`](https://github.com/eea/volto-cca-policy/commit/50645b75ae7d204a1cd47a78c3caa10bd3f63499)]
22
+
23
+ #### :hammer_and_wrench: Others
24
+
25
+ - No console.log [Tiberiu Ichim - [`6479dcc`](https://github.com/eea/volto-cca-policy/commit/6479dcc3a0f134d81fd4c3407692fa1d839a7093)]
7
26
  ### [0.3.42](https://github.com/eea/volto-cca-policy/compare/0.3.41...0.3.42) - 15 May 2025
8
27
 
9
28
  #### :rocket: Dependency updates
@@ -2352,13 +2371,10 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2352
2371
  - Refs #260715 rast-block wip [Tripon Eugen - [`f19d54e`](https://github.com/eea/volto-cca-policy/commit/f19d54e0b9a6a86bf344eb85b6a1cda7f3de91bf)]
2353
2372
  - Refs #260715 rast-block wip [Tripon Eugen - [`2828537`](https://github.com/eea/volto-cca-policy/commit/2828537b6c084cd1a82162d552fb4ef025b71f9f)]
2354
2373
  - 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
2374
  ### [0.1.49](https://github.com/eea/volto-cca-policy/compare/0.1.48...0.1.49) - 15 November 2023
2358
2375
 
2359
2376
  #### :house: Internal changes
2360
2377
 
2361
- - chore: [JENKINS] Refactor automated testing [valentinab25 - [`7b820a6`](https://github.com/eea/volto-cca-policy/commit/7b820a6369c2ddd5203b1a4abe352cb4bb43db7a)]
2362
2378
  - chore: husky, lint-staged use fixed versions [valentinab25 - [`f0a8061`](https://github.com/eea/volto-cca-policy/commit/f0a8061c275c236deb00087c23fac9860a073106)]
2363
2379
 
2364
2380
  #### :hammer_and_wrench: Others
@@ -2375,9 +2391,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2375
2391
  - Refs #259267 - jenkins test [Tripon Eugen - [`cacd31e`](https://github.com/eea/volto-cca-policy/commit/cacd31e7b1afe0983674ed5c7632d2e1d7fa752e)]
2376
2392
  - Refs #259267 - jenkins [Tripon Eugen - [`5b3affe`](https://github.com/eea/volto-cca-policy/commit/5b3affee8401239de10097884c1b7f2349d15ec0)]
2377
2393
  - 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
2394
  - Refs #259267 - cca event blocks attachments and check not mandatoty fields [Tripon Eugen - [`3138e5a`](https://github.com/eea/volto-cca-policy/commit/3138e5afb5bfbdbed14e27ed457b16867b7fa414)]
2382
2395
  - 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
2396
  - Refs #161485 - Fix ECDE name conflict. [GhitaB - [`8bfd99f`](https://github.com/eea/volto-cca-policy/commit/8bfd99ff68bb82a04d1c0ed625fa514fcf46289e)]
@@ -2594,7 +2607,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
2594
2607
 
2595
2608
  #### :house: Internal changes
2596
2609
 
2597
- - chore: [JENKINS] Remove alpha testing version [valentinab25 - [`ad1ced0`](https://github.com/eea/volto-cca-policy/commit/ad1ced0971ba116c13a3b5fcc039172cc915c919)]
2598
2610
 
2599
2611
  #### :hammer_and_wrench: Others
2600
2612
 
@@ -3075,7 +3087,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
3075
3087
  #### :hammer_and_wrench: Others
3076
3088
 
3077
3089
  - 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
3090
  ### [0.1.1](https://github.com/eea/volto-cca-policy/compare/0.1.0...0.1.1) - 13 December 2022
3080
3091
 
3081
3092
  #### :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.43",
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">CaseStudyExplorer</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
  });
@@ -51,21 +51,36 @@ const ActionsTabContent = ({ action }) => {
51
51
  {action.Funding_Sources && (
52
52
  <>
53
53
  <br />
54
- <p>
54
+ <div className="funding-sources">
55
55
  <span>{action.Funding_Sources_Label} </span>
56
- <strong>{action.Funding_Sources}</strong>
57
- </p>
56
+ <strong>
57
+ <HTMLField
58
+ value={{ data: formatTextToHTML(action.Funding_Sources) }}
59
+ />
60
+ </strong>
61
+ </div>
58
62
  </>
59
63
  )}
60
64
  </>
61
65
  );
62
66
  };
63
67
 
64
- const ActionPagesTab = ({ result }) => {
65
- const { Title, Abstract, Abstract_Line } = result.action_text?.[0] || [];
66
- const actions = result.actions || [];
68
+ const ActionPagesTab = ({ result, general_text }) => {
69
+ const { action_text, actions } = result || {};
70
+ const { No_Data_Reported_Label } = general_text || {};
71
+ const { Title, Abstract, Abstract_Line } = action_text?.[0] || {};
72
+ const hasNoActions = !(actions?.length > 0);
73
+ const hasNoText = !(action_text?.length > 0);
74
+
75
+ const sortedActions = [...(actions || [])].sort((a, b) => a.Order - b.Order);
67
76
 
68
- const sortedActions = [...actions].sort((a, b) => a.Order - b.Order);
77
+ if (hasNoActions && hasNoText) {
78
+ return (
79
+ <Tab.Pane>
80
+ <h5>{No_Data_Reported_Label}</h5>
81
+ </Tab.Pane>
82
+ );
83
+ }
69
84
 
70
85
  return (
71
86
  <Tab.Pane>
@@ -77,11 +92,11 @@ const ActionPagesTab = ({ result }) => {
77
92
  </Callout>
78
93
  )}
79
94
 
80
- {sortedActions.map((action, index) => {
95
+ {sortedActions?.map((action, index) => {
81
96
  return (
82
97
  <div key={index} className="section-wrapper">
83
98
  <h5 className="section-title">
84
- <span className="section-number">{action.Order}. </span>
99
+ <span className="section-number">{action?.Order}. </span>
85
100
  <HTMLField value={{ data: formatTextToHTML(action?.Action) }} />
86
101
  </h5>
87
102
 
@@ -89,7 +104,7 @@ const ActionPagesTab = ({ result }) => {
89
104
  variation="secondary"
90
105
  accordions={[
91
106
  {
92
- title: action?.More_Details_Label || 'More details',
107
+ title: action?.More_Details_Label,
93
108
  content: <ActionsTabContent action={action} />,
94
109
  },
95
110
  ]}
@@ -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
  }));
@@ -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', () => ({
@@ -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', () => {
@@ -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
  }