@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,495 @@
1
+ /**
2
+ * Sharing container.
3
+ * @module components/manage/Sharing/Sharing
4
+ */
5
+ import React, { Component } from 'react';
6
+ import PropTypes from 'prop-types';
7
+ import { Plug, Pluggable } from '@plone/volto/components/manage/Pluggable';
8
+ import { Helmet } from '@plone/volto/helpers';
9
+ import { connect } from 'react-redux';
10
+ import { compose } from 'redux';
11
+ import { Link, withRouter } from 'react-router-dom';
12
+ import { find, isEqual, map } from 'lodash';
13
+ import { Portal } from 'react-portal';
14
+ import {
15
+ Button,
16
+ Checkbox,
17
+ Container,
18
+ Form,
19
+ Icon as IconOld,
20
+ Input,
21
+ Segment,
22
+ Table,
23
+ } from 'semantic-ui-react';
24
+ import jwtDecode from 'jwt-decode';
25
+ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
26
+
27
+ import { updateSharing, getSharing } from '@plone/volto/actions';
28
+ import { getBaseUrl } from '@plone/volto/helpers';
29
+ import { Icon, Toolbar, Toast } from '@plone/volto/components';
30
+ import { toast } from 'react-toastify';
31
+
32
+ import aheadSVG from '@plone/volto/icons/ahead.svg';
33
+ import clearSVG from '@plone/volto/icons/clear.svg';
34
+ import backSVG from '@plone/volto/icons/back.svg';
35
+
36
+ const messages = defineMessages({
37
+ searchForUserOrGroup: {
38
+ id: 'Search for user or group',
39
+ defaultMessage: 'Search for user or group',
40
+ },
41
+ inherit: {
42
+ id: 'Inherit permissions from higher levels',
43
+ defaultMessage: 'Inherit permissions from higher levels',
44
+ },
45
+ save: {
46
+ id: 'Save',
47
+ defaultMessage: 'Save',
48
+ },
49
+ cancel: {
50
+ id: 'Cancel',
51
+ defaultMessage: 'Cancel',
52
+ },
53
+ back: {
54
+ id: 'Back',
55
+ defaultMessage: 'Back',
56
+ },
57
+ sharing: {
58
+ id: 'Sharing',
59
+ defaultMessage: 'Sharing',
60
+ },
61
+ user: {
62
+ id: 'User',
63
+ defaultMessage: 'User',
64
+ },
65
+ group: {
66
+ id: 'Group',
67
+ defaultMessage: 'Group',
68
+ },
69
+ globalRole: {
70
+ id: 'Global role',
71
+ defaultMessage: 'Global role',
72
+ },
73
+ inheritedValue: {
74
+ id: 'Inherited value',
75
+ defaultMessage: 'Inherited value',
76
+ },
77
+ permissionsUpdated: {
78
+ id: 'Permissions updated',
79
+ defaultMessage: 'Permissions updated',
80
+ },
81
+ permissionsUpdatedSuccessfully: {
82
+ id: 'Permissions have been updated successfully',
83
+ defaultMessage: 'Permissions have been updated successfully',
84
+ },
85
+ });
86
+
87
+ /**
88
+ * SharingComponent class.
89
+ * @class SharingComponent
90
+ * @extends Component
91
+ */
92
+ class SharingComponent extends Component {
93
+ /**
94
+ * Property types.
95
+ * @property {Object} propTypes Property types.
96
+ * @static
97
+ */
98
+ static propTypes = {
99
+ updateSharing: PropTypes.func.isRequired,
100
+ getSharing: PropTypes.func.isRequired,
101
+ updateRequest: PropTypes.shape({
102
+ loading: PropTypes.bool,
103
+ loaded: PropTypes.bool,
104
+ }).isRequired,
105
+ pathname: PropTypes.string.isRequired,
106
+ entries: PropTypes.arrayOf(
107
+ PropTypes.shape({
108
+ id: PropTypes.string,
109
+ login: PropTypes.string,
110
+ roles: PropTypes.object,
111
+ title: PropTypes.string,
112
+ type: PropTypes.string,
113
+ }),
114
+ ).isRequired,
115
+ available_roles: PropTypes.arrayOf(PropTypes.object).isRequired,
116
+ inherit: PropTypes.bool,
117
+ title: PropTypes.string.isRequired,
118
+ login: PropTypes.string,
119
+ };
120
+
121
+ /**
122
+ * Default properties
123
+ * @property {Object} defaultProps Default properties.
124
+ * @static
125
+ */
126
+ static defaultProps = {
127
+ inherit: null,
128
+ login: '',
129
+ };
130
+
131
+ /**
132
+ * Constructor
133
+ * @method constructor
134
+ * @param {Object} props Component properties
135
+ * @constructs Sharing
136
+ */
137
+ constructor(props) {
138
+ super(props);
139
+ this.onCancel = this.onCancel.bind(this);
140
+ this.onChange = this.onChange.bind(this);
141
+ this.onChangeSearch = this.onChangeSearch.bind(this);
142
+ this.onSearch = this.onSearch.bind(this);
143
+ this.onSubmit = this.onSubmit.bind(this);
144
+ this.onToggleInherit = this.onToggleInherit.bind(this);
145
+ this.state = {
146
+ search: '',
147
+ inherit: props.inherit,
148
+ entries: props.entries,
149
+ isClient: false,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Component did mount
155
+ * @method componentDidMount
156
+ * @returns {undefined}
157
+ */
158
+ componentDidMount() {
159
+ this.props.getSharing(getBaseUrl(this.props.pathname), this.state.search);
160
+ this.setState({ isClient: true });
161
+ }
162
+
163
+ /**
164
+ * Component will receive props
165
+ * @method componentWillReceiveProps
166
+ * @param {Object} nextProps Next properties
167
+ * @returns {undefined}
168
+ */
169
+ UNSAFE_componentWillReceiveProps(nextProps) {
170
+ if (this.props.updateRequest.loading && nextProps.updateRequest.loaded) {
171
+ this.props.getSharing(getBaseUrl(this.props.pathname), this.state.search);
172
+ toast.success(
173
+ <Toast
174
+ success
175
+ title={this.props.intl.formatMessage(messages.permissionsUpdated)}
176
+ content={this.props.intl.formatMessage(
177
+ messages.permissionsUpdatedSuccessfully,
178
+ )}
179
+ />,
180
+ );
181
+ }
182
+ this.setState({
183
+ inherit:
184
+ this.props.inherit === null ? nextProps.inherit : this.state.inherit,
185
+ entries: map(nextProps.entries, (entry) => {
186
+ const values = find(this.state.entries, { id: entry.id });
187
+ return {
188
+ ...entry,
189
+ roles: values ? values.roles : entry.roles,
190
+ };
191
+ }),
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Submit handler
197
+ * @method onSubmit
198
+ * @param {object} event Event object.
199
+ * @returns {undefined}
200
+ */
201
+ onSubmit(event) {
202
+ const data = { entries: [] };
203
+ event.preventDefault();
204
+ if (this.props.inherit !== this.state.inherit) {
205
+ data.inherit = this.state.inherit;
206
+ }
207
+ for (let i = 0; i < this.props.entries.length; i += 1) {
208
+ if (!isEqual(this.props.entries[i].roles, this.state.entries[i].roles)) {
209
+ data.entries.push({
210
+ id: this.state.entries[i].id,
211
+ type: this.state.entries[i].type,
212
+ roles: this.state.entries[i].roles,
213
+ });
214
+ }
215
+ }
216
+ this.props.updateSharing(getBaseUrl(this.props.pathname), data);
217
+ }
218
+
219
+ /**
220
+ * Search handler
221
+ * @method onSearch
222
+ * @param {object} event Event object.
223
+ * @returns {undefined}
224
+ */
225
+ onSearch(event) {
226
+ event.preventDefault();
227
+ this.props.getSharing(getBaseUrl(this.props.pathname), this.state.search);
228
+ }
229
+
230
+ /**
231
+ * On change search handler
232
+ * @method onChangeSearch
233
+ * @param {object} event Event object.
234
+ * @returns {undefined}
235
+ */
236
+ onChangeSearch(event) {
237
+ this.setState({
238
+ search: event.target.value,
239
+ });
240
+ }
241
+
242
+ /**
243
+ * On toggle inherit handler
244
+ * @method onToggleInherit
245
+ * @returns {undefined}
246
+ */
247
+ onToggleInherit() {
248
+ this.setState({
249
+ inherit: !this.state.inherit,
250
+ });
251
+ }
252
+
253
+ /**
254
+ * On change handler
255
+ * @method onChange
256
+ * @param {object} event Event object
257
+ * @param {string} value Entry value
258
+ * @returns {undefined}
259
+ */
260
+ onChange(event, { value }) {
261
+ const [principal, role] = value.split(':');
262
+ this.setState({
263
+ entries: map(this.state.entries, (entry) => ({
264
+ ...entry,
265
+ roles:
266
+ entry.id === principal
267
+ ? {
268
+ ...entry.roles,
269
+ [role]: !entry.roles[role],
270
+ }
271
+ : entry.roles,
272
+ })),
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Cancel handler
278
+ * @method onCancel
279
+ * @returns {undefined}
280
+ */
281
+ onCancel() {
282
+ this.props.history.push(getBaseUrl(this.props.pathname));
283
+ }
284
+
285
+ /**
286
+ * Render method.
287
+ * @method render
288
+ * @returns {string} Markup for the component.
289
+ */
290
+ render() {
291
+ return (
292
+ <Container id="page-sharing">
293
+ <Helmet title={this.props.intl.formatMessage(messages.sharing)} />
294
+ <Segment.Group raised>
295
+ <Pluggable name="sharing-component" />
296
+ <Plug pluggable="sharing-component" id="sharing-component-title">
297
+ <Segment className="primary">
298
+ <FormattedMessage
299
+ id="Sharing for {title}"
300
+ defaultMessage="Sharing for {title}"
301
+ values={{ title: <q>{this.props.title}</q> }}
302
+ />
303
+ </Segment>
304
+ </Plug>
305
+ <Plug
306
+ pluggable="sharing-component"
307
+ id="sharing-component-description"
308
+ >
309
+ <Segment secondary>
310
+ <FormattedMessage
311
+ id="You can control who can view and edit your item using the list below."
312
+ defaultMessage="You can control who can view and edit your item using the list below."
313
+ />
314
+ </Segment>
315
+ </Plug>
316
+ <Plug pluggable="sharing-component" id="sharing-component-search">
317
+ <Segment>
318
+ <Form onSubmit={this.onSearch}>
319
+ <Form.Field>
320
+ <Input
321
+ name="SearchableText"
322
+ action={{ icon: 'search' }}
323
+ placeholder={this.props.intl.formatMessage(
324
+ messages.searchForUserOrGroup,
325
+ )}
326
+ onChange={this.onChangeSearch}
327
+ />
328
+ </Form.Field>
329
+ </Form>
330
+ </Segment>
331
+ </Plug>
332
+ <Plug
333
+ pluggable="sharing-component"
334
+ id="sharing-component-form"
335
+ dependencies={[this.state.entries, this.props.available_roles]}
336
+ >
337
+ <Form onSubmit={this.onSubmit}>
338
+ <Table celled padded striped attached>
339
+ <Table.Header>
340
+ <Table.Row>
341
+ <Table.HeaderCell>
342
+ <FormattedMessage id="Name" defaultMessage="Name" />
343
+ </Table.HeaderCell>
344
+ {this.props.available_roles?.map((role) => (
345
+ <Table.HeaderCell key={role.id}>
346
+ {role.title}
347
+ </Table.HeaderCell>
348
+ ))}
349
+ </Table.Row>
350
+ </Table.Header>
351
+ <Table.Body>
352
+ {this.state.entries?.map((entry) => (
353
+ <Table.Row key={entry.id}>
354
+ <Table.Cell>
355
+ <IconOld
356
+ name={entry.type === 'user' ? 'user' : 'users'}
357
+ title={
358
+ entry.type === 'user'
359
+ ? this.props.intl.formatMessage(messages.user)
360
+ : this.props.intl.formatMessage(messages.group)
361
+ }
362
+ />{' '}
363
+ {entry.title}
364
+ {entry.login && ` (${entry.login})`}
365
+ </Table.Cell>
366
+ {this.props.available_roles?.map((role) => (
367
+ <Table.Cell key={role.id}>
368
+ {entry.roles[role.id] === 'global' && (
369
+ <IconOld
370
+ name="check circle outline"
371
+ title={this.props.intl.formatMessage(
372
+ messages.globalRole,
373
+ )}
374
+ color="blue"
375
+ />
376
+ )}
377
+ {entry.roles[role.id] === 'acquired' && (
378
+ <IconOld
379
+ name="check circle outline"
380
+ color="green"
381
+ title={this.props.intl.formatMessage(
382
+ messages.inheritedValue,
383
+ )}
384
+ />
385
+ )}
386
+ {typeof entry.roles[role.id] === 'boolean' && (
387
+ <Checkbox
388
+ onChange={this.onChange}
389
+ value={`${entry.id}:${role.id}`}
390
+ checked={entry.roles[role.id]}
391
+ disabled={entry.login === this.props.login}
392
+ />
393
+ )}
394
+ </Table.Cell>
395
+ ))}
396
+ </Table.Row>
397
+ ))}
398
+ </Table.Body>
399
+ </Table>
400
+ <Segment attached>
401
+ <Form.Field>
402
+ <Checkbox
403
+ checked={this.state.inherit}
404
+ onChange={this.onToggleInherit}
405
+ label={this.props.intl.formatMessage(messages.inherit)}
406
+ />
407
+ </Form.Field>
408
+ <p className="help">
409
+ <FormattedMessage
410
+ id="By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator."
411
+ defaultMessage="By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator."
412
+ values={{
413
+ inherited: (
414
+ <IconOld name="check circle outline" color="green" />
415
+ ),
416
+ global: (
417
+ <IconOld name="check circle outline" color="blue" />
418
+ ),
419
+ }}
420
+ />
421
+ </p>
422
+ </Segment>
423
+ <Segment className="actions" attached clearing>
424
+ <Button
425
+ basic
426
+ icon
427
+ primary
428
+ floated="right"
429
+ type="submit"
430
+ aria-label={this.props.intl.formatMessage(messages.save)}
431
+ title={this.props.intl.formatMessage(messages.save)}
432
+ loading={this.props.updateRequest.loading}
433
+ onClick={this.onSubmit}
434
+ >
435
+ <Icon className="circled" name={aheadSVG} size="30px" />
436
+ </Button>
437
+ <Button
438
+ basic
439
+ icon
440
+ secondary
441
+ aria-label={this.props.intl.formatMessage(messages.cancel)}
442
+ title={this.props.intl.formatMessage(messages.cancel)}
443
+ floated="right"
444
+ onClick={this.onCancel}
445
+ >
446
+ <Icon className="circled" name={clearSVG} size="30px" />
447
+ </Button>
448
+ </Segment>
449
+ </Form>
450
+ </Plug>
451
+ </Segment.Group>
452
+ {this.state.isClient && (
453
+ <Portal node={document.getElementById('toolbar')}>
454
+ <Toolbar
455
+ pathname={this.props.pathname}
456
+ hideDefaultViewButtons
457
+ inner={
458
+ <Link
459
+ to={`${getBaseUrl(this.props.pathname)}`}
460
+ className="item"
461
+ >
462
+ <Icon
463
+ name={backSVG}
464
+ className="contents circled"
465
+ size="30px"
466
+ title={this.props.intl.formatMessage(messages.back)}
467
+ />
468
+ </Link>
469
+ }
470
+ />
471
+ </Portal>
472
+ )}
473
+ </Container>
474
+ );
475
+ }
476
+ }
477
+
478
+ export default compose(
479
+ withRouter,
480
+ injectIntl,
481
+ connect(
482
+ (state, props) => ({
483
+ entries: state.sharing.data.entries,
484
+ inherit: state.sharing.data.inherit,
485
+ available_roles: state.sharing.data.available_roles,
486
+ updateRequest: state.sharing.update,
487
+ pathname: props.location.pathname,
488
+ title: state.content.data.title,
489
+ login: state.userSession.token
490
+ ? jwtDecode(state.userSession.token).sub
491
+ : '',
492
+ }),
493
+ { updateSharing, getSharing },
494
+ ),
495
+ )(SharingComponent);