@eeacms/volto-bise-policy 1.3.4 → 1.3.6
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 +39 -13
- package/package.json +2 -1
- package/src/components/Widgets/EUNISObjectListWidget.jsx +8 -6
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerEdit.jsx +22 -1
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.jsx +17 -12
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.test.jsx +1 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.jsx +59 -13
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.jsx +28 -16
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.test.jsx +54 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.jsx +68 -35
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.test.jsx +37 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.jsx +41 -39
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.test.jsx +39 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/index.js +2 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/schema.js +19 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/styles.less +33 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/utils.js +171 -93
- package/src/components/manage/Blocks/CaseStudyExplorer/utils.test.js +96 -1
- package/theme/globals/eunismarinehabitat.less +26 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,45 @@ 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
|
-
### [1.3.
|
|
7
|
+
### [1.3.6](https://github.com/eea/volto-bise-policy/compare/1.3.5...1.3.6) - 19 August 2026
|
|
8
|
+
|
|
9
|
+
#### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
- feat: add dompurify dependency [laszlocseh - [`8e2eda4`](https://github.com/eea/volto-bise-policy/commit/8e2eda4eba5084e231e8457da1a98a47036bccb7)]
|
|
12
|
+
- feat(CaseStudyExplorer): add path filter and edit form [laszlocseh - [`ef277dc`](https://github.com/eea/volto-bise-policy/commit/ef277dc8a60a8ae746e635e4da48dfefc67685dc)]
|
|
13
|
+
- feat(CaseStudyExplorer): add status filters and map legend [laszlocseh - [`f2c28d2`](https://github.com/eea/volto-bise-policy/commit/f2c28d25d259d3d661cfbb261419ee435f7470d5)]
|
|
14
|
+
|
|
15
|
+
#### :bug: Bug Fixes
|
|
16
|
+
|
|
17
|
+
- fix(CaseStudyMap): properly display the selected case study style on map [laszlocseh - [`0b374a7`](https://github.com/eea/volto-bise-policy/commit/0b374a70803be40421e11de9776155299d1c0e22)]
|
|
18
|
+
- fix(CaseStudyExplorer): sanitize HTML and escape regex input [laszlocseh - [`e6ab8e1`](https://github.com/eea/volto-bise-policy/commit/e6ab8e1bd7a2d4e6b9341b19eda440828715a4d9)]
|
|
19
|
+
|
|
20
|
+
#### :nail_care: Enhancements
|
|
21
|
+
|
|
22
|
+
- refactor(case-study-explorer): replace hardcoded interaction index with helper [laszlocseh - [`fb69ec0`](https://github.com/eea/volto-bise-policy/commit/fb69ec082ff5e468be0f6e872e6a45e9e7c3695f)]
|
|
23
|
+
|
|
24
|
+
#### :house: Internal changes
|
|
25
|
+
|
|
26
|
+
- style: Automated code fix [eea-jenkins - [`66ec7fa`](https://github.com/eea/volto-bise-policy/commit/66ec7faab819bb27a0a786d459b6389a86bde21d)]
|
|
27
|
+
- style(CaseStudyExplorer): update status colors and fix indentation [laszlocseh - [`54cb574`](https://github.com/eea/volto-bise-policy/commit/54cb574a4947580b19a01307484fcedbf23175a3)]
|
|
28
|
+
- style: Automated code fix [eea-jenkins - [`5c742ae`](https://github.com/eea/volto-bise-policy/commit/5c742ae2581275f9c3bd83a8376d848bf5d33ead)]
|
|
29
|
+
- style: Automated code fix [eea-jenkins - [`4441648`](https://github.com/eea/volto-bise-policy/commit/44416480a18e217db2ebaf86bc2aaa4f60d31787)]
|
|
30
|
+
- style: Automated code fix [eea-jenkins - [`5eb0e32`](https://github.com/eea/volto-bise-policy/commit/5eb0e3284d2ef540c2cdc51984839cf50aad8b75)]
|
|
31
|
+
|
|
32
|
+
#### :hammer_and_wrench: Others
|
|
33
|
+
|
|
34
|
+
- test: fix case study utils.test.js [laszlocseh - [`adcffba`](https://github.com/eea/volto-bise-policy/commit/adcffba55f0cab391eef6db2b88880acffcea918)]
|
|
35
|
+
### [1.3.5](https://github.com/eea/volto-bise-policy/compare/1.3.4...1.3.5) - 5 August 2026
|
|
36
|
+
|
|
37
|
+
#### :rocket: New Features
|
|
38
|
+
|
|
39
|
+
- feat(widgets): add fallback url for EUNIS code [laszlocseh - [`2ad4400`](https://github.com/eea/volto-bise-policy/commit/2ad4400ac0dbb17ff7e207125d8b40c5b4a268cf)]
|
|
40
|
+
|
|
41
|
+
#### :bug: Bug Fixes
|
|
42
|
+
|
|
43
|
+
- fix(css): fix Marine EUNIS Habitat content type css [laszlocseh - [`e818c10`](https://github.com/eea/volto-bise-policy/commit/e818c10d5806f42b181f1ab3c51a565de5bb36e9)]
|
|
44
|
+
|
|
45
|
+
### [1.3.4](https://github.com/eea/volto-bise-policy/compare/1.3.3...1.3.4) - 10 July 2026
|
|
8
46
|
|
|
9
47
|
#### :hammer_and_wrench: Others
|
|
10
48
|
|
|
@@ -13,7 +51,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
13
51
|
|
|
14
52
|
#### :house: Internal changes
|
|
15
53
|
|
|
16
|
-
- chore: [JENKINSFILE] sanitize sonarqube command [valentinab25 - [`8e2dc1a`](https://github.com/eea/volto-bise-policy/commit/8e2dc1a14f3230f197f009a76bf87587724b6e3a)]
|
|
17
54
|
|
|
18
55
|
#### :hammer_and_wrench: Others
|
|
19
56
|
|
|
@@ -117,8 +154,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
117
154
|
|
|
118
155
|
#### :house: Internal changes
|
|
119
156
|
|
|
120
|
-
- chore: [JENKINSFILE] add package version in sonarqube [valentinab25 - [`4c2d75b`](https://github.com/eea/volto-bise-policy/commit/4c2d75bbf6dca47929c6613749169b40ed483985)]
|
|
121
|
-
- chore: [JENKINSFILE] use sonarqube branches [EEA Jenkins - [`f99c622`](https://github.com/eea/volto-bise-policy/commit/f99c622726cfd711704f50218a2f1fe833974ec3)]
|
|
122
157
|
|
|
123
158
|
#### :hammer_and_wrench: Others
|
|
124
159
|
|
|
@@ -381,7 +416,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
381
416
|
- chore: customize root theme to use volto-spotlight - refs #259794 [ana-oprea - [`347f96e`](https://github.com/eea/volto-bise-policy/commit/347f96e335139f9656abd34c308c531b05de85e7)]
|
|
382
417
|
- chore: change the theme and alias to volto-spotlight and add volto-spotlight to package.json - refs #259794 [ana-oprea - [`52732a1`](https://github.com/eea/volto-bise-policy/commit/52732a17b3247a04ffae0a3f5118e173afa98157)]
|
|
383
418
|
- chore: change theme to eea-volto-theme, change config.alias - refs #259794 [ana-oprea - [`c2d08ea`](https://github.com/eea/volto-bise-policy/commit/c2d08ea08a7f0e5f6f7d32149e60ea3f3e901923)]
|
|
384
|
-
- chore: [JENKINS] Refactor automated testing [valentinab25 - [`a3c9488`](https://github.com/eea/volto-bise-policy/commit/a3c9488c5d09ba97fe5976675524d436080a2460)]
|
|
385
419
|
- chore: husky, lint-staged use fixed versions [valentinab25 - [`fddc30f`](https://github.com/eea/volto-bise-policy/commit/fddc30f11a1d8ec290edaca2d924282f5e113bc6)]
|
|
386
420
|
- chore:volto 16 in tests, update docs, fix stylelint overrides [valentinab25 - [`005ce74`](https://github.com/eea/volto-bise-policy/commit/005ce742dbd014fde27e417059251233365766dd)]
|
|
387
421
|
|
|
@@ -394,17 +428,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
394
428
|
- test: Update jest,Jenkinsfile,lint to volto-addons-template PR30 [valentinab25 - [`c1277ba`](https://github.com/eea/volto-bise-policy/commit/c1277bacf840ee9feccbfd6a2c85f0b25a9a9125)]
|
|
395
429
|
- test: Update jest,Jenkinsfile,lint to volto-addons-template PR30 [valentinab25 - [`c944153`](https://github.com/eea/volto-bise-policy/commit/c944153710fd53c5d48863cf363464273e25f52e)]
|
|
396
430
|
- test: Update jest,Jenkinsfile,lint to volto-addons-template PR30 [valentinab25 - [`d648d52`](https://github.com/eea/volto-bise-policy/commit/d648d525a2fc5076577fe0f75ff9782bf0d1d4e9)]
|
|
397
|
-
- test: [JENKINS] fix jest config, fail with logs on coverage [valentinab25 - [`2a7affe`](https://github.com/eea/volto-bise-policy/commit/2a7affe33d737704bdba117c4a5c8c6748ef7a47)]
|
|
398
|
-
- test: [JENKINS] fix jest config, fail with logs on coverage [valentinab25 - [`5929642`](https://github.com/eea/volto-bise-policy/commit/59296420105d0e8c1f1547aa0cd17a019e478c2b)]
|
|
399
|
-
- test: [JENKINS] fix jest config, fail with logs on coverage [valentinab25 - [`b1142f1`](https://github.com/eea/volto-bise-policy/commit/b1142f1da3667a9f56720f65bebce897a5dd8005)]
|
|
400
431
|
- test: cypress should not wait for navigation, actions, types - refs #259794 [ana-oprea - [`188637c`](https://github.com/eea/volto-bise-policy/commit/188637c78a0cae71b5ee48022507d559dfaceb3e)]
|
|
401
|
-
- test: [JENKINS] Use java17 for sonarqube scanner [valentinab25 - [`fa4e473`](https://github.com/eea/volto-bise-policy/commit/fa4e473018386ec3f09d7300d655c8cccdc19a0e)]
|
|
402
|
-
- test: [JENKINS] Run cypress in started frontend container [valentinab25 - [`1d9135b`](https://github.com/eea/volto-bise-policy/commit/1d9135bb878b3c7a1b828085b99b9d27d78ce60e)]
|
|
403
432
|
- remove cypress tests as it doesn t work on bise [Dobricean Ioan Dorian - [`ba7e2de`](https://github.com/eea/volto-bise-policy/commit/ba7e2defea2dcfd01537c3abb41fa9515ff0a3e3)]
|
|
404
433
|
- add mega menu layout settings as #259794 said [dobri1408 - [`b9a3683`](https://github.com/eea/volto-bise-policy/commit/b9a3683e7d85f8c88ad8feac975c47c434f62c4a)]
|
|
405
|
-
- test: [JENKINS] Add cpu limit on cypress docker [valentinab25 - [`9181908`](https://github.com/eea/volto-bise-policy/commit/9181908c7f4b51bdf83a34f8c7f25827de9b1cc6)]
|
|
406
|
-
- test: [JENKINS] Increase shm-size to cypress docker [valentinab25 - [`099d796`](https://github.com/eea/volto-bise-policy/commit/099d79669471e6924bf1e0bc51a79d54379c3a1a)]
|
|
407
|
-
- test: [JENKINS] Improve cypress time [valentinab25 - [`6d5d8e2`](https://github.com/eea/volto-bise-policy/commit/6d5d8e29b04ddb92628245570055067ca3dcbb6c)]
|
|
408
434
|
- test: fix comment [valentinab25 - [`4f27d5a`](https://github.com/eea/volto-bise-policy/commit/4f27d5af2e8895c7af4f1157521ca42682b96e23)]
|
|
409
435
|
- test: EN locales, pre-commit fix, feature PRs checks Refs #257193 [valentinab25 - [`4c2fcb0`](https://github.com/eea/volto-bise-policy/commit/4c2fcb01065e1ef962317ba7be788fd1df079618)]
|
|
410
436
|
- test: Update Makefile and docker-compose to align it with Jenkinsfile [valentinab25 - [`89afaa1`](https://github.com/eea/volto-bise-policy/commit/89afaa15892930452027ef8c8593fc6d193cda70)]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eeacms/volto-bise-policy",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "@eeacms/volto-bise-policy: Volto add-on",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "European Environment Agency: IDM2 A-Team",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@eeacms/volto-openlayers-map": "2.0.1",
|
|
34
34
|
"@eeacms/volto-resize-helper": "*",
|
|
35
35
|
"@plone-collective/volto-authomatic": "2.0.1",
|
|
36
|
+
"dompurify": "^3.3.1",
|
|
36
37
|
"esri-loader": "3.7.0",
|
|
37
38
|
"plotly.js": "^2.28.0",
|
|
38
39
|
"querystring": "0.2.1",
|
|
@@ -14,6 +14,7 @@ import deleteSVG from '@plone/volto/icons/delete.svg';
|
|
|
14
14
|
import addSVG from '@plone/volto/icons/add.svg';
|
|
15
15
|
import dragSVG from '@plone/volto/icons/drag.svg';
|
|
16
16
|
import { v4 as uuid } from 'uuid';
|
|
17
|
+
import { useSelector } from 'react-redux';
|
|
17
18
|
|
|
18
19
|
import { EuropeanRedListVocab, countryCodesVocab } from './vocabularies';
|
|
19
20
|
import './styles.less';
|
|
@@ -254,16 +255,17 @@ export const EUNISMSFDView = ({ value }) => {
|
|
|
254
255
|
);
|
|
255
256
|
};
|
|
256
257
|
|
|
257
|
-
export const EUNISCodeView = ({ value }) => {
|
|
258
|
+
export const EUNISCodeView = ({ value, content }) => {
|
|
259
|
+
const storeData = useSelector((state) => state.content?.data);
|
|
258
260
|
if (!value) return value;
|
|
259
261
|
|
|
262
|
+
const contentData = content ?? storeData;
|
|
263
|
+
const altUrl = contentData?.['eunis_code_alt_url']?.trim();
|
|
264
|
+
const href = altUrl || `/habitats_eunis_revised/EUNISrev_${value}`;
|
|
265
|
+
|
|
260
266
|
return (
|
|
261
267
|
<span>
|
|
262
|
-
<a
|
|
263
|
-
href={`/habitats_eunis_revised/EUNISrev_${value}`}
|
|
264
|
-
target="_blank"
|
|
265
|
-
rel="noopener"
|
|
266
|
-
>
|
|
268
|
+
<a href={href} target="_blank" rel="noopener">
|
|
267
269
|
{value}
|
|
268
270
|
</a>
|
|
269
271
|
</span>
|
|
@@ -1,5 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal';
|
|
3
|
+
import InlineForm from '@plone/volto/components/manage/Form/InlineForm';
|
|
1
4
|
import CaseStudyExplorerView from './CaseStudyExplorerView';
|
|
5
|
+
import schema from './schema';
|
|
2
6
|
|
|
3
7
|
export default function CaseStudyExplorerEdit(props) {
|
|
4
|
-
return
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<CaseStudyExplorerView {...props} mode="edit" />
|
|
11
|
+
<SidebarPortal selected={props.selected}>
|
|
12
|
+
<InlineForm
|
|
13
|
+
schema={schema}
|
|
14
|
+
title={schema.title}
|
|
15
|
+
onChangeField={(id, value) => {
|
|
16
|
+
props.onChangeBlock(props.block, {
|
|
17
|
+
...props.data,
|
|
18
|
+
[id]: value,
|
|
19
|
+
});
|
|
20
|
+
}}
|
|
21
|
+
formData={props.data}
|
|
22
|
+
/>
|
|
23
|
+
</SidebarPortal>
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
5
26
|
}
|
|
@@ -6,7 +6,7 @@ import CaseStudyMap from './CaseStudyMap';
|
|
|
6
6
|
import { ActiveFilters, CaseStudyFilters, SearchBox } from './CaseStudyFilters';
|
|
7
7
|
// import CaseStudyList from './CaseStudyListing';
|
|
8
8
|
|
|
9
|
-
import { filterCases, getFilters } from './utils';
|
|
9
|
+
import { filterCases, filterCasesByPath, getFilters } from './utils';
|
|
10
10
|
import { useCases } from './hooks';
|
|
11
11
|
|
|
12
12
|
import './styles.less';
|
|
@@ -14,7 +14,8 @@ import './styles.less';
|
|
|
14
14
|
export default function CaseStudyExplorerView(props) {
|
|
15
15
|
const cases_url = '/@@case-studies-map.arcgis.json';
|
|
16
16
|
let cases = useCases(addAppURL(cases_url));
|
|
17
|
-
const { caseStudiesIds } = props; // case studies from measure view
|
|
17
|
+
const { caseStudiesIds, data } = props; // case studies from measure view
|
|
18
|
+
const { filter_path } = data || {};
|
|
18
19
|
const [selectedCase, onSelectedCase] = React.useState(null);
|
|
19
20
|
const [searchInput, setSearchInput] = React.useState('');
|
|
20
21
|
const hideFilters = caseStudiesIds ? true : false;
|
|
@@ -22,21 +23,24 @@ export default function CaseStudyExplorerView(props) {
|
|
|
22
23
|
const [activeFilters, setActiveFilters] = React.useState({
|
|
23
24
|
measures_implemented: [],
|
|
24
25
|
typology_of_measures: [],
|
|
26
|
+
current_status: [],
|
|
27
|
+
habitat_ecosystem_type: [],
|
|
28
|
+
nrr_article: [],
|
|
29
|
+
scale_of_planning: [],
|
|
25
30
|
});
|
|
26
31
|
|
|
27
32
|
const [activeItems, setActiveItems] = React.useState(cases);
|
|
28
33
|
const [filters, setFilters] = React.useState([]);
|
|
29
34
|
const [map, setMap] = React.useState();
|
|
35
|
+
const pathFilteredCases = React.useMemo(
|
|
36
|
+
() => filterCasesByPath(cases, filter_path),
|
|
37
|
+
[cases, filter_path],
|
|
38
|
+
);
|
|
30
39
|
|
|
31
40
|
React.useEffect(() => {
|
|
32
|
-
const _filters = getFilters(
|
|
41
|
+
const _filters = getFilters(pathFilteredCases);
|
|
33
42
|
setFilters(_filters);
|
|
34
|
-
}, [
|
|
35
|
-
cases,
|
|
36
|
-
activeFilters.measures_implemented,
|
|
37
|
-
activeFilters.typology_of_measures,
|
|
38
|
-
activeItems.length,
|
|
39
|
-
]);
|
|
43
|
+
}, [pathFilteredCases, activeFilters, activeItems.length]);
|
|
40
44
|
|
|
41
45
|
React.useEffect(() => {
|
|
42
46
|
let activeItems = filterCases(
|
|
@@ -44,10 +48,11 @@ export default function CaseStudyExplorerView(props) {
|
|
|
44
48
|
activeFilters,
|
|
45
49
|
caseStudiesIds,
|
|
46
50
|
searchInput,
|
|
51
|
+
filter_path,
|
|
47
52
|
);
|
|
48
53
|
|
|
49
54
|
setActiveItems(activeItems);
|
|
50
|
-
}, [caseStudiesIds, activeFilters, cases, searchInput]);
|
|
55
|
+
}, [caseStudiesIds, activeFilters, cases, searchInput, filter_path]);
|
|
51
56
|
|
|
52
57
|
if (__SERVER__) return '';
|
|
53
58
|
|
|
@@ -85,11 +90,11 @@ export default function CaseStudyExplorerView(props) {
|
|
|
85
90
|
)}
|
|
86
91
|
</Grid.Row>
|
|
87
92
|
<Grid.Row>
|
|
88
|
-
{
|
|
93
|
+
{pathFilteredCases.length ? (
|
|
89
94
|
<Grid columns={12}>
|
|
90
95
|
<Grid.Column mobile={12} tablet={12} computer={12}>
|
|
91
96
|
<CaseStudyMap
|
|
92
|
-
items={
|
|
97
|
+
items={pathFilteredCases}
|
|
93
98
|
activeItems={activeItems}
|
|
94
99
|
hideFilters={hideFilters}
|
|
95
100
|
selectedCase={selectedCase}
|
|
@@ -3,16 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { centerAndResetMapZoom, scrollToElement } from './utils';
|
|
4
4
|
import { withOpenLayers } from '@eeacms/volto-openlayers-map';
|
|
5
5
|
|
|
6
|
-
const normalizeSearchInput = (searchInput) =>
|
|
7
|
-
let normInput = searchInput
|
|
8
|
-
.toLowerCase()
|
|
9
|
-
.replace('(', '')
|
|
10
|
-
.replace(')', '')
|
|
11
|
-
.replace('?', '.')
|
|
12
|
-
.replace('*', '[^\\s]+');
|
|
13
|
-
|
|
14
|
-
return '\\b' + normInput + '\\b';
|
|
15
|
-
};
|
|
6
|
+
const normalizeSearchInput = (searchInput) => searchInput.trim().toLowerCase();
|
|
16
7
|
|
|
17
8
|
export function CaseStudyFilter(props) {
|
|
18
9
|
const {
|
|
@@ -24,6 +15,7 @@ export function CaseStudyFilter(props) {
|
|
|
24
15
|
map,
|
|
25
16
|
ol,
|
|
26
17
|
} = props;
|
|
18
|
+
const filterEntries = Object.entries(filters?.[filterName] || {});
|
|
27
19
|
|
|
28
20
|
const showInputs = (event) => {
|
|
29
21
|
event.currentTarget.parentElement.classList.add('active');
|
|
@@ -33,6 +25,7 @@ export function CaseStudyFilter(props) {
|
|
|
33
25
|
<div className="filter-wrapper">
|
|
34
26
|
<button
|
|
35
27
|
className="ui basic button facet-btn"
|
|
28
|
+
disabled={!filterEntries.length}
|
|
36
29
|
onClick={(e) => showInputs(e)}
|
|
37
30
|
>
|
|
38
31
|
<span>
|
|
@@ -41,7 +34,9 @@ export function CaseStudyFilter(props) {
|
|
|
41
34
|
</span>
|
|
42
35
|
</button>
|
|
43
36
|
<div className="filter-inputs-wrapper">
|
|
44
|
-
{
|
|
37
|
+
{!filterEntries.length ? (
|
|
38
|
+
<div className="filter-empty">No options available</div>
|
|
39
|
+
) : filterEntries.length > 7 ? (
|
|
45
40
|
<input
|
|
46
41
|
type="text"
|
|
47
42
|
className="filterInputText"
|
|
@@ -66,7 +61,7 @@ export function CaseStudyFilter(props) {
|
|
|
66
61
|
)}
|
|
67
62
|
|
|
68
63
|
<div className="filter-inputs">
|
|
69
|
-
{
|
|
64
|
+
{filterEntries
|
|
70
65
|
.sort((item1, item2) => item1[1].localeCompare(item2[1]))
|
|
71
66
|
.map(([value, label], index) => (
|
|
72
67
|
<label
|
|
@@ -138,6 +133,27 @@ function CaseStudyFiltersComponent(props) {
|
|
|
138
133
|
map={map}
|
|
139
134
|
ol={ol}
|
|
140
135
|
/>
|
|
136
|
+
|
|
137
|
+
<CaseStudyFilter
|
|
138
|
+
filterTitle="Current status"
|
|
139
|
+
filterName="current_status"
|
|
140
|
+
{...props}
|
|
141
|
+
/>
|
|
142
|
+
<CaseStudyFilter
|
|
143
|
+
filterTitle="Habitat/ecosystem type"
|
|
144
|
+
filterName="habitat_ecosystem_type"
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
<CaseStudyFilter
|
|
148
|
+
filterTitle="NRR Article"
|
|
149
|
+
filterName="nrr_article"
|
|
150
|
+
{...props}
|
|
151
|
+
/>
|
|
152
|
+
<CaseStudyFilter
|
|
153
|
+
filterTitle="Scale of Planning"
|
|
154
|
+
filterName="scale_of_planning"
|
|
155
|
+
{...props}
|
|
156
|
+
/>
|
|
141
157
|
</>
|
|
142
158
|
);
|
|
143
159
|
}
|
|
@@ -266,7 +282,9 @@ function ActiveFiltersComponent(props) {
|
|
|
266
282
|
for (let i = 0; i < filterInputs.length; i++) {
|
|
267
283
|
filterInputs[i].checked = false;
|
|
268
284
|
}
|
|
269
|
-
setActiveFilters(
|
|
285
|
+
setActiveFilters(
|
|
286
|
+
Object.fromEntries(Object.keys(activeFilters).map((key) => [key, []])),
|
|
287
|
+
);
|
|
270
288
|
scrollToElement('search-input');
|
|
271
289
|
};
|
|
272
290
|
|
|
@@ -314,6 +332,34 @@ function ActiveFiltersComponent(props) {
|
|
|
314
332
|
filters={filters}
|
|
315
333
|
onRemoveFilter={removeFilter}
|
|
316
334
|
/>
|
|
335
|
+
<FilterGroup
|
|
336
|
+
filterName="current_status"
|
|
337
|
+
filterLabel="Current status:"
|
|
338
|
+
activeFilterCodes={activeFilters.current_status}
|
|
339
|
+
filters={filters}
|
|
340
|
+
onRemoveFilter={removeFilter}
|
|
341
|
+
/>
|
|
342
|
+
<FilterGroup
|
|
343
|
+
filterName="habitat_ecosystem_type"
|
|
344
|
+
filterLabel="Habitat/ecosystem type:"
|
|
345
|
+
activeFilterCodes={activeFilters.habitat_ecosystem_type}
|
|
346
|
+
filters={filters}
|
|
347
|
+
onRemoveFilter={removeFilter}
|
|
348
|
+
/>
|
|
349
|
+
<FilterGroup
|
|
350
|
+
filterName="nrr_article"
|
|
351
|
+
filterLabel="NRR Article:"
|
|
352
|
+
activeFilterCodes={activeFilters.nrr_article}
|
|
353
|
+
filters={filters}
|
|
354
|
+
onRemoveFilter={removeFilter}
|
|
355
|
+
/>
|
|
356
|
+
<FilterGroup
|
|
357
|
+
filterName="scale_of_planning"
|
|
358
|
+
filterLabel="Scale of Planning:"
|
|
359
|
+
activeFilterCodes={activeFilters.scale_of_planning}
|
|
360
|
+
filters={filters}
|
|
361
|
+
onRemoveFilter={removeFilter}
|
|
362
|
+
/>
|
|
317
363
|
</div>
|
|
318
364
|
</div>
|
|
319
365
|
</div>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import DOMPurify from 'dompurify';
|
|
2
3
|
import { withOpenLayers } from '@eeacms/volto-openlayers-map';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
centerAndResetMapZoom,
|
|
7
|
+
escapeRegExp,
|
|
8
|
+
getSelectInteraction,
|
|
6
9
|
scrollToElement,
|
|
7
10
|
zoomMapToFeatures,
|
|
8
11
|
} from './utils';
|
|
@@ -33,7 +36,9 @@ const showPageNr = (pageNr, currentPage, numberOfPages) => {
|
|
|
33
36
|
function CaseStudyList(props) {
|
|
34
37
|
const { selectedCase, onSelectedCase, pointsSource, map, searchInput, ol } =
|
|
35
38
|
props;
|
|
36
|
-
const reSearch =
|
|
39
|
+
const reSearch = searchInput
|
|
40
|
+
? new RegExp(`\\b(${escapeRegExp(searchInput)})\\b`, 'gi')
|
|
41
|
+
: null;
|
|
37
42
|
const [currentPage, setCurrentPage] = React.useState(1);
|
|
38
43
|
|
|
39
44
|
const features = pointsSource
|
|
@@ -132,7 +137,7 @@ function CaseStudyList(props) {
|
|
|
132
137
|
// reset map zoom
|
|
133
138
|
onSelectedCase(null);
|
|
134
139
|
centerAndResetMapZoom({ map, ol });
|
|
135
|
-
map
|
|
140
|
+
getSelectInteraction(map)?.getFeatures().clear();
|
|
136
141
|
}}
|
|
137
142
|
>
|
|
138
143
|
<span
|
|
@@ -167,12 +172,17 @@ function CaseStudyList(props) {
|
|
|
167
172
|
<p
|
|
168
173
|
className="listing-description"
|
|
169
174
|
dangerouslySetInnerHTML={{
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
// description is server data and may contain HTML;
|
|
176
|
+
// sanitize the highlighted result so no scripts or
|
|
177
|
+
// event handlers can reach the DOM
|
|
178
|
+
__html: DOMPurify.sanitize(
|
|
179
|
+
searchInput && reSearch && item.values_.description
|
|
180
|
+
? item.values_.description.replaceAll(
|
|
181
|
+
reSearch,
|
|
182
|
+
'<b>$1</b>',
|
|
183
|
+
)
|
|
184
|
+
: item.values_.description || '',
|
|
185
|
+
),
|
|
176
186
|
}}
|
|
177
187
|
></p>
|
|
178
188
|
<div className="slot-bottom">
|
|
@@ -212,10 +222,9 @@ function CaseStudyList(props) {
|
|
|
212
222
|
role="button"
|
|
213
223
|
onKeyDown={() => {}}
|
|
214
224
|
onClick={() => {
|
|
215
|
-
map
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
.clear();
|
|
225
|
+
const selectInteraction = getSelectInteraction(map);
|
|
226
|
+
selectInteraction?.getFeatures().clear();
|
|
227
|
+
|
|
219
228
|
// scroll to the map
|
|
220
229
|
scrollToElement('ol-map-container');
|
|
221
230
|
|
|
@@ -231,10 +240,13 @@ function CaseStudyList(props) {
|
|
|
231
240
|
const coords =
|
|
232
241
|
item.values_.geometry.flatCoordinates;
|
|
233
242
|
const pixel = map.getPixelFromCoordinate(coords);
|
|
234
|
-
|
|
235
|
-
.
|
|
236
|
-
|
|
237
|
-
|
|
243
|
+
const selectedFeature =
|
|
244
|
+
map.getFeaturesAtPixel(pixel)[0];
|
|
245
|
+
if (selectedFeature) {
|
|
246
|
+
selectInteraction
|
|
247
|
+
?.getFeatures()
|
|
248
|
+
.push(selectedFeature);
|
|
249
|
+
}
|
|
238
250
|
}, 1100);
|
|
239
251
|
}}
|
|
240
252
|
>
|
|
@@ -14,6 +14,7 @@ jest.mock('@eeacms/volto-openlayers-map', () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
jest.mock('./utils', () => ({
|
|
17
|
+
...jest.requireActual('./utils'),
|
|
17
18
|
centerAndResetMapZoom: jest.fn(),
|
|
18
19
|
scrollToElement: jest.fn(),
|
|
19
20
|
zoomMapToFeatures: jest.fn(),
|
|
@@ -119,6 +120,59 @@ describe('CaseStudyList', () => {
|
|
|
119
120
|
expect(getByText('Feature 1').getAttribute('href')).toBe('/feature1');
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
it('does not crash on regex metacharacters in search input', () => {
|
|
124
|
+
const { getByText } = render(
|
|
125
|
+
<CaseStudyList
|
|
126
|
+
selectedCase={null}
|
|
127
|
+
onSelectedCase={jest.fn()}
|
|
128
|
+
pointsSource={mockPointsSource}
|
|
129
|
+
map={mockMap}
|
|
130
|
+
searchInput="("
|
|
131
|
+
ol={{}}
|
|
132
|
+
/>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// must not throw (previously: SyntaxError from invalid RegExp)
|
|
136
|
+
expect(getByText('Feature 1')).toBeInTheDocument();
|
|
137
|
+
expect(getByText('Description 1')).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('sanitizes description HTML and keeps search highlights', () => {
|
|
141
|
+
const evilPointsSource = createMockPointsSource([
|
|
142
|
+
{
|
|
143
|
+
values_: {
|
|
144
|
+
title: 'Evil Case',
|
|
145
|
+
path: '/evil',
|
|
146
|
+
description:
|
|
147
|
+
'<img src=x onerror=alert(1)> <script>alert(2)</script> a river bed',
|
|
148
|
+
typology_of_measures: ['SectorA'],
|
|
149
|
+
measures_implemented: [{ title: 'M1', path: '/m1' }],
|
|
150
|
+
geometry: { flatCoordinates: [0, 0] },
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
const { container } = render(
|
|
156
|
+
<CaseStudyList
|
|
157
|
+
selectedCase={null}
|
|
158
|
+
onSelectedCase={jest.fn()}
|
|
159
|
+
pointsSource={evilPointsSource}
|
|
160
|
+
map={mockMap}
|
|
161
|
+
searchInput="river"
|
|
162
|
+
ol={{}}
|
|
163
|
+
/>,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// XSS payloads must not survive into the DOM
|
|
167
|
+
expect(container.querySelector('img[onerror]')).toBeNull();
|
|
168
|
+
expect(container.querySelector('script')).toBeNull();
|
|
169
|
+
// harmless text still renders and the highlight survives sanitization
|
|
170
|
+
expect(container.textContent).toContain('a river bed');
|
|
171
|
+
const highlight = container.querySelector('b');
|
|
172
|
+
expect(highlight).not.toBeNull();
|
|
173
|
+
expect(highlight.textContent).toBe('river');
|
|
174
|
+
});
|
|
175
|
+
|
|
122
176
|
it('calls zoomMapToFeatures and onSelectedCase when clicking Show on map', async () => {
|
|
123
177
|
const onSelectedCase = jest.fn();
|
|
124
178
|
|