@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,487 @@
1
+ /**
2
+ * Comments components.
3
+ * @module components/theme/Comments/Comments
4
+ */
5
+
6
+ import {
7
+ addComment,
8
+ deleteComment,
9
+ listComments,
10
+ listMoreComments,
11
+ } from '@plone/volto/actions';
12
+ import { Avatar, CommentEditModal, Form } from '@plone/volto/components';
13
+ import { flattenToAppURL, getBaseUrl, getColor } from '@plone/volto/helpers';
14
+ import PropTypes from 'prop-types';
15
+ import React, { Component } from 'react';
16
+ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
17
+ import { Portal } from 'react-portal';
18
+ import { connect } from 'react-redux';
19
+ import { compose } from 'redux';
20
+ import { Button, Comment, Container, Icon } from 'semantic-ui-react';
21
+ import { formatRelativeDate } from '@plone/volto/helpers/Utils/Date';
22
+ import config from '@plone/volto/registry';
23
+
24
+ const messages = defineMessages({
25
+ comment: {
26
+ id: 'Comment',
27
+ defaultMessage: 'Comment',
28
+ },
29
+ comments: {
30
+ id: 'Comments',
31
+ defaultMessage: 'Comments',
32
+ },
33
+ commentDescription: {
34
+ id:
35
+ 'You can add a comment by filling out the form below. Plain text formatting.',
36
+ defaultMessage:
37
+ 'You can add a comment by filling out the form below. Plain text formatting.',
38
+ },
39
+ default: {
40
+ id: 'Default',
41
+ defaultMessage: 'Default',
42
+ },
43
+ delete: {
44
+ id: 'Delete',
45
+ defaultMessage: 'Delete',
46
+ },
47
+ edit: {
48
+ id: 'Edit',
49
+ defaultMessage: 'Edit',
50
+ },
51
+ reply: {
52
+ id: 'Reply',
53
+ defaultMessage: 'Reply',
54
+ },
55
+ hideReplies: {
56
+ id: 'Hide Replies',
57
+ defaultMessage: 'Hide Replies',
58
+ },
59
+ showReplies: {
60
+ id: 'Show Replies',
61
+ defaultMessage: 'Show Replies',
62
+ },
63
+ loadMoreComments: {
64
+ id: 'Load more',
65
+ defaultMessage: 'Load more...',
66
+ },
67
+ });
68
+ /**
69
+ * Schema for the Form components to show an input field with it's label
70
+ * @param {Object} intl
71
+ */
72
+ const makeFormSchema = (intl) => ({
73
+ fieldsets: [
74
+ {
75
+ fields: ['comment'],
76
+ id: 'default',
77
+ title: intl.formatMessage(messages.default),
78
+ },
79
+ ],
80
+ properties: {
81
+ comment: {
82
+ title: intl.formatMessage(messages.comment),
83
+ type: 'string',
84
+ widget: 'textarea',
85
+ },
86
+ },
87
+ required: ['comment1'],
88
+ });
89
+
90
+ /**
91
+ * Comments container class.
92
+ * @class Comments
93
+ * @extends Component
94
+ */
95
+ class Comments extends Component {
96
+ /**
97
+ * Property types.
98
+ * @property {Object} propTypes Property types.
99
+ * @static
100
+ */
101
+ static propTypes = {
102
+ addComment: PropTypes.func.isRequired,
103
+ deleteComment: PropTypes.func.isRequired,
104
+ listComments: PropTypes.func.isRequired,
105
+ listMoreComments: PropTypes.func.isRequired,
106
+ pathname: PropTypes.string.isRequired,
107
+ items: PropTypes.arrayOf(
108
+ PropTypes.shape({
109
+ author_name: PropTypes.string,
110
+ creation_date: PropTypes.string,
111
+ text: PropTypes.shape({
112
+ data: PropTypes.string,
113
+ 'mime-type': PropTypes.string,
114
+ }),
115
+ is_deletable: PropTypes.bool,
116
+ is_editable: PropTypes.bool,
117
+ }),
118
+ ).isRequired,
119
+ addRequest: PropTypes.shape({
120
+ loading: PropTypes.bool,
121
+ loaded: PropTypes.bool,
122
+ }).isRequired,
123
+ deleteRequest: PropTypes.shape({
124
+ loading: PropTypes.bool,
125
+ loaded: PropTypes.bool,
126
+ }).isRequired,
127
+ };
128
+
129
+ /**
130
+ * Constructor
131
+ * @method constructor
132
+ * @param {Object} props Component properties
133
+ * @constructs Comments
134
+ */
135
+ constructor(props) {
136
+ super(props);
137
+ this.onSubmit = this.onSubmit.bind(this);
138
+ this.onDelete = this.onDelete.bind(this);
139
+ this.onEdit = this.onEdit.bind(this);
140
+ this.onEditOk = this.onEditOk.bind(this);
141
+ this.onEditCancel = this.onEditCancel.bind(this);
142
+ this.setReplyTo = this.setReplyTo.bind(this);
143
+ this.loadMoreComments = this.loadMoreComments.bind(this);
144
+ this.state = {
145
+ showEdit: false,
146
+ editId: null,
147
+ editText: null,
148
+ replyTo: null,
149
+ collapsedComments: {},
150
+ };
151
+ }
152
+
153
+ componentDidMount() {
154
+ this.props.listComments(getBaseUrl(this.props.pathname));
155
+ }
156
+
157
+ /**
158
+ * Component will receive props
159
+ * @method componentWillReceiveProps
160
+ * @param {Object} nextProps Next properties
161
+ * @returns {undefined}
162
+ */
163
+ UNSAFE_componentWillReceiveProps(nextProps) {
164
+ if (
165
+ nextProps.pathname !== this.props.pathname ||
166
+ (this.props.addRequest.loading && nextProps.addRequest.loaded) ||
167
+ (this.props.deleteRequest.loading && nextProps.deleteRequest.loaded)
168
+ ) {
169
+ this.props.listComments(getBaseUrl(nextProps.pathname));
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Submit handler
175
+ * @method onSubmit
176
+ * @param {Object} formData Form data.
177
+ * @returns {undefined}
178
+ */
179
+ onSubmit(formData) {
180
+ this.props.addComment(
181
+ getBaseUrl(this.props.pathname),
182
+ formData.comment,
183
+ this.state.replyTo,
184
+ );
185
+ this.setState({ replyTo: null });
186
+ }
187
+
188
+ /**
189
+ * The id of the comment that will receive a reply
190
+ * @param {string} commentId
191
+ */
192
+ setReplyTo(commentId) {
193
+ this.setState({ replyTo: commentId });
194
+ }
195
+
196
+ /**
197
+ * Calls the action listMoreComments passing the received url for next array of comments
198
+ */
199
+ loadMoreComments() {
200
+ this.props.listMoreComments(this.props.next);
201
+ }
202
+
203
+ /**
204
+ * Delete handler
205
+ * @method onDelete
206
+ * @param {Object} event Event object.
207
+ * @param {string} value Delete value.
208
+ * @returns {undefined}
209
+ */
210
+ onDelete(value) {
211
+ this.props.deleteComment(value);
212
+ }
213
+
214
+ /**
215
+ * Will hide all replies to the specific comment
216
+ * including replies to any of the replies
217
+ * @param {string} commentId
218
+ */
219
+ hideReply(commentId) {
220
+ this.setState((prevState) => {
221
+ const hasComment = prevState.collapsedComments[commentId];
222
+ const { collapsedComments } = prevState;
223
+
224
+ return {
225
+ collapsedComments: {
226
+ ...collapsedComments,
227
+ [commentId]: !hasComment,
228
+ },
229
+ };
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Edit handler
235
+ * @method onEdit
236
+ * @param {Object} event Event object.
237
+ * @param {string} value Delete value.
238
+ * @returns {undefined}
239
+ */
240
+ onEdit(value) {
241
+ this.setState({
242
+ showEdit: true,
243
+ editId: value.id,
244
+ editText: value.text,
245
+ });
246
+ }
247
+
248
+ /**
249
+ * On edit ok
250
+ * @method onEditOk
251
+ * @returns {undefined}
252
+ */
253
+ onEditOk() {
254
+ this.setState({
255
+ showEdit: false,
256
+ editId: null,
257
+ editText: null,
258
+ });
259
+ this.props.listComments(getBaseUrl(this.props.pathname));
260
+ }
261
+
262
+ /**
263
+ * On edit cancel
264
+ * @method onEditCancel
265
+ * @returns {undefined}
266
+ */
267
+ onEditCancel(ev) {
268
+ this.setState({
269
+ showEdit: false,
270
+ editId: null,
271
+ editText: null,
272
+ replyTo: null,
273
+ });
274
+ }
275
+
276
+ addRepliesAsChildrenToComments(items) {
277
+ let initialValue = {};
278
+ const allCommentsWithCildren = items.reduce((accumulator, item) => {
279
+ return {
280
+ [item.comment_id]: { comment: item, children: [] },
281
+ ...accumulator,
282
+ };
283
+ }, initialValue);
284
+
285
+ items.forEach((comment) => {
286
+ if (comment.in_reply_to) {
287
+ allCommentsWithCildren[comment.in_reply_to].children.push(comment);
288
+ }
289
+ });
290
+ return allCommentsWithCildren;
291
+ }
292
+
293
+ /**
294
+ * Render method.
295
+ * @method render
296
+ * @returns {string} Markup for the component.
297
+ */
298
+ render() {
299
+ const { items } = this.props;
300
+ const { collapsedComments } = this.state;
301
+ // object with comment ids, to easily verify if any comment has children
302
+ const allCommentsWithCildren = this.addRepliesAsChildrenToComments(items);
303
+ // all comments that are not a reply will be shown in the first iteration
304
+ const allPrimaryComments = items.filter((comment) => !comment.in_reply_to);
305
+
306
+ // recursively makes comments with their replies nested
307
+ // each iteration will show replies to the specific comment using allCommentsWithCildren
308
+ const commentElement = (comment) => (
309
+ <Comment key={comment.comment_id}>
310
+ <Avatar
311
+ src={comment.author_image}
312
+ title={comment.author_name || 'Anonymous'}
313
+ color={getColor(comment.author_username)}
314
+ />
315
+ <Comment.Content>
316
+ <Comment.Author as="span">{comment.author_name}</Comment.Author>
317
+ <Comment.Metadata>
318
+ <span>
319
+ {' '}
320
+ <span title={comment.creation_date}>
321
+ {formatRelativeDate({
322
+ date: comment.creation_date,
323
+ locale: config.settings.dateLocale || 'en-gb',
324
+ })}
325
+ </span>
326
+ </span>
327
+ </Comment.Metadata>
328
+ <Comment.Text>
329
+ {' '}
330
+ {comment.text['mime-type'] === 'text/html' ? (
331
+ <div
332
+ dangerouslySetInnerHTML={{
333
+ __html: comment.text.data,
334
+ }}
335
+ />
336
+ ) : (
337
+ comment.text.data
338
+ )}
339
+ </Comment.Text>
340
+ <Comment.Actions>
341
+ <Comment.Action
342
+ as="a"
343
+ aria-label={this.props.intl.formatMessage(messages.reply)}
344
+ onClick={() => this.setReplyTo(comment.comment_id)}
345
+ >
346
+ <FormattedMessage id="Reply" defaultMessage="Reply" />
347
+ </Comment.Action>
348
+ {comment.is_editable && (
349
+ <Comment.Action
350
+ onClick={() =>
351
+ this.onEdit({
352
+ id: flattenToAppURL(comment['@id']),
353
+ text: comment.text.data,
354
+ })
355
+ }
356
+ aria-label={this.props.intl.formatMessage(messages.edit)}
357
+ value={{
358
+ id: flattenToAppURL(comment['@id']),
359
+ text: comment.text.data,
360
+ }}
361
+ >
362
+ <FormattedMessage id="Edit" defaultMessage="Edit" />
363
+ </Comment.Action>
364
+ )}
365
+ {comment.is_deletable && (
366
+ <Comment.Action
367
+ aria-label={this.props.intl.formatMessage(messages.delete)}
368
+ onClick={() => this.onDelete(flattenToAppURL(comment['@id']))}
369
+ color="red"
370
+ >
371
+ <Icon name="delete" color="red" />
372
+ <FormattedMessage
373
+ id="Delete"
374
+ defaultMessage="Delete"
375
+ color="red"
376
+ />
377
+ </Comment.Action>
378
+ )}
379
+ <Comment.Action
380
+ as="a"
381
+ onClick={() => this.hideReply(comment.comment_id)}
382
+ >
383
+ {allCommentsWithCildren[comment.comment_id].children.length >
384
+ 0 ? (
385
+ this.state.collapsedComments[comment.comment_id] ? (
386
+ <>
387
+ <Icon name="eye" color="blue" />
388
+ <FormattedMessage
389
+ id="Show Replies"
390
+ defaultMessage="Show Replies"
391
+ />
392
+ </>
393
+ ) : (
394
+ <>
395
+ <Icon name="minus" color="blue" />
396
+ <FormattedMessage
397
+ id="Hide Replies"
398
+ defaultMessage="Hide Replies"
399
+ />
400
+ </>
401
+ )
402
+ ) : null}
403
+ </Comment.Action>
404
+ </Comment.Actions>
405
+ <div id={`reply-place-${comment.comment_id}`}></div>
406
+ </Comment.Content>
407
+
408
+ {allCommentsWithCildren[comment.comment_id].children.length > 0
409
+ ? allCommentsWithCildren[comment.comment_id].children.map(
410
+ (child, index) => (
411
+ <Comment.Group
412
+ collapsed={collapsedComments[comment.comment_id]}
413
+ key={`group-${index}-${comment.comment_id}`}
414
+ >
415
+ {commentElement(child)}
416
+ </Comment.Group>
417
+ ),
418
+ )
419
+ : null}
420
+ </Comment>
421
+ );
422
+
423
+ return (
424
+ <Container className="comments">
425
+ <CommentEditModal
426
+ open={this.state.showEdit}
427
+ onCancel={this.onEditCancel}
428
+ onOk={this.onEditOk}
429
+ id={this.state.editId}
430
+ text={this.state.editText}
431
+ />
432
+ <div id="comment-add-id">
433
+ <Form
434
+ onSubmit={this.onSubmit}
435
+ onCancel={this.onEditCancel}
436
+ submitLabel={this.props.intl.formatMessage(messages.comment)}
437
+ resetAfterSubmit
438
+ schema={makeFormSchema(this.props.intl)}
439
+ />
440
+ </div>
441
+
442
+ {/* all comments */}
443
+ <Comment.Group threaded={false}>
444
+ {allPrimaryComments.map((item) => commentElement(item))}
445
+ </Comment.Group>
446
+
447
+ {/* load more button */}
448
+ {this.props.items_total > this.props.items.length && (
449
+ <Button fluid basic color="blue" onClick={this.loadMoreComments}>
450
+ <FormattedMessage id="Load more" defaultMessage="Load more..." />
451
+ </Button>
452
+ )}
453
+
454
+ {this.state.replyTo && (
455
+ <Portal
456
+ node={
457
+ document &&
458
+ document.getElementById(`reply-place-${this.state.replyTo}`)
459
+ }
460
+ >
461
+ <Form
462
+ onSubmit={this.onSubmit}
463
+ onCancel={this.onEditCancel}
464
+ submitLabel={this.props.intl.formatMessage(messages.comment)}
465
+ resetAfterSubmit
466
+ schema={makeFormSchema(this.props.intl)}
467
+ />
468
+ </Portal>
469
+ )}
470
+ </Container>
471
+ );
472
+ }
473
+ }
474
+
475
+ export default compose(
476
+ injectIntl,
477
+ connect(
478
+ (state) => ({
479
+ items: state.comments.items,
480
+ next: state.comments.next,
481
+ items_total: state.comments.items_total,
482
+ addRequest: state.comments.add,
483
+ deleteRequest: state.comments.delete,
484
+ }),
485
+ { addComment, deleteComment, listComments, listMoreComments },
486
+ ),
487
+ )(Comments);
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Footer component.
3
+ * @module components/theme/Footer/Footer
4
+ */
5
+
6
+ import React from 'react';
7
+ import { useSelector, shallowEqual } from 'react-redux';
8
+ import { flattenToAppURL } from '@plone/volto/helpers';
9
+ import EEAFooter from '@eeacms/volto-eea-design-system/ui/Footer/Footer';
10
+ import config from '@plone/volto/registry';
11
+
12
+ const Footer = () => {
13
+ const { eea } = config.settings;
14
+ const {
15
+ footerActions = [],
16
+ copyrightActions = [],
17
+ socialActions = [],
18
+ contactActions = [],
19
+ contactExtraActions = [],
20
+ } = useSelector(
21
+ (state) => ({
22
+ footerActions: state.actions?.actions?.footer_actions,
23
+ copyrightActions: state.actions?.actions?.copyright_actions,
24
+ socialActions: state.actions?.actions?.social_actions,
25
+ contactActions: state.actions?.actions?.contact_actions,
26
+ contactExtraActions: state.actions?.actions?.contact_extra_actions,
27
+ }),
28
+ shallowEqual,
29
+ );
30
+ // ZMI > portal_actions > footer_actions
31
+ const actions = footerActions.length
32
+ ? footerActions.map((action) => ({
33
+ title: action.title,
34
+ link: flattenToAppURL(action.url),
35
+ }))
36
+ : eea.footerOpts.actions;
37
+
38
+ // ZMI > portal_actions > copyright_actions
39
+ const copyright = copyrightActions.length
40
+ ? copyrightActions.map((action) => ({
41
+ title: action.title,
42
+ site: action.title,
43
+ link: flattenToAppURL(action.url),
44
+ }))
45
+ : eea.footerOpts.copyright;
46
+
47
+ // ZMI > portal_actions > social_actions
48
+ const social = socialActions.length
49
+ ? socialActions.map((action) => ({
50
+ name: action.id,
51
+ icon: action.icon,
52
+ link: action.url,
53
+ }))
54
+ : eea.footerOpts.social;
55
+
56
+ // ZMI > portal_actions > contact_actions
57
+ const contacts = contactActions.length
58
+ ? contactActions.map((action, idx) => ({
59
+ text: action.title,
60
+ icon: action.icon,
61
+ link: flattenToAppURL(action.url),
62
+ children:
63
+ idx === 0
64
+ ? contactExtraActions.map((child) => ({
65
+ text: child.title,
66
+ icon: child.icon,
67
+ link: flattenToAppURL(child.url),
68
+ }))
69
+ : [],
70
+ }))
71
+ : eea.footerOpts.contacts;
72
+
73
+ // Update options with actions from backend
74
+ const options = {
75
+ ...eea.footerOpts,
76
+ social,
77
+ contacts,
78
+ };
79
+
80
+ return (
81
+ <EEAFooter>
82
+ <EEAFooter.SubFooter {...options} />
83
+ <EEAFooter.Header>{eea.footerOpts.header}</EEAFooter.Header>
84
+ <EEAFooter.Sites sites={eea.footerOpts.sites} />
85
+ <EEAFooter.Actions actions={actions} copyright={copyright} />
86
+ </EEAFooter>
87
+ );
88
+ };
89
+
90
+ export default Footer;