@eeacms/volto-n2k 1.0.20 → 1.0.22

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.
@@ -1,524 +0,0 @@
1
- /**
2
- * Navigation components.
3
- * @module components/theme/Navigation/Navigation
4
- */
5
-
6
- import React, { Component } from 'react';
7
- import PropTypes from 'prop-types';
8
- import { connect } from 'react-redux';
9
- import { compose } from 'redux';
10
- import { matchPath, withRouter } from 'react-router';
11
- import { Link } from 'react-router-dom';
12
- import { defineMessages, injectIntl } from 'react-intl';
13
- import { Menu, Dropdown } from 'semantic-ui-react';
14
- import cx from 'classnames';
15
- import {
16
- getBaseUrl,
17
- flattenToAppURL,
18
- hasApiExpander,
19
- } from '@plone/volto/helpers';
20
- import { UniversalLink } from '@plone/volto/components';
21
- import qs from 'querystring';
22
- import { getNavigation } from '@plone/volto/actions';
23
- import config from '@plone/volto/registry';
24
- import { withLocalStorage } from '@eeacms/volto-n2k/hocs';
25
- import LanguageSelector from '../LanguageSelector/LanguageSelector';
26
-
27
- const dropdownBlacklist = ['/natura2000'];
28
-
29
- const messages = defineMessages({
30
- closeMobileMenu: {
31
- id: 'Close menu',
32
- defaultMessage: 'Close menu',
33
- },
34
- openMobileMenu: {
35
- id: 'Open menu',
36
- defaultMessage: 'Open menu',
37
- },
38
- });
39
-
40
- const Hamburger = (props) => (
41
- <div className="hamburger-wrapper mobile only">
42
- <button
43
- className={cx('hamburger hamburger--collapse', {
44
- 'is-active': props.isMobileMenuOpen,
45
- })}
46
- aria-label={
47
- props.isMobileMenuOpen
48
- ? props.intl.formatMessage(messages.closeMobileMenu, {
49
- type: props.type,
50
- })
51
- : props.intl.formatMessage(messages.openMobileMenu, {
52
- type: props.type,
53
- })
54
- }
55
- title={
56
- props.isMobileMenuOpen
57
- ? props.intl.formatMessage(messages.closeMobileMenu, {
58
- type: props.type,
59
- })
60
- : props.intl.formatMessage(messages.openMobileMenu, {
61
- type: props.type,
62
- })
63
- }
64
- type="button"
65
- onClick={props.toggleMobileMenu}
66
- >
67
- <span className="hamburger-box">
68
- <span className="hamburger-inner" />
69
- </span>
70
- </button>
71
- </div>
72
- );
73
-
74
- /**
75
- * Navigation container class.
76
- * @class Navigation
77
- * @extends Component
78
- */
79
- class Navigation extends Component {
80
- /**
81
- * Property types.
82
- * @property {Object} propTypes Property types.
83
- * @static
84
- */
85
- static propTypes = {
86
- getNavigation: PropTypes.func.isRequired,
87
- pathname: PropTypes.string.isRequired,
88
- items: PropTypes.arrayOf(
89
- PropTypes.shape({
90
- title: PropTypes.string,
91
- url: PropTypes.string,
92
- items: PropTypes.array,
93
- }),
94
- ).isRequired,
95
- };
96
-
97
- static defaultProps = {
98
- token: null,
99
- };
100
-
101
- /**
102
- * Constructor
103
- * @method constructor
104
- * @param {Object} props Component properties
105
- * @constructs Navigation
106
- */
107
- constructor(props) {
108
- super(props);
109
- this.toggleMobileMenu = this.toggleMobileMenu.bind(this);
110
- this.closeMobileMenu = this.closeMobileMenu.bind(this);
111
- this.state = {
112
- isMobileMenuOpen: false,
113
- isSdf: false,
114
- };
115
- this.container = React.createRef();
116
- }
117
-
118
- /**
119
- * Component will mount
120
- * @method componentWillMount
121
- * @returns {undefined}
122
- */
123
- componentDidMount() {
124
- const { settings } = config;
125
- if (!hasApiExpander('navigation', getBaseUrl(this.props.pathname))) {
126
- this.props.getNavigation(
127
- getBaseUrl(this.props.pathname),
128
- settings.navDepth,
129
- );
130
- }
131
- this.setState({
132
- isSdf: this.isSdf(),
133
- });
134
- }
135
-
136
- handleClickOutsideNav = (event) => {
137
- if (
138
- this.container.current &&
139
- !this.container.current.contains(event.target)
140
- ) {
141
- this.setState({
142
- isMobileMenuOpen: false,
143
- });
144
- }
145
- };
146
-
147
- /**
148
- * Component will receive props
149
- * @method componentWillReceiveProps
150
- * @param {Object} nextProps Next properties
151
- * @returns {undefined}
152
- */
153
- UNSAFE_componentWillReceiveProps(nextProps) {
154
- const { settings } = config;
155
- if (
156
- nextProps.pathname !== this.props.pathname ||
157
- nextProps.token !== this.props.token
158
- ) {
159
- if (!hasApiExpander('navigation', getBaseUrl(this.props.pathname))) {
160
- this.props.getNavigation(
161
- getBaseUrl(nextProps.pathname),
162
- settings.navDepth,
163
- );
164
- }
165
- this.closeMobileMenu();
166
- }
167
-
168
- // Hide submenu on route change
169
- if (document.querySelector('body')) {
170
- document.querySelector('body').click();
171
- }
172
- }
173
-
174
- /**
175
- * Check if menu is active
176
- * @method isActive
177
- * @param {string} url Url of the navigation item.
178
- * @returns {bool} Is menu active?
179
- */
180
- isActive(url) {
181
- return (
182
- (url === '' && this.props.pathname === '/') ||
183
- // (url !== '' && isMatch(this.props.pathname.split('/'), url.split('/')))
184
- (url !== '' && url === this.props.pathname)
185
- );
186
- }
187
-
188
- /**
189
- * Check if page si sdf
190
- * @method isSdf
191
- * @returns {bool} Is sdf?
192
- */
193
- isSdf() {
194
- if (
195
- matchPath(this.props.pathname, {
196
- path: config.settings.sdf,
197
- exact: true,
198
- strict: false,
199
- })
200
- ) {
201
- return true;
202
- }
203
- return false;
204
- }
205
-
206
- /**
207
- * Toggle mobile menu's open state
208
- * @method toggleMobileMenu
209
- * @returns {undefined}
210
- */
211
- toggleMobileMenu() {
212
- this.setState({ isMobileMenuOpen: !this.state.isMobileMenuOpen }, () => {
213
- if (this.state.isMobileMenuOpen) {
214
- document.addEventListener('mousedown', this.handleClickOutsideNav);
215
- }
216
- });
217
- }
218
-
219
- /**
220
- * Close mobile menu
221
- * @method closeMobileMenu
222
- * @returns {undefined}
223
- */
224
- closeMobileMenu() {
225
- if (!this.state.isMobileMenuOpen) {
226
- return;
227
- }
228
- this.setState({ isMobileMenuOpen: false }, () => {
229
- document.removeEventListener('mousedown', this.handleClickOutsideNav);
230
- });
231
- }
232
-
233
- /**
234
- * Render method.
235
- * @method render
236
- * @returns {string} Markup for the component.
237
- */
238
- render() {
239
- const params = {
240
- ...this.props.match.params,
241
- ...(this.props.route_parameters || {}),
242
- ...qs.parse(this.props.location.search.replace('?', '')),
243
- };
244
-
245
- return (
246
- <nav
247
- className={cx('navigation', this.props.className || '')}
248
- ref={this.container}
249
- >
250
- <div className="mobile only tablet only computer only">
251
- <Hamburger
252
- {...this.props}
253
- isMobileMenuOpen={this.state.isMobileMenuOpen}
254
- toggleMobileMenu={this.toggleMobileMenu}
255
- />
256
- </div>
257
- <Menu
258
- stackable
259
- pointing
260
- secondary
261
- className={cx({
262
- open: this.state.isMobileMenuOpen,
263
- 'is-sdf': this.state.isSdf,
264
- 'is-sticky': this.state.isSdf || this.props.isSticky,
265
- 'mobile hidden tablet hidden computer hidden': !this.state
266
- .isMobileMenuOpen,
267
- 'mobile only tablet only computer only': this.state
268
- .isMobileMenuOpen,
269
- })}
270
- onClick={this.closeMobileMenu}
271
- onBlur={() => this.closeMobileMenu}
272
- >
273
- {this.state.isSdf ? (
274
- <>
275
- <button
276
- to={this.props.pathname}
277
- title="At a glance"
278
- className="item firstLevel at-glance"
279
- onClick={() => {
280
- window.scrollTo({
281
- top: document.body.scrollHeight,
282
- behavior: 'smooth',
283
- });
284
- }}
285
- >
286
- AT A GLANCE
287
- </button>
288
-
289
- <UniversalLink
290
- href={
291
- params.site_code
292
- ? `https://natura2000.eea.europa.eu/Natura2000/SDF.aspx?site=${params.site_code}`
293
- : '#'
294
- }
295
- openLinkInNewTab={true}
296
- title="Go to expert view"
297
- className="item firstLevel deep-dive"
298
- >
299
- GO TO EXPERT VIEW
300
- </UniversalLink>
301
- </>
302
- ) : (
303
- ''
304
- )}
305
-
306
- {(this.props.isRoot || this.props.isExplorer) && (
307
- <>
308
- {this.props.biseItems
309
- .filter((item) => !['/natura2000'].includes(item.url))
310
- .map((item) => {
311
- const flatUrl = flattenToAppURL(item.url);
312
- const itemID = item.title.split(' ').join('-').toLowerCase();
313
- return item.items &&
314
- item.items.length &&
315
- !dropdownBlacklist.includes(item.url) ? (
316
- <Dropdown
317
- id={itemID}
318
- className={
319
- this.isActive(flatUrl)
320
- ? 'item firstLevel menuActive'
321
- : 'item firstLevel'
322
- }
323
- key={flatUrl}
324
- trigger={
325
- <Link to={flatUrl === '' ? '/' : flatUrl} key={flatUrl}>
326
- {item.title}
327
- </Link>
328
- }
329
- item
330
- simple
331
- >
332
- {item.title === 'Countries' ? (
333
- <Dropdown.Menu>
334
- <div className="submenu-wrapper">
335
- <div className="submenu countries-submenu">
336
- {item.items.map((subsubitem) => {
337
- const flatSubSubUrl = flattenToAppURL(
338
- subsubitem.url,
339
- );
340
- return (
341
- <Link
342
- to={
343
- flatSubSubUrl === '' ? '/' : flatSubSubUrl
344
- }
345
- title={subsubitem.title}
346
- key={flatSubSubUrl}
347
- className={
348
- this.isActive(flatSubSubUrl)
349
- ? 'item thirdLevel menuActive'
350
- : 'item thirdLevel'
351
- }
352
- >
353
- {subsubitem.title}
354
- </Link>
355
- );
356
- })}
357
- </div>
358
- </div>
359
- </Dropdown.Menu>
360
- ) : (
361
- <Dropdown.Menu>
362
- {item.items.map((subitem) => {
363
- const flatSubUrl = flattenToAppURL(subitem.url);
364
- const subItemID = subitem.title
365
- .split(' ')
366
- .join('-')
367
- .toLowerCase();
368
- return (
369
- <Dropdown.Item key={flatSubUrl}>
370
- <div className="secondLevel-wrapper">
371
- <Link
372
- id={subItemID}
373
- to={flatSubUrl === '' ? '/' : flatSubUrl}
374
- key={flatSubUrl}
375
- className={
376
- this.isActive(flatSubUrl)
377
- ? 'item secondLevel menuActive'
378
- : 'item secondLevel'
379
- }
380
- >
381
- {subitem.title}
382
- </Link>
383
- </div>
384
- {subitem.items && (
385
- <div className="submenu-wrapper">
386
- <div className="submenu">
387
- {subitem.items.map((subsubitem) => {
388
- const flatSubSubUrl = flattenToAppURL(
389
- subsubitem.url,
390
- );
391
- return (
392
- <Link
393
- to={
394
- flatSubSubUrl === ''
395
- ? '/'
396
- : flatSubSubUrl
397
- }
398
- title={subsubitem.title}
399
- key={flatSubSubUrl}
400
- className={
401
- this.isActive(flatSubSubUrl)
402
- ? 'item thirdLevel menuActive'
403
- : 'item thirdLevel'
404
- }
405
- >
406
- {subsubitem.title}
407
- </Link>
408
- );
409
- })}
410
- </div>
411
- </div>
412
- )}
413
- </Dropdown.Item>
414
- );
415
- })}
416
- </Dropdown.Menu>
417
- )}
418
- </Dropdown>
419
- ) : (
420
- <div
421
- key={flatUrl}
422
- className={
423
- this.isActive(flatUrl)
424
- ? 'item menuActive firstLevel'
425
- : 'item firstLevel'
426
- }
427
- >
428
- <Link to={flatUrl === '' ? '/' : flatUrl}>
429
- {item.title}
430
- </Link>
431
- </div>
432
- );
433
- })}
434
- </>
435
- )}
436
-
437
- {!this.state.isSdf && !this.props.isExplorer && !this.props.isRoot
438
- ? this.props.items.map((item) => {
439
- const flatUrl = flattenToAppURL(item.url);
440
- return (
441
- <div
442
- key={flatUrl}
443
- className={cx('item firstLevel', {
444
- menuActive: this.isActive(flatUrl),
445
- })}
446
- role="listbox"
447
- tabIndex={0}
448
- aria-expanded={false}
449
- >
450
- <Link to={flatUrl === '' ? '/' : flatUrl}>
451
- {item.title}
452
- </Link>
453
- </div>
454
- );
455
- })
456
- : ''}
457
-
458
- {!this.state.isSdf && !this.state.isExplorer ? (
459
- <Menu.Item className="firstLevel language-selector-wrapper mobile only tablet only computer only">
460
- <LanguageSelector navigation={this.props.navigation} />
461
- </Menu.Item>
462
- ) : (
463
- ''
464
- )}
465
- </Menu>
466
- </nav>
467
- );
468
- }
469
- }
470
-
471
- const getBiseItems = (items) => {
472
- if (__SERVER__) return [];
473
- return items.filter((item) => item.url !== '/natura2000') || [];
474
- };
475
-
476
- const getN2kItems = (items, localStorage) => {
477
- if (__SERVER__) return [];
478
- const currentLang = localStorage.get('N2K_LANGUAGE');
479
- let navItems = [];
480
- const natura2000 =
481
- items.filter((item) => item.url === '/natura2000')?.[0]?.items || [];
482
- natura2000.forEach((item) => {
483
- const languageFolder = matchPath(item.url, {
484
- path: config.settings.n2k.multilingualRoot,
485
- exact: true,
486
- strict: false,
487
- });
488
- if (languageFolder && languageFolder.params.lang === currentLang) {
489
- navItems = [...navItems, ...(item.items || [])];
490
- }
491
- if (
492
- languageFolder &&
493
- !config.settings.n2k.supportedLanguages.includes(
494
- languageFolder.params.lang,
495
- ) &&
496
- item.url !== '/natura2000/sites' &&
497
- item.url !== '/natura2000/habitats' &&
498
- item.url !== '/natura2000/species' &&
499
- item.url !== '/natura2000/copyright-notice'
500
- ) {
501
- navItems.push(item);
502
- }
503
- });
504
-
505
- return navItems;
506
- };
507
-
508
- export default compose(
509
- withRouter,
510
- withLocalStorage,
511
- injectIntl,
512
- connect(
513
- (state, props) => {
514
- return {
515
- token: state.userSession.token,
516
- route_parameters: state.route_parameters,
517
- navigation: state.navigation,
518
- items: getN2kItems(state.navigation.items, props.localStorage),
519
- biseItems: getBiseItems(state.navigation.items),
520
- };
521
- },
522
- { getNavigation },
523
- ),
524
- )(Navigation);