@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,436 @@
1
+ /**
2
+ * ObjectBrowserWidget component.
3
+ * @module components/manage/Widgets/ObjectBrowserWidget
4
+ */
5
+
6
+ import React, { Component } from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import { compose } from 'redux';
9
+ import { compact, isArray, isEmpty, remove } from 'lodash';
10
+ import { connect } from 'react-redux';
11
+ import { Label, Popup, Button } from 'semantic-ui-react';
12
+ import {
13
+ flattenToAppURL,
14
+ isInternalURL,
15
+ isUrl,
16
+ normalizeUrl,
17
+ removeProtocol,
18
+ } from '@plone/volto/helpers/Url/Url';
19
+ import { searchContent } from '@plone/volto/actions/search/search';
20
+ import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
21
+ import { defineMessages, injectIntl } from 'react-intl';
22
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
23
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
24
+
25
+ import navTreeSVG from '@plone/volto/icons/nav.svg';
26
+ import clearSVG from '@plone/volto/icons/clear.svg';
27
+ import homeSVG from '@plone/volto/icons/home.svg';
28
+ import aheadSVG from '@plone/volto/icons/ahead.svg';
29
+ import blankSVG from '@plone/volto/icons/blank.svg';
30
+ import { withRouter } from 'react-router';
31
+
32
+ const messages = defineMessages({
33
+ placeholder: {
34
+ id: 'No items selected',
35
+ defaultMessage: 'No items selected',
36
+ },
37
+ edit: {
38
+ id: 'Edit',
39
+ defaultMessage: 'Edit',
40
+ },
41
+ delete: {
42
+ id: 'Delete',
43
+ defaultMessage: 'Delete',
44
+ },
45
+ openObjectBrowser: {
46
+ id: 'Open object browser',
47
+ defaultMessage: 'Open object browser',
48
+ },
49
+ });
50
+
51
+ /**
52
+ * ObjectBrowserWidget component class.
53
+ * @class ObjectBrowserWidget
54
+ * @extends Component
55
+ */
56
+ export class ObjectBrowserWidgetComponent extends Component {
57
+ /**
58
+ * Property types.
59
+ * @property {Object} propTypes Property types.
60
+ * @static
61
+ */
62
+ static propTypes = {
63
+ id: PropTypes.string.isRequired,
64
+ title: PropTypes.string.isRequired,
65
+ description: PropTypes.string,
66
+ mode: PropTypes.string, // link, image, multiple
67
+ return: PropTypes.string, // single, multiple
68
+ initialPath: PropTypes.string,
69
+ required: PropTypes.bool,
70
+ error: PropTypes.arrayOf(PropTypes.string),
71
+ value: PropTypes.oneOfType([
72
+ PropTypes.arrayOf(PropTypes.object),
73
+ PropTypes.object,
74
+ ]),
75
+ onChange: PropTypes.func.isRequired,
76
+ openObjectBrowser: PropTypes.func.isRequired,
77
+ allowExternals: PropTypes.bool,
78
+ placeholder: PropTypes.string,
79
+ };
80
+
81
+ /**
82
+ * Default properties
83
+ * @property {Object} defaultProps Default properties.
84
+ * @static
85
+ */
86
+ static defaultProps = {
87
+ description: null,
88
+ required: false,
89
+ error: [],
90
+ value: [],
91
+ mode: 'multiple',
92
+ return: 'multiple',
93
+ initialPath: '',
94
+ allowExternals: false,
95
+ };
96
+
97
+ state = {
98
+ manualLinkInput: '',
99
+ validURL: false,
100
+ };
101
+
102
+ constructor(props) {
103
+ super(props);
104
+ this.selectedItemsRef = React.createRef();
105
+ this.placeholderRef = React.createRef();
106
+ }
107
+ renderLabel(item) {
108
+ const href = item['@id'];
109
+ return (
110
+ <Popup
111
+ key={flattenToAppURL(href)}
112
+ content={
113
+ <div style={{ display: 'flex' }}>
114
+ {isInternalURL(href) ? (
115
+ <Icon name={homeSVG} size="18px" />
116
+ ) : (
117
+ <Icon name={blankSVG} size="18px" />
118
+ )}
119
+ &nbsp;
120
+ {flattenToAppURL(href)}
121
+ </div>
122
+ }
123
+ trigger={
124
+ <Label>
125
+ <div className="item-title">{item.title}</div>
126
+ <div>
127
+ {this.props.mode === 'multiple' && (
128
+ <Icon
129
+ name={clearSVG}
130
+ size="12px"
131
+ className="right"
132
+ onClick={(event) => {
133
+ event.preventDefault();
134
+ this.removeItem(item);
135
+ }}
136
+ />
137
+ )}
138
+ </div>
139
+ </Label>
140
+ }
141
+ />
142
+ );
143
+ }
144
+
145
+ removeItem = (item) => {
146
+ let value = [...this.props.value];
147
+ remove(value, function (_item) {
148
+ return _item['@id'] === item['@id'];
149
+ });
150
+ this.props.onChange(this.props.id, value);
151
+ };
152
+
153
+ onChange = (item) => {
154
+ let value =
155
+ this.props.mode === 'multiple' && this.props.value
156
+ ? [...this.props.value]
157
+ : [];
158
+ value = value.filter((item) => item != null);
159
+ const maxSize =
160
+ this.props.widgetOptions?.pattern_options?.maximumSelectionSize || -1;
161
+ if (maxSize === 1 && value.length === 1) {
162
+ value = []; //enable replace of selected item with another value, if maxsize is 1
163
+ }
164
+ let exists = false;
165
+ let index = -1;
166
+ value.forEach((_item, _index) => {
167
+ if (flattenToAppURL(_item['@id']) === flattenToAppURL(item['@id'])) {
168
+ exists = true;
169
+ index = _index;
170
+ }
171
+ });
172
+ //find(value, {
173
+ // '@id': flattenToAppURL(item['@id']),
174
+ // });
175
+ if (!exists) {
176
+ // add item
177
+ // Check if we want to filter the attributes of the selected item
178
+ let resultantItem = item;
179
+ if (this.props.selectedItemAttrs) {
180
+ const allowedItemKeys = [
181
+ ...this.props.selectedItemAttrs,
182
+ // Add the required attributes for the widget to work
183
+ '@id',
184
+ 'title',
185
+ ];
186
+ resultantItem = Object.keys(item)
187
+ .filter((key) => allowedItemKeys.includes(key))
188
+ .reduce((obj, key) => {
189
+ obj[key] = item[key];
190
+ return obj;
191
+ }, {});
192
+ }
193
+ // Add required @id field, just in case
194
+ resultantItem = { ...resultantItem, '@id': item['@id'] };
195
+ value.push(resultantItem);
196
+ if (this.props.return === 'single') {
197
+ this.props.onChange(this.props.id, value[0]);
198
+ } else {
199
+ this.props.onChange(this.props.id, value);
200
+ }
201
+ } else {
202
+ //remove item
203
+ value.splice(index, 1);
204
+ this.props.onChange(this.props.id, value);
205
+ }
206
+ };
207
+
208
+ onManualLinkInput = (e) => {
209
+ this.setState({ manualLinkInput: e.target.value });
210
+ if (this.validateManualLink(e.target.value)) {
211
+ this.setState({ validURL: true });
212
+ } else {
213
+ this.setState({ validURL: false });
214
+ }
215
+ };
216
+
217
+ validateManualLink = (url) => {
218
+ if (this.props.allowExternals) {
219
+ return isUrl(url);
220
+ } else {
221
+ return isInternalURL(url);
222
+ }
223
+ };
224
+
225
+ onSubmitManualLink = () => {
226
+ if (this.validateManualLink(this.state.manualLinkInput)) {
227
+ if (isInternalURL(this.state.manualLinkInput)) {
228
+ const link = this.state.manualLinkInput;
229
+ // convert it into an internal on if possible
230
+ this.props
231
+ .searchContent(
232
+ '/',
233
+ {
234
+ 'path.query': flattenToAppURL(this.state.manualLinkInput),
235
+ 'path.depth': '0',
236
+ sort_on: 'getObjPositionInParent',
237
+ metadata_fields: '_all',
238
+ b_size: 1000,
239
+ },
240
+ `${this.props.block}-${this.props.mode}`,
241
+ )
242
+ .then((resp) => {
243
+ if (resp.items?.length > 0) {
244
+ this.onChange(resp.items[0]);
245
+ } else {
246
+ this.props.onChange(this.props.id, [
247
+ {
248
+ '@id': flattenToAppURL(link),
249
+ title: removeProtocol(link),
250
+ },
251
+ ]);
252
+ }
253
+ });
254
+ } else {
255
+ this.props.onChange(this.props.id, [
256
+ {
257
+ '@id': normalizeUrl(this.state.manualLinkInput),
258
+ title: removeProtocol(this.state.manualLinkInput),
259
+ },
260
+ ]);
261
+ }
262
+ this.setState({ validURL: true, manualLinkInput: '' });
263
+ }
264
+ };
265
+
266
+ onKeyDownManualLink = (e) => {
267
+ if (e.key === 'Enter') {
268
+ e.preventDefault();
269
+ e.stopPropagation();
270
+ this.onSubmitManualLink();
271
+ } else if (e.key === 'Escape') {
272
+ e.preventDefault();
273
+ e.stopPropagation();
274
+ // TODO: Do something on ESC key
275
+ }
276
+ };
277
+
278
+ showObjectBrowser = (ev) => {
279
+ ev.preventDefault();
280
+ this.props.openObjectBrowser({
281
+ mode: this.props.mode,
282
+ currentPath: this.props.initialPath || this.props.location.pathname,
283
+ propDataName: 'value',
284
+ onSelectItem: (url, item) => {
285
+ this.onChange(item);
286
+ },
287
+ selectableTypes:
288
+ this.props.widgetOptions?.pattern_options?.selectableTypes ||
289
+ this.props.selectableTypes,
290
+ maximumSelectionSize:
291
+ this.props.widgetOptions?.pattern_options?.maximumSelectionSize ||
292
+ this.props.maximumSelectionSize,
293
+ });
294
+ };
295
+
296
+ handleSelectedItemsRefClick = (e) => {
297
+ if (this.props.isDisabled) {
298
+ return;
299
+ }
300
+
301
+ if (
302
+ e.target.contains(this.selectedItemsRef.current) ||
303
+ e.target.contains(this.placeholderRef.current)
304
+ ) {
305
+ this.showObjectBrowser(e);
306
+ }
307
+ };
308
+
309
+ /**
310
+ * Render method.
311
+ * @method render
312
+ * @returns {string} Markup for the component.
313
+ */
314
+ render() {
315
+ const {
316
+ id,
317
+ description,
318
+ fieldSet,
319
+ value,
320
+ mode,
321
+ onChange,
322
+ isDisabled,
323
+ } = this.props;
324
+
325
+ let items = compact(!isArray(value) && value ? [value] : value || []);
326
+
327
+ let icon =
328
+ mode === 'multiple' || items.length === 0 ? navTreeSVG : clearSVG;
329
+ let iconAction =
330
+ mode === 'multiple' || items.length === 0
331
+ ? this.showObjectBrowser
332
+ : (e) => {
333
+ e.preventDefault();
334
+ onChange(id, this.props.return === 'single' ? null : []);
335
+ };
336
+
337
+ return (
338
+ <FormFieldWrapper
339
+ {...this.props}
340
+ className={description ? 'help text' : 'text'}
341
+ >
342
+ <div
343
+ className="objectbrowser-field"
344
+ aria-labelledby={`fieldset-${
345
+ fieldSet || 'default'
346
+ }-field-label-${id}`}
347
+ >
348
+ <div
349
+ className="selected-values"
350
+ onClick={this.handleSelectedItemsRefClick}
351
+ onKeyDown={this.handleSelectedItemsRefClick}
352
+ role="searchbox"
353
+ tabIndex={0}
354
+ ref={this.selectedItemsRef}
355
+ >
356
+ {items.map((item) => this.renderLabel(item))}
357
+
358
+ {items.length === 0 && this.props.mode === 'multiple' && (
359
+ <div className="placeholder" ref={this.placeholderRef}>
360
+ {this.props.placeholder ??
361
+ this.props.intl.formatMessage(messages.placeholder)}
362
+ </div>
363
+ )}
364
+ {this.props.allowExternals &&
365
+ items.length === 0 &&
366
+ this.props.mode !== 'multiple' && (
367
+ <input
368
+ onKeyDown={this.onKeyDownManualLink}
369
+ onChange={this.onManualLinkInput}
370
+ value={this.state.manualLinkInput}
371
+ placeholder={
372
+ this.props.placeholder ??
373
+ this.props.intl.formatMessage(messages.placeholder)
374
+ }
375
+ />
376
+ )}
377
+ </div>
378
+ {this.state.manualLinkInput && isEmpty(items) && (
379
+ <Button.Group>
380
+ <Button
381
+ basic
382
+ icon
383
+ className="cancel"
384
+ onClick={(e) => {
385
+ e.stopPropagation();
386
+ this.setState({ manualLinkInput: '' });
387
+ }}
388
+ >
389
+ <Icon name={clearSVG} size="18px" color="#e40166" />
390
+ </Button>
391
+ <Button
392
+ basic
393
+ icon
394
+ primary
395
+ disabled={!this.state.validURL}
396
+ onClick={(e) => {
397
+ e.stopPropagation();
398
+ this.onSubmitManualLink();
399
+ }}
400
+ >
401
+ <Icon name={aheadSVG} size="18px" />
402
+ </Button>
403
+ </Button.Group>
404
+ )}
405
+ {!this.state.manualLinkInput && (
406
+ <Button
407
+ aria-label={this.props.intl.formatMessage(
408
+ messages.openObjectBrowser,
409
+ )}
410
+ onClick={iconAction}
411
+ className="action"
412
+ disabled={isDisabled}
413
+ >
414
+ <Icon name={icon} size="18px" />
415
+ </Button>
416
+ )}
417
+ </div>
418
+ </FormFieldWrapper>
419
+ );
420
+ }
421
+ }
422
+
423
+ const ObjectBrowserWidgetMode = (mode) =>
424
+ compose(
425
+ injectIntl,
426
+ withObjectBrowser,
427
+ withRouter,
428
+ connect(null, { searchContent }),
429
+ )((props) => <ObjectBrowserWidgetComponent {...props} mode={mode} />);
430
+ export { ObjectBrowserWidgetMode };
431
+ export default compose(
432
+ injectIntl,
433
+ withObjectBrowser,
434
+ withRouter,
435
+ connect(null, { searchContent }),
436
+ )(ObjectBrowserWidgetComponent);
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Breadcrumbs components.
3
+ * @module components/theme/Breadcrumbs/Breadcrumbs
4
+ */
5
+
6
+ import React, { useEffect } from 'react';
7
+ import { useDispatch, useSelector } from 'react-redux';
8
+
9
+ import { useLocation } from 'react-router';
10
+ import { getBaseUrl, hasApiExpander } from '@plone/volto/helpers';
11
+ import { getBreadcrumbs } from '@plone/volto/actions';
12
+ import config from '@plone/volto/registry';
13
+
14
+ import EEABreadcrumbs from '@eeacms/volto-eea-design-system/ui/Breadcrumbs/Breadcrumbs';
15
+
16
+ const isContentRoute = (pathname) => {
17
+ const { settings } = config;
18
+ const normalized_nonContentRoutes = settings.nonContentRoutes.map((item) => {
19
+ if (item.test) {
20
+ return item;
21
+ } else {
22
+ return new RegExp(item + '$');
23
+ }
24
+ });
25
+
26
+ const isNonContentRoute =
27
+ normalized_nonContentRoutes.findIndex((item) => item.test(pathname)) > -1;
28
+
29
+ return !isNonContentRoute;
30
+ };
31
+
32
+ const Breadcrumbs = (props) => {
33
+ const dispatch = useDispatch();
34
+ const { items = [], root = '/' } = useSelector((state) => state?.breadcrumbs);
35
+ // const pathname = useSelector((state) => state.location.pathname);
36
+ const location = useLocation();
37
+ const { pathname } = location;
38
+
39
+ const sections = items.map((item) => ({
40
+ title: item.title,
41
+ href: item.url,
42
+ key: item.title,
43
+ }));
44
+
45
+ useEffect(() => {
46
+ if (
47
+ !hasApiExpander('breadcrumbs', getBaseUrl(pathname)) &&
48
+ isContentRoute(pathname)
49
+ ) {
50
+ dispatch(getBreadcrumbs(getBaseUrl(pathname)));
51
+ }
52
+ }, [dispatch, pathname]);
53
+
54
+ return (
55
+ <React.Fragment>
56
+ <div id="page-header" />
57
+ <EEABreadcrumbs pathname={pathname} sections={sections} root={root} />
58
+ </React.Fragment>
59
+ );
60
+ };
61
+
62
+ export default Breadcrumbs;