@eeacms/volto-cca-policy 0.1.1

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.
Files changed (65) hide show
  1. package/.coverage.babel.config.js +9 -0
  2. package/.i18n.babel.config.js +1 -0
  3. package/.project.eslintrc.js +48 -0
  4. package/.release-it.json +17 -0
  5. package/CHANGELOG.md +30 -0
  6. package/DEVELOP.md +52 -0
  7. package/LICENSE.md +9 -0
  8. package/README.md +85 -0
  9. package/RELEASE.md +74 -0
  10. package/babel.config.js +17 -0
  11. package/bootstrap +41 -0
  12. package/cypress.config.js +26 -0
  13. package/jest-addon.config.js +36 -0
  14. package/locales/volto.pot +0 -0
  15. package/package.json +51 -0
  16. package/src/components/index.js +1 -0
  17. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx +31 -0
  18. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx +17 -0
  19. package/src/components/manage/Blocks/ContextNavigation/index.js +26 -0
  20. package/src/components/manage/Blocks/ContextNavigation/schema.js +81 -0
  21. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.jsx +32 -0
  22. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsView.jsx +15 -0
  23. package/src/components/manage/Blocks/LayoutSettings/edit.less +4 -0
  24. package/src/components/manage/Blocks/LayoutSettings/index.js +24 -0
  25. package/src/components/manage/Blocks/LayoutSettings/schema.js +32 -0
  26. package/src/components/manage/Blocks/Title/Edit.jsx +226 -0
  27. package/src/components/manage/Blocks/Title/View.jsx +35 -0
  28. package/src/components/manage/Blocks/Title/index.js +13 -0
  29. package/src/components/manage/Blocks/Title/schema.js +80 -0
  30. package/src/components/manage/Blocks/schema-utils.js +16 -0
  31. package/src/components/manage/Blocks/schema.js +52 -0
  32. package/src/components/theme/Banner/Banner.jsx +99 -0
  33. package/src/components/theme/Banner/View.jsx +241 -0
  34. package/src/components/theme/Banner/styles.less +20 -0
  35. package/src/components/theme/CustomCSS/CustomCSS.jsx +12 -0
  36. package/src/components/theme/DraftBackground/DraftBackground.jsx +16 -0
  37. package/src/components/theme/DraftBackground/draft.css +3 -0
  38. package/src/components/theme/DraftBackground/draft.png +0 -0
  39. package/src/components/theme/Homepage/HomePageInverseView.jsx +60 -0
  40. package/src/components/theme/Homepage/HomePageView.jsx +60 -0
  41. package/src/components/theme/Logo.jsx +34 -0
  42. package/src/components/theme/SubsiteClass.jsx +23 -0
  43. package/src/components/theme/Widgets/TokenWidget.jsx +16 -0
  44. package/src/config.js +307 -0
  45. package/src/customizations/@eeacms/volto-block-style/StyleWrapper/schema.js +44 -0
  46. package/src/customizations/@eeacms/volto-eea-design-system/ui/Header/HeaderSearchPopUp.js +80 -0
  47. package/src/customizations/@eeacms/volto-tabs-block/components/templates/default/schema.js +109 -0
  48. package/src/customizations/@eeacms/volto-tabs-block/components/templates/horizontal-responsive/schema.js +109 -0
  49. package/src/customizations/volto/components/manage/Form/Form.jsx +784 -0
  50. package/src/customizations/volto/components/manage/Form/ModalForm.jsx +326 -0
  51. package/src/customizations/volto/components/manage/Sharing/Sharing.jsx +495 -0
  52. package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.jsx +436 -0
  53. package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +62 -0
  54. package/src/customizations/volto/components/theme/Comments/Comments.jsx +487 -0
  55. package/src/customizations/volto/components/theme/Footer/Footer.jsx +90 -0
  56. package/src/customizations/volto/components/theme/Header/Header.jsx +258 -0
  57. package/src/customizations/volto/components/theme/Tags/Tags.jsx +53 -0
  58. package/src/customizations/volto/components/theme/Unauthorized/Unauthorized.jsx +91 -0
  59. package/src/customizations/volto/components/theme/View/EventView.jsx +90 -0
  60. package/src/helpers/index.js +44 -0
  61. package/src/icons/content-box.svg +5 -0
  62. package/src/icons/image-narrow.svg +5 -0
  63. package/src/index.js +13 -0
  64. package/src/middleware/voltoCustom.js +37 -0
  65. package/src/policy.js +136 -0
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { compose } from 'redux';
3
+ import { connect } from 'react-redux';
4
+ import { Icon, Button, Grid } from 'semantic-ui-react';
5
+ import { formatDate } from '@plone/volto/helpers/Utils/Date';
6
+ import config from '@plone/volto/registry';
7
+
8
+ const socialPlatforms = {
9
+ facebook: {
10
+ shareLink: (url) => `https://facebook.com/sharer.php?u=${url}`,
11
+ },
12
+ twitter: {
13
+ shareLink: (url) => `https://www.twitter.com/share?url=${url}`,
14
+ },
15
+ linkedin: {
16
+ shareLink: (url) =>
17
+ `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
18
+ },
19
+ reddit: {
20
+ shareLink: (url, title) => `https://reddit.com/submit?url=${url}`,
21
+ },
22
+ };
23
+
24
+ export const getImageSource = (image) => {
25
+ if (image?.download) return image.download;
26
+ if (image?.encoding)
27
+ return `data:${image['content-type']};${image['encoding']},${image['data']}`;
28
+ return null;
29
+ };
30
+
31
+ export const sharePage = (url, platform) => {
32
+ if (!socialPlatforms[platform]) return;
33
+ const link = document.createElement('a');
34
+ link.setAttribute('href', socialPlatforms[platform].shareLink(url));
35
+ link.setAttribute('target', '_blank');
36
+ link.setAttribute('rel', 'noreferrer');
37
+ link.click();
38
+ };
39
+
40
+ const Banner = ({ children }) => {
41
+ return <div className="eea banner">{children}</div>;
42
+ };
43
+
44
+ Banner.Action = ({ title, icon, color, onClick, className }) => {
45
+ return (
46
+ <div className="action">
47
+ <Button className={className} basic icon inverted onClick={onClick}>
48
+ <Icon className={icon} color={color}></Icon>
49
+ <span className="mobile hidden">{title}</span>
50
+ </Button>
51
+ </div>
52
+ );
53
+ };
54
+
55
+ Banner.Content = ({ children, actions }) => {
56
+ return (
57
+ <div className="content">
58
+ <Grid>
59
+ <Grid.Column mobile={10} tablet={9} computer={9}>
60
+ {children}
61
+ </Grid.Column>
62
+ <Grid.Column mobile={2} tablet={3} computer={3} className="actions">
63
+ {actions}
64
+ </Grid.Column>
65
+ </Grid>
66
+ </div>
67
+ );
68
+ };
69
+
70
+ Banner.Title = ({ children }) => (
71
+ <h1 className="documentFirstHeading">{children}</h1>
72
+ );
73
+ Banner.Metadata = ({ children }) => <p className="metadata">{children}</p>;
74
+ Banner.MetadataField = ({ hidden, type = 'text', label, value, title }) => {
75
+ const locale = config.settings.dateLocale || 'en-gb';
76
+ if (hidden || !value) return '';
77
+ if (type === 'date' && value)
78
+ return (
79
+ <span className={`field ${type}`} title={title.replace('{}', value)}>
80
+ {label}{' '}
81
+ {formatDate({
82
+ date: value,
83
+ format: {
84
+ year: 'numeric',
85
+ month: 'short',
86
+ day: '2-digit',
87
+ },
88
+ locale: locale,
89
+ })}
90
+ </span>
91
+ );
92
+ return <span className={`field ${type}`}>{value}</span>;
93
+ };
94
+
95
+ export default compose(
96
+ connect((state) => ({
97
+ content: state.content.data,
98
+ })),
99
+ )(Banner);
@@ -0,0 +1,241 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import { compose } from 'redux';
3
+ import { connect } from 'react-redux';
4
+ import { withRouter } from 'react-router';
5
+ import { defineMessages, injectIntl } from 'react-intl';
6
+ import { startCase } from 'lodash';
7
+ import qs from 'querystring';
8
+ import { Container, Popup } from 'semantic-ui-react';
9
+ import { flattenToAppURL } from '@plone/volto/helpers';
10
+ import Banner from './Banner';
11
+ import { getImageSource, sharePage } from './Banner';
12
+
13
+ import './styles.less';
14
+
15
+ const messages = defineMessages({
16
+ share: {
17
+ id: 'Share',
18
+ defaultMessage: 'Share',
19
+ },
20
+ share_to: {
21
+ id: 'Share to',
22
+ defaultMessage: 'Share to',
23
+ },
24
+ download: {
25
+ id: 'Download',
26
+ defaultMessage: 'Download',
27
+ },
28
+ created: {
29
+ id: 'Created',
30
+ defaultMessage: 'Created',
31
+ },
32
+ created_on: {
33
+ id: 'Created on',
34
+ defaultMessage: 'Created on',
35
+ },
36
+ published: {
37
+ id: 'Published',
38
+ defaultMessage: 'Published',
39
+ },
40
+ published_on: {
41
+ id: 'Published on',
42
+ defaultMessage: 'Published on',
43
+ },
44
+ modified: {
45
+ id: 'Modified',
46
+ defaultMessage: 'Modified',
47
+ },
48
+ modified_on: {
49
+ id: 'Modified on',
50
+ defaultMessage: 'Modified on',
51
+ },
52
+ });
53
+
54
+ const friendlyId = (id) => {
55
+ if (typeof id !== 'string') return id;
56
+ return startCase(id);
57
+ };
58
+
59
+ const ContainerWrapper = ({ fluid, children }) => {
60
+ if (fluid) return <React.Fragment>{children}</React.Fragment>;
61
+ return <Container>{children}</Container>;
62
+ };
63
+
64
+ const Title = ({ config = {}, properties }) => {
65
+ const view = useMemo(() => {
66
+ return config.view;
67
+ }, [config.view]);
68
+
69
+ if (view) {
70
+ return view;
71
+ }
72
+ return <Banner.Title>{properties['title']}</Banner.Title>;
73
+ };
74
+
75
+ const View = (props) => {
76
+ const { banner = {}, fluid, intl, location, types = [] } = props;
77
+ const metadata = props.metadata || props.properties;
78
+ const {
79
+ info = [],
80
+ hideContentType,
81
+ hideCreationDate,
82
+ hidePublishingDate,
83
+ hideModificationDate,
84
+ hideShareButton,
85
+ hideDownloadButton,
86
+ // contentType,
87
+ } = props.data;
88
+ // Set query parameters
89
+ const parameters = useMemo(
90
+ () => qs.parse(location.search.replace('?', '')) || {},
91
+ [location],
92
+ );
93
+ // Set dates
94
+ const getDate = useCallback(
95
+ (hidden, key) => {
96
+ return !hidden && metadata[key] ? metadata[key] : null;
97
+ },
98
+ [metadata],
99
+ );
100
+ const creationDate = useMemo(() => getDate(hideCreationDate, 'created'), [
101
+ getDate,
102
+ hideCreationDate,
103
+ ]);
104
+ const publishingDate = useMemo(
105
+ () => getDate(hidePublishingDate, 'effective'),
106
+ [getDate, hidePublishingDate],
107
+ );
108
+ const modificationDate = useMemo(
109
+ () => getDate(hideModificationDate, 'modified'),
110
+ [getDate, hideModificationDate],
111
+ );
112
+ // Set image source
113
+ const image = getImageSource(metadata['image']);
114
+ // Get type
115
+ const type = useMemo(() => {
116
+ return (
117
+ types.filter(
118
+ (type) =>
119
+ flattenToAppURL(type['@id']) ===
120
+ `/@types/${metadata['@type'] || parameters.type}`,
121
+ )[0]?.title ||
122
+ friendlyId(metadata['@type']) ||
123
+ metadata['@type'] ||
124
+ parameters.type
125
+ );
126
+ }, [types, metadata, parameters]);
127
+
128
+ return (
129
+ <Banner {...props}>
130
+ <div
131
+ className={image ? 'image' : ''}
132
+ style={image ? { backgroundImage: `url(${image})` } : {}}
133
+ >
134
+ <div className="gradient">
135
+ <ContainerWrapper fluid={fluid}>
136
+ <Banner.Content
137
+ actions={
138
+ <>
139
+ {!hideShareButton && (
140
+ <Popup
141
+ className="share-popup"
142
+ content={() => (
143
+ <>
144
+ <p>{intl.formatMessage(messages.share_to)}</p>
145
+ <div className="actions">
146
+ <Banner.Action
147
+ icon="ri-facebook-fill"
148
+ onClick={() => {
149
+ sharePage(metadata['@id'], 'facebook');
150
+ }}
151
+ />
152
+ <Banner.Action
153
+ icon="ri-twitter-fill"
154
+ onClick={() => {
155
+ sharePage(metadata['@id'], 'twitter');
156
+ }}
157
+ />
158
+ <Banner.Action
159
+ icon="ri-linkedin-fill"
160
+ onClick={() => {
161
+ sharePage(metadata['@id'], 'linkedin');
162
+ }}
163
+ />
164
+ </div>
165
+ </>
166
+ )}
167
+ position="bottom center"
168
+ size="small"
169
+ trigger={
170
+ <Banner.Action
171
+ icon="ri-share-fill"
172
+ title={intl.formatMessage(messages.share)}
173
+ className="share"
174
+ onClick={() => {}}
175
+ />
176
+ }
177
+ />
178
+ )}
179
+ {!hideDownloadButton && (
180
+ <Banner.Action
181
+ icon="ri-download-2-fill"
182
+ title={intl.formatMessage(messages.download)}
183
+ className="download"
184
+ onClick={() => {
185
+ window.print();
186
+ }}
187
+ />
188
+ )}
189
+ </>
190
+ }
191
+ >
192
+ <Title config={banner.title} properties={metadata} />
193
+ <Banner.Metadata>
194
+ <Banner.MetadataField
195
+ type="type"
196
+ hidden={hideContentType}
197
+ // value={contentType || properties['@type'] || parameters.type}
198
+ value={type}
199
+ />
200
+ <Banner.MetadataField
201
+ type="date"
202
+ label={intl.formatMessage(messages.created)}
203
+ value={creationDate}
204
+ title={`${intl.formatMessage(messages.created_on)} {}`}
205
+ />
206
+ <Banner.MetadataField
207
+ type="date"
208
+ label={intl.formatMessage(messages.published)}
209
+ value={publishingDate}
210
+ title={`${intl.formatMessage(messages.published_on)} {}`}
211
+ />
212
+ <Banner.MetadataField
213
+ type="date"
214
+ label={intl.formatMessage(messages.modified)}
215
+ value={modificationDate}
216
+ title={`${intl.formatMessage(messages.modified_on)} {}`}
217
+ />
218
+ {info.map((item, index) => (
219
+ <Banner.MetadataField
220
+ key={`header-info-${index}`}
221
+ value={item.description}
222
+ />
223
+ ))}
224
+ </Banner.Metadata>
225
+ </Banner.Content>
226
+ </ContainerWrapper>
227
+ </div>
228
+ </div>
229
+ </Banner>
230
+ );
231
+ };
232
+
233
+ export default compose(
234
+ injectIntl,
235
+ withRouter,
236
+ connect((state) => {
237
+ return {
238
+ types: state.types.types,
239
+ };
240
+ }),
241
+ )(View);
@@ -0,0 +1,20 @@
1
+ .ui.block.title .eea.banner .content {
2
+ padding-right: 1rem;
3
+ padding-left: 1rem;
4
+ }
5
+
6
+ .eea.banner .documentFirstHeading {
7
+ border-bottom: none;
8
+ margin-bottom: 0;
9
+
10
+ &::before {
11
+ content: none;
12
+ }
13
+ }
14
+
15
+ .share-popup {
16
+ .actions {
17
+ display: flex;
18
+ flex-flow: row;
19
+ }
20
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import config from '@plone/volto/registry';
3
+
4
+ const CustomCSS = (props) => {
5
+ return (
6
+ <link
7
+ rel={'stylesheet'}
8
+ href={`${config.settings.apiPath}/voltoCustom.css`}
9
+ />
10
+ );
11
+ };
12
+ export default CustomCSS;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { connect } from 'react-redux';
3
+ import './draft.css';
4
+ import { BodyClass } from '@plone/volto/helpers';
5
+
6
+ const DraftBackground = ({ review_state }) => {
7
+ const draftClass = `wf-state-${review_state}`;
8
+ return <BodyClass className={draftClass} />;
9
+ };
10
+ function propsAreEqual(prevProps, nextProps) {
11
+ return prevProps.review_state === nextProps.review_state;
12
+ }
13
+
14
+ export default connect((state) => ({
15
+ review_state: state.content.data?.review_state,
16
+ }))(React.memo(DraftBackground, propsAreEqual));
@@ -0,0 +1,3 @@
1
+ .view-viewview:not(.wf-state-published) .content-area {
2
+ background-image: url('draft.png') !important;
3
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Document view component.
3
+ * @module components/theme/View/HomePageInverseView
4
+ */
5
+
6
+ import React from 'react';
7
+ import PropTypes from 'prop-types';
8
+
9
+ import { DefaultView } from '@plone/volto/components/';
10
+
11
+ import { BodyClass } from '@plone/volto/helpers';
12
+
13
+ import { hasBlocksData } from '@plone/volto/helpers';
14
+
15
+ /**
16
+ * Component to display the default view.
17
+ * @function HomePageInverseView
18
+ * @param {Object} content Content object.
19
+ * @returns {string} Markup of the component.
20
+ */
21
+ const HomePageInverseView = ({ content }) => {
22
+ return hasBlocksData(content) ? (
23
+ <>
24
+ <BodyClass className="homepage homepage-inverse" />
25
+ <DefaultView content={content} />
26
+ </>
27
+ ) : null;
28
+ };
29
+
30
+ /**
31
+ * Property types.
32
+ * @property {Object} propTypes Property types.
33
+ * @static
34
+ */
35
+ HomePageInverseView.propTypes = {
36
+ /**
37
+ * Content of the object
38
+ */
39
+ content: PropTypes.shape({
40
+ /**
41
+ * Title of the object
42
+ */
43
+ title: PropTypes.string,
44
+ /**
45
+ * Description of the object
46
+ */
47
+ description: PropTypes.string,
48
+ /**
49
+ * Text of the object
50
+ */
51
+ text: PropTypes.shape({
52
+ /**
53
+ * Data of the text of the object
54
+ */
55
+ data: PropTypes.string,
56
+ }),
57
+ }).isRequired,
58
+ };
59
+
60
+ export default HomePageInverseView;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Document view component.
3
+ * @module components/theme/View/HomePageView
4
+ */
5
+
6
+ import React from 'react';
7
+ import PropTypes from 'prop-types';
8
+
9
+ import { DefaultView } from '@plone/volto/components/';
10
+
11
+ import { BodyClass } from '@plone/volto/helpers';
12
+
13
+ import { hasBlocksData } from '@plone/volto/helpers';
14
+
15
+ /**
16
+ * Component to display the default view.
17
+ * @function HomePageView
18
+ * @param {Object} content Content object.
19
+ * @returns {string} Markup of the component.
20
+ */
21
+ const HomePageView = ({ content }) => {
22
+ return hasBlocksData(content) ? (
23
+ <>
24
+ <BodyClass className="homepage" />
25
+ <DefaultView content={content} />
26
+ </>
27
+ ) : null;
28
+ };
29
+
30
+ /**
31
+ * Property types.
32
+ * @property {Object} propTypes Property types.
33
+ * @static
34
+ */
35
+ HomePageView.propTypes = {
36
+ /**
37
+ * Content of the object
38
+ */
39
+ content: PropTypes.shape({
40
+ /**
41
+ * Title of the object
42
+ */
43
+ title: PropTypes.string,
44
+ /**
45
+ * Description of the object
46
+ */
47
+ description: PropTypes.string,
48
+ /**
49
+ * Text of the object
50
+ */
51
+ text: PropTypes.shape({
52
+ /**
53
+ * Data of the text of the object
54
+ */
55
+ data: PropTypes.string,
56
+ }),
57
+ }).isRequired,
58
+ };
59
+
60
+ export default HomePageView;
@@ -0,0 +1,34 @@
1
+ import { useSelector } from 'react-redux';
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import config from '@plone/volto/registry';
4
+
5
+ import { Logo } from '@eeacms/volto-eea-design-system/ui';
6
+ import LogoImage from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/eea-logo.svg';
7
+
8
+ const messages = defineMessages({
9
+ site: {
10
+ id: 'Site',
11
+ defaultMessage: 'Site',
12
+ },
13
+ eeasite: {
14
+ id: 'European Environment Agency',
15
+ defaultMessage: 'European Environment Agency',
16
+ },
17
+ });
18
+
19
+ const EEALogo = () => {
20
+ const lang = useSelector((state) => state.intl.locale);
21
+ const intl = useIntl();
22
+ const url = config.settings.isMultilingual ? `/${lang}` : '/';
23
+
24
+ return (
25
+ <Logo
26
+ src={LogoImage}
27
+ url={url}
28
+ title={intl.formatMessage(messages.site)}
29
+ alt={intl.formatMessage(messages.eeasite)}
30
+ />
31
+ );
32
+ };
33
+
34
+ export default EEALogo;
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import cx from 'classnames';
3
+ import { useSelector } from 'react-redux';
4
+ import { useLocation } from 'react-router-dom';
5
+
6
+ import { BodyClass } from '@plone/volto/helpers';
7
+ import { isSubsiteRoot } from 'volto-subsites/utils';
8
+
9
+ const SubsiteClass = () => {
10
+ const subsite = useSelector(
11
+ (state) => state.content?.data?.['@components']?.subsite || {},
12
+ );
13
+ const location = useLocation();
14
+
15
+ return (
16
+ <BodyClass
17
+ className={cx('subsite', `subsite-${subsite.subsite_css_class?.token}`, {
18
+ 'subsite-root': isSubsiteRoot(location.pathname, subsite),
19
+ })}
20
+ />
21
+ );
22
+ };
23
+ export default SubsiteClass;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import cx from 'classnames';
3
+ import Tag from '@eeacms/volto-eea-design-system/ui/Tag/Tag';
4
+
5
+ export const TokenWidget = ({ value, children, className }) =>
6
+ value ? (
7
+ <span className={cx(className, 'token', 'widget')}>
8
+ {value.map((tag) => (
9
+ <Tag href={`http://search.apps.eea.europa.eu/?q=${tag}`} key={tag}>
10
+ {children ? children(tag) : tag}
11
+ </Tag>
12
+ ))}
13
+ </span>
14
+ ) : (
15
+ ''
16
+ );