@eeacms/volto-cca-policy 0.3.41 → 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,35 @@ 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)]
26
+ ### [0.3.42](https://github.com/eea/volto-cca-policy/compare/0.3.41...0.3.42) - 15 May 2025
27
+
28
+ #### :rocket: Dependency updates
29
+
30
+ - Release @eeacms/volto-globalsearch@2.1.2 [EEA Jenkins - [`e8281dd`](https://github.com/eea/volto-cca-policy/commit/e8281dd309f4c2734d137946554d1ff50d3e5c97)]
31
+
32
+ #### :bug: Bug Fixes
33
+
34
+ - fix: production crash by using safe Statistic subcomponent access [kreafox - [`af7b1d7`](https://github.com/eea/volto-cca-policy/commit/af7b1d745b0080e0a01325b35b860dc537510689)]
35
+
7
36
  ### [0.3.41](https://github.com/eea/volto-cca-policy/compare/0.3.40...0.3.41) - 14 May 2025
8
37
 
9
38
  #### :rocket: Dependency updates
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.41",
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",
@@ -32,7 +32,7 @@
32
32
  "@eeacms/volto-eea-design-system": "<=1.36.3",
33
33
  "@eeacms/volto-eea-website-theme": "^1.35.0",
34
34
  "@eeacms/volto-embed": "^9.1.1",
35
- "@eeacms/volto-globalsearch": "2.1.1",
35
+ "@eeacms/volto-globalsearch": "2.1.2",
36
36
  "@eeacms/volto-hero-block": "^7.1.0",
37
37
  "@eeacms/volto-openlayers-map": "*",
38
38
  "@eeacms/volto-searchlib": "2.0.16",
@@ -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
  });
@@ -1,20 +1,15 @@
1
- import {
2
- Statistic,
3
- StatisticValue,
4
- StatisticLabel,
5
- StatisticGroup,
6
- } from 'semantic-ui-react';
1
+ import { Statistic } from 'semantic-ui-react';
7
2
 
8
3
  const StatisticSection = ({ statistic }) => {
9
4
  return (
10
- <StatisticGroup widths="two" size="small">
5
+ <Statistic.Group widths="two" size="small">
11
6
  {statistic.map((stat, index) => (
12
7
  <Statistic key={index}>
13
- <StatisticValue>{stat.value}</StatisticValue>
14
- <StatisticLabel>{stat.label}</StatisticLabel>
8
+ <Statistic.Value>{stat.value}</Statistic.Value>
9
+ <Statistic.Label>{stat.label}</Statistic.Label>
15
10
  </Statistic>
16
11
  ))}
17
- </StatisticGroup>
12
+ </Statistic.Group>
18
13
  );
19
14
  };
20
15
 
@@ -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
  }