@eeacms/volto-cca-policy 0.3.82 → 0.3.84
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 +31 -2
- package/package.json +1 -1
- package/src/components/theme/MissionSignatoryProfile/ItemsSection.jsx +35 -0
- package/src/components/theme/MissionSignatoryProfile/MissionSignatoryProfileView.jsx +10 -4
- package/src/components/theme/MissionSignatoryProfile/MissionSignatoryProfileView.test.jsx +1 -1
- package/src/components/theme/MissionSignatoryProfile/TabSections/{ActionPagesTab.jsx → ActionTab.jsx} +30 -15
- package/src/components/theme/MissionSignatoryProfile/TabSections/{ActionPagesTab.test.jsx → ActionTab.test.jsx} +5 -4
- package/src/components/theme/MissionSignatoryProfile/TabSections/AssessmentTab.jsx +9 -21
- package/src/components/theme/MissionSignatoryProfile/TabSections/AssessmentTab.test.jsx +1 -0
- package/src/components/theme/MissionSignatoryProfile/TabSections/GovernanceTab.jsx +1 -1
- package/src/components/theme/MissionSignatoryProfile/TabSections/PlanningTab.jsx +84 -99
- package/src/components/theme/MissionSignatoryProfile/TabSections/PlanningTab.test.jsx +2 -1
- package/src/customizations/volto/README.md +2 -0
- package/src/customizations/volto/server.jsx +43 -19
- package/src/utils.js +19 -0
- package/theme/assets/images/administrative_support_service.png +0 -0
- package/theme/globals/mission.less +37 -10
- package/src/customizations/volto/helpers/Html/Html.jsx +0 -213
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,7 @@ 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.
|
|
7
|
+
### [0.3.84](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.1...0.3.84) - 7 October 2025
|
|
8
8
|
|
|
9
9
|
#### :rocket: Dependency updates
|
|
10
10
|
|
|
@@ -14,17 +14,34 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
14
14
|
|
|
15
15
|
#### :bug: Bug Fixes
|
|
16
16
|
|
|
17
|
+
- fix: duplicated code - make ItemsSection reusable [kreafox - [`52e2625`](https://github.com/eea/volto-cca-policy/commit/52e2625914f699c194f75d561f97959ab6c21fbf)]
|
|
17
18
|
- fix: add missing props for title block [kreafox - [`1d8efa0`](https://github.com/eea/volto-cca-policy/commit/1d8efa0136960261196e7585f89f4ff7db75ae17)]
|
|
18
19
|
- fix: trailing space issue in HtmlSlateWidget - refs #290137 [kreafox - [`34c1ff6`](https://github.com/eea/volto-cca-policy/commit/34c1ff6896a0fe3fae40527f31ea34220e945163)]
|
|
19
20
|
|
|
21
|
+
#### :nail_care: Enhancements
|
|
22
|
+
|
|
23
|
+
- refactor(mission): use ItemsSection in PlanningTab - refs #291190 [kreafox - [`9e96aaa`](https://github.com/eea/volto-cca-policy/commit/9e96aaac0686f6688b3406aca578b598882a9c29)]
|
|
24
|
+
- change: update sectors with icons for Planning tab - refs #291190 [kreafox - [`8b82168`](https://github.com/eea/volto-cca-policy/commit/8b821686fc6b9d98c542d9f0315734b446070a17)]
|
|
25
|
+
- change: update factors with icons for Assessment tab - refs #291189 [kreafox - [`d8b7afc`](https://github.com/eea/volto-cca-policy/commit/d8b7afc36b2129b60ef81d9a057c4c9be35cbb03)]
|
|
26
|
+
- change: update sectors with icons for Action tab - refs #291188 [kreafox - [`a9f15c7`](https://github.com/eea/volto-cca-policy/commit/a9f15c70fa261c9da5b0f01d0bf07ede0136085f)]
|
|
27
|
+
- change(mission): use result_beta in sandbox for signatory reporting - refs #292508 [kreafox - [`b425801`](https://github.com/eea/volto-cca-policy/commit/b42580100bdd2b4d79be53bbfa05098856deb4be)]
|
|
28
|
+
- change: update signatory profile action tab with icons [kreafox - [`0e72902`](https://github.com/eea/volto-cca-policy/commit/0e7290259b966ff72d2a4d77efca8fbeec8655f8)]
|
|
29
|
+
|
|
20
30
|
#### :house: Internal changes
|
|
21
31
|
|
|
32
|
+
- style(mission): mobile fixes on signatory reporting pages [kreafox - [`cbeb584`](https://github.com/eea/volto-cca-policy/commit/cbeb5846c8047c164c29635bd30491b4f810246a)]
|
|
33
|
+
- chore: better component naming [kreafox - [`d61e462`](https://github.com/eea/volto-cca-policy/commit/d61e4629da184ec14d6ad0d056ac29751223e2f9)]
|
|
22
34
|
- style: Automated code fix [eea-jenkins - [`f8934ba`](https://github.com/eea/volto-cca-policy/commit/f8934baf29317bb81cb5a816bfe66ee1724e040f)]
|
|
23
35
|
- style: Automated code fix [eea-jenkins - [`0e75a69`](https://github.com/eea/volto-cca-policy/commit/0e75a6978a2b43e8ad4545ea5fb41191354fd9d0)]
|
|
24
36
|
- style: fix item spacing [kreafox - [`7d4535d`](https://github.com/eea/volto-cca-policy/commit/7d4535dbc486e6cb80fc55d6ac8c7c01d9cf48af)]
|
|
25
37
|
|
|
26
38
|
#### :hammer_and_wrench: Others
|
|
27
39
|
|
|
40
|
+
- test: resolve Jest errors in component tests [kreafox - [`6dbb735`](https://github.com/eea/volto-cca-policy/commit/6dbb73540e8301fcb42a4d52db7004f5a42db4d8)]
|
|
41
|
+
- test: fix PlanningTab mock data to match component structure [kreafox - [`48e76e1`](https://github.com/eea/volto-cca-policy/commit/48e76e1e50e0c6d8a6a500fec3d1831419faeee9)]
|
|
42
|
+
- test: resolve Jest errors in component tests [kreafox - [`f309b96`](https://github.com/eea/volto-cca-policy/commit/f309b96b0767358530d969e21102887a9db76b5a)]
|
|
43
|
+
- Add customization for server.jsx [Tiberiu Ichim - [`c625d39`](https://github.com/eea/volto-cca-policy/commit/c625d397493ca9c1bcbd4083dd0b93969d89ef97)]
|
|
44
|
+
- Remove customizations for server, to benefit from CSP headers implemented in eea-website-theme [Tiberiu Ichim - [`0c9ce15`](https://github.com/eea/volto-cca-policy/commit/0c9ce154c68a589ad6a0c553b36ea6e4f155ed09)]
|
|
28
45
|
- Refs #290787 - sum of countries [Tripon Eugen - [`f1033cf`](https://github.com/eea/volto-cca-policy/commit/f1033cf0beca2d7cace3d1ee2bc93d03cbf7b3c8)]
|
|
29
46
|
- Refs #290787 - contry map update flat/round earth and diffrent countries list [Tripon Eugen - [`6009e9f`](https://github.com/eea/volto-cca-policy/commit/6009e9f2ef28aeacd8f27d2735e0270822a13a5e)]
|
|
30
47
|
### [1.0.0-alpha.1](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.0...1.0.0-alpha.1) - 28 July 2025
|
|
@@ -108,7 +125,19 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
108
125
|
- Add some loadable for components [Tiberiu Ichim - [`1793962`](https://github.com/eea/volto-cca-policy/commit/179396211c66a6a2465b2d1b6c0f2afc40fc7189)]
|
|
109
126
|
- Refs #284961 - test [Tripon Eugen - [`c989f1f`](https://github.com/eea/volto-cca-policy/commit/c989f1f8638c0c5233c5c49f8673c9a2cdc7937e)]
|
|
110
127
|
- Refs #284961 - add translations [Tripon Eugen - [`04ee988`](https://github.com/eea/volto-cca-policy/commit/04ee988c086d393b9b37ce1ea8d24f5e84f266aa)]
|
|
111
|
-
### [1.0.0-alpha.0](https://github.com/eea/volto-cca-policy/compare/0.3.
|
|
128
|
+
### [1.0.0-alpha.0](https://github.com/eea/volto-cca-policy/compare/0.3.83...1.0.0-alpha.0) - 15 July 2025
|
|
129
|
+
|
|
130
|
+
### [0.3.83](https://github.com/eea/volto-cca-policy/compare/0.3.82...0.3.83) - 10 September 2025
|
|
131
|
+
|
|
132
|
+
#### :hammer_and_wrench: Others
|
|
133
|
+
|
|
134
|
+
- Add customization for server.jsx [Tiberiu Ichim - [`c625d39`](https://github.com/eea/volto-cca-policy/commit/c625d397493ca9c1bcbd4083dd0b93969d89ef97)]
|
|
135
|
+
- Remove customizations for server, to benefit from CSP headers implemented in eea-website-theme [Tiberiu Ichim - [`0c9ce15`](https://github.com/eea/volto-cca-policy/commit/0c9ce154c68a589ad6a0c553b36ea6e4f155ed09)]
|
|
136
|
+
### [0.3.82](https://github.com/eea/volto-cca-policy/compare/0.3.81...0.3.82) - 5 September 2025
|
|
137
|
+
|
|
138
|
+
#### :rocket: Dependency updates
|
|
139
|
+
|
|
140
|
+
- Release @eeacms/volto-searchlib@2.1.10 [EEA Jenkins - [`ec1bf82`](https://github.com/eea/volto-cca-policy/commit/ec1bf82df7258b9df4e2496b98aa7a8eccdbeb17)]
|
|
112
141
|
|
|
113
142
|
### [0.3.81](https://github.com/eea/volto-cca-policy/compare/0.3.80...0.3.81) - 26 August 2025
|
|
114
143
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import cx from 'classnames';
|
|
2
|
+
import { Item, Image } from 'semantic-ui-react';
|
|
3
|
+
import { normalizeImageFileName } from '@eeacms/volto-cca-policy/utils';
|
|
4
|
+
import defaultImage from '@eeacms/volto-cca-policy/../theme/assets/images/image-narrow.svg';
|
|
5
|
+
|
|
6
|
+
const ItemsSection = ({ items, field, iconPath }) => {
|
|
7
|
+
if (!items?.length) return null;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Item.Group
|
|
11
|
+
unstackable
|
|
12
|
+
className={cx('items-group', { column: items.length > 3 })}
|
|
13
|
+
>
|
|
14
|
+
{items.map((item, index) => {
|
|
15
|
+
const normalizedIcon = normalizeImageFileName(item.Icon);
|
|
16
|
+
return (
|
|
17
|
+
<Item key={index}>
|
|
18
|
+
{item.Icon ? (
|
|
19
|
+
<Image
|
|
20
|
+
src={`/en/mission/icons/signatory-reporting/${iconPath}/${normalizedIcon}/@@images/image`}
|
|
21
|
+
/>
|
|
22
|
+
) : (
|
|
23
|
+
<Image size="small" src={defaultImage} />
|
|
24
|
+
)}
|
|
25
|
+
<Item.Content verticalAlign="middle">
|
|
26
|
+
<p>{item[field]}</p>
|
|
27
|
+
</Item.Content>
|
|
28
|
+
</Item>
|
|
29
|
+
);
|
|
30
|
+
})}
|
|
31
|
+
</Item.Group>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default ItemsSection;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Link } from 'react-router-dom';
|
|
2
|
+
import { Link, useLocation } from 'react-router-dom';
|
|
3
3
|
import { Tab, Container, Divider, Button, Icon } from 'semantic-ui-react';
|
|
4
4
|
import { formatTextToHTML } from '@eeacms/volto-cca-policy/utils';
|
|
5
5
|
import { BannerTitle, HTMLField } from '@eeacms/volto-cca-policy/helpers';
|
|
@@ -8,18 +8,24 @@ import { flattenToAppURL } from '@plone/volto/helpers';
|
|
|
8
8
|
import GovernanceTab from './TabSections/GovernanceTab';
|
|
9
9
|
import AssessmentTab from './TabSections/AssessmentTab';
|
|
10
10
|
import PlanningTab from './TabSections/PlanningTab';
|
|
11
|
-
import
|
|
11
|
+
import ActionTab from './TabSections/ActionTab';
|
|
12
12
|
|
|
13
13
|
const tabRenderers = {
|
|
14
14
|
Governance_Label: (props) => <GovernanceTab {...props} />,
|
|
15
15
|
Assessment_Label: (props) => <AssessmentTab {...props} />,
|
|
16
16
|
Planning_Label: (props) => <PlanningTab {...props} />,
|
|
17
|
-
Action_Label: (props) => <
|
|
17
|
+
Action_Label: (props) => <ActionTab {...props} />,
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const MissionSignatoryProfileView = ({ content }) => {
|
|
21
|
+
const location = useLocation();
|
|
22
|
+
const isSandbox = location.pathname.includes(
|
|
23
|
+
'/mission/sandbox/eea-sandbox/signatory-reporting',
|
|
24
|
+
);
|
|
21
25
|
const signatoryData =
|
|
22
|
-
content?.['@components']?.missionsignatoryprofile?.
|
|
26
|
+
content?.['@components']?.missionsignatoryprofile?.[
|
|
27
|
+
isSandbox ? 'result_beta' : 'result'
|
|
28
|
+
] || {};
|
|
23
29
|
|
|
24
30
|
const {
|
|
25
31
|
governance = [{}],
|
|
@@ -10,7 +10,7 @@ jest.mock('./TabSections/AssessmentTab', () => () => (
|
|
|
10
10
|
<div>Mocked Assessment</div>
|
|
11
11
|
));
|
|
12
12
|
jest.mock('./TabSections/PlanningTab', () => () => <div>Mocked Planning</div>);
|
|
13
|
-
jest.mock('./TabSections/
|
|
13
|
+
jest.mock('./TabSections/ActionTab', () => () => <div>Mocked Action</div>);
|
|
14
14
|
|
|
15
15
|
jest.mock('@eeacms/volto-cca-policy/helpers', () => ({
|
|
16
16
|
BannerTitle: ({ children }) => <div>{children}</div>,
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
+
import { useLocation } from 'react-router-dom';
|
|
1
2
|
import { Tab, Grid } from 'semantic-ui-react';
|
|
2
3
|
import { Callout } from '@eeacms/volto-eea-design-system/ui';
|
|
3
4
|
import { HTMLField } from '@eeacms/volto-cca-policy/helpers';
|
|
4
5
|
import { formatTextToHTML, isEmpty } from '@eeacms/volto-cca-policy/utils';
|
|
5
6
|
import AccordionList from '../AccordionList';
|
|
6
7
|
import NoDataReported from '../NoDataReported';
|
|
8
|
+
import ItemsSection from '../ItemsSection';
|
|
7
9
|
|
|
8
|
-
const
|
|
10
|
+
const ActionTabContent = ({ action }) => {
|
|
11
|
+
const location = useLocation();
|
|
9
12
|
const hasHazards = action?.Climate_Hazards?.length > 0;
|
|
10
13
|
const hasSectors = action?.Sectors.length > 0;
|
|
11
14
|
const hasBenefits = action?.Co_Benefits.length > 0;
|
|
12
15
|
|
|
16
|
+
const isSandbox = location.pathname.includes(
|
|
17
|
+
'/mission/sandbox/eea-sandbox/signatory-reporting',
|
|
18
|
+
);
|
|
19
|
+
|
|
13
20
|
return (
|
|
14
21
|
<>
|
|
15
22
|
<Grid columns="12">
|
|
16
|
-
<Grid.Column mobile={12} tablet={12} computer={6}>
|
|
23
|
+
<Grid.Column mobile={12} tablet={12} computer={hasBenefits ? 6 : 9}>
|
|
17
24
|
{hasHazards && (
|
|
18
25
|
<>
|
|
19
26
|
<h5 className="small-label">{action.Hazards_Addressed_Label}</h5>
|
|
@@ -27,17 +34,25 @@ const ActionsTabContent = ({ action }) => {
|
|
|
27
34
|
{hasSectors && (
|
|
28
35
|
<>
|
|
29
36
|
<h5 className="small-label">{action.Sectors_Label}</h5>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
{isSandbox ? (
|
|
38
|
+
<ItemsSection
|
|
39
|
+
items={action.Sectors}
|
|
40
|
+
field="Sector"
|
|
41
|
+
iconPath="sector"
|
|
42
|
+
/>
|
|
43
|
+
) : (
|
|
44
|
+
<ul>
|
|
45
|
+
{action.Sectors.map((item, index) => (
|
|
46
|
+
<li key={index}>{item.Sector}</li>
|
|
47
|
+
))}
|
|
48
|
+
</ul>
|
|
49
|
+
)}
|
|
35
50
|
</>
|
|
36
51
|
)}
|
|
37
52
|
</Grid.Column>
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
{
|
|
54
|
+
{hasBenefits && (
|
|
55
|
+
<Grid.Column mobile={12} tablet={12} computer={6}>
|
|
41
56
|
<>
|
|
42
57
|
<h5 className="small-label">{action.Co_Benefits_Label}</h5>
|
|
43
58
|
<ul>
|
|
@@ -46,8 +61,8 @@ const ActionsTabContent = ({ action }) => {
|
|
|
46
61
|
))}
|
|
47
62
|
</ul>
|
|
48
63
|
</>
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
</Grid.Column>
|
|
65
|
+
)}
|
|
51
66
|
</Grid>
|
|
52
67
|
{action.Funding_Sources && (
|
|
53
68
|
<>
|
|
@@ -66,7 +81,7 @@ const ActionsTabContent = ({ action }) => {
|
|
|
66
81
|
);
|
|
67
82
|
};
|
|
68
83
|
|
|
69
|
-
const
|
|
84
|
+
const ActionTab = ({ result, general_text }) => {
|
|
70
85
|
const { action_text, actions } = result || {};
|
|
71
86
|
const { No_Data_Reported_Label } = general_text || {};
|
|
72
87
|
const { Title, Abstract, Abstract_Line } = action_text?.[0] || {};
|
|
@@ -80,7 +95,7 @@ const ActionPagesTab = ({ result, general_text }) => {
|
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
return (
|
|
83
|
-
<Tab.Pane>
|
|
98
|
+
<Tab.Pane className="action-tab">
|
|
84
99
|
{Title && <h2>{Title}</h2>}
|
|
85
100
|
{Abstract && <HTMLField value={{ data: formatTextToHTML(Abstract) }} />}
|
|
86
101
|
{Abstract_Line && (
|
|
@@ -102,7 +117,7 @@ const ActionPagesTab = ({ result, general_text }) => {
|
|
|
102
117
|
accordions={[
|
|
103
118
|
{
|
|
104
119
|
title: action?.More_Details_Label,
|
|
105
|
-
content: <
|
|
120
|
+
content: <ActionTabContent action={action} />,
|
|
106
121
|
},
|
|
107
122
|
]}
|
|
108
123
|
/>
|
|
@@ -113,4 +128,4 @@ const ActionPagesTab = ({ result, general_text }) => {
|
|
|
113
128
|
);
|
|
114
129
|
};
|
|
115
130
|
|
|
116
|
-
export default
|
|
131
|
+
export default ActionTab;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '@testing-library/jest-dom';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
2
3
|
import { render, within } from '@testing-library/react';
|
|
3
|
-
import
|
|
4
|
+
import ActionTab from './ActionTab';
|
|
4
5
|
|
|
5
6
|
jest.mock('@eeacms/volto-eea-design-system/ui', () => ({
|
|
6
7
|
Callout: ({ children }) => <div>{children}</div>,
|
|
@@ -65,7 +66,9 @@ describe('ActionPagesTab', () => {
|
|
|
65
66
|
|
|
66
67
|
it('renders action tab content correctly', () => {
|
|
67
68
|
const { getByText, container } = render(
|
|
68
|
-
<
|
|
69
|
+
<MemoryRouter>
|
|
70
|
+
<ActionTab result={mockResult} />
|
|
71
|
+
</MemoryRouter>,
|
|
69
72
|
);
|
|
70
73
|
|
|
71
74
|
expect(getByText('Adaptation Actions')).toBeInTheDocument();
|
|
@@ -76,7 +79,6 @@ describe('ActionPagesTab', () => {
|
|
|
76
79
|
|
|
77
80
|
const sections = container.querySelectorAll('.section-wrapper');
|
|
78
81
|
|
|
79
|
-
// First action
|
|
80
82
|
const firstAction = within(sections[0]);
|
|
81
83
|
expect(firstAction.getByText('First action')).toBeInTheDocument();
|
|
82
84
|
expect(firstAction.getByText('Hazards addressed')).toBeInTheDocument();
|
|
@@ -84,7 +86,6 @@ describe('ActionPagesTab', () => {
|
|
|
84
86
|
expect(firstAction.getByText('Funding source:')).toBeInTheDocument();
|
|
85
87
|
expect(firstAction.getByText('National Funds')).toBeInTheDocument();
|
|
86
88
|
|
|
87
|
-
// Second action
|
|
88
89
|
const secondAction = within(sections[1]);
|
|
89
90
|
expect(secondAction.getByText('Second action')).toBeInTheDocument();
|
|
90
91
|
expect(secondAction.getByText('Hazards addressed')).toBeInTheDocument();
|
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
import { Tab,
|
|
1
|
+
import { Tab, Segment } from 'semantic-ui-react';
|
|
2
2
|
import { Callout } from '@eeacms/volto-eea-design-system/ui';
|
|
3
3
|
import { HTMLField } from '@eeacms/volto-cca-policy/helpers';
|
|
4
|
-
import {
|
|
4
|
+
import { isEmpty, formatTextToHTML } from '@eeacms/volto-cca-policy/utils';
|
|
5
5
|
import AccordionList from '../AccordionList';
|
|
6
6
|
import NoDataReported from '../NoDataReported';
|
|
7
|
-
|
|
8
|
-
import image from '@eeacms/volto-cca-policy/../theme//assets/images/image-narrow.svg';
|
|
9
|
-
|
|
10
|
-
const ItemsSection = ({ items }) => {
|
|
11
|
-
if (!items?.length) return null;
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<Item.Group className="items-group">
|
|
15
|
-
{items.map((item, index) => (
|
|
16
|
-
<Item key={index}>
|
|
17
|
-
<Image size="small" src={image} />
|
|
18
|
-
<Item.Content verticalAlign="middle">{item.Factor}</Item.Content>
|
|
19
|
-
</Item>
|
|
20
|
-
))}
|
|
21
|
-
</Item.Group>
|
|
22
|
-
);
|
|
23
|
-
};
|
|
7
|
+
import ItemsSection from '../ItemsSection';
|
|
24
8
|
|
|
25
9
|
const AssessmentAccordionContent = ({ result }) => {
|
|
26
10
|
return (
|
|
@@ -72,7 +56,7 @@ const AssessmentTab = ({ result, general_text }) => {
|
|
|
72
56
|
}
|
|
73
57
|
|
|
74
58
|
return (
|
|
75
|
-
<Tab.Pane>
|
|
59
|
+
<Tab.Pane className="assessment-tab">
|
|
76
60
|
{Title && <h2>{Title}</h2>}
|
|
77
61
|
{Subheading && (
|
|
78
62
|
<Callout>
|
|
@@ -88,7 +72,11 @@ const AssessmentTab = ({ result, general_text }) => {
|
|
|
88
72
|
{result.assessment_factors.length > 0 && (
|
|
89
73
|
<>
|
|
90
74
|
{Cra_Abstract && <h5>{Cra_Abstract}</h5>}
|
|
91
|
-
<ItemsSection
|
|
75
|
+
<ItemsSection
|
|
76
|
+
items={result.assessment_factors}
|
|
77
|
+
field="Factor"
|
|
78
|
+
iconPath="factors"
|
|
79
|
+
/>
|
|
92
80
|
</>
|
|
93
81
|
)}
|
|
94
82
|
|
|
@@ -9,6 +9,7 @@ jest.mock('@eeacms/volto-eea-design-system/ui', () => ({
|
|
|
9
9
|
jest.mock('@eeacms/volto-cca-policy/utils', () => ({
|
|
10
10
|
isEmpty: (arr) => !arr || arr.length === 0,
|
|
11
11
|
formatTextToHTML: (text) => text,
|
|
12
|
+
normalizeImageFileName: (filename) => filename || '',
|
|
12
13
|
}));
|
|
13
14
|
|
|
14
15
|
const mockData = {
|
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Tab, Message, Segment, Grid
|
|
2
|
+
import { Tab, Message, Segment, Grid } from 'semantic-ui-react';
|
|
3
3
|
import { Callout } from '@eeacms/volto-eea-design-system/ui';
|
|
4
4
|
import { HTMLField } from '@eeacms/volto-cca-policy/helpers';
|
|
5
5
|
import {
|
|
6
|
+
isEmpty,
|
|
6
7
|
formatTextToHTML,
|
|
7
8
|
extractPlanNameAndURL,
|
|
8
|
-
isEmpty,
|
|
9
9
|
} from '@eeacms/volto-cca-policy/utils';
|
|
10
10
|
import AccordionList from '../AccordionList';
|
|
11
11
|
import NoDataReported from '../NoDataReported';
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
const ItemsSection = ({ items }) => {
|
|
15
|
-
if (!items?.length) return null;
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<Item.Group className="items-group">
|
|
19
|
-
{items.map((sector, index) => (
|
|
20
|
-
<Item key={index}>
|
|
21
|
-
<Image size="small" src={image} />
|
|
22
|
-
<Item.Content verticalAlign="middle">{sector}</Item.Content>
|
|
23
|
-
</Item>
|
|
24
|
-
))}
|
|
25
|
-
</Item.Group>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
12
|
+
import ItemsSection from '../ItemsSection';
|
|
28
13
|
|
|
29
14
|
const PlanningGoalContent = ({ goal }) => {
|
|
30
15
|
const hasHazards = goal?.Climate_Hazards?.length > 0;
|
|
@@ -98,8 +83,9 @@ const PlanningTab = ({ result, general_text }) => {
|
|
|
98
83
|
}
|
|
99
84
|
|
|
100
85
|
return (
|
|
101
|
-
<Tab.Pane>
|
|
86
|
+
<Tab.Pane className="planning-tab">
|
|
102
87
|
{titleData?.Title && <h2>{titleData.Title}</h2>}
|
|
88
|
+
|
|
103
89
|
{titleData?.Abstract_Line && (
|
|
104
90
|
<Callout>
|
|
105
91
|
<HTMLField
|
|
@@ -108,27 +94,24 @@ const PlanningTab = ({ result, general_text }) => {
|
|
|
108
94
|
</Callout>
|
|
109
95
|
)}
|
|
110
96
|
|
|
111
|
-
{sortedGoals.map((goal, index) =>
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
</div>
|
|
130
|
-
);
|
|
131
|
-
})}
|
|
97
|
+
{sortedGoals.map((goal, index) => (
|
|
98
|
+
<div key={index} className="section-wrapper">
|
|
99
|
+
<h5 className="section-title">
|
|
100
|
+
<span className="section-number">{goal?.Title_Label} </span>
|
|
101
|
+
<HTMLField value={{ data: formatTextToHTML(goal?.Title) }} />
|
|
102
|
+
</h5>
|
|
103
|
+
|
|
104
|
+
<AccordionList
|
|
105
|
+
variation="secondary"
|
|
106
|
+
accordions={[
|
|
107
|
+
{
|
|
108
|
+
title: goal?.More_Details_Label || 'More details',
|
|
109
|
+
content: <PlanningGoalContent goal={goal} />,
|
|
110
|
+
},
|
|
111
|
+
]}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
132
115
|
|
|
133
116
|
{goalData?.Climate_Action_Title && (
|
|
134
117
|
<h2>{goalData.Climate_Action_Title}</h2>
|
|
@@ -142,71 +125,73 @@ const PlanningTab = ({ result, general_text }) => {
|
|
|
142
125
|
</Callout>
|
|
143
126
|
)}
|
|
144
127
|
|
|
145
|
-
{planning_climate_action.map((action, index) =>
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<Message>
|
|
151
|
-
<HTMLField
|
|
152
|
-
value={{
|
|
153
|
-
data: formatTextToHTML(action.Sectors_Introduction),
|
|
154
|
-
}}
|
|
155
|
-
/>
|
|
156
|
-
</Message>
|
|
157
|
-
)}
|
|
158
|
-
|
|
159
|
-
<ItemsSection items={action?.Sectors} />
|
|
160
|
-
|
|
161
|
-
{action?.Description && (
|
|
128
|
+
{planning_climate_action.map((action, index) => (
|
|
129
|
+
<React.Fragment key={index}>
|
|
130
|
+
<br />
|
|
131
|
+
{action?.Sectors_Introduction && (
|
|
132
|
+
<Message>
|
|
162
133
|
<HTMLField
|
|
163
|
-
|
|
164
|
-
|
|
134
|
+
value={{
|
|
135
|
+
data: formatTextToHTML(action.Sectors_Introduction),
|
|
136
|
+
}}
|
|
165
137
|
/>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
138
|
+
</Message>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
<ItemsSection
|
|
142
|
+
items={action?.Sectors}
|
|
143
|
+
field="Sector"
|
|
144
|
+
iconPath="sector"
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
{action?.Description && (
|
|
148
|
+
<HTMLField
|
|
149
|
+
className="description"
|
|
150
|
+
value={{ data: formatTextToHTML(action.Description) }}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{(action?.Approval_Year || action?.End_Year) && (
|
|
155
|
+
<p>
|
|
156
|
+
{action?.Year_Of_Approval_Label}{' '}
|
|
157
|
+
<strong className="date">{action.Approval_Year}</strong>{' '}
|
|
158
|
+
{action?.End_Year_Of_Plan_Label}{' '}
|
|
159
|
+
<strong className="date">{action.End_Year}</strong>
|
|
160
|
+
</p>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{action?.Name_Of_Plan_And_Hyperlink && (
|
|
164
|
+
<p>
|
|
165
|
+
{(() => {
|
|
166
|
+
const { name, url } = extractPlanNameAndURL(
|
|
167
|
+
action.Name_Of_Plan_And_Hyperlink,
|
|
168
|
+
);
|
|
169
|
+
return url ? (
|
|
170
|
+
<a href={url} title={name} target="_blank" rel="noreferrer">
|
|
192
171
|
<strong>
|
|
193
172
|
{action.Further_Information_Link_Text}
|
|
194
173
|
{name && ` [${name}]`}
|
|
195
174
|
</strong>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
175
|
+
</a>
|
|
176
|
+
) : (
|
|
177
|
+
<strong>
|
|
178
|
+
{action.Further_Information_Link_Text}
|
|
179
|
+
{name && ` [${name}]`}
|
|
180
|
+
</strong>
|
|
181
|
+
);
|
|
182
|
+
})()}
|
|
183
|
+
</p>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{action?.Attachment && (
|
|
187
|
+
<p>
|
|
188
|
+
<a href={action.Attachment}>
|
|
189
|
+
<strong>{action.Explore_Plan_Link_Text}</strong>
|
|
190
|
+
</a>
|
|
191
|
+
</p>
|
|
192
|
+
)}
|
|
193
|
+
</React.Fragment>
|
|
194
|
+
))}
|
|
210
195
|
</Tab.Pane>
|
|
211
196
|
);
|
|
212
197
|
};
|
|
@@ -15,6 +15,7 @@ jest.mock('@eeacms/volto-cca-policy/utils', () => ({
|
|
|
15
15
|
name: 'Plan Example',
|
|
16
16
|
url: 'https://plan-link.com',
|
|
17
17
|
}),
|
|
18
|
+
normalizeImageFileName: (filename) => filename || '',
|
|
18
19
|
}));
|
|
19
20
|
|
|
20
21
|
describe('PlanningTab', () => {
|
|
@@ -65,7 +66,7 @@ describe('PlanningTab', () => {
|
|
|
65
66
|
Further_Information_Link_Text: 'More Info',
|
|
66
67
|
Attachment: 'https://attachment.com',
|
|
67
68
|
Explore_Plan_Link_Text: 'Explore Plan',
|
|
68
|
-
Sectors: ['Agriculture'],
|
|
69
|
+
Sectors: [{ Sector: 'Agriculture', Icon: '' }],
|
|
69
70
|
},
|
|
70
71
|
],
|
|
71
72
|
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/* Original: https://github.com/plone/volto/blob/16.x.x/src/server.jsx */
|
|
2
|
-
/* Line: 59 - Fix crash when a supported language it's not in volto/locales folder */
|
|
3
|
-
|
|
4
1
|
/* eslint no-console: 0 */
|
|
5
2
|
import '@plone/volto/config'; // This is the bootstrap for the global config - server side
|
|
6
3
|
import { existsSync, lstatSync, readFileSync } from 'fs';
|
|
@@ -20,7 +17,7 @@ import { resetServerContext } from 'react-beautiful-dnd';
|
|
|
20
17
|
import { CookiesProvider } from 'react-cookie';
|
|
21
18
|
import cookiesMiddleware from 'universal-cookie-express';
|
|
22
19
|
import debug from 'debug';
|
|
23
|
-
|
|
20
|
+
import crypto from 'crypto';
|
|
24
21
|
|
|
25
22
|
import routes from '@plone/volto/routes';
|
|
26
23
|
import config from '@plone/volto/registry';
|
|
@@ -49,7 +46,18 @@ import {
|
|
|
49
46
|
} from '@plone/volto/helpers/AsyncConnect';
|
|
50
47
|
|
|
51
48
|
let locales = {};
|
|
49
|
+
const isCSP = process.env.CSP_HEADER || config.settings.serverConfig.csp;
|
|
50
|
+
|
|
51
|
+
// if (config.settings) {
|
|
52
|
+
// config.settings.supportedLanguages.forEach((lang) => {
|
|
53
|
+
// const langFileName = toGettextLang(lang);
|
|
54
|
+
// import('@root/../locales/' + langFileName + '.json').then((locale) => {
|
|
55
|
+
// locales = { ...locales, [toReactIntlLang(lang)]: locale.default };
|
|
56
|
+
// });
|
|
57
|
+
// });
|
|
58
|
+
// }
|
|
52
59
|
|
|
60
|
+
// customized
|
|
53
61
|
if (config.settings) {
|
|
54
62
|
config.settings.supportedLanguages.forEach((lang) => {
|
|
55
63
|
const langFileName = toGettextLang(lang);
|
|
@@ -64,18 +72,7 @@ if (config.settings) {
|
|
|
64
72
|
// end customization
|
|
65
73
|
});
|
|
66
74
|
}
|
|
67
|
-
|
|
68
|
-
// function buildCSPHeader(opts, nonce) {
|
|
69
|
-
// return Object.keys(opts)
|
|
70
|
-
// .sort()
|
|
71
|
-
// .reduce((acc, key) => {
|
|
72
|
-
// return [
|
|
73
|
-
// ...acc,
|
|
74
|
-
// `${key} ${opts[key].replaceAll('{nonce}', `'nonce-${nonce}'`)}`,
|
|
75
|
-
// ];
|
|
76
|
-
// }, [])
|
|
77
|
-
// .join('; ');
|
|
78
|
-
// }
|
|
75
|
+
//end customized
|
|
79
76
|
|
|
80
77
|
function reactIntlErrorHandler(error) {
|
|
81
78
|
debug('i18n')(error);
|
|
@@ -84,8 +81,8 @@ function reactIntlErrorHandler(error) {
|
|
|
84
81
|
const supported = new locale.Locales(keys(languages), 'en');
|
|
85
82
|
|
|
86
83
|
const server = express()
|
|
87
|
-
.set('etag', false)
|
|
88
84
|
.disable('x-powered-by')
|
|
85
|
+
.set('etag', false)
|
|
89
86
|
.head('/*', function (req, res) {
|
|
90
87
|
// Support for HEAD requests. Required by start-test utility in CI.
|
|
91
88
|
res.send('');
|
|
@@ -125,7 +122,28 @@ server.use(function (err, req, res, next) {
|
|
|
125
122
|
}
|
|
126
123
|
});
|
|
127
124
|
|
|
125
|
+
function buildCSPHeader(opts, nonce) {
|
|
126
|
+
if (typeof opts === 'string') {
|
|
127
|
+
//CSP_HEADER
|
|
128
|
+
return opts.replaceAll('{nonce}', `'nonce-${nonce}'`);
|
|
129
|
+
}
|
|
130
|
+
return Object.keys(opts)
|
|
131
|
+
.sort()
|
|
132
|
+
.reduce((acc, key) => {
|
|
133
|
+
return [
|
|
134
|
+
...acc,
|
|
135
|
+
`${key} ${opts[key].replaceAll('{nonce}', `'nonce-${nonce}'`)}`,
|
|
136
|
+
];
|
|
137
|
+
}, [])
|
|
138
|
+
.join('; ');
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
function setupServer(req, res, next) {
|
|
142
|
+
if (isCSP) {
|
|
143
|
+
const nonce = crypto.randomBytes(16).toString('base64');
|
|
144
|
+
res.locals.nonce = nonce;
|
|
145
|
+
}
|
|
146
|
+
|
|
129
147
|
const api = new Api(req);
|
|
130
148
|
|
|
131
149
|
const lang = toReactIntlLang(
|
|
@@ -199,7 +217,11 @@ function setupServer(req, res, next) {
|
|
|
199
217
|
}
|
|
200
218
|
|
|
201
219
|
server.get('/*', (req, res) => {
|
|
202
|
-
const { errorHandler } = res.locals;
|
|
220
|
+
const { errorHandler, nonce } = res.locals;
|
|
221
|
+
|
|
222
|
+
if (isCSP) {
|
|
223
|
+
res.setHeader('Content-Security-Policy', buildCSPHeader(isCSP, nonce));
|
|
224
|
+
}
|
|
203
225
|
|
|
204
226
|
const api = new Api(req);
|
|
205
227
|
|
|
@@ -265,7 +287,7 @@ server.get('/*', (req, res) => {
|
|
|
265
287
|
: store.getState().content.data?.language?.token ||
|
|
266
288
|
config.settings.defaultLanguage;
|
|
267
289
|
|
|
268
|
-
if (toBackendLang(initialLang) !== contentLang) {
|
|
290
|
+
if (toBackendLang(initialLang) !== contentLang && url !== '/') {
|
|
269
291
|
const newLang = toReactIntlLang(
|
|
270
292
|
new locale.Locales(contentLang).best(supported).toString(),
|
|
271
293
|
);
|
|
@@ -310,6 +332,7 @@ server.get('/*', (req, res) => {
|
|
|
310
332
|
${renderToString(
|
|
311
333
|
<Html
|
|
312
334
|
extractor={extractor}
|
|
335
|
+
nonce={nonce}
|
|
313
336
|
markup={markup}
|
|
314
337
|
store={store}
|
|
315
338
|
extractScripts={
|
|
@@ -331,6 +354,7 @@ server.get('/*', (req, res) => {
|
|
|
331
354
|
${renderToString(
|
|
332
355
|
<Html
|
|
333
356
|
extractor={extractor}
|
|
357
|
+
nonce={nonce}
|
|
334
358
|
markup={markup}
|
|
335
359
|
store={store}
|
|
336
360
|
criticalCss={readCriticalCss(req)}
|
package/src/utils.js
CHANGED
|
@@ -134,3 +134,22 @@ export const extractPlanNameAndURL = (text) => {
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
export const isEmpty = (arr) => !Array.isArray(arr) || arr.length === 0;
|
|
137
|
+
|
|
138
|
+
export const normalizeImageFileName = (filename) => {
|
|
139
|
+
if (!filename) return '';
|
|
140
|
+
|
|
141
|
+
const parts = filename.split('.');
|
|
142
|
+
if (parts.length < 2)
|
|
143
|
+
return filename.split('(').join('-').split(')').join('-');
|
|
144
|
+
|
|
145
|
+
const ext = parts.pop();
|
|
146
|
+
let name = parts.join('.');
|
|
147
|
+
|
|
148
|
+
name = name.split('(').join('-').split(')').join('-');
|
|
149
|
+
|
|
150
|
+
if (name.endsWith('-')) {
|
|
151
|
+
name = name.slice(0, -1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return `${name}.${ext}`;
|
|
155
|
+
};
|
|
Binary file
|
|
@@ -14,15 +14,11 @@
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
body.view-viewview.contenttype-subsite.section-mission
|
|
18
|
-
.block.gridBlock
|
|
19
|
-
.image {
|
|
17
|
+
body.view-viewview.contenttype-subsite.section-mission .block.gridBlock .image {
|
|
20
18
|
height: 260px;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
body.view-viewview.contenttype-subsite.section-mission
|
|
24
|
-
.block.gridBlock
|
|
25
|
-
img {
|
|
21
|
+
body.view-viewview.contenttype-subsite.section-mission .block.gridBlock img {
|
|
26
22
|
height: 260px;
|
|
27
23
|
object-fit: contain;
|
|
28
24
|
}
|
|
@@ -78,7 +74,6 @@ body.subsite-mkh {
|
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
|
|
82
77
|
.search-action {
|
|
83
78
|
background-color: @green-1;
|
|
84
79
|
}
|
|
@@ -114,7 +109,25 @@ body.subsite-mkh {
|
|
|
114
109
|
/* Mission signatory profile */
|
|
115
110
|
.signatory-profile {
|
|
116
111
|
.ui.items.items-group {
|
|
117
|
-
margin: 2em 0
|
|
112
|
+
margin: 2em 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.action-tab {
|
|
116
|
+
.ui.items.items-group {
|
|
117
|
+
margin: 1em 0;
|
|
118
|
+
|
|
119
|
+
.item {
|
|
120
|
+
.content {
|
|
121
|
+
font-size: 16px;
|
|
122
|
+
max-width: 300px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ui.image {
|
|
127
|
+
width: 35px;
|
|
128
|
+
height: 35px;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
.ui.inverted.button.back-to-map {
|
|
@@ -211,14 +224,28 @@ body.subsite-mkh {
|
|
|
211
224
|
|
|
212
225
|
.items-group {
|
|
213
226
|
display: block !important;
|
|
214
|
-
|
|
227
|
+
|
|
228
|
+
@media only screen and (min-width: 900px) {
|
|
229
|
+
&.column {
|
|
230
|
+
column-count: 2;
|
|
231
|
+
column-gap: 15px;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
215
234
|
|
|
216
235
|
.item {
|
|
217
236
|
padding: 0.5em 0 !important;
|
|
218
237
|
break-inside: avoid-column;
|
|
238
|
+
align-items: center;
|
|
239
|
+
|
|
240
|
+
.ui.image {
|
|
241
|
+
width: 40px;
|
|
242
|
+
height: 40px;
|
|
243
|
+
}
|
|
219
244
|
|
|
220
245
|
.content {
|
|
221
|
-
|
|
246
|
+
font-size: 18px;
|
|
247
|
+
padding-left: 0.6rem !important;
|
|
248
|
+
max-width: 300px;
|
|
222
249
|
}
|
|
223
250
|
}
|
|
224
251
|
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Html helper.
|
|
3
|
-
* @module helpers/Html
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { Component } from 'react';
|
|
7
|
-
import PropTypes from 'prop-types';
|
|
8
|
-
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
9
|
-
import serialize from 'serialize-javascript';
|
|
10
|
-
import { join } from 'lodash';
|
|
11
|
-
import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
|
|
12
|
-
import { runtimeConfig } from '@plone/volto/runtime_config';
|
|
13
|
-
import config from '@plone/volto/registry';
|
|
14
|
-
|
|
15
|
-
const CRITICAL_CSS_TEMPLATE = `function alter() {
|
|
16
|
-
document.querySelectorAll("head link[rel='prefetch']").forEach(function(el) { el.rel = 'stylesheet'});
|
|
17
|
-
}
|
|
18
|
-
if (window.addEventListener) {
|
|
19
|
-
window.addEventListener('DOMContentLoaded', alter, false)
|
|
20
|
-
} else {
|
|
21
|
-
window.onload=alter
|
|
22
|
-
}`;
|
|
23
|
-
|
|
24
|
-
export const loadReducers = (state = {}) => {
|
|
25
|
-
const { settings } = config;
|
|
26
|
-
return Object.assign(
|
|
27
|
-
{},
|
|
28
|
-
...Object.keys(state).map((name) =>
|
|
29
|
-
settings.initialReducersBlacklist.includes(name)
|
|
30
|
-
? {}
|
|
31
|
-
: { [name]: state[name] },
|
|
32
|
-
),
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Html class.
|
|
38
|
-
* Wrapper component containing HTML metadata and boilerplate tags.
|
|
39
|
-
* Used in server-side code only to wrap the string output of the
|
|
40
|
-
* rendered route component.
|
|
41
|
-
*
|
|
42
|
-
* The only thing this component doesn't (and can't) include is the
|
|
43
|
-
* HTML doctype declaration, which is added to the rendered output
|
|
44
|
-
* by the server.js file.
|
|
45
|
-
*
|
|
46
|
-
* Critical.css behaviour: when a file `public/critical.css` is present, the
|
|
47
|
-
* loading of stylesheets is changed. The styles in critical.css are inlined in
|
|
48
|
-
* the generated HTML, and the whole story needs to change completely: instead
|
|
49
|
-
* of treating stylesheets as priority for rendering, we want to defer their
|
|
50
|
-
* loading as much as possible. So we change the stylesheets to be prefetched
|
|
51
|
-
* and we switch their rel back to stylesheets at document ready event.
|
|
52
|
-
*
|
|
53
|
-
* @function Html
|
|
54
|
-
* @param {Object} props Component properties.
|
|
55
|
-
* @param {Object} props.assets Assets to be rendered.
|
|
56
|
-
* @param {Object} props.component Content to be rendered as child node.
|
|
57
|
-
* @param {Object} props.store Store object.
|
|
58
|
-
* @returns {string} Markup of the not found page.
|
|
59
|
-
*/
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Html class.
|
|
63
|
-
* @class Html
|
|
64
|
-
* @extends Component
|
|
65
|
-
*/
|
|
66
|
-
class Html extends Component {
|
|
67
|
-
/**
|
|
68
|
-
* Property types.
|
|
69
|
-
* @property {Object} propTypes Property types.
|
|
70
|
-
* @static
|
|
71
|
-
*/
|
|
72
|
-
static propTypes = {
|
|
73
|
-
extractor: PropTypes.shape({
|
|
74
|
-
getLinkElements: PropTypes.func.isRequired,
|
|
75
|
-
getScriptElements: PropTypes.func.isRequired,
|
|
76
|
-
getStyleElements: PropTypes.func.isRequired,
|
|
77
|
-
}).isRequired,
|
|
78
|
-
markup: PropTypes.string.isRequired,
|
|
79
|
-
store: PropTypes.shape({
|
|
80
|
-
getState: PropTypes.func,
|
|
81
|
-
}).isRequired,
|
|
82
|
-
nonce: PropTypes.string,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Render method.
|
|
87
|
-
* @method render
|
|
88
|
-
* @returns {string} Markup for the component.
|
|
89
|
-
*/
|
|
90
|
-
render() {
|
|
91
|
-
const { extractor, markup, store, criticalCss, apiPath, publicURL, nonce } =
|
|
92
|
-
this.props;
|
|
93
|
-
const head = Helmet.rewind();
|
|
94
|
-
const bodyClass = join(BodyClass.rewind(), ' ');
|
|
95
|
-
const htmlAttributes = head.htmlAttributes.toComponent();
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<html lang={htmlAttributes.lang}>
|
|
99
|
-
<head>
|
|
100
|
-
<meta charSet="utf-8" />
|
|
101
|
-
{head.base.toComponent()}
|
|
102
|
-
{head.title.toComponent()}
|
|
103
|
-
{head.meta.toComponent()}
|
|
104
|
-
{head.link.toComponent()}
|
|
105
|
-
{head.script.toComponent()}
|
|
106
|
-
|
|
107
|
-
{React.createElement('script', {
|
|
108
|
-
nonce: nonce,
|
|
109
|
-
dangerouslySetInnerHTML: {
|
|
110
|
-
__html: `window.env = ${serialize({
|
|
111
|
-
...runtimeConfig,
|
|
112
|
-
// Seamless mode requirement, the client need to know where the API is located
|
|
113
|
-
// if not set in the API_PATH
|
|
114
|
-
...(apiPath && {
|
|
115
|
-
apiPath,
|
|
116
|
-
}),
|
|
117
|
-
...(publicURL && {
|
|
118
|
-
publicURL,
|
|
119
|
-
}),
|
|
120
|
-
})};`,
|
|
121
|
-
},
|
|
122
|
-
})}
|
|
123
|
-
<link
|
|
124
|
-
rel="sitemap"
|
|
125
|
-
type="application/xml"
|
|
126
|
-
title="Sitemap"
|
|
127
|
-
href="/sitemap-index.xml"
|
|
128
|
-
></link>
|
|
129
|
-
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
130
|
-
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
|
131
|
-
<link
|
|
132
|
-
rel="apple-touch-icon"
|
|
133
|
-
sizes="180x180"
|
|
134
|
-
href="/apple-touch-icon.png"
|
|
135
|
-
/>
|
|
136
|
-
<link rel="manifest" href="/site.webmanifest" />
|
|
137
|
-
<meta name="generator" content="Plone 6 - https://plone.org" />
|
|
138
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
139
|
-
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
140
|
-
{process.env.NODE_ENV === 'production' && criticalCss && (
|
|
141
|
-
<style
|
|
142
|
-
dangerouslySetInnerHTML={{ __html: this.props.criticalCss }}
|
|
143
|
-
/>
|
|
144
|
-
)}
|
|
145
|
-
{/* Add the crossorigin while in development */}
|
|
146
|
-
{extractor.getLinkElements().map((elem) =>
|
|
147
|
-
React.cloneElement(elem, {
|
|
148
|
-
crossOrigin:
|
|
149
|
-
process.env.NODE_ENV === 'production' ? undefined : 'true',
|
|
150
|
-
rel: !criticalCss
|
|
151
|
-
? elem.props.rel
|
|
152
|
-
: elem.props.as === 'style'
|
|
153
|
-
? 'prefetch'
|
|
154
|
-
: elem.props.rel,
|
|
155
|
-
}),
|
|
156
|
-
)}
|
|
157
|
-
{/* Styles in development are loaded with Webpack's style-loader, in production,
|
|
158
|
-
they need to be static*/}
|
|
159
|
-
{process.env.NODE_ENV === 'production' ? (
|
|
160
|
-
criticalCss ? (
|
|
161
|
-
<>
|
|
162
|
-
<script
|
|
163
|
-
dangerouslySetInnerHTML={{
|
|
164
|
-
__html: CRITICAL_CSS_TEMPLATE,
|
|
165
|
-
}}
|
|
166
|
-
></script>
|
|
167
|
-
{extractor.getStyleElements().map((elem) => (
|
|
168
|
-
<noscript>
|
|
169
|
-
{React.cloneElement(elem, {
|
|
170
|
-
rel: 'stylesheet',
|
|
171
|
-
crossOrigin:
|
|
172
|
-
process.env.NODE_ENV === 'production'
|
|
173
|
-
? undefined
|
|
174
|
-
: 'true',
|
|
175
|
-
})}
|
|
176
|
-
</noscript>
|
|
177
|
-
))}
|
|
178
|
-
</>
|
|
179
|
-
) : (
|
|
180
|
-
extractor.getStyleElements()
|
|
181
|
-
)
|
|
182
|
-
) : undefined}
|
|
183
|
-
</head>
|
|
184
|
-
<body className={bodyClass}>
|
|
185
|
-
<div role="navigation" aria-label="Toolbar" id="toolbar" />
|
|
186
|
-
<div id="main" dangerouslySetInnerHTML={{ __html: markup }} />
|
|
187
|
-
<div role="complementary" aria-label="Sidebar" id="sidebar" />
|
|
188
|
-
{React.createElement('script', {
|
|
189
|
-
nonce: nonce,
|
|
190
|
-
dangerouslySetInnerHTML: {
|
|
191
|
-
__html: `window.__data=${serialize(
|
|
192
|
-
loadReducers(store.getState()),
|
|
193
|
-
)};`,
|
|
194
|
-
},
|
|
195
|
-
charSet: 'UTF-8',
|
|
196
|
-
})}
|
|
197
|
-
{/* Add the crossorigin while in development */}
|
|
198
|
-
{this.props.extractScripts !== false
|
|
199
|
-
? extractor.getScriptElements().map((elem) =>
|
|
200
|
-
React.cloneElement(elem, {
|
|
201
|
-
nonce: nonce,
|
|
202
|
-
crossOrigin:
|
|
203
|
-
process.env.NODE_ENV === 'production' ? undefined : 'true',
|
|
204
|
-
}),
|
|
205
|
-
)
|
|
206
|
-
: ''}
|
|
207
|
-
</body>
|
|
208
|
-
</html>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export default Html;
|