@eeacms/volto-eea-website-theme 2.2.2 → 2.4.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 +26 -22
- package/package.json +3 -1
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx +45 -0
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.test.jsx +88 -0
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx +14 -0
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.test.jsx +71 -0
- package/src/components/manage/Blocks/ContextNavigation/index.js +30 -0
- package/src/components/manage/Blocks/ContextNavigation/schema.js +88 -0
- package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +179 -0
- package/src/components/manage/Blocks/ContextNavigation/variations/Default.jsx +9 -0
- package/src/components/manage/Blocks/ContextNavigation/variations/index.js +18 -0
- package/src/components/manage/Blocks/Title/Edit.jsx +7 -4
- package/src/components/manage/Blocks/Title/View.jsx +14 -24
- package/src/components/manage/Blocks/Title/index.js +52 -0
- package/src/components/manage/Blocks/Title/variations/Default.jsx +43 -0
- package/src/components/manage/Blocks/Title/variations/WebReport.jsx +69 -0
- package/src/components/manage/Blocks/Title/variations/WebReportPage.jsx +59 -0
- package/src/components/manage/Blocks/Title/variations/styles.less +28 -0
- package/src/components/theme/Banner/View.jsx +5 -1
- package/src/components/theme/SubsiteClass.jsx +3 -1
- package/src/components/theme/WebReport/WebReportSectionView.jsx +49 -0
- package/src/components/theme/Widgets/DateWidget.jsx +32 -0
- package/src/components/theme/Widgets/DateWidget.test.js +67 -0
- package/src/components/theme/Widgets/DatetimeWidget.jsx +45 -0
- package/src/components/theme/Widgets/DatetimeWidget.test.js +63 -0
- package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +20 -2
- package/src/customizations/volto/components/theme/Header/Header.jsx +3 -3
- package/src/customizations/volto/components/theme/View/DefaultView.jsx +190 -0
- package/src/hocs/withDeviceSize.test.jsx +79 -0
- package/src/index.js +37 -14
- package/src/index.test.js +2 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import renderer from 'react-test-renderer';
|
3
|
+
import { DateWidget } from './DateWidget';
|
4
|
+
import { Provider } from 'react-intl-redux';
|
5
|
+
import configureStore from 'redux-mock-store';
|
6
|
+
import thunk from 'redux-thunk';
|
7
|
+
|
8
|
+
const mockStore = configureStore([thunk]);
|
9
|
+
|
10
|
+
const store = mockStore({
|
11
|
+
intl: {
|
12
|
+
locale: 'en-gb',
|
13
|
+
messages: {},
|
14
|
+
},
|
15
|
+
});
|
16
|
+
|
17
|
+
describe('DateWidget', () => {
|
18
|
+
it('renders an empty date view widget component', () => {
|
19
|
+
const component = renderer.create(
|
20
|
+
<Provider store={store}>
|
21
|
+
<DateWidget />
|
22
|
+
</Provider>,
|
23
|
+
);
|
24
|
+
const json = component.toJSON();
|
25
|
+
expect(json).toMatchSnapshot();
|
26
|
+
});
|
27
|
+
|
28
|
+
it('renders a date view widget component', () => {
|
29
|
+
const component = renderer.create(
|
30
|
+
<Provider store={store}>
|
31
|
+
<DateWidget className="metadata" value="2020-08-04T09:00:00" />
|
32
|
+
</Provider>,
|
33
|
+
);
|
34
|
+
const json = component.toJSON();
|
35
|
+
expect(json).toMatchSnapshot();
|
36
|
+
});
|
37
|
+
|
38
|
+
it('renders a date view widget component with custom format', () => {
|
39
|
+
const component = renderer.create(
|
40
|
+
<Provider store={store}>
|
41
|
+
<DateWidget
|
42
|
+
className="metadata"
|
43
|
+
value="2020-08-04T09:00:00"
|
44
|
+
format={{
|
45
|
+
year: 'numeric',
|
46
|
+
month: 'short',
|
47
|
+
day: '2-digit',
|
48
|
+
}}
|
49
|
+
/>
|
50
|
+
</Provider>,
|
51
|
+
);
|
52
|
+
const json = component.toJSON();
|
53
|
+
expect(json).toMatchSnapshot();
|
54
|
+
});
|
55
|
+
|
56
|
+
it('renders a date view widget component with children', () => {
|
57
|
+
const component = renderer.create(
|
58
|
+
<Provider store={store}>
|
59
|
+
<DateWidget className="metadata" value="2020-08-04T09:00:00">
|
60
|
+
{(child) => <strong>{child}</strong>}
|
61
|
+
</DateWidget>
|
62
|
+
</Provider>,
|
63
|
+
);
|
64
|
+
const json = component.toJSON();
|
65
|
+
expect(json).toMatchSnapshot();
|
66
|
+
});
|
67
|
+
});
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import cx from 'classnames';
|
3
|
+
import { useSelector } from 'react-redux';
|
4
|
+
import { toBackendLang } from '@plone/volto/helpers';
|
5
|
+
import { formatDate } from '@plone/volto/helpers/Utils/Date';
|
6
|
+
import config from '@plone/volto/registry';
|
7
|
+
|
8
|
+
export const DatetimeWidget = ({ value, children, className }) => {
|
9
|
+
const lang = useSelector((state) => state.intl.locale);
|
10
|
+
const backendLang = toBackendLang(lang);
|
11
|
+
const locale =
|
12
|
+
backendLang === 'en' ? config.settings.dateLocale : backendLang;
|
13
|
+
const formatOptions = {
|
14
|
+
date: value,
|
15
|
+
format: {
|
16
|
+
year: 'numeric',
|
17
|
+
month: 'short',
|
18
|
+
day: '2-digit',
|
19
|
+
hour: '2-digit',
|
20
|
+
minute: '2-digit',
|
21
|
+
},
|
22
|
+
locale,
|
23
|
+
includeTime: true,
|
24
|
+
formatToParts: true,
|
25
|
+
};
|
26
|
+
|
27
|
+
let formattedParts = formatDate(formatOptions);
|
28
|
+
|
29
|
+
const formattedDate = formattedParts
|
30
|
+
.map((part) => {
|
31
|
+
if (part.type === 'literal' && part.value === ', ') {
|
32
|
+
return ' ';
|
33
|
+
}
|
34
|
+
return part.value;
|
35
|
+
})
|
36
|
+
.join('');
|
37
|
+
|
38
|
+
return value ? (
|
39
|
+
<span className={cx(className, 'datetime', 'widget')}>
|
40
|
+
{children ? children(formattedDate) : formattedDate}
|
41
|
+
</span>
|
42
|
+
) : (
|
43
|
+
''
|
44
|
+
);
|
45
|
+
};
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import renderer from 'react-test-renderer';
|
3
|
+
import { DatetimeWidget } from './DatetimeWidget';
|
4
|
+
import { Provider } from 'react-intl-redux';
|
5
|
+
import configureStore from 'redux-mock-store';
|
6
|
+
import thunk from 'redux-thunk';
|
7
|
+
|
8
|
+
const mockStore = configureStore([thunk]);
|
9
|
+
|
10
|
+
const store = mockStore({
|
11
|
+
intl: {
|
12
|
+
locale: 'en-gb',
|
13
|
+
messages: {},
|
14
|
+
},
|
15
|
+
});
|
16
|
+
|
17
|
+
describe('DatetimeWidget', () => {
|
18
|
+
it('renders an empty datetime view widget component', () => {
|
19
|
+
const component = renderer.create(
|
20
|
+
<Provider store={store}>
|
21
|
+
<DatetimeWidget />
|
22
|
+
</Provider>,
|
23
|
+
);
|
24
|
+
const json = component.toJSON();
|
25
|
+
expect(json).toMatchSnapshot();
|
26
|
+
});
|
27
|
+
|
28
|
+
it('renders a datetime view widget component with a date and time', () => {
|
29
|
+
const component = renderer.create(
|
30
|
+
<Provider store={store}>
|
31
|
+
<DatetimeWidget className="metadata" value="2024-09-05T15:34:00" />
|
32
|
+
</Provider>,
|
33
|
+
);
|
34
|
+
const json = component.toJSON();
|
35
|
+
expect(json).toMatchSnapshot();
|
36
|
+
});
|
37
|
+
|
38
|
+
it('renders a datetime view widget component with children formatting', () => {
|
39
|
+
const component = renderer.create(
|
40
|
+
<Provider store={store}>
|
41
|
+
<DatetimeWidget className="metadata" value="2024-09-05T15:34:00">
|
42
|
+
{(formattedDate) => <strong>{formattedDate}</strong>}
|
43
|
+
</DatetimeWidget>
|
44
|
+
</Provider>,
|
45
|
+
);
|
46
|
+
const json = component.toJSON();
|
47
|
+
expect(json).toMatchSnapshot();
|
48
|
+
});
|
49
|
+
|
50
|
+
it('removes the comma in the formatted date and shows correct time', () => {
|
51
|
+
const component = renderer.create(
|
52
|
+
<Provider store={store}>
|
53
|
+
<DatetimeWidget className="metadata" value="2024-09-05T15:34:00" />
|
54
|
+
</Provider>,
|
55
|
+
);
|
56
|
+
const json = component.toJSON();
|
57
|
+
expect(json).toMatchSnapshot();
|
58
|
+
|
59
|
+
const instance = component.root;
|
60
|
+
const span = instance.findByType('span');
|
61
|
+
expect(span.props.children).toContain('05 Sept 2024 15:34');
|
62
|
+
});
|
63
|
+
});
|
@@ -3,7 +3,7 @@
|
|
3
3
|
* @module components/theme/Breadcrumbs/Breadcrumbs
|
4
4
|
*/
|
5
5
|
|
6
|
-
import React, { useEffect } from 'react';
|
6
|
+
import React, { useEffect, useMemo } from 'react';
|
7
7
|
import { useDispatch, useSelector } from 'react-redux';
|
8
8
|
|
9
9
|
import { useLocation } from 'react-router';
|
@@ -32,10 +32,23 @@ 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);
|
36
|
+
|
35
37
|
// const pathname = useSelector((state) => state.location.pathname);
|
36
38
|
const location = useLocation();
|
37
39
|
const { pathname } = location;
|
38
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]);
|
51
|
+
|
39
52
|
const sections = items.map((item) => ({
|
40
53
|
title: item.title,
|
41
54
|
href: item.url,
|
@@ -54,7 +67,12 @@ const Breadcrumbs = (props) => {
|
|
54
67
|
return (
|
55
68
|
<React.Fragment>
|
56
69
|
<div id="page-header" />
|
57
|
-
<EEABreadcrumbs
|
70
|
+
<EEABreadcrumbs
|
71
|
+
pathname={pathname}
|
72
|
+
sections={sections}
|
73
|
+
root={root}
|
74
|
+
linkLevels={linkLevels}
|
75
|
+
/>
|
58
76
|
</React.Fragment>
|
59
77
|
);
|
60
78
|
};
|
@@ -17,7 +17,6 @@ import eeaFlag from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/
|
|
17
17
|
|
18
18
|
import config from '@plone/volto/registry';
|
19
19
|
import { compose } from 'recompose';
|
20
|
-
import { BodyClass } from '@plone/volto/helpers';
|
21
20
|
|
22
21
|
import cx from 'classnames';
|
23
22
|
import loadable from '@loadable/component';
|
@@ -43,10 +42,12 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
43
42
|
const has_home_layout =
|
44
43
|
layout === 'homepage_inverse_view' ||
|
45
44
|
(__CLIENT__ && document.body.classList.contains('homepage-inverse'));
|
45
|
+
|
46
46
|
return (
|
47
47
|
has_home_layout &&
|
48
48
|
(removeTrailingSlash(pathname) === router_pathname ||
|
49
|
-
router_pathname.endsWith('/edit')
|
49
|
+
router_pathname.endsWith('/edit') ||
|
50
|
+
router_pathname.endsWith('/add'))
|
50
51
|
);
|
51
52
|
});
|
52
53
|
|
@@ -75,7 +76,6 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
75
76
|
|
76
77
|
return (
|
77
78
|
<Header menuItems={items}>
|
78
|
-
{isHomePageInverse && <BodyClass className="homepage" />}
|
79
79
|
<Header.TopHeader>
|
80
80
|
<Header.TopItem className="official-union">
|
81
81
|
<Image src={eeaFlag} alt="European Union flag"></Image>
|
@@ -0,0 +1,190 @@
|
|
1
|
+
/**
|
2
|
+
* Document view component.
|
3
|
+
* @module components/theme/View/DefaultView
|
4
|
+
*/
|
5
|
+
|
6
|
+
import React from 'react';
|
7
|
+
import PropTypes from 'prop-types';
|
8
|
+
|
9
|
+
import {
|
10
|
+
Container as SemanticContainer,
|
11
|
+
Segment,
|
12
|
+
Grid,
|
13
|
+
Label,
|
14
|
+
} from 'semantic-ui-react';
|
15
|
+
import config from '@plone/volto/registry';
|
16
|
+
import { getSchema } from '@plone/volto/actions';
|
17
|
+
import { getWidget } from '@plone/volto/helpers/Widget/utils';
|
18
|
+
import { RenderBlocks } from '@plone/volto/components';
|
19
|
+
|
20
|
+
import { hasBlocksData, getBaseUrl } from '@plone/volto/helpers';
|
21
|
+
import { useDispatch, shallowEqual, useSelector } from 'react-redux';
|
22
|
+
|
23
|
+
import isEqual from 'lodash/isEqual';
|
24
|
+
import AccordionContextNavigation from '@eeacms/volto-eea-website-theme/components/manage/Blocks/ContextNavigation/variations/Accordion';
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Component to display the default view.
|
28
|
+
* @function DefaultView
|
29
|
+
* @param {Object} content Content object.
|
30
|
+
* @returns {string} Markup of the component.
|
31
|
+
*/
|
32
|
+
const DefaultView = (props) => {
|
33
|
+
const { content, location } = props;
|
34
|
+
const [hasLightLayout, setHasLightLayout] = React.useState(false);
|
35
|
+
|
36
|
+
React.useEffect(() => {
|
37
|
+
const updateLightLayout = () => {
|
38
|
+
if (__CLIENT__) {
|
39
|
+
setHasLightLayout(document.body.classList.contains('light-header'));
|
40
|
+
}
|
41
|
+
};
|
42
|
+
|
43
|
+
updateLightLayout();
|
44
|
+
|
45
|
+
if (__CLIENT__) {
|
46
|
+
const observer = new MutationObserver(updateLightLayout);
|
47
|
+
observer.observe(document.body, {
|
48
|
+
attributes: true,
|
49
|
+
attributeFilter: ['class'],
|
50
|
+
});
|
51
|
+
|
52
|
+
return () => observer.disconnect();
|
53
|
+
}
|
54
|
+
}, []);
|
55
|
+
|
56
|
+
const { contextNavigationActions } = useSelector(
|
57
|
+
(state) => ({
|
58
|
+
contextNavigationActions: state.actions?.actions?.context_navigation,
|
59
|
+
}),
|
60
|
+
shallowEqual,
|
61
|
+
);
|
62
|
+
|
63
|
+
const navigation_paths = contextNavigationActions || [];
|
64
|
+
const path = getBaseUrl(location?.pathname || '');
|
65
|
+
const dispatch = useDispatch();
|
66
|
+
const { views } = config.widgets;
|
67
|
+
const contentSchema = useSelector((state) => state.schema?.schema);
|
68
|
+
const fieldsetsToExclude = [
|
69
|
+
'categorization',
|
70
|
+
'dates',
|
71
|
+
'ownership',
|
72
|
+
'settings',
|
73
|
+
];
|
74
|
+
const fieldsets = contentSchema?.fieldsets.filter(
|
75
|
+
(fs) => !fieldsetsToExclude.includes(fs.id),
|
76
|
+
);
|
77
|
+
|
78
|
+
// TL;DR: There is a flash of the non block-based view because of the reset
|
79
|
+
// of the content on route change. Subscribing to the content change at this
|
80
|
+
// level has nasty implications, so we can't watch the Redux state for loaded
|
81
|
+
// content flag here (because it forces an additional component update)
|
82
|
+
// Instead, we can watch if the content is "empty", but this has a drawback
|
83
|
+
// since the locking mechanism inserts a `lock` key before the content is there.
|
84
|
+
// So "empty" means `content` is present, but only with a `lock` key, thus the next
|
85
|
+
// ugly condition comes to life
|
86
|
+
const contentLoaded = content && !isEqual(Object.keys(content), ['lock']);
|
87
|
+
|
88
|
+
React.useEffect(() => {
|
89
|
+
content?.['@type'] &&
|
90
|
+
!hasBlocksData(content) &&
|
91
|
+
dispatch(getSchema(content['@type'], location.pathname));
|
92
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
93
|
+
}, []);
|
94
|
+
|
95
|
+
const Container =
|
96
|
+
config.getComponent({ name: 'Container' }).component || SemanticContainer;
|
97
|
+
const matchingNavigationPath = navigation_paths.find((navPath) =>
|
98
|
+
path.includes(navPath.url),
|
99
|
+
);
|
100
|
+
|
101
|
+
// If the content is not yet loaded, then do not show anything
|
102
|
+
return contentLoaded ? (
|
103
|
+
hasBlocksData(content) ? (
|
104
|
+
<>
|
105
|
+
<Container id="page-document">
|
106
|
+
<RenderBlocks {...props} path={path} />
|
107
|
+
</Container>
|
108
|
+
{hasLightLayout && matchingNavigationPath && (
|
109
|
+
<AccordionContextNavigation
|
110
|
+
params={{
|
111
|
+
name: matchingNavigationPath.title,
|
112
|
+
no_thumbs: matchingNavigationPath.no_thumbs || true,
|
113
|
+
no_icons: matchingNavigationPath.no_icons || true,
|
114
|
+
root_path: matchingNavigationPath.url,
|
115
|
+
includeTop: matchingNavigationPath.includeTop || true,
|
116
|
+
bottomLevel: matchingNavigationPath.bottomLevel || 3,
|
117
|
+
topLevel: matchingNavigationPath.topLevel || 1,
|
118
|
+
currentFolderOnly:
|
119
|
+
matchingNavigationPath.currentFolderOnly || false,
|
120
|
+
}}
|
121
|
+
/>
|
122
|
+
)}
|
123
|
+
</>
|
124
|
+
) : (
|
125
|
+
<Container id="page-document">
|
126
|
+
{fieldsets?.map((fs) => {
|
127
|
+
return (
|
128
|
+
<div className="fieldset" key={fs.id}>
|
129
|
+
{fs.id !== 'default' && <h2>{fs.title}</h2>}
|
130
|
+
{fs.fields?.map((f, key) => {
|
131
|
+
let field = {
|
132
|
+
...contentSchema?.properties[f],
|
133
|
+
id: f,
|
134
|
+
widget: getWidget(f, contentSchema?.properties[f]),
|
135
|
+
};
|
136
|
+
let Widget = views?.getWidget(field);
|
137
|
+
return f !== 'title' ? (
|
138
|
+
<Grid celled="internally" key={key}>
|
139
|
+
<Grid.Row>
|
140
|
+
<Label title={field.id}>{field.title}:</Label>
|
141
|
+
</Grid.Row>
|
142
|
+
<Grid.Row>
|
143
|
+
<Segment basic>
|
144
|
+
<Widget value={content[f]} />
|
145
|
+
</Segment>
|
146
|
+
</Grid.Row>
|
147
|
+
</Grid>
|
148
|
+
) : (
|
149
|
+
<Widget key={key} value={content[f]} />
|
150
|
+
);
|
151
|
+
})}
|
152
|
+
</div>
|
153
|
+
);
|
154
|
+
})}
|
155
|
+
</Container>
|
156
|
+
)
|
157
|
+
) : null;
|
158
|
+
};
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Property types.
|
162
|
+
* @property {Object} propTypes Property types.
|
163
|
+
* @static
|
164
|
+
*/
|
165
|
+
DefaultView.propTypes = {
|
166
|
+
/**
|
167
|
+
* Content of the object
|
168
|
+
*/
|
169
|
+
content: PropTypes.shape({
|
170
|
+
/**
|
171
|
+
* Title of the object
|
172
|
+
*/
|
173
|
+
title: PropTypes.string,
|
174
|
+
/**
|
175
|
+
* Description of the object
|
176
|
+
*/
|
177
|
+
description: PropTypes.string,
|
178
|
+
/**
|
179
|
+
* Text of the object
|
180
|
+
*/
|
181
|
+
text: PropTypes.shape({
|
182
|
+
/**
|
183
|
+
* Data of the text of the object
|
184
|
+
*/
|
185
|
+
data: PropTypes.string,
|
186
|
+
}),
|
187
|
+
}).isRequired,
|
188
|
+
};
|
189
|
+
|
190
|
+
export default DefaultView;
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, act } from '@testing-library/react';
|
3
|
+
import withDeviceSize from './withDeviceSize.jsx';
|
4
|
+
|
5
|
+
describe('withDeviceSize HOC', () => {
|
6
|
+
// Mock the WrappedComponent
|
7
|
+
const WrappedComponent = ({ device }) => (
|
8
|
+
<div data-testid="device">{device}</div>
|
9
|
+
);
|
10
|
+
|
11
|
+
const mockResize = (width) => {
|
12
|
+
Object.defineProperty(document.documentElement, 'clientWidth', {
|
13
|
+
writable: true,
|
14
|
+
configurable: true,
|
15
|
+
value: width,
|
16
|
+
});
|
17
|
+
window.dispatchEvent(new Event('resize'));
|
18
|
+
};
|
19
|
+
|
20
|
+
it('should return mobile for screen width less than 768px', () => {
|
21
|
+
const ComponentWithDeviceSize = withDeviceSize(WrappedComponent);
|
22
|
+
|
23
|
+
const { getByTestId } = render(<ComponentWithDeviceSize />);
|
24
|
+
|
25
|
+
act(() => {
|
26
|
+
mockResize(500); // Simulating a mobile screen
|
27
|
+
});
|
28
|
+
|
29
|
+
expect(getByTestId('device').textContent).toBe('mobile');
|
30
|
+
});
|
31
|
+
|
32
|
+
it('should return tablet for screen width between 768px and 992px', () => {
|
33
|
+
const ComponentWithDeviceSize = withDeviceSize(WrappedComponent);
|
34
|
+
|
35
|
+
const { getByTestId } = render(<ComponentWithDeviceSize />);
|
36
|
+
|
37
|
+
act(() => {
|
38
|
+
mockResize(800); // Simulating a tablet screen
|
39
|
+
});
|
40
|
+
|
41
|
+
expect(getByTestId('device').textContent).toBe('tablet');
|
42
|
+
});
|
43
|
+
|
44
|
+
it('should return computer for screen width between 992px and 1200px', () => {
|
45
|
+
const ComponentWithDeviceSize = withDeviceSize(WrappedComponent);
|
46
|
+
|
47
|
+
const { getByTestId } = render(<ComponentWithDeviceSize />);
|
48
|
+
|
49
|
+
act(() => {
|
50
|
+
mockResize(1000); // Simulating a computer screen
|
51
|
+
});
|
52
|
+
|
53
|
+
expect(getByTestId('device').textContent).toBe('computer');
|
54
|
+
});
|
55
|
+
|
56
|
+
it('should return large for screen width between 1200px and 1920px', () => {
|
57
|
+
const ComponentWithDeviceSize = withDeviceSize(WrappedComponent);
|
58
|
+
|
59
|
+
const { getByTestId } = render(<ComponentWithDeviceSize />);
|
60
|
+
|
61
|
+
act(() => {
|
62
|
+
mockResize(1500); // Simulating a large screen
|
63
|
+
});
|
64
|
+
|
65
|
+
expect(getByTestId('device').textContent).toBe('large');
|
66
|
+
});
|
67
|
+
|
68
|
+
it('should return widescreen for screen width above 1920px', () => {
|
69
|
+
const ComponentWithDeviceSize = withDeviceSize(WrappedComponent);
|
70
|
+
|
71
|
+
const { getByTestId } = render(<ComponentWithDeviceSize />);
|
72
|
+
|
73
|
+
act(() => {
|
74
|
+
mockResize(2000); // Simulating a widescreen display
|
75
|
+
});
|
76
|
+
|
77
|
+
expect(getByTestId('device').textContent).toBe('widescreen');
|
78
|
+
});
|
79
|
+
});
|
package/src/index.js
CHANGED
@@ -1,16 +1,24 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { v4 as uuid } from 'uuid';
|
3
|
+
import { Icon } from '@plone/volto/components';
|
4
|
+
import { default as TokenWidgetEdit } from '@plone/volto/components/manage/Widgets/TokenWidget';
|
5
|
+
import SelectAutoCompleteWidget from '@plone/volto/components/manage/Widgets/SelectAutoComplete';
|
6
|
+
import { serializeNodesToText } from '@plone/volto-slate/editor/render';
|
7
|
+
import { nanoid } from '@plone/volto-slate/utils';
|
8
|
+
|
1
9
|
import InpageNavigation from '@eeacms/volto-eea-design-system/ui/InpageNavigation/InpageNavigation';
|
2
10
|
import CustomCSS from '@eeacms/volto-eea-website-theme/components/theme/CustomCSS/CustomCSS';
|
3
11
|
import DraftBackground from '@eeacms/volto-eea-website-theme/components/theme/DraftBackground/DraftBackground';
|
4
12
|
import HomePageInverseView from '@eeacms/volto-eea-website-theme/components/theme/Homepage/HomePageInverseView';
|
5
13
|
import HomePageView from '@eeacms/volto-eea-website-theme/components/theme/Homepage/HomePageView';
|
14
|
+
import WebReportSectionView from '@eeacms/volto-eea-website-theme/components/theme/WebReport/WebReportSectionView';
|
6
15
|
import NotFound from '@eeacms/volto-eea-website-theme/components/theme/NotFound/NotFound';
|
7
16
|
import { TokenWidget } from '@eeacms/volto-eea-website-theme/components/theme/Widgets/TokenWidget';
|
8
17
|
import { TopicsWidget } from '@eeacms/volto-eea-website-theme/components/theme/Widgets/TopicsWidget';
|
18
|
+
import { DateWidget } from '@eeacms/volto-eea-website-theme/components/theme/Widgets/DateWidget';
|
19
|
+
import { DatetimeWidget } from '@eeacms/volto-eea-website-theme/components/theme/Widgets/DatetimeWidget';
|
9
20
|
import CreatableSelectWidget from '@eeacms/volto-eea-website-theme/components/theme/Widgets/CreatableSelectWidget';
|
10
21
|
|
11
|
-
import { Icon } from '@plone/volto/components';
|
12
|
-
import { default as TokenWidgetEdit } from '@plone/volto/components/manage/Widgets/TokenWidget';
|
13
|
-
import { serializeNodesToText } from '@plone/volto-slate/editor/render';
|
14
22
|
import Tag from '@eeacms/volto-eea-design-system/ui/Tag/Tag';
|
15
23
|
|
16
24
|
import {
|
@@ -19,6 +27,7 @@ import {
|
|
19
27
|
} from '@eeacms/volto-eea-website-theme/helpers/schema-utils';
|
20
28
|
|
21
29
|
import installLayoutSettingsBlock from '@eeacms/volto-eea-website-theme/components/manage/Blocks/LayoutSettings';
|
30
|
+
import installContextNavigationBlock from '@eeacms/volto-eea-website-theme/components/manage/Blocks/ContextNavigation';
|
22
31
|
import installCustomTitle from '@eeacms/volto-eea-website-theme/components/manage/Blocks/Title';
|
23
32
|
|
24
33
|
import FlexGroup from '@eeacms/volto-eea-website-theme/components/manage/Blocks/GroupBlockTemplate/FlexGroup/FlexGroup';
|
@@ -30,11 +39,8 @@ import okMiddleware from './middleware/ok';
|
|
30
39
|
import voltoCustomMiddleware from './middleware/voltoCustom';
|
31
40
|
import installSlate from './slate';
|
32
41
|
import { print } from './reducers';
|
33
|
-
import { nanoid } from '@plone/volto-slate/utils';
|
34
|
-
import { v4 as uuid } from 'uuid';
|
35
42
|
|
36
43
|
import * as eea from './config';
|
37
|
-
import React from 'react';
|
38
44
|
|
39
45
|
const restrictedBlocks = ['imagesGrid', 'teaser', 'dataFigure', 'plotly_chart'];
|
40
46
|
|
@@ -237,7 +243,14 @@ const applyConfig = (config) => {
|
|
237
243
|
...(config.views.layoutViewsNamesMapping || {}),
|
238
244
|
homepage_view: 'Homepage view',
|
239
245
|
homepage_inverse_view: 'Homepage white view',
|
246
|
+
web_report_section: 'Web report section',
|
240
247
|
};
|
248
|
+
|
249
|
+
config.views.contentTypesViews = {
|
250
|
+
...(config.views.contentTypesViews || {}),
|
251
|
+
web_report_section: WebReportSectionView,
|
252
|
+
};
|
253
|
+
|
241
254
|
config.views.errorViews = {
|
242
255
|
...config.views.errorViews,
|
243
256
|
404: NotFound,
|
@@ -330,11 +343,17 @@ const applyConfig = (config) => {
|
|
330
343
|
}
|
331
344
|
|
332
345
|
// Custom Widgets
|
333
|
-
config.widgets.id.other_organisations = TokenWidgetEdit;
|
346
|
+
// config.widgets.id.other_organisations = TokenWidgetEdit;
|
347
|
+
config.widgets.vocabulary['eea.coremetadata.other_organisations'] =
|
348
|
+
TokenWidgetEdit;
|
349
|
+
config.widgets.views.widget.datetime = DatetimeWidget;
|
350
|
+
config.widgets.views.widget.date = DateWidget;
|
334
351
|
config.widgets.views.id.topics = TopicsWidget;
|
335
352
|
config.widgets.views.id.subjects = TokenWidget;
|
336
353
|
config.widgets.views.widget.tags = TokenWidget;
|
337
354
|
config.widgets.widget.creatable_select = CreatableSelectWidget;
|
355
|
+
config.widgets.vocabulary['plone.app.vocabularies.Users'] =
|
356
|
+
SelectAutoCompleteWidget;
|
338
357
|
|
339
358
|
// /voltoCustom.css express-middleware
|
340
359
|
// /ok express-middleware - see also: https://github.com/plone/volto/pull/4432
|
@@ -476,11 +495,11 @@ const applyConfig = (config) => {
|
|
476
495
|
// },
|
477
496
|
};
|
478
497
|
|
479
|
-
//
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
+
};
|
484
503
|
|
485
504
|
// Group
|
486
505
|
if (config.blocks.blocksConfig.group) {
|
@@ -549,8 +568,12 @@ const applyConfig = (config) => {
|
|
549
568
|
GET_CONTENT: ['breadcrumbs'], // 'navigation', 'actions', 'types'],
|
550
569
|
});
|
551
570
|
|
552
|
-
// Custom blocks: Title
|
553
|
-
return [
|
571
|
+
// Custom blocks: Title,Layout settings, Context navigation
|
572
|
+
return [
|
573
|
+
installCustomTitle,
|
574
|
+
installLayoutSettingsBlock,
|
575
|
+
installContextNavigationBlock,
|
576
|
+
].reduce((acc, apply) => apply(acc), config);
|
554
577
|
};
|
555
578
|
|
556
579
|
export default applyConfig;
|
package/src/index.test.js
CHANGED
@@ -92,6 +92,7 @@ describe('applyConfig', () => {
|
|
92
92
|
},
|
93
93
|
},
|
94
94
|
widget: {},
|
95
|
+
vocabulary: {},
|
95
96
|
id: {},
|
96
97
|
},
|
97
98
|
settings: {
|
@@ -247,6 +248,7 @@ describe('applyConfig', () => {
|
|
247
248
|
},
|
248
249
|
widget: {},
|
249
250
|
id: {},
|
251
|
+
vocabulary: {},
|
250
252
|
},
|
251
253
|
settings: {
|
252
254
|
eea: {},
|