@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 +32 -3
- package/package.json +1 -1
- package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +58 -12
- package/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.jsx +144 -0
- package/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.test.jsx +131 -0
- package/src/components/manage/Blocks/ContextNavigation/variations/index.js +6 -0
- package/src/components/manage/Blocks/Title/index.js +0 -1
- package/src/components/manage/Blocks/Title/schema.js +18 -3
- package/src/components/manage/Blocks/Title/variations/WebReport.jsx +3 -2
- package/src/components/manage/Blocks/Title/variations/WebReportPage.jsx +5 -4
- package/src/components/theme/Banner/View.jsx +3 -1
- package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +7 -16
- package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.test.jsx +75 -0
- package/src/customizations/volto/components/theme/View/DefaultView.jsx +8 -3
- package/src/customizations/volto/reducers/breadcrumbs/breadcrumbs.js +98 -0
- package/src/index.js +15 -6
- package/src/components/manage/Blocks/Title/variations/styles.less +0 -28
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.
|
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.
|
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
@@ -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 {
|
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
|
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
|
-
|
139
|
-
|
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
|
-
<
|
142
|
-
{
|
143
|
-
|
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: '
|
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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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={
|
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
|
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
|
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
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
98
|
-
|
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 ||
|
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
|
499
|
-
//
|
500
|
-
config.settings.
|
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
|
-
}
|