@eeacms/volto-cca-policy 0.1.44 → 0.1.46

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,29 @@ 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.1.46](https://github.com/eea/volto-cca-policy/compare/0.1.45...0.1.46) - 6 October 2023
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: add icon for external links [kreafox - [`6cfc86b`](https://github.com/eea/volto-cca-policy/commit/6cfc86b1e15773578de3ba6f80467daf5b11b087)]
12
+
13
+ #### :nail_care: Enhancements
14
+
15
+ - change: add padding on listing summary view [kreafox - [`d86e321`](https://github.com/eea/volto-cca-policy/commit/d86e321a905dbb925847b2b0b88c20eea7da0003)]
16
+
17
+ #### :hammer_and_wrench: Others
18
+
19
+ - test: use volto version 16 [kreafox - [`a7ed678`](https://github.com/eea/volto-cca-policy/commit/a7ed678ac981213177d0377d312692b69e36c7d8)]
20
+ - Add style on listing view as well [kreafox - [`b741ca8`](https://github.com/eea/volto-cca-policy/commit/b741ca82a959a1261e8ec54ef0c7ecce9b8bfb15)]
21
+ ### [0.1.45](https://github.com/eea/volto-cca-policy/compare/0.1.44...0.1.45) - 25 September 2023
22
+
23
+ #### :hammer_and_wrench: Others
24
+
25
+ - Refs #161483 - Fix name. [GhitaB - [`e614a27`](https://github.com/eea/volto-cca-policy/commit/e614a275681b42d23292f0eb8f371686fefa8d70)]
26
+ - Code cleanup [Tiberiu Ichim - [`c0521db`](https://github.com/eea/volto-cca-policy/commit/c0521dbd4642516c49ec1560d7cc648b2918fddc)]
27
+ - Refs #161483 - Fix jenkins and infinite loop render. [GhitaB - [`2080298`](https://github.com/eea/volto-cca-policy/commit/20802988bbf2bee0759f524c0907da8dd6769284)]
28
+ - Refs #161483 - Add ECDE C3S indicators glossary block. [GhitaB - [`806a768`](https://github.com/eea/volto-cca-policy/commit/806a76888290cb52616dfd6a937a8e49fc6a9ed3)]
29
+ - Refs #161483 - Fix error in documents list when no files. (in add/edit/view publication report form) [GhitaB - [`954f19d`](https://github.com/eea/volto-cca-policy/commit/954f19dd2f09fd2a884c34c95a7c818304b231ae)]
7
30
  ### [0.1.44](https://github.com/eea/volto-cca-policy/compare/0.1.43...0.1.44) - 22 September 2023
8
31
 
9
32
  #### :hammer_and_wrench: Others
@@ -173,7 +196,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
173
196
 
174
197
  #### :house: Internal changes
175
198
 
176
- - chore: [JENKINS] Remove alpha testing version [valentinab25 - [`ad1ced0`](https://github.com/eea/volto-cca-policy/commit/ad1ced0971ba116c13a3b5fcc039172cc915c919)]
177
199
 
178
200
  #### :hammer_and_wrench: Others
179
201
 
@@ -654,7 +676,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
654
676
  #### :hammer_and_wrench: Others
655
677
 
656
678
  - Refs #158294 - Update supported languages list. [GhitaB - [`0a4f91f`](https://github.com/eea/volto-cca-policy/commit/0a4f91f39b7edc367bd4c127d6a8f273c7788361)]
657
- - Add Sonarqube tag using cca-frontend addons list [EEA Jenkins - [`8f1f9ce`](https://github.com/eea/volto-cca-policy/commit/8f1f9ce6c22805670cc0800d3c779b6d619d0f31)]
658
679
  ### [0.1.1](https://github.com/eea/volto-cca-policy/compare/0.1.0...0.1.1) - 13 December 2022
659
680
 
660
681
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
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",
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+
3
+ import { SidebarPortal } from '@plone/volto/components';
4
+ import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
5
+
6
+ import C3SIndicatorsGlossaryBlockView from './C3SIndicatorsGlossaryBlockView';
7
+ import schema from './schema';
8
+
9
+ export default function C3SIndicatorsGlossaryBlockEdit(props) {
10
+ const { block, data, onChangeBlock, selected, id } = props;
11
+
12
+ return (
13
+ <div>
14
+ <C3SIndicatorsGlossaryBlockView data={data} id={id} mode="edit" />
15
+ <SidebarPortal selected={selected}>
16
+ <BlockDataForm
17
+ block={block}
18
+ title={schema.title}
19
+ schema={schema}
20
+ onChangeField={(id, value) => {
21
+ onChangeBlock(block, {
22
+ ...data,
23
+ [id]: value,
24
+ });
25
+ }}
26
+ onChangeBlock={onChangeBlock}
27
+ formData={data}
28
+ />
29
+ </SidebarPortal>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import axios from 'axios';
3
+
4
+ export default function C3SIndicatorsGlossaryBlockView(props) {
5
+ const [tableHTML, setTableHTML] = React.useState('');
6
+
7
+ const getIndicatorsData = () => {
8
+ const url =
9
+ '/++api++/en/knowledge/european-climate-data-explorer/@c3s_indicators_glossary_table';
10
+
11
+ axios
12
+ .get(url)
13
+ .then((response) => {
14
+ setTableHTML(response.data.c3s_indicators_glossary_table);
15
+ })
16
+ .catch((error) => {
17
+ // console.error(error);
18
+ });
19
+ };
20
+
21
+ React.useEffect(() => {
22
+ getIndicatorsData();
23
+ });
24
+
25
+ return (
26
+ <div className="block c3sindicators-glossary-block">
27
+ <div
28
+ className="glossary-table"
29
+ dangerouslySetInnerHTML={{
30
+ __html: tableHTML,
31
+ }}
32
+ />
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,28 @@
1
+ import zoomSVG from '@plone/volto/icons/zoom.svg';
2
+ import C3SIndicatorsGlossaryBlockEdit from './C3SIndicatorsGlossaryBlockEdit';
3
+ import C3SIndicatorsGlossaryBlockView from './C3SIndicatorsGlossaryBlockView';
4
+ import { blockAvailableInMission } from '@eeacms/volto-cca-policy/utils';
5
+
6
+ export default function installBlock(config) {
7
+ const blocksConfig = config.blocks.blocksConfig;
8
+
9
+ blocksConfig.c3SIndicatorsGlossaryBlock = {
10
+ id: 'c3SIndicatorsGlossaryBlock',
11
+ title: 'C3S Indicators Glossary',
12
+ icon: zoomSVG,
13
+ group: 'site',
14
+ view: C3SIndicatorsGlossaryBlockView,
15
+ edit: C3SIndicatorsGlossaryBlockEdit,
16
+ sidebarTab: 1,
17
+ security: {
18
+ addPermission: [],
19
+ view: [],
20
+ },
21
+ variations: [],
22
+ restricted: ({ properties, block }) => {
23
+ return blockAvailableInMission(properties, block);
24
+ },
25
+ };
26
+
27
+ return config;
28
+ }
@@ -0,0 +1,14 @@
1
+ export default {
2
+ title: 'C3S Indicators Glossary',
3
+
4
+ fieldsets: [
5
+ {
6
+ id: 'default',
7
+ title: 'Default',
8
+ },
9
+ ],
10
+
11
+ properties: {},
12
+
13
+ required: [],
14
+ };
@@ -27,7 +27,8 @@ export default function C3SIndicatorsOverviewBlockView(props) {
27
27
 
28
28
  React.useEffect(() => {
29
29
  getIndicatorsData();
30
- });
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, [category]);
31
32
 
32
33
  return (
33
34
  <div className="block c3sindicators-overview-block">
@@ -12,6 +12,7 @@ import installCountryMapProfile from './CountryMapProfile';
12
12
  import installListing from './Listing';
13
13
  import installRAST from './RASTBlock';
14
14
  import installC3SIndicatorsOverviewBlock from './C3SIndicatorsOverviewBlock';
15
+ import installC3SIndicatorsGlossaryBlock from './C3SIndicatorsGlossaryBlock';
15
16
  import installReadMore from './ReadMore';
16
17
 
17
18
  export default function installBlocks(config) {
@@ -22,6 +23,7 @@ export default function installBlocks(config) {
22
23
  installRAST,
23
24
  installReadMore,
24
25
  installC3SIndicatorsOverviewBlock,
26
+ installC3SIndicatorsGlossaryBlock,
25
27
  installMKHMap,
26
28
  installECDEIndicatorsBlock,
27
29
  installCaseStudyExplorerBlock,
@@ -138,16 +138,6 @@ function AdaptationOptionView(props) {
138
138
 
139
139
  <h4>Adaptation Details</h4>
140
140
 
141
- <div id={sectionID('Category')} className="section">
142
- <h5 className="section-title">Category</h5>
143
- {content.category
144
- .map((item) => item.token)
145
- .sort()
146
- .map((cat, index) => (
147
- <Fragment key={index}>{cat}</Fragment>
148
- ))}
149
- </div>
150
-
151
141
  <div id={sectionID('IPCC categories')} className="section">
152
142
  <h5 className="section-title">IPCC categories</h5>
153
143
  {content.ipcc_category
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Header component.
3
+ * @module components/theme/Header/Header
4
+ */
5
+
6
+ import React from 'react'; // , { Component }
7
+ import { useHistory } from 'react-router-dom';
8
+ import cx from 'classnames';
9
+ import { Container, Image, Menu, Dropdown } from 'semantic-ui-react'; // Dropdown,
10
+
11
+ import closeIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/close-line.svg';
12
+ import searchIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/search-line.svg';
13
+ import burgerIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/menu-line.svg';
14
+
15
+ import HeaderSearchPopUp from './HeaderSearchPopUp';
16
+ import HeaderMenuPopUp from './HeaderMenuPopUp';
17
+ import PropTypes from 'prop-types';
18
+
19
+ import { isInternalURL } from '@plone/volto/helpers';
20
+ import config from '@plone/volto/registry';
21
+
22
+ Header.propTypes = {
23
+ transparency: PropTypes.bool,
24
+ inverted: PropTypes.bool,
25
+ };
26
+
27
+ function Header({ children }) {
28
+ return <div className="eea header">{children}</div>;
29
+ }
30
+
31
+ const TopHeader = ({ children }) => (
32
+ <div className="top bar">
33
+ <Container>{children}</Container>
34
+ </div>
35
+ );
36
+
37
+ const TopItem = ({ children, className, id }) => (
38
+ <div className={cx('item', 'header-top-item', className)} id={id}>
39
+ {children}
40
+ </div>
41
+ );
42
+
43
+ const onKeyDownHandler = (event) => {
44
+ if (event.key === 'Enter') {
45
+ event.preventDefault();
46
+ event.target.click();
47
+ //event.target.focus();
48
+ }
49
+ };
50
+
51
+ const TopDropdownMenu = ({
52
+ children,
53
+ className,
54
+ icon,
55
+ hasLanguageDropdown = false,
56
+ id,
57
+ tabletText,
58
+ mobileText,
59
+ text,
60
+ viewportWidth,
61
+ }) => {
62
+ const isTablet = viewportWidth < 991;
63
+ const isMobile = viewportWidth < 767;
64
+
65
+ const Component = ({ mobileText }) => {
66
+ return (
67
+ <>
68
+ {children.props['aria-label'] === 'language switcher' ? (
69
+ hasLanguageDropdown && (
70
+ <Dropdown
71
+ id={id}
72
+ className={className}
73
+ text={mobileText || text}
74
+ icon={icon || 'chevron down'}
75
+ aria-label="dropdown"
76
+ role="dropdown"
77
+ lazyLoad
78
+ closeOnChange={true}
79
+ closeOnBlur={false}
80
+ closeOnEscape={true}
81
+ openOnFocus={false}
82
+ onKeyDown={onKeyDownHandler}
83
+ >
84
+ <Dropdown.Menu role="option">{children}</Dropdown.Menu>
85
+ </Dropdown>
86
+ )
87
+ ) : (
88
+ <Dropdown
89
+ id={id}
90
+ className={className}
91
+ text={mobileText || text}
92
+ icon={icon || 'chevron down'}
93
+ role="dropdown"
94
+ aria-label="dropdown"
95
+ lazyLoad
96
+ closeOnChange={true}
97
+ closeOnBlur={false}
98
+ closeOnEscape={true}
99
+ openOnFocus={false}
100
+ onKeyDown={onKeyDownHandler}
101
+ >
102
+ <Dropdown.Menu role="option">{children}</Dropdown.Menu>
103
+ </Dropdown>
104
+ )}
105
+ </>
106
+ );
107
+ };
108
+ return (
109
+ <>
110
+ {isMobile ? (
111
+ <Component mobileText={mobileText} />
112
+ ) : isTablet ? (
113
+ <Component mobileText={tabletText} />
114
+ ) : (
115
+ <Component />
116
+ )}
117
+ </>
118
+ );
119
+ };
120
+
121
+ // disable sticky until it's more stable
122
+ // const useScrollingUp = () => {
123
+ // let prevScroll;
124
+ //
125
+ // if (process.browser) {
126
+ // prevScroll = window.pageYOffset;
127
+ // }
128
+ // const [scrollingUp, setScrollingUp] = React.useState(false);
129
+ // const handleScroll = () => {
130
+ // const currScroll = window.pageYOffset;
131
+ // const isScrolled = prevScroll > currScroll;
132
+ // setScrollingUp(isScrolled);
133
+ // prevScroll = currScroll;
134
+ // };
135
+ // React.useEffect(() => {
136
+ // window.addEventListener('scroll', handleScroll, { passive: true });
137
+ // return () => {
138
+ // window.removeEventListener('scroll', handleScroll, { passive: true });
139
+ // };
140
+ // });
141
+ // return scrollingUp;
142
+ // };
143
+
144
+ const Main = ({
145
+ logo,
146
+ menuItems,
147
+ menuItemsLayouts,
148
+ renderMenuItem,
149
+ renderGlobalMenuItem,
150
+ headerSearchBox,
151
+ pathname,
152
+ transparency,
153
+ inverted,
154
+ hideSearch,
155
+ isMultilingual,
156
+ }) => {
157
+ const history = useHistory();
158
+ const [activeItem, setActiveItem] = React.useState(pathname);
159
+ const [menuIsActive, setMenuIsActive] = React.useState(false);
160
+ const [searchIsActive, setSearchIsActive] = React.useState(false);
161
+ const [burger, setBurger] = React.useState('');
162
+ const searchInputRef = React.useRef(null);
163
+ const [isClient, setIsClient] = React.useState();
164
+ const itemsLayouts = menuItemsLayouts || config.settings?.menuItemsLayouts;
165
+
166
+ React.useEffect(() => setIsClient(true), []);
167
+
168
+ React.useEffect(() => {
169
+ setMenuIsActive(false);
170
+ setSearchIsActive(false);
171
+ setBurger('');
172
+ // remove active menu when we have no pathname which means we hit logo to go home
173
+ //remove the lang route in order to check if empty
174
+ //setActiveItem as pathname when pathname changed
175
+ if (
176
+ !pathname ||
177
+ (isMultilingual === true && !pathname?.split('/')?.slice(2)?.join('/'))
178
+ ) {
179
+ setActiveItem('');
180
+ } else setActiveItem(pathname);
181
+ }, [isMultilingual, pathname]);
182
+
183
+ React.useEffect(() => {
184
+ if (searchIsActive) {
185
+ searchInputRef.current && searchInputRef.current.focus();
186
+ }
187
+ }, [searchIsActive]);
188
+
189
+ const searchOnClick = (e, x) => {
190
+ if (menuIsActive === true) {
191
+ setBurger('');
192
+ setMenuIsActive(false);
193
+ setActiveItem('');
194
+ }
195
+ setSearchIsActive(!searchIsActive);
196
+ };
197
+
198
+ const mobileBurgerOnClick = () => {
199
+ if (searchIsActive === true) {
200
+ setSearchIsActive(false);
201
+ }
202
+
203
+ if (burger === '') {
204
+ setBurger('open');
205
+ setMenuIsActive(true);
206
+ } else {
207
+ setBurger('');
208
+ setMenuIsActive(false);
209
+ setActiveItem('');
210
+ }
211
+ };
212
+
213
+ const menuOnClickOutside = () => {
214
+ // restore active element if nothing was selected from the menu dropdown
215
+ if (pathname !== activeItem) {
216
+ setActiveItem(pathname);
217
+ }
218
+ // close mobile navigation when clicking outside if we have value for nav
219
+ if (burger) {
220
+ setBurger('');
221
+ }
222
+ // always close the menu
223
+ setMenuIsActive(false);
224
+ };
225
+
226
+ const menuOnClick = (e, item) => {
227
+ if (searchIsActive) setSearchIsActive(false);
228
+ setActiveItem(item['@id'] || item.url);
229
+ if (item.items.length) {
230
+ setMenuIsActive(true);
231
+ } else {
232
+ if (isInternalURL(item.url)) {
233
+ history.push(item.url);
234
+ } else if (isClient) {
235
+ window.location.replace(item.url);
236
+ }
237
+ }
238
+ };
239
+
240
+ // Listens for escape keydown event
241
+ React.useEffect(() => {
242
+ const escKeyPressed = (e) => {
243
+ if (e.key === 'Escape') {
244
+ // menuOnClickOutside();
245
+ // restore active element if nothing was selected from the menu dropdown
246
+ if (pathname !== activeItem) {
247
+ setActiveItem(pathname);
248
+ }
249
+ // close mobile navigation when clicking outside if we have value for nav
250
+ if (burger) {
251
+ setBurger('');
252
+ }
253
+ // always close the menu & search
254
+ setMenuIsActive(false);
255
+ setSearchIsActive(false);
256
+ }
257
+ };
258
+
259
+ document.addEventListener('keydown', escKeyPressed);
260
+
261
+ return () => {
262
+ document.removeEventListener('keydown', escKeyPressed);
263
+ };
264
+ }, [activeItem, burger, pathname]);
265
+
266
+ // React.useEffect(() => {
267
+ // if (searchIsActive || burger === 'open' || menuIsActive) {
268
+ // document.body.style.overflow = 'hidden';
269
+ // } else {
270
+ // document.body.style.overflow = 'unset';
271
+ // }
272
+ // }, [searchIsActive, burger, menuIsActive]);
273
+
274
+ const node = React.useRef();
275
+ const searchButtonRef = React.useRef();
276
+ const mobileMenuBurgerRef = React.useRef();
277
+ const desktopMenuRef = React.useRef();
278
+
279
+ // disable sticky setting until feature is more stable
280
+ // const isScrollingUp = useScrollingUp();
281
+ // <div
282
+ // className={`main bar ${transparency ? 'transparency' : ''} ${
283
+ // isScrollingUp ? 'sticky' : ''
284
+ // }`}
285
+ // >
286
+
287
+ return (
288
+ <div
289
+ className={`main bar ${transparency ? 'transparency' : ''}`}
290
+ ref={node}
291
+ >
292
+ <Container>
293
+ <div className={inverted ? 'main-menu inverted' : 'main-menu'}>
294
+ <div className="header-wrapper">
295
+ {logo}
296
+
297
+ <div className={inverted ? 'main-menu inverted' : 'main-menu'}>
298
+ {menuItems && (
299
+ <ul
300
+ className="ui text eea-main-menu tablet or lower hidden menu"
301
+ ref={desktopMenuRef}
302
+ id={'navigation'}
303
+ >
304
+ {menuItems.map((item) => (
305
+ <Menu.Item
306
+ name={item['@id'] || item.url}
307
+ key={item['@id'] || item.url}
308
+ as={'li'}
309
+ active={
310
+ activeItem.indexOf(item['@id']) !== -1 ||
311
+ activeItem.indexOf(item.url) !== -1
312
+ }
313
+ >
314
+ {renderGlobalMenuItem(item, {
315
+ onClick: menuOnClick,
316
+ })}
317
+ </Menu.Item>
318
+ ))}
319
+ </ul>
320
+ )}
321
+ </div>
322
+ </div>
323
+
324
+ <div className="header-actions">
325
+ {!hideSearch && (
326
+ <button
327
+ className="search-action"
328
+ onClick={searchOnClick}
329
+ tabIndex="0"
330
+ aria-pressed="false"
331
+ aria-haspopup="true"
332
+ ref={searchButtonRef}
333
+ >
334
+ {/* <Icon name={!state.activeSearch ? 'search' : 'close'} /> */}
335
+ <Image
336
+ src={!searchIsActive ? `${searchIcon}` : `${closeIcon}`}
337
+ alt="search button open/close"
338
+ />
339
+ </button>
340
+ )}
341
+ <Header.BurgerAction
342
+ className={`mobile ${burger}`}
343
+ onClick={mobileBurgerOnClick}
344
+ ref={mobileMenuBurgerRef}
345
+ >
346
+ <Image
347
+ src={burger === 'open' ? `${closeIcon}` : `${burgerIcon}`}
348
+ alt="menu icon open/close"
349
+ />
350
+ </Header.BurgerAction>
351
+ </div>
352
+ </div>
353
+ </Container>
354
+ {searchIsActive && (
355
+ <HeaderSearchPopUp
356
+ onClose={searchOnClick}
357
+ searchInputRef={searchInputRef}
358
+ triggerRefs={[searchButtonRef]}
359
+ headerSearchBox={headerSearchBox}
360
+ />
361
+ )}
362
+ <HeaderMenuPopUp
363
+ renderMenuItem={renderMenuItem}
364
+ activeItem={activeItem}
365
+ menuItems={menuItems}
366
+ menuItemsLayouts={itemsLayouts}
367
+ pathName={pathname}
368
+ onClose={menuOnClickOutside}
369
+ triggerRefs={[mobileMenuBurgerRef, desktopMenuRef]}
370
+ visible={menuIsActive}
371
+ />
372
+ </div>
373
+ );
374
+ };
375
+
376
+ const BurgerAction = React.forwardRef((props, ref) => (
377
+ <button
378
+ ref={ref}
379
+ className={`burger-action ${props.className}`}
380
+ tabIndex="0"
381
+ aria-pressed="false"
382
+ aria-haspopup="true"
383
+ onClick={props.onClick}
384
+ >
385
+ {props.children}
386
+ </button>
387
+ ));
388
+
389
+ Header.BurgerAction = BurgerAction;
390
+ Header.Main = Main;
391
+ Header.TopDropdownMenu = TopDropdownMenu;
392
+ Header.TopHeader = TopHeader;
393
+ Header.TopItem = TopItem;
394
+
395
+ export default Header;
@@ -0,0 +1,403 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ Accordion,
4
+ Container,
5
+ Grid,
6
+ Icon,
7
+ List,
8
+ Transition,
9
+ } from 'semantic-ui-react';
10
+
11
+ import { cloneDeep } from 'lodash';
12
+
13
+ import { useClickOutside } from '@eeacms/volto-eea-design-system/helpers';
14
+
15
+ const createColumns = (item, renderMenuItem, item_id) => {
16
+ return item.items.map((item, index) => (
17
+ <React.Fragment key={index}>
18
+ {renderMenuItem(item, {
19
+ className: 'item',
20
+ key: index,
21
+ id: item_id,
22
+ })}
23
+ </React.Fragment>
24
+ ));
25
+ };
26
+
27
+ const ItemGrid = ({
28
+ item,
29
+ columns,
30
+ renderMenuItem,
31
+ hideChildrenFromNavigation,
32
+ }) => {
33
+ const item_id = item.title.toLowerCase().replaceAll(' ', '-') + '-sub-title';
34
+ return (
35
+ <>
36
+ {renderMenuItem(item, { className: 'sub-title', id: item_id })}
37
+ {item.items.length && !hideChildrenFromNavigation ? (
38
+ <List
39
+ aria-labelledby={item_id}
40
+ className={columns && columns > 1 ? `has--${columns}--columns` : ''}
41
+ >
42
+ {createColumns(item, renderMenuItem, item_id)}
43
+ </List>
44
+ ) : null}
45
+ </>
46
+ );
47
+ };
48
+
49
+ const Item = ({
50
+ item,
51
+ icon = false,
52
+ iconName,
53
+ renderMenuItem,
54
+ hideChildrenFromNavigation,
55
+ }) => {
56
+ const item_id = item.title.toLowerCase().replaceAll(' ', '-') + '-sub-title';
57
+ return (
58
+ <>
59
+ {renderMenuItem(item, {
60
+ className: 'sub-title',
61
+ id: item_id,
62
+ })}
63
+ {!hideChildrenFromNavigation && (
64
+ <List className="menu-list" aria-labelledby={item_id}>
65
+ {item.items.map((listItem, index) => (
66
+ <React.Fragment key={index}>
67
+ {renderMenuItem(
68
+ listItem,
69
+ {
70
+ className: 'item',
71
+ key: index,
72
+ },
73
+ { children: icon && <Icon className={iconName} /> },
74
+ )}
75
+ </React.Fragment>
76
+ ))}
77
+ </List>
78
+ )}
79
+ </>
80
+ );
81
+ };
82
+
83
+ const RenderItem = ({ layout, section, renderMenuItem, index }) => {
84
+ const hideChildrenFromNavigation =
85
+ layout.hideChildrenFromNavigation === undefined
86
+ ? true
87
+ : layout.hideChildrenFromNavigation;
88
+ return !layout.menuItemChildrenListColumns ||
89
+ layout.menuItemChildrenListColumns[index] === 1 ? (
90
+ <Item
91
+ item={section}
92
+ renderMenuItem={renderMenuItem}
93
+ hideChildrenFromNavigation={hideChildrenFromNavigation}
94
+ />
95
+ ) : (
96
+ <ItemGrid
97
+ item={section}
98
+ columns={layout.menuItemChildrenListColumns[index]}
99
+ renderMenuItem={renderMenuItem}
100
+ hideChildrenFromNavigation={hideChildrenFromNavigation}
101
+ />
102
+ );
103
+ };
104
+
105
+ export const StandardMegaMenuGrid = ({ menuItem, renderMenuItem, layout }) => {
106
+ const menuItemColumns = layout && layout.menuItemColumns;
107
+ const menuItemColumnsLength =
108
+ (menuItemColumns && menuItemColumns.length - 1) || 0;
109
+
110
+ const renderColumnContent = (section, columnIndex) => (
111
+ <RenderItem
112
+ layout={layout}
113
+ section={section}
114
+ renderMenuItem={renderMenuItem}
115
+ index={columnIndex}
116
+ />
117
+ );
118
+
119
+ const renderColumns = () => (
120
+ <Grid>
121
+ {menuItemColumns.map((section, columnIndex) => (
122
+ <div className={layout.menuItemColumns[columnIndex]} key={columnIndex}>
123
+ {columnIndex !== menuItemColumnsLength
124
+ ? renderColumnContent(menuItem.items[columnIndex], columnIndex)
125
+ : menuItem.items
126
+ .slice(menuItemColumnsLength)
127
+ .map((section, _idx) =>
128
+ renderColumnContent(section, columnIndex),
129
+ )}
130
+ </div>
131
+ ))}
132
+ </Grid>
133
+ );
134
+
135
+ const renderDefaultColumns = () => (
136
+ <div className={layout?.gridContainerClass || 'ui four column grid'}>
137
+ {menuItem.items.map((section, index) => (
138
+ <Grid.Column key={index}>
139
+ {renderColumnContent(section, index)}
140
+ </Grid.Column>
141
+ ))}
142
+ </div>
143
+ );
144
+
145
+ return menuItemColumns ? renderColumns() : renderDefaultColumns();
146
+ };
147
+
148
+ const FirstLevelContent = ({ element, renderMenuItem, pathName }) => {
149
+ const topics = element.title === 'Topics';
150
+ let defaultIndex = -1;
151
+
152
+ return (
153
+ <>
154
+ {!topics ? (
155
+ <React.Fragment>
156
+ {element.items.map((item, index) => {
157
+ let firstLevelPanels = [];
158
+ if (!item.items.length) {
159
+ return (
160
+ <React.Fragment key={index}>
161
+ {renderMenuItem(item, { className: 'item sub-title' })}
162
+ </React.Fragment>
163
+ );
164
+ }
165
+ let x = {};
166
+ x.key = item['@id'] || item['url'];
167
+ if (pathName.indexOf(item.url) !== -1) {
168
+ defaultIndex = index;
169
+ }
170
+ x.title = (
171
+ <Accordion.Title
172
+ key={`title=${index}`}
173
+ as="button"
174
+ aria-expanded={false}
175
+ onClick={(e) => {
176
+ e.currentTarget.setAttribute(
177
+ 'aria-expanded',
178
+ e.currentTarget.className.indexOf('active') === -1,
179
+ );
180
+ }}
181
+ >
182
+ {item.title}
183
+ <Icon className="ri-arrow-down-s-line" size="small" />
184
+ </Accordion.Title>
185
+ );
186
+ let overflow_item = cloneDeep(item);
187
+ overflow_item.title = 'See all';
188
+ x.content = (
189
+ <Accordion.Content>
190
+ {renderMenuItem(overflow_item, {
191
+ className: 'item title-item',
192
+ })}
193
+ <SecondLevelContent
194
+ element={item}
195
+ renderMenuItem={renderMenuItem}
196
+ />
197
+ </Accordion.Content>
198
+ );
199
+ firstLevelPanels.push(x);
200
+ return (
201
+ <Accordion.Accordion
202
+ panels={firstLevelPanels}
203
+ key={index}
204
+ defaultActiveIndex={defaultIndex === index ? 0 : -1}
205
+ />
206
+ );
207
+ })}
208
+ </React.Fragment>
209
+ ) : (
210
+ <SecondLevelContent
211
+ element={element}
212
+ topics={true}
213
+ renderMenuItem={renderMenuItem}
214
+ />
215
+ )}
216
+ </>
217
+ );
218
+ };
219
+
220
+ const SecondLevelContent = ({ element, topics = false, renderMenuItem }) => {
221
+ let content;
222
+ if (topics) {
223
+ const atAGlance = element.items.find(
224
+ (element) => element.title === 'At a glance',
225
+ );
226
+ const inDepth = element.items.find(
227
+ (element) => element.url.indexOf('in-depth') !== -1,
228
+ );
229
+ content = (
230
+ <List>
231
+ {atAGlance &&
232
+ atAGlance.items.map((item, index) => (
233
+ <React.Fragment key={index}>
234
+ {renderMenuItem(item, {
235
+ key: index,
236
+ className: 'item',
237
+ })}
238
+ </React.Fragment>
239
+ ))}
240
+ {inDepth && (
241
+ <React.Fragment key={inDepth.url}>
242
+ {renderMenuItem(inDepth, {
243
+ key: inDepth.url,
244
+ className: 'item',
245
+ })}
246
+ </React.Fragment>
247
+ )}
248
+ </List>
249
+ );
250
+ } else {
251
+ content = (
252
+ <List>
253
+ {element.items.map((item, index) => (
254
+ <React.Fragment key={index}>
255
+ {renderMenuItem(item, {
256
+ key: index,
257
+ className: 'item',
258
+ })}
259
+ </React.Fragment>
260
+ ))}
261
+ </List>
262
+ );
263
+ }
264
+
265
+ return <>{content}</>;
266
+ };
267
+
268
+ const NestedAccordion = ({ menuItems, renderMenuItem, pathName }) => {
269
+ const [activeIndex, setActiveIndex] = useState(-1);
270
+
271
+ useEffect(() => {
272
+ let index = 0;
273
+ menuItems.forEach((menuItem) => {
274
+ if (pathName.includes(menuItem.url)) setActiveIndex(index);
275
+ ++index;
276
+ });
277
+ }, [menuItems, pathName]);
278
+
279
+ const rootPanels = [];
280
+ menuItems.forEach((element, index) => {
281
+ let x = {};
282
+ x.key = index;
283
+ x.title = (
284
+ <Accordion.Title
285
+ key={`title-${index}`}
286
+ index={index}
287
+ aria-expanded={activeIndex === index}
288
+ as="button"
289
+ onClick={() => {
290
+ if (activeIndex === index) {
291
+ setActiveIndex(-1);
292
+ } else setActiveIndex(index);
293
+ }}
294
+ >
295
+ {element.title}
296
+ <Icon className="ri-arrow-down-s-line" size="small" />
297
+ </Accordion.Title>
298
+ );
299
+ let overview = cloneDeep(element);
300
+ x.content = (
301
+ <Accordion.Content key={index}>
302
+ <div className="mega-menu-title">
303
+ {/* Inverted right labeled button as a category title - Mobile */}
304
+ {renderMenuItem(
305
+ overview,
306
+ { className: 'ui button inverted icon right labeled' },
307
+ {
308
+ iconPosition: 'right',
309
+ children: (
310
+ <>
311
+ {/* Add word overview to titles */}
312
+ <span> overview</span>
313
+ <Icon className={'arrow right icon'} alt={'Title icon'} />
314
+ </>
315
+ ),
316
+ },
317
+ )}
318
+ </div>
319
+ <FirstLevelContent
320
+ element={element}
321
+ renderMenuItem={renderMenuItem}
322
+ pathName={pathName}
323
+ />
324
+ </Accordion.Content>
325
+ );
326
+ rootPanels.push(x);
327
+ });
328
+
329
+ return <Accordion activeIndex={activeIndex} panels={rootPanels} />;
330
+ };
331
+
332
+ function HeaderMenuPopUp({
333
+ menuItems,
334
+ menuItemsLayouts,
335
+ renderMenuItem,
336
+ pathName,
337
+ onClose,
338
+ triggerRefs,
339
+ activeItem,
340
+ visible,
341
+ }) {
342
+ const nodeRef = React.useRef();
343
+ useClickOutside({ targetRefs: [nodeRef, ...triggerRefs], callback: onClose });
344
+
345
+ const menuItem = menuItems.find(
346
+ (current) => current.url === activeItem || current['@id'] === activeItem,
347
+ );
348
+
349
+ const layout =
350
+ !!menuItemsLayouts &&
351
+ Object.keys(menuItemsLayouts).includes(menuItem?.url) &&
352
+ menuItemsLayouts[menuItem.url];
353
+
354
+ return (
355
+ <Transition visible={visible} animation="slide down" duration={300}>
356
+ <div id="mega-menu" ref={nodeRef}>
357
+ <Container>
358
+ {menuItem && (
359
+ <div className="menu-content tablet hidden mobile hidden">
360
+ {/* Inverted right labeled button as a category title,
361
+ for topics the button goes inside the grid */}
362
+ {menuItem.title && (
363
+ <div className="mega-menu-title">
364
+ {renderMenuItem(
365
+ menuItem,
366
+ { className: 'ui button inverted icon right labeled' },
367
+ {
368
+ iconPosition: 'right',
369
+ children: (
370
+ <>
371
+ {/* Add word overview to titles */}
372
+ <span> overview</span>
373
+ <Icon
374
+ className={'arrow right icon'}
375
+ alt={'Title icon'}
376
+ />
377
+ </>
378
+ ),
379
+ },
380
+ )}
381
+ </div>
382
+ )}
383
+ <StandardMegaMenuGrid
384
+ menuItem={menuItem}
385
+ renderMenuItem={renderMenuItem}
386
+ layout={layout}
387
+ />
388
+ </div>
389
+ )}
390
+ <div className="tablet only mobile only">
391
+ <NestedAccordion
392
+ menuItems={menuItems}
393
+ renderMenuItem={renderMenuItem}
394
+ pathName={pathName}
395
+ />
396
+ </div>
397
+ </Container>
398
+ </div>
399
+ </Transition>
400
+ );
401
+ }
402
+
403
+ export default HeaderMenuPopUp;
@@ -0,0 +1,131 @@
1
+ import React, { useEffect } from 'react';
2
+ import { Container, Input, List } from 'semantic-ui-react';
3
+ import { withRouter, Link } from 'react-router-dom';
4
+ import { useClickOutside } from '@eeacms/volto-eea-design-system/helpers';
5
+ import { handleEnterKeyPress } from '@eeacms/volto-eea-design-system/helpers';
6
+
7
+ const getRandomItems = (arr, max) => {
8
+ return (
9
+ arr?.slice(0, max).map(function () {
10
+ return this.splice(Math.floor(Math.random() * this.length), 1)[0];
11
+ }, arr.slice()) || []
12
+ );
13
+ };
14
+
15
+ function HeaderSearchPopUp({
16
+ history,
17
+ location,
18
+ onClose,
19
+ searchInputRef,
20
+ headerSearchBox,
21
+ triggerRefs = [],
22
+ }) {
23
+ const nodeRef = React.useRef();
24
+ const headerSearchViews = headerSearchBox || [];
25
+ const defaultView = headerSearchViews.filter((v) => v.isDefault);
26
+ const localView = headerSearchViews.filter((v) =>
27
+ location.pathname.match(v.matchpath ? v.matchpath : v.path),
28
+ );
29
+ const activeView = localView.length > 0 ? localView[0] : defaultView[0];
30
+
31
+ const {
32
+ path = '',
33
+ buttonTitle,
34
+ buttonUrl,
35
+ description,
36
+ placeholder = 'Search',
37
+ searchSuggestions,
38
+ } = activeView || {};
39
+ const { suggestionsTitle, suggestions, maxToShow } = searchSuggestions || {};
40
+
41
+ const [visibleSuggestions, setVisibileSuggestions] = React.useState(
42
+ getRandomItems(suggestions, maxToShow),
43
+ );
44
+
45
+ useEffect(() => {
46
+ setVisibileSuggestions(getRandomItems(suggestions, maxToShow));
47
+ }, [maxToShow, suggestions]);
48
+
49
+ useClickOutside({ targetRefs: [nodeRef, ...triggerRefs], callback: onClose });
50
+
51
+ const onSubmit = (event) => {
52
+ const text = searchInputRef?.current?.inputRef?.current?.value;
53
+ history.push(`${path}?q=${text}`);
54
+
55
+ if (window?.searchContext?.resetSearch) {
56
+ window.searchContext.resetSearch({ searchTerm: text });
57
+ }
58
+
59
+ onClose();
60
+ event.preventDefault();
61
+ };
62
+
63
+ const onClickHandler = (suggestion) => {
64
+ if (window?.searchContext?.resetSearch) {
65
+ window.searchContext.resetSearch({ searchTerm: suggestion });
66
+ }
67
+
68
+ onClose();
69
+ };
70
+
71
+ return (
72
+ <div id="search-box" ref={nodeRef}>
73
+ <div className="wrapper">
74
+ <Container>
75
+ <form method="get" onSubmit={onSubmit}>
76
+ <Input
77
+ ref={searchInputRef}
78
+ className="icon search"
79
+ action={{
80
+ className: 'icon ri-search-line',
81
+ 'aria-label': 'Submit search',
82
+ onClick: onSubmit,
83
+ onKeyDown: (event) => {
84
+ handleEnterKeyPress(event, onSubmit);
85
+ },
86
+ }}
87
+ placeholder={placeholder}
88
+ fluid
89
+ />
90
+ </form>
91
+ {searchSuggestions && suggestions.length > 0 && (
92
+ <div className="search-suggestions">
93
+ {suggestionsTitle && <h4>{suggestionsTitle}</h4>}
94
+
95
+ <List>
96
+ {visibleSuggestions.map((item, i) => {
97
+ return (
98
+ <List.Item key={i}>
99
+ <Link
100
+ to={`${path}?q=${item}`}
101
+ onClick={() => onClickHandler(item)}
102
+ >
103
+ {item}
104
+ </Link>
105
+ </List.Item>
106
+ );
107
+ })}
108
+ </List>
109
+ </div>
110
+ )}
111
+ </Container>
112
+ {buttonTitle && (
113
+ <div className="advanced-search">
114
+ <Container>
115
+ <div>{description}</div>
116
+ <a
117
+ href={buttonUrl || defaultView[0].path}
118
+ className="ui button white inverted"
119
+ title="Advanced search"
120
+ >
121
+ {buttonTitle}
122
+ </a>
123
+ </Container>
124
+ </div>
125
+ )}
126
+ </div>
127
+ </div>
128
+ );
129
+ }
130
+
131
+ export default withRouter(HeaderSearchPopUp);
@@ -121,7 +121,7 @@ export const PublishedModifiedInfo = (props) => {
121
121
  export const DocumentsList = (props) => {
122
122
  const { content } = props;
123
123
  const files = content.cca_files;
124
- if (files.length === 0) {
124
+ if (!files || files.length === 0) {
125
125
  return null;
126
126
  }
127
127
 
package/src/index.js CHANGED
@@ -310,5 +310,3 @@ const applyConfig = (config) => {
310
310
  };
311
311
 
312
312
  export default applyConfig;
313
-
314
- // ('emit("_all_"); def clusters_settings = [["name": "News", "values": ["News","Article"]],["name": "Publications", "values": ["Report","Indicator","Briefing","Topic page","Country fact sheet"]],["name": "Maps and charts", "values": ["Figure (chart/map)","Chart (interactive)","Infographic","Dashboard","Map (interactive)"]],["name": "Data", "values": ["Data set"]],["name": "Others", "values": ["Webpage","Organisation","FAQ","Video","Contract opportunity","Glossary term","Collection","File","Adaptation option","Guidance","Research and knowledge project","Information portal","Tool","Case study","External data reference","Publication reference"]]]; def vals = doc[\'objectProvides\']; def clusters = [\'All\']; for (val in vals) { for (cs in clusters_settings) { if (cs.values.contains(val)) { emit(cs.name) } } }');
@@ -23,6 +23,38 @@ p.has--clear--both:empty {
23
23
  display: block;
24
24
  }
25
25
 
26
+ // Add icon for external links
27
+ #page-document {
28
+ a[rel='noopener noreferrer']:not(.ui.button),
29
+ a[target='_blank']:not(.ui.button) {
30
+ &:before {
31
+ position: relative;
32
+ top: 2px;
33
+ margin-right: 3px;
34
+ color: inherit;
35
+ content: '\ecaf';
36
+ font-family: remixicon;
37
+ font-size: 1em;
38
+ }
39
+
40
+ &:visited {
41
+ &:before {
42
+ // fix visited link icon color
43
+ // doesn't work with color: inherit
44
+ color: @linkVisitedColor;
45
+ }
46
+ }
47
+
48
+ &:active {
49
+ &:before {
50
+ // fix active link icon color
51
+ // doesn't work with color: inherit
52
+ color: @linkActiveColor;
53
+ }
54
+ }
55
+ }
56
+ }
57
+
26
58
  // Adaptation option view styles
27
59
  div.adaptation-option-view,
28
60
  div.case-study-view {
@@ -126,10 +158,6 @@ body.subsite-mkh {
126
158
  }
127
159
  }
128
160
 
129
- .eea.header .eea-logo {
130
- max-width: 252px;
131
- }
132
-
133
161
  .subfooter .footer-description {
134
162
  margin-top: 3em;
135
163
  margin-bottom: 1em;
@@ -147,12 +175,6 @@ body.subsite-mkh {
147
175
  }
148
176
 
149
177
  .main-menu {
150
- .ui.text.menu {
151
- position: absolute;
152
- right: 80px;
153
- width: 120% !important;
154
- }
155
-
156
178
  .item a {
157
179
  color: @blue-1;
158
180
  }
@@ -171,24 +193,6 @@ body.subsite-mkh {
171
193
  background-color: @green-1;
172
194
  }
173
195
 
174
- .eea.header .subsite-logo {
175
- z-index: 1;
176
- height: 100%;
177
-
178
- .logo {
179
- display: inline-block;
180
- height: 100%;
181
-
182
- img {
183
- max-width: 320px;
184
- }
185
- }
186
-
187
- &:before {
188
- background-color: transparent;
189
- }
190
- }
191
-
192
196
  .eea.banner {
193
197
  background: @green-1;
194
198
  }
@@ -209,12 +213,6 @@ body.subsite-mkh {
209
213
  }
210
214
  }
211
215
 
212
- @media only screen and (min-width: @largestSmallMonitor) {
213
- .eea.header .subsite-logo {
214
- left: 280px !important;
215
- }
216
- }
217
-
218
216
  body.view-viewview .full {
219
217
  position: relative !important;
220
218
  right: 50%;
@@ -294,28 +292,98 @@ body.view-viewview.has-toolbar:not(.has-sidebar):not(.has-sidebar-collapsed) {
294
292
  /* Mission: Latest news updates, Latest events */
295
293
  body.subsite-root.section-mission {
296
294
  div.columns-view {
297
- div.block.listing.default {
295
+ div.block.listing.default,
296
+ div.block.listing.summary {
298
297
  div.items {
299
298
  div.listing-item {
300
- h4 {
301
- padding-left: 1.5em;
302
- margin-bottom: 0.5em;
303
-
304
- &:before {
305
- position: absolute;
306
- left: 0;
307
- content: '\ea6c';
308
- font-family: remixicon !important;
309
- font-style: normal;
310
- font-weight: normal;
311
- }
312
- }
313
- padding-bottom: 0.5em;
314
299
  border: none;
315
300
  margin-bottom: 0px !important;
316
301
  }
302
+
303
+ .listing-body {
304
+ position: relative;
305
+ padding-left: 2em;
306
+
307
+ &:before {
308
+ position: absolute;
309
+ top: -5px;
310
+ left: 0;
311
+ color: inherit;
312
+ content: '\ea6c';
313
+ font-family: remixicon !important;
314
+ font-size: 1.5em;
315
+ }
316
+ }
317
317
  margin-top: 1.5em;
318
318
  }
319
319
  }
320
+
321
+ div.block.listing.summary {
322
+ .u-item {
323
+ padding: 0 0 0.8em 0;
324
+ }
325
+ }
326
+
327
+ div.block.listing.default {
328
+ div.listing-item {
329
+ padding-bottom: 0.5em;
330
+
331
+ h4 {
332
+ margin-bottom: 0.5em;
333
+ }
334
+ }
335
+ }
320
336
  }
321
337
  }
338
+
339
+ body.subsite {
340
+ .eea.header {
341
+ .logo-wrapper {
342
+ .eea-logo {
343
+ max-width: 252px;
344
+ }
345
+ }
346
+
347
+ .subsite-logo {
348
+ position: relative !important;
349
+ top: auto !important;
350
+ left: auto !important;
351
+ transform: none !important;
352
+
353
+ .logo {
354
+ img {
355
+ max-width: 320px;
356
+ }
357
+ }
358
+
359
+ &:before {
360
+ background-color: transparent !important;
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ #main .main.bar {
367
+ .ui.container {
368
+ .ui.text.menu {
369
+ width: 100%;
370
+ }
371
+ }
372
+ }
373
+
374
+ .main-menu {
375
+ align-items: normal !important;
376
+ column-gap: 1em;
377
+
378
+ > .header-wrapper {
379
+ display: flex;
380
+ width: 100%;
381
+ flex-direction: column;
382
+ justify-content: space-between;
383
+ }
384
+ }
385
+
386
+ .header-actions {
387
+ display: flex;
388
+ margin-right: auto;
389
+ }