@eeacms/volto-eea-website-theme 3.1.0 → 3.3.0

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,11 +4,28 @@ 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
- ### [3.1.0](https://github.com/eea/volto-eea-website-theme/compare/3.0.0...3.1.0) - 5 November 2024
7
+ ### [3.3.0](https://github.com/eea/volto-eea-website-theme/compare/3.2.0...3.3.0) - 28 November 2024
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix(tests): add unit tests for ReportNavigation [ana-oprea - [`55ac4c2`](https://github.com/eea/volto-eea-website-theme/commit/55ac4c2a1edf0c8abdb83a2c7e3c7d578464708a)]
12
+
13
+ #### :house: Internal changes
14
+
15
+ - chore: package.json [alin - [`4a8a4cb`](https://github.com/eea/volto-eea-website-theme/commit/4a8a4cb014db839b90eceed935c97b85785ddf71)]
16
+
17
+ #### :hammer_and_wrench: Others
18
+
19
+ - Update package.json [Ichim David - [`53be025`](https://github.com/eea/volto-eea-website-theme/commit/53be025c116dfc71a2de708075e4e77262eeecf8)]
20
+ ### [3.2.0](https://github.com/eea/volto-eea-website-theme/compare/3.1.0...3.2.0) - 14 November 2024
21
+
22
+ #### :hammer_and_wrench: Others
23
+
24
+ - Update package.json [Ichim David - [`4b5073b`](https://github.com/eea/volto-eea-website-theme/commit/4b5073b60a7d144597363f89d5cba6357baa9e19)]
25
+ ### [3.1.0](https://github.com/eea/volto-eea-website-theme/compare/3.0.0...3.1.0) - 6 November 2024
8
26
 
9
27
  #### :hammer_and_wrench: Others
10
28
 
11
- - fix Grid conversion, ref #278618 [Miu Razvan - [`f80b786`](https://github.com/eea/volto-eea-website-theme/commit/f80b7869b4ac060bf35250cf5045ab930a0003de)]
12
29
  ## [3.0.0](https://github.com/eea/volto-eea-website-theme/compare/2.4.0...3.0.0) - 21 October 2024
13
30
 
14
31
  #### :nail_care: Enhancements
@@ -115,7 +132,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
115
132
  #### :hammer_and_wrench: Others
116
133
 
117
134
  - modified param of dateIsInFuture to signal it's a string and not a date [David Ichim - [`e3243a0`](https://github.com/eea/volto-eea-website-theme/commit/e3243a075969a379462ba0b7889fe0d8b52af62a)]
118
- ## [2.0.0](https://github.com/eea/volto-eea-website-theme/compare/1.34.0...2.0.0) - 13 May 2024
135
+ ## [2.0.0](https://github.com/eea/volto-eea-website-theme/compare/1.35.0...2.0.0) - 13 May 2024
119
136
 
120
137
  #### :rocket: New Features
121
138
 
@@ -140,6 +157,18 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
140
157
  #### :hammer_and_wrench: Others
141
158
 
142
159
  - Bump package version to 2.0.0 to signal major release due to Volto 17 jump [David Ichim - [`ffe3049`](https://github.com/eea/volto-eea-website-theme/commit/ffe3049b3b656093a44f05044dbe7cd63bac495f)]
160
+ ### [1.35.0](https://github.com/eea/volto-eea-website-theme/compare/1.34.0...1.35.0) - 6 November 2024
161
+
162
+ #### :bug: Bug Fixes
163
+
164
+ - fix(slate) - fix console error on list element - ref #278427 [kreafox - [`2ea7dd1`](https://github.com/eea/volto-eea-website-theme/commit/2ea7dd1488782db09ca3b8636a932930326f1175)]
165
+ - fix: cypress 13.1.0 [kreafox - [`0636604`](https://github.com/eea/volto-eea-website-theme/commit/0636604cf023730927c0006dcb146d5f9e086f1c)]
166
+
167
+ #### :hammer_and_wrench: Others
168
+
169
+ - Revert "Release 1.35.0" [alin - [`dcc318a`](https://github.com/eea/volto-eea-website-theme/commit/dcc318a5082e3c7fff44d00bfd9bb140c4efc21a)]
170
+ - Revert "1.35.1" [alin - [`6012bc6`](https://github.com/eea/volto-eea-website-theme/commit/6012bc68cf7a692fc964610a6071c82d4211fb2e)]
171
+ - Release 1.35.0 [kreafox - [`e21bee6`](https://github.com/eea/volto-eea-website-theme/commit/e21bee61e55e6c6b46d0119059f7a6063b0edad8)]
143
172
  ### [1.34.0](https://github.com/eea/volto-eea-website-theme/compare/1.33.2...1.34.0) - 9 May 2024
144
173
 
145
174
  #### :bug: Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-website-theme",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "@eeacms/volto-eea-website-theme: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -4,18 +4,15 @@ import React from 'react';
4
4
  import { defineMessages, useIntl } from 'react-intl';
5
5
  import { withRouter } from 'react-router';
6
6
  import { compose } from 'redux';
7
- import { Accordion } from 'semantic-ui-react';
7
+ import { Accordion, Icon } from 'semantic-ui-react';
8
8
 
9
9
  import Slugger from 'github-slugger';
10
10
 
11
- import { Icon, UniversalLink } from '@plone/volto/components';
11
+ import { UniversalLink, MaybeWrap } from '@plone/volto/components';
12
12
  import { withContentNavigation } from '@plone/volto/components/theme/Navigation/withContentNavigation';
13
13
  import withEEASideMenu from '@eeacms/volto-block-toc/hocs/withEEASideMenu';
14
14
  import { flattenToAppURL } from '@plone/volto/helpers';
15
15
 
16
- import downIcon from '@plone/volto/icons/down-key.svg';
17
- import upIcon from '@plone/volto/icons/up-key.svg';
18
-
19
16
  const messages = defineMessages({
20
17
  navigation: {
21
18
  id: 'Navigation',
@@ -33,6 +30,8 @@ const AccordionNavigation = ({
33
30
  const navOpen = ['mobile', 'tablet'].includes(device) ? false : true;
34
31
  const [isNavOpen, setIsNavOpen] = React.useState(navOpen);
35
32
  const [activeItems, setActiveItems] = React.useState({});
33
+ const contextNavigationListRef = React.useRef(null);
34
+ const summaryRef = React.useRef(null);
36
35
 
37
36
  const onClickSummary = React.useCallback((e) => {
38
37
  e.preventDefault();
@@ -43,6 +42,26 @@ const AccordionNavigation = ({
43
42
  if (isMenuOpenOnOutsideClick === false) setIsNavOpen(false);
44
43
  }, [isMenuOpenOnOutsideClick]);
45
44
 
45
+ React.useEffect(() => {
46
+ if (!navOpen) {
47
+ const handleOutsideClick = (event) => {
48
+ if (
49
+ summaryRef.current &&
50
+ contextNavigationListRef.current &&
51
+ !summaryRef.current.contains(event.target) &&
52
+ !contextNavigationListRef.current.contains(event.target)
53
+ ) {
54
+ setIsNavOpen(false);
55
+ }
56
+ };
57
+
58
+ document.addEventListener('click', handleOutsideClick);
59
+ return () => {
60
+ document.removeEventListener('click', handleOutsideClick);
61
+ };
62
+ }
63
+ }, [summaryRef, navOpen]);
64
+
46
65
  const onKeyDownSummary = React.useCallback(
47
66
  (e) => {
48
67
  if (e.keyCode === 13 || e.keyCode === 32) {
@@ -95,7 +114,11 @@ const AccordionNavigation = ({
95
114
  id={`accordion-title-${normalizedTitle}`}
96
115
  >
97
116
  <span className="title-text">{title}</span>
98
- <Icon name={isActive ? upIcon : downIcon} size="32px" />
117
+ <Icon
118
+ className={
119
+ isActive ? 'ri-arrow-up-s-line' : 'ri-arrow-down-s-line'
120
+ }
121
+ />
99
122
  </Accordion.Title>
100
123
  <Accordion.Content
101
124
  active={isActive}
@@ -127,20 +150,40 @@ const AccordionNavigation = ({
127
150
 
128
151
  return items.length ? (
129
152
  <>
130
- <nav className="context-navigation">
153
+ <nav className="context-navigation" aria-label={title}>
131
154
  <details open={isNavOpen}>
132
155
  {/* eslint-disable-next-line */}
133
156
  <summary
134
157
  className="context-navigation-header accordion-header"
135
158
  onClick={onClickSummary}
136
159
  onKeyDown={onKeyDownSummary}
160
+ ref={summaryRef}
137
161
  >
138
- {has_custom_name ? title : intl.formatMessage(messages.navigation)}
139
- <Icon name={isNavOpen ? upIcon : downIcon} size="40px" />
162
+ <MaybeWrap
163
+ condition={!navOpen}
164
+ className="ui container d-flex flex-items-center"
165
+ >
166
+ {has_custom_name
167
+ ? title
168
+ : intl.formatMessage(messages.navigation)}
169
+ <Icon
170
+ className={
171
+ isNavOpen ? 'ri-arrow-up-s-line' : 'ri-arrow-down-s-line'
172
+ }
173
+ />
174
+ </MaybeWrap>
140
175
  </summary>
141
- <ul className="context-navigation-list accordion-list">
142
- {items.map((item) => renderItems({ item }))}
143
- </ul>
176
+ <MaybeWrap
177
+ condition={!navOpen}
178
+ className="ui container d-flex flex-items-center"
179
+ >
180
+ <ul
181
+ className="context-navigation-list accordion-list"
182
+ ref={contextNavigationListRef}
183
+ >
184
+ {items.map((item) => renderItems({ item }))}
185
+ </ul>
186
+ </MaybeWrap>
144
187
  </details>
145
188
  </nav>
146
189
  </>
@@ -174,6 +217,9 @@ export default compose(
174
217
  (WrappedComponent) => (props) =>
175
218
  withEEASideMenu(WrappedComponent)({
176
219
  ...props,
220
+ targetParent: '.eea.header ',
221
+ fixedVisibilitySwitchTarget: '.main.bar',
222
+ insertBeforeOnMobile: '.banner',
177
223
  shouldRender: props.navigation?.items?.length > 0,
178
224
  }),
179
225
  )(AccordionNavigation);
@@ -0,0 +1,144 @@
1
+ import PropTypes from 'prop-types';
2
+ import React from 'react';
3
+ import { Link as RouterLink } from 'react-router-dom';
4
+ import cx from 'classnames';
5
+ import { compose } from 'redux';
6
+ import { withRouter } from 'react-router';
7
+ import { defineMessages, useIntl } from 'react-intl';
8
+
9
+ import { flattenToAppURL } from '@plone/volto/helpers';
10
+ import { UniversalLink, MaybeWrap } from '@plone/volto/components';
11
+ import { withContentNavigation } from '@plone/volto/components/theme/Navigation/withContentNavigation';
12
+
13
+ const messages = defineMessages({
14
+ navigation: {
15
+ id: 'Navigation',
16
+ defaultMessage: 'Navigation',
17
+ },
18
+ });
19
+
20
+ /**
21
+ * Handles click on summary links and closes parent details elements
22
+ * @param {Event} e - Click event
23
+ * @param {boolean} wrapWithDetails - Whether the element is wrapped in details
24
+ */
25
+ function handleSummaryClick(e, wrapWithDetails) {
26
+ if (wrapWithDetails) {
27
+ e.preventDefault();
28
+
29
+ const currentDetails = e.target.closest('details');
30
+ // toggle the current details
31
+ if (currentDetails) {
32
+ currentDetails.open = !currentDetails.open;
33
+ }
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Renders a navigation node as a list item with proper styling and links
39
+ * @param {Object} node - Navigation node object containing title, href, type etc
40
+ * @param {number} parentLevel - Parent level in navigation hierarchy
41
+ * @returns {React.Component} UL component with navigation node content
42
+ */
43
+ function renderNode(node, parentLevel) {
44
+ const level = parentLevel + 1;
45
+ const hasChildItems = node.items?.length;
46
+ const nodeType = node.type;
47
+ const isDocument = nodeType === 'document';
48
+ let wrapWithDetails = isDocument && level > 2;
49
+ return (
50
+ <li
51
+ key={node['@id']}
52
+ active={node.is_current}
53
+ className={`list-item level-${level}`}
54
+ >
55
+ <MaybeWrap
56
+ condition={wrapWithDetails}
57
+ as="details"
58
+ className="context-navigation-detail"
59
+ >
60
+ {nodeType !== 'link' ? (
61
+ <MaybeWrap
62
+ condition={wrapWithDetails}
63
+ as="summary"
64
+ className="context-navigation-summary"
65
+ >
66
+ <RouterLink
67
+ to={flattenToAppURL(node.href)}
68
+ tabIndex={wrapWithDetails ? '-1' : 0}
69
+ title={node.description}
70
+ className={cx(`list-link contenttype-${nodeType}`, {
71
+ in_path: node.is_in_path,
72
+ })}
73
+ onClick={(e) =>
74
+ wrapWithDetails && handleSummaryClick(e, wrapWithDetails)
75
+ }
76
+ >
77
+ {node.title}
78
+ {nodeType === 'file' && node.getObjSize
79
+ ? ' [' + node.getObjSize + ']'
80
+ : ''}
81
+ </RouterLink>
82
+ </MaybeWrap>
83
+ ) : (
84
+ <UniversalLink href={flattenToAppURL(node.href)}>
85
+ {node.title}
86
+ </UniversalLink>
87
+ )}
88
+ {(hasChildItems && (
89
+ <ul className="list">
90
+ {node.items.map((node) => renderNode(node, level))}
91
+ </ul>
92
+ )) ||
93
+ ''}
94
+ </MaybeWrap>
95
+ </li>
96
+ );
97
+ }
98
+ /**
99
+ * A navigation slot implementation, similar to the classic Plone navigation
100
+ * portlet. It uses the same API, so the options are similar to
101
+ * INavigationPortlet
102
+ */
103
+ export function ReportNavigation(props) {
104
+ const { navigation = {} } = props;
105
+ const { items = [] } = navigation;
106
+ const intl = useIntl();
107
+
108
+ return items.length ? (
109
+ <nav className="context-navigation smart-toc">
110
+ {navigation.has_custom_name ? (
111
+ <div className="context-navigation-header">
112
+ <RouterLink to={flattenToAppURL(navigation.url || '')}>
113
+ {navigation.title}
114
+ </RouterLink>
115
+ </div>
116
+ ) : (
117
+ <div className="context-navigation-header">
118
+ {intl.formatMessage(messages.navigation)}
119
+ </div>
120
+ )}
121
+ <ul className="list">{items.map((node) => renderNode(node, 0))}</ul>
122
+ </nav>
123
+ ) : (
124
+ ''
125
+ );
126
+ }
127
+
128
+ ReportNavigation.propTypes = {
129
+ /**
130
+ * Navigation tree returned from @contextnavigation restapi endpoint
131
+ */
132
+ navigation: PropTypes.shape({
133
+ items: PropTypes.arrayOf(
134
+ PropTypes.shape({
135
+ title: PropTypes.string,
136
+ url: PropTypes.string,
137
+ }),
138
+ ),
139
+ has_custom_name: PropTypes.bool,
140
+ title: PropTypes.string,
141
+ }),
142
+ };
143
+
144
+ export default compose(withRouter, withContentNavigation)(ReportNavigation);
@@ -0,0 +1,131 @@
1
+ import { render, fireEvent } from '@testing-library/react';
2
+ import { Provider } from 'react-intl-redux';
3
+ import { Router } from 'react-router-dom';
4
+ import { createMemoryHistory } from 'history';
5
+ import ReportNavigation from './ReportNavigation';
6
+ import configureStore from 'redux-mock-store';
7
+ import '@testing-library/jest-dom/extend-expect';
8
+
9
+ const mockStore = configureStore();
10
+ const store = mockStore({
11
+ intl: {
12
+ locale: 'en',
13
+ messages: {},
14
+ },
15
+ });
16
+
17
+ jest.mock(
18
+ '@plone/volto/components/theme/Navigation/withContentNavigation',
19
+ () => ({
20
+ withContentNavigation: (Component) => (props) => (
21
+ <Component {...props} navigation={mockNavigation} />
22
+ ),
23
+ }),
24
+ );
25
+
26
+ // Mock navigation data
27
+ const mockNavigation = {
28
+ items: [
29
+ {
30
+ '@id': '/item1',
31
+ title: 'Item 1',
32
+ href: '/item1',
33
+ type: 'document',
34
+ description: 'Item 1 description',
35
+ is_current: false,
36
+ is_in_path: false,
37
+ items: [
38
+ {
39
+ '@id': '/item1/subitem1',
40
+ title: 'Subitem 1',
41
+ href: '/item1/subitem1',
42
+ type: 'document',
43
+ is_current: false,
44
+ is_in_path: false,
45
+ items: [],
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ '@id': '/item2',
51
+ title: 'Item 2',
52
+ href: '/item2',
53
+ type: 'document',
54
+ description: 'Item 2 description',
55
+ is_current: true,
56
+ is_in_path: true,
57
+ items: [],
58
+ },
59
+ ],
60
+ has_custom_name: true,
61
+ title: 'Custom Navigation',
62
+ url: '/custom-navigation',
63
+ };
64
+
65
+ describe('ReportNavigation', () => {
66
+ it('renders navigation items correctly', () => {
67
+ const history = createMemoryHistory();
68
+ const { getByText } = render(
69
+ <Provider store={store}>
70
+ <Router history={history}>
71
+ <ReportNavigation />
72
+ </Router>
73
+ </Provider>,
74
+ );
75
+
76
+ // Check if the navigation header is rendered
77
+ expect(getByText('Custom Navigation')).toBeInTheDocument();
78
+
79
+ // Check if the navigation items are rendered
80
+ expect(getByText('Item 1')).toBeInTheDocument();
81
+ expect(getByText('Item 2')).toBeInTheDocument();
82
+ expect(getByText('Subitem 1')).toBeInTheDocument();
83
+ });
84
+
85
+ it('toggles details on summary click', () => {
86
+ const history = createMemoryHistory();
87
+ const { container } = render(
88
+ <Provider store={store}>
89
+ <Router history={history}>
90
+ <ReportNavigation />
91
+ </Router>
92
+ </Provider>,
93
+ );
94
+
95
+ const detailsElement = container.querySelector('a[href="/item1"]');
96
+
97
+ // Simulate click on summary
98
+ fireEvent.click(detailsElement);
99
+ });
100
+
101
+ it('renders links with correct href attributes', () => {
102
+ const history = createMemoryHistory();
103
+ const { getByText } = render(
104
+ <Provider store={store}>
105
+ <Router history={history}>
106
+ <ReportNavigation />
107
+ </Router>
108
+ </Provider>,
109
+ );
110
+
111
+ expect(getByText('Item 1').closest('a')).toHaveAttribute('href', '/item1');
112
+ expect(getByText('Subitem 1').closest('a')).toHaveAttribute(
113
+ 'href',
114
+ '/item1/subitem1',
115
+ );
116
+ });
117
+
118
+ it('applies active class to the current navigation item', () => {
119
+ const history = createMemoryHistory();
120
+ const { getByText } = render(
121
+ <Provider store={store}>
122
+ <Router history={history}>
123
+ <ReportNavigation />
124
+ </Router>
125
+ </Provider>,
126
+ );
127
+
128
+ const activeItem = getByText('Item 2');
129
+ expect(activeItem).toHaveClass('in_path');
130
+ });
131
+ });
@@ -1,5 +1,6 @@
1
1
  import Accordion from './Accordion';
2
2
  import Default from './Default';
3
+ import ReportNavigation from './ReportNavigation';
3
4
 
4
5
  const contextBlockVariations = [
5
6
  {
@@ -13,6 +14,11 @@ const contextBlockVariations = [
13
14
  title: 'Accordion',
14
15
  view: Accordion,
15
16
  },
17
+ {
18
+ id: 'report_navigation',
19
+ title: 'Additional files',
20
+ view: ReportNavigation,
21
+ },
16
22
  ];
17
23
 
18
24
  export default contextBlockVariations;
@@ -3,7 +3,6 @@ import View from './View';
3
3
  import DefaultTemplate from './variations/Default';
4
4
  import WebReport from './variations/WebReport';
5
5
  import WebReportPage from './variations/WebReportPage';
6
- import './variations/styles.less';
7
6
 
8
7
  const applyConfig = (config) => {
9
8
  config.blocks.blocksConfig.title = {
@@ -2,12 +2,20 @@ import alignTopSVG from '@plone/volto/icons/move-up.svg';
2
2
  import alignCenterSVG from '@plone/volto/icons/row.svg';
3
3
  import alignBottomSVG from '@plone/volto/icons/move-down.svg';
4
4
 
5
+ import alignTextLeftSVG from '@plone/volto/icons/align-left.svg';
6
+ import alignTextCenterSVG from '@plone/volto/icons/align-center.svg';
7
+
5
8
  const ALIGN_INFO_MAP_IMAGE_POSITION = {
6
9
  'has--bg--top': [alignTopSVG, 'Top'],
7
10
  'has--bg--center': [alignCenterSVG, 'Center'],
8
11
  'has--bg--bottom': [alignBottomSVG, 'Bottom'],
9
12
  };
10
13
 
14
+ const ALIGN_INFO_MAP_TEXT_ALIGN = {
15
+ 'has--text--left': [alignTextLeftSVG, 'Left'],
16
+ 'has--text--center': [alignTextCenterSVG, 'Center'],
17
+ };
18
+
11
19
  const infoSchema = {
12
20
  title: 'Info',
13
21
  fieldsets: [
@@ -145,8 +153,8 @@ const titleSchema = {
145
153
  copyrightPosition: {
146
154
  title: 'Align',
147
155
  widget: 'style_align',
148
- actions: ['left', 'right'],
149
- defaultValue: 'left',
156
+ actions: ['left', 'center', 'right'],
157
+ defaultValue: '',
150
158
  },
151
159
  styles: {
152
160
  widget: 'object',
@@ -156,7 +164,7 @@ const titleSchema = {
156
164
  {
157
165
  id: 'default',
158
166
  title: 'Default',
159
- fields: ['bg'],
167
+ fields: ['bg', 'textAlign'],
160
168
  },
161
169
  ],
162
170
  properties: {
@@ -167,6 +175,13 @@ const titleSchema = {
167
175
  actionsInfoMap: ALIGN_INFO_MAP_IMAGE_POSITION,
168
176
  defaultValue: 'has--bg--center',
169
177
  },
178
+ textAlign: {
179
+ title: 'Text align',
180
+ widget: 'style_align',
181
+ actions: Object.keys(ALIGN_INFO_MAP_TEXT_ALIGN),
182
+ actionsInfoMap: ALIGN_INFO_MAP_TEXT_ALIGN,
183
+ defaultValue: '',
184
+ },
170
185
  },
171
186
  required: [],
172
187
  },
@@ -38,11 +38,12 @@ const WebReport = (props) => {
38
38
  {...props}
39
39
  data={{
40
40
  ...props.data,
41
- hideContentType: true,
42
- aboveTitle: (
41
+ aboveTitle: !props.data.hideContentType ? (
43
42
  <div className="content-type">
44
43
  {props.data.content_type || props.properties.type_title}
45
44
  </div>
45
+ ) : (
46
+ ' '
46
47
  ),
47
48
  belowTitle: (
48
49
  <>
@@ -32,12 +32,13 @@ const WebReportPage = (props) => {
32
32
  {...props}
33
33
  data={{
34
34
  ...props.data,
35
- hideContentType: true,
36
35
  aboveTitle: (
37
36
  <>
38
- <div className="content-type">
39
- {props.data.content_type || props.properties.type_title}
40
- </div>
37
+ {!props.data.hideContentType && (
38
+ <div className="content-type">
39
+ {props.data.content_type || props.properties.type_title}
40
+ </div>
41
+ )}
41
42
  <div className="subtitle">{props.data.subtitle}</div>
42
43
  </>
43
44
  ),
@@ -300,7 +300,9 @@ const View = (props) => {
300
300
  <Banner.Metadata>
301
301
  <Banner.MetadataField
302
302
  type="type"
303
- hidden={hideContentType}
303
+ hidden={
304
+ hideContentType || props.variation.id.indexOf('report') !== -1
305
+ }
304
306
  value={type}
305
307
  />
306
308
  <Banner.MetadataField
@@ -3,7 +3,7 @@
3
3
  * @module components/theme/Breadcrumbs/Breadcrumbs
4
4
  */
5
5
 
6
- import React, { useEffect, useMemo } from 'react';
6
+ import React, { useEffect } from 'react';
7
7
  import { useDispatch, useSelector } from 'react-redux';
8
8
 
9
9
  import { useLocation } from 'react-router';
@@ -32,26 +32,17 @@ const isContentRoute = (pathname) => {
32
32
  const Breadcrumbs = (props) => {
33
33
  const dispatch = useDispatch();
34
34
  const { items = [], root = '/' } = useSelector((state) => state?.breadcrumbs);
35
- const content = useSelector((state) => state?.content?.data);
35
+ const token = useSelector((state) => state?.userSession?.token);
36
36
 
37
37
  // const pathname = useSelector((state) => state.location.pathname);
38
38
  const location = useLocation();
39
39
  const { pathname } = location;
40
-
41
- const linkLevels = useMemo(() => {
42
- if (content) {
43
- const type = content['@type'];
44
- const isContentTypesToAvoid =
45
- config.settings.contentTypeToAvoidAsLinks || {};
46
- if (isContentTypesToAvoid[type]) {
47
- return isContentTypesToAvoid[type];
48
- }
49
- }
50
- }, [content]);
40
+ const contentTypesAsBreadcrumbSection = !token
41
+ ? config.settings.contentTypesAsBreadcrumbSection
42
+ : [];
51
43
 
52
44
  const sections = items.map((item) => ({
53
- title: item.title,
54
- href: item.url,
45
+ ...item,
55
46
  key: item.title,
56
47
  }));
57
48
 
@@ -71,7 +62,7 @@ const Breadcrumbs = (props) => {
71
62
  pathname={pathname}
72
63
  sections={sections}
73
64
  root={root}
74
- linkLevels={linkLevels}
65
+ contentTypesAsBreadcrumbSection={contentTypesAsBreadcrumbSection}
75
66
  />
76
67
  </React.Fragment>
77
68
  );
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import renderer from 'react-test-renderer';
3
+ import configureStore from 'redux-mock-store';
4
+ import { Provider } from 'react-intl-redux';
5
+ import { MemoryRouter } from 'react-router-dom';
6
+ import config from '@plone/volto/registry';
7
+
8
+ import Breadcrumbs from './Breadcrumbs';
9
+
10
+ const mockStore = configureStore();
11
+
12
+ describe('Breadcrumbs', () => {
13
+ it('renders a breadcrumbs component', () => {
14
+ const store = mockStore({
15
+ breadcrumbs: {
16
+ items: [
17
+ { title: 'Blog', url: '/blog' },
18
+ { title: 'My first blog', url: '/blog/my-first-blog' },
19
+ ],
20
+ },
21
+ intl: {
22
+ locale: 'en',
23
+ messages: {},
24
+ },
25
+ });
26
+ const component = renderer.create(
27
+ <Provider store={store}>
28
+ <MemoryRouter>
29
+ <Breadcrumbs pathname="/blog" />
30
+ </MemoryRouter>
31
+ </Provider>,
32
+ );
33
+ const json = component.toJSON();
34
+ expect(json).toMatchSnapshot();
35
+ });
36
+
37
+ it('renders breadcrumbs with contentTypesAsBreadcrumbSection', () => {
38
+ // Mock the config settings
39
+ config.settings = {
40
+ ...config.settings,
41
+ contentTypesAsBreadcrumbSection: ['Folder', 'News Item'],
42
+ };
43
+
44
+ const store = mockStore({
45
+ breadcrumbs: {
46
+ items: [
47
+ { title: 'Home', url: '/' },
48
+ { title: 'News', url: '/news', portal_type: 'Folder' },
49
+ {
50
+ title: 'Latest Update',
51
+ url: '/news/latest-update',
52
+ portal_type: 'News Item',
53
+ },
54
+ ],
55
+ },
56
+ intl: {
57
+ locale: 'en',
58
+ messages: {},
59
+ },
60
+ userSession: {
61
+ token: null,
62
+ },
63
+ });
64
+
65
+ const component = renderer.create(
66
+ <Provider store={store}>
67
+ <MemoryRouter>
68
+ <Breadcrumbs pathname="/news/latest-update" />
69
+ </MemoryRouter>
70
+ </Provider>,
71
+ );
72
+ const json = component.toJSON();
73
+ expect(json).toMatchSnapshot();
74
+ });
75
+ });
@@ -94,8 +94,13 @@ const DefaultView = (props) => {
94
94
 
95
95
  const Container =
96
96
  config.getComponent({ name: 'Container' }).component || SemanticContainer;
97
- const matchingNavigationPath = navigation_paths.find((navPath) =>
98
- path.includes(navPath.url),
97
+
98
+ // choose last matching navigation path in case we get more specific paths
99
+ const matchingNavigationPath = navigation_paths.reduceRight(
100
+ (acc, navPath) => {
101
+ return acc || (path.includes(navPath.url) ? navPath : null);
102
+ },
103
+ null,
99
104
  );
100
105
 
101
106
  // If the content is not yet loaded, then do not show anything
@@ -113,7 +118,7 @@ const DefaultView = (props) => {
113
118
  no_icons: matchingNavigationPath.no_icons || true,
114
119
  root_path: matchingNavigationPath.url,
115
120
  includeTop: matchingNavigationPath.includeTop || true,
116
- bottomLevel: matchingNavigationPath.bottomLevel || 3,
121
+ bottomLevel: matchingNavigationPath.bottomLevel || 4,
117
122
  topLevel: matchingNavigationPath.topLevel || 1,
118
123
  currentFolderOnly:
119
124
  matchingNavigationPath.currentFolderOnly || false,
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Breadcrumbs reducer.
3
+ * @module reducers/breadcrumbs/breadcrumbs
4
+ * Customized reducer as part of ticket 271001 in order to receive portal_type info
5
+ */
6
+
7
+ import { map } from 'lodash';
8
+ import {
9
+ flattenToAppURL,
10
+ getBaseUrl,
11
+ hasApiExpander,
12
+ } from '@plone/volto/helpers';
13
+
14
+ import {
15
+ GET_BREADCRUMBS,
16
+ GET_CONTENT,
17
+ } from '@plone/volto/constants/ActionTypes';
18
+
19
+ const initialState = {
20
+ error: null,
21
+ items: [],
22
+ root: null,
23
+ loaded: false,
24
+ loading: false,
25
+ };
26
+
27
+ /**
28
+ * Breadcrumbs reducer.
29
+ * @function breadcrumbs
30
+ * @param {Object} state Current state.
31
+ * @param {Object} action Action to be handled.
32
+ * @returns {Object} New state.
33
+ */
34
+ export default function breadcrumbs(state = initialState, action = {}) {
35
+ let hasExpander;
36
+ switch (action.type) {
37
+ case `${GET_BREADCRUMBS}_PENDING`:
38
+ return {
39
+ ...state,
40
+ error: null,
41
+ loaded: false,
42
+ loading: true,
43
+ };
44
+ case `${GET_CONTENT}_SUCCESS`:
45
+ hasExpander = hasApiExpander(
46
+ 'breadcrumbs',
47
+ getBaseUrl(flattenToAppURL(action.result['@id'])),
48
+ );
49
+ if (hasExpander && !action.subrequest) {
50
+ return {
51
+ ...state,
52
+ error: null,
53
+ items: map(
54
+ action.result['@components'].breadcrumbs.items,
55
+ (item) => ({
56
+ ...item,
57
+ portal_type: item.portal_type,
58
+ url: flattenToAppURL(item['@id']),
59
+ }),
60
+ ),
61
+ root: flattenToAppURL(action.result['@components'].breadcrumbs.root),
62
+ loaded: true,
63
+ loading: false,
64
+ };
65
+ }
66
+ return state;
67
+ case `${GET_BREADCRUMBS}_SUCCESS`:
68
+ hasExpander = hasApiExpander(
69
+ 'breadcrumbs',
70
+ getBaseUrl(flattenToAppURL(action.result['@id'])),
71
+ );
72
+ if (!hasExpander) {
73
+ return {
74
+ ...state,
75
+ error: null,
76
+ items: map(action.result.items, (item) => ({
77
+ ...item,
78
+ portal_type: item.portal_type,
79
+ url: flattenToAppURL(item['@id']),
80
+ })),
81
+ root: flattenToAppURL(action.result.root),
82
+ loaded: true,
83
+ loading: false,
84
+ };
85
+ }
86
+ return state;
87
+ case `${GET_BREADCRUMBS}_FAIL`:
88
+ return {
89
+ ...state,
90
+ error: action.error,
91
+ items: [],
92
+ loaded: false,
93
+ loading: false,
94
+ };
95
+ default:
96
+ return state;
97
+ }
98
+ }
package/src/index.js CHANGED
@@ -4,6 +4,8 @@ import { Icon } from '@plone/volto/components';
4
4
  import { default as TokenWidgetEdit } from '@plone/volto/components/manage/Widgets/TokenWidget';
5
5
  import SelectAutoCompleteWidget from '@plone/volto/components/manage/Widgets/SelectAutoComplete';
6
6
  import { serializeNodesToText } from '@plone/volto-slate/editor/render';
7
+ import TableBlockEdit from '@plone/volto-slate/blocks/Table/TableBlockEdit';
8
+ import TableBlockView from '@plone/volto-slate/blocks/Table/TableBlockView';
7
9
  import { nanoid } from '@plone/volto-slate/utils';
8
10
 
9
11
  import InpageNavigation from '@eeacms/volto-eea-design-system/ui/InpageNavigation/InpageNavigation';
@@ -230,6 +232,15 @@ const applyConfig = (config) => {
230
232
  config.blocks.blocksConfig.description.restricted = false;
231
233
  config.blocks.requiredBlocks = [];
232
234
 
235
+ // 281166 fix paste of tables in edit mode where paste action deemed the type
236
+ // of slate type to be table which in Volto 17 is mapped to the Table block which is draftjs based
237
+ // with this fix we load the edit and view of the slateTable avoiding any draftjs loading and error
238
+ config.blocks.blocksConfig.table = {
239
+ ...config.blocks.blocksConfig.table,
240
+ view: TableBlockView,
241
+ edit: TableBlockEdit,
242
+ };
243
+
233
244
  // Date format for EU
234
245
  config.settings.dateLocale = 'en-gb';
235
246
 
@@ -495,11 +506,9 @@ const applyConfig = (config) => {
495
506
  // },
496
507
  };
497
508
 
498
- //If you don't want to show the content type as a link in the breadcrumbs, you can set it to a number
499
- // where 1 is the last item in the breadcrumbs, 2 is the second last, etc.
500
- config.settings.contentTypeToAvoidAsLinks = {
501
- web_report_section: 2,
502
- };
509
+ // If you don't want to show the content type as a link in the breadcrumbs, you can set it
510
+ // contentTypesAsBreadcrumbSection
511
+ config.settings.contentTypesAsBreadcrumbSection = ['web_report_section'];
503
512
 
504
513
  // Group
505
514
  if (config.blocks.blocksConfig.group) {
@@ -568,7 +577,7 @@ const applyConfig = (config) => {
568
577
  GET_CONTENT: ['breadcrumbs'], // 'navigation', 'actions', 'types'],
569
578
  });
570
579
 
571
- // Custom blocks: Title,Layout settings, Context navigation
580
+ // Custom blocks: Title, Layout settings, Context navigation
572
581
  return [
573
582
  installCustomTitle,
574
583
  installLayoutSettingsBlock,
@@ -1,28 +0,0 @@
1
- .view-viewview.light-header .main.bar {
2
- position: relative;
3
- z-index: 1;
4
- width: 100%;
5
- margin-bottom: -160px;
6
- }
7
- //Gradient styles for web report
8
- .light-header .gradient {
9
- background: linear-gradient(
10
- 0deg,
11
- #ffffff,
12
- rgba(255, 255, 255, 0.9) 30%,
13
- rgba(46, 82, 114, 0.7) 70%,
14
- rgba(14, 21, 26, 0.8) 100%
15
- ) !important;
16
- }
17
-
18
- .ui.block.title .eea.banner .content {
19
- padding-right: 1rem;
20
- padding-left: 1rem;
21
- }
22
-
23
- .share-popup {
24
- .actions {
25
- display: flex;
26
- flex-flow: row;
27
- }
28
- }