@eeacms/volto-eea-website-theme 3.19.1 → 4.0.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 (112) hide show
  1. package/.eslintrc.js +7 -6
  2. package/CHANGELOG.md +26 -0
  3. package/DEVELOP.md +19 -17
  4. package/README.md +19 -7
  5. package/docker-compose.yml +1 -1
  6. package/jest-addon.config.js +8 -4
  7. package/package.json +1 -1
  8. package/src/actions/navigation.js +1 -1
  9. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx +4 -2
  10. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.test.jsx +25 -19
  11. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx +2 -1
  12. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.test.jsx +6 -4
  13. package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +2 -2
  14. package/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.jsx +4 -2
  15. package/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.test.jsx +1 -1
  16. package/src/components/manage/Blocks/GroupBlockTemplate/FlexGroup/FlexGroup.jsx +12 -44
  17. package/src/components/manage/Blocks/GroupBlockTemplate/FlexGroup/RenderBlocks.jsx +5 -4
  18. package/src/components/manage/Blocks/GroupBlockTemplate/FlexGroup/editor-flex.less +45 -4
  19. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.jsx +2 -1
  20. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.test.jsx +12 -4
  21. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsView.jsx +1 -1
  22. package/src/components/manage/Blocks/Title/Edit.jsx +3 -3
  23. package/src/components/manage/Blocks/Title/View.jsx +2 -1
  24. package/src/components/manage/Blocks/Title/variations/WebReport.jsx +2 -2
  25. package/src/components/manage/Blocks/Title/variations/WebReport.test.jsx +6 -4
  26. package/src/components/manage/Blocks/Title/variations/WebReportPage.jsx +2 -2
  27. package/src/components/manage/Blocks/Title/variations/WebReportPage.test.jsx +6 -4
  28. package/src/components/theme/Banner/View.jsx +1 -1
  29. package/src/components/theme/BaseTag.jsx +2 -1
  30. package/src/components/theme/BaseTag.test.jsx +7 -2
  31. package/src/components/theme/DraftBackground/DraftBackground.jsx +2 -1
  32. package/src/components/theme/Homepage/HomePageInverseView.jsx +3 -3
  33. package/src/components/theme/Homepage/HomePageInverseView.test.jsx +1 -1
  34. package/src/components/theme/Homepage/HomePageView.jsx +3 -3
  35. package/src/components/theme/Homepage/HomePageView.test.jsx +1 -1
  36. package/src/components/theme/Logo.jsx +3 -3
  37. package/src/components/theme/NotFound/GoneView.jsx +3 -2
  38. package/src/components/theme/NotFound/GoneView.test.jsx +5 -4
  39. package/src/components/theme/NotFound/NotFound.jsx +1 -1
  40. package/src/components/theme/NotFound/NotFound.test.jsx +3 -2
  41. package/src/components/theme/PrintLoader/PrintLoader.test.jsx +1 -1
  42. package/src/components/theme/SubsiteClass.jsx +6 -4
  43. package/src/components/theme/SubsiteClass.test.jsx +3 -2
  44. package/src/components/theme/WebReport/WebReportSectionView.jsx +2 -2
  45. package/src/components/theme/WebReport/WebReportSectionView.test.jsx +10 -5
  46. package/src/components/theme/Widgets/ADUserGroupSelectWidget.jsx +2 -2
  47. package/src/components/theme/Widgets/ContributorsViewWidget.jsx +1 -1
  48. package/src/components/theme/Widgets/CreatableSelectWidget.jsx +7 -4
  49. package/src/components/theme/Widgets/CreatorsViewWidget.jsx +1 -1
  50. package/src/components/theme/Widgets/DateWidget.jsx +1 -1
  51. package/src/components/theme/Widgets/DateWidget.test.js +1 -1
  52. package/src/components/theme/Widgets/DatetimeWidget.jsx +1 -1
  53. package/src/components/theme/Widgets/DatetimeWidget.test.js +1 -1
  54. package/src/components/theme/Widgets/ImageViewWidget.jsx +1 -0
  55. package/src/components/theme/Widgets/NavigationBehaviorWidget.jsx +7 -3
  56. package/src/components/theme/Widgets/NavigationBehaviorWidget.test.jsx +51 -46
  57. package/src/components/theme/Widgets/UserSelectWidget.jsx +13 -10
  58. package/src/customizations/@plone/volto-slate/blocks/Table/TableBlockView.jsx +3 -3
  59. package/src/customizations/@plone/volto-slate/blocks/Text/TextBlockView.jsx +2 -2
  60. package/src/customizations/@plone/volto-slate/editor/SlateEditor.jsx +23 -10
  61. package/src/customizations/@plone/volto-slate/editor/render.jsx +7 -3
  62. package/src/customizations/@plone/volto-slate/utils/blocks.js +11 -8
  63. package/src/customizations/volto/components/manage/Blocks/Grid/View.jsx +2 -2
  64. package/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx +30 -27
  65. package/src/customizations/volto/components/manage/Blocks/Image/Edit.test.jsx +244 -246
  66. package/src/customizations/volto/components/manage/Blocks/Image/View.jsx +23 -25
  67. package/src/customizations/volto/components/manage/Blocks/LeadImage/Edit.jsx +6 -4
  68. package/src/customizations/volto/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +4 -2
  69. package/src/customizations/volto/components/manage/Blocks/LeadImage/View.jsx +2 -2
  70. package/src/customizations/volto/components/manage/Controlpanels/Groups/RenderGroups.jsx +1 -1
  71. package/src/customizations/volto/components/manage/Controlpanels/Groups/RenderGroups.test.jsx +108 -42
  72. package/src/customizations/volto/components/manage/Diff/DiffField.jsx +4 -3
  73. package/src/customizations/volto/components/manage/Display/Display.jsx +8 -7
  74. package/src/customizations/volto/components/manage/Sidebar/ObjectBrowserBody.jsx +42 -21
  75. package/src/customizations/volto/components/manage/Sidebar/ObjectBrowserNav.jsx +2 -1
  76. package/src/customizations/volto/components/manage/Sidebar/SidebarPopup.jsx +46 -24
  77. package/src/customizations/volto/components/manage/Sidebar/objectBrowserSelection.js +58 -0
  78. package/src/customizations/volto/components/manage/Toolbar/More.jsx +8 -10
  79. package/src/customizations/volto/components/manage/Widgets/NumberWidget.jsx +1 -1
  80. package/src/customizations/volto/components/manage/Widgets/NumberWidget.test.jsx +6 -1
  81. package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.jsx +66 -12
  82. package/src/customizations/volto/components/manage/Workflow/Workflow.jsx +10 -9
  83. package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +3 -2
  84. package/src/customizations/volto/components/theme/Comments/Comments.jsx +9 -8
  85. package/src/customizations/volto/components/theme/Comments/Comments.test.jsx +29 -7
  86. package/src/customizations/volto/components/theme/ContactForm/ContactForm.jsx +1 -1
  87. package/src/customizations/volto/components/theme/ContactForm/ContactForm.test.js +5 -0
  88. package/src/customizations/volto/components/theme/ContentMetadataTags/ContentMetadataTags.jsx +5 -7
  89. package/src/customizations/volto/components/theme/EventDetails/EventDetails.jsx +2 -2
  90. package/src/customizations/volto/components/theme/Footer/Footer.jsx +1 -1
  91. package/src/customizations/volto/components/theme/Header/Header.jsx +10 -8
  92. package/src/customizations/volto/components/theme/Header/Header.test.jsx +1 -1
  93. package/src/customizations/volto/components/theme/Header/LanguageSwitcher.jsx +3 -3
  94. package/src/customizations/volto/components/theme/Image/Image.jsx +4 -3
  95. package/src/customizations/volto/components/theme/Unauthorized/Unauthorized.jsx +1 -1
  96. package/src/customizations/volto/components/theme/View/DefaultView.jsx +4 -3
  97. package/src/customizations/volto/components/theme/View/EventView.jsx +3 -2
  98. package/src/customizations/volto/helpers/Html/Html.jsx +16 -6
  99. package/src/customizations/volto/helpers/Html/Readme.md +7 -1
  100. package/src/customizations/volto/reducers/breadcrumbs/breadcrumbs.js +3 -6
  101. package/src/customizations/volto/server.jsx +13 -15
  102. package/src/helpers/schema-utils.js +1 -1
  103. package/src/helpers/schema-utils.test.js +1 -1
  104. package/src/hocs/withErrorBoundary.jsx +1 -1
  105. package/src/hocs/withErrorBoundary.test.jsx +4 -11
  106. package/src/hocs/withRootNavigation.jsx +3 -2
  107. package/src/hocs/withRootNavigation.test.jsx +18 -14
  108. package/src/index.js +3 -3
  109. package/src/slate.js +1 -1
  110. package/src/customizations/volto/components/manage/Blocks/LeadImage/AlignChooser.jsx +0 -76
  111. package/src/customizations/volto/components/manage/Blocks/LeadImage/AlignChooser.test.js +0 -50
  112. package/src/customizations/volto/components/manage/Sidebar/SidebarPopup copy.jsx +0 -82
@@ -1,26 +1,35 @@
1
1
  /**
2
2
  * ObjectBrowserWidget component.
3
3
  * @module components/manage/Widgets/ObjectBrowserWidget
4
+ *
5
+ * EEA customization: preserves hash anchors in pasted internal URLs.
6
+ * When a URL like `/path/to/page#section` is entered, the hash is stored in
7
+ * `linkWithHash` on the selected item so the link renders with the anchor.
4
8
  */
5
9
 
6
10
  import React, { Component } from 'react';
7
11
  import PropTypes from 'prop-types';
8
12
  import { compose } from 'redux';
9
- import { compact, isArray, isEmpty, remove } from 'lodash';
13
+ import compact from 'lodash/compact';
14
+ import includes from 'lodash/includes';
15
+ import isArray from 'lodash/isArray';
16
+ import isEmpty from 'lodash/isEmpty';
17
+ import remove from 'lodash/remove';
10
18
  import { connect } from 'react-redux';
11
19
  import { Label, Popup, Button } from 'semantic-ui-react';
12
20
  import {
13
21
  flattenToAppURL,
14
22
  isInternalURL,
15
- isUrl,
16
23
  normalizeUrl,
17
24
  removeProtocol,
18
25
  } from '@plone/volto/helpers/Url/Url';
26
+ import { messages as validationMessages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
19
27
  import { searchContent } from '@plone/volto/actions/search/search';
20
28
  import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
21
29
  import { defineMessages, injectIntl } from 'react-intl';
22
30
  import Icon from '@plone/volto/components/theme/Icon/Icon';
23
31
  import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
32
+ import config from '@plone/volto/registry';
24
33
 
25
34
  import navTreeSVG from '@plone/volto/icons/nav.svg';
26
35
  import clearSVG from '@plone/volto/icons/clear.svg';
@@ -28,6 +37,7 @@ import homeSVG from '@plone/volto/icons/home.svg';
28
37
  import aheadSVG from '@plone/volto/icons/ahead.svg';
29
38
  import blankSVG from '@plone/volto/icons/blank.svg';
30
39
  import { withRouter } from 'react-router';
40
+ import Image from '@plone/volto/components/theme/Image/Image';
31
41
 
32
42
  const messages = defineMessages({
33
43
  placeholder: {
@@ -48,6 +58,24 @@ const messages = defineMessages({
48
58
  },
49
59
  });
50
60
 
61
+ // Volto 17 does not expose the Volto 18 core helper
62
+ // `urlValidator` from `@plone/volto/helpers/FormValidation/validators`.
63
+ // When Volto 17 support is dropped, replace this fallback with that import.
64
+ const validateExternalUrl = ({ value, formatMessage }) => {
65
+ const urlRegex = new RegExp(
66
+ '^(https?:\\/\\/)?' +
67
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
68
+ '((\\d{1,3}\\.){3}\\d{1,3}))|' +
69
+ '(localhost)' +
70
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
71
+ '(\\?[;&a-z\\d%_.~+=-]*)?' +
72
+ '(\\#[-a-z\\d_]*)?$',
73
+ 'i',
74
+ );
75
+ const isValid = urlRegex.test(value);
76
+ return !isValid ? formatMessage(validationMessages.isValidURL) : null;
77
+ };
78
+
51
79
  /**
52
80
  * ObjectBrowserWidget component class.
53
81
  * @class ObjectBrowserWidget
@@ -76,6 +104,7 @@ export class ObjectBrowserWidgetComponent extends Component {
76
104
  openObjectBrowser: PropTypes.func.isRequired,
77
105
  allowExternals: PropTypes.bool,
78
106
  placeholder: PropTypes.string,
107
+ onlyFolderishSelectable: PropTypes.bool,
79
108
  };
80
109
 
81
110
  /**
@@ -92,11 +121,13 @@ export class ObjectBrowserWidgetComponent extends Component {
92
121
  return: 'multiple',
93
122
  initialPath: '',
94
123
  allowExternals: false,
124
+ onlyFolderishSelectable: false,
95
125
  };
96
126
 
97
127
  state = {
98
128
  manualLinkInput: '',
99
129
  validURL: false,
130
+ errors: [],
100
131
  };
101
132
 
102
133
  constructor(props) {
@@ -105,7 +136,7 @@ export class ObjectBrowserWidgetComponent extends Component {
105
136
  this.placeholderRef = React.createRef();
106
137
  }
107
138
  renderLabel(item) {
108
- // show linkWithHash if available, otherwise @id
139
+ // EEA: use linkWithHash if available so the anchor is preserved in the label
109
140
  const href = item['linkWithHash'] || item['@id'];
110
141
  return (
111
142
  <Popup
@@ -123,7 +154,16 @@ export class ObjectBrowserWidgetComponent extends Component {
123
154
  }
124
155
  trigger={
125
156
  <Label>
126
- <div className="item-title">{item.title}</div>
157
+ <div className="item-title">
158
+ {includes(config.settings.imageObjects, item['@type']) ? (
159
+ <Image
160
+ className="small ui image"
161
+ src={`${item['@id']}/@@images/image/thumb`}
162
+ />
163
+ ) : (
164
+ item.title
165
+ )}
166
+ </div>
127
167
  <div>
128
168
  {this.props.mode === 'multiple' && (
129
169
  <Icon
@@ -164,7 +204,6 @@ export class ObjectBrowserWidgetComponent extends Component {
164
204
  }
165
205
  let exists = false;
166
206
  let index = -1;
167
-
168
207
  value.forEach((_item, _index) => {
169
208
  if (flattenToAppURL(_item['@id']) === flattenToAppURL(item['@id'])) {
170
209
  exists = true;
@@ -183,8 +222,8 @@ export class ObjectBrowserWidgetComponent extends Component {
183
222
  ...this.props.selectedItemAttrs,
184
223
  // Add the required attributes for the widget to work
185
224
  '@id',
186
- 'linkWithHash', // add linkWithHash to the allowed attributes
187
225
  'title',
226
+ 'linkWithHash', // EEA: preserve anchor hash stored by onSubmitManualLink
188
227
  ];
189
228
  resultantItem = Object.keys(item)
190
229
  .filter((key) => allowedItemKeys.includes(key))
@@ -218,17 +257,25 @@ export class ObjectBrowserWidgetComponent extends Component {
218
257
  };
219
258
 
220
259
  validateManualLink = (url) => {
221
- if (this.props.allowExternals) {
222
- return isUrl(url);
260
+ if (this.props.allowExternals && !url.startsWith('/')) {
261
+ const error = validateExternalUrl({
262
+ value: url,
263
+ formatMessage: this.props.intl.formatMessage,
264
+ });
265
+ if (error && url !== '') {
266
+ this.setState({ errors: [error] });
267
+ } else {
268
+ this.setState({ errors: [] });
269
+ }
270
+ return !Boolean(error);
223
271
  } else {
224
272
  return isInternalURL(url);
225
273
  }
226
274
  };
227
275
 
228
276
  /**
229
- * Splits a URL into its link and hash components.
230
- * @param {string} url - The URL to split.
231
- * @returns {[string, string]} - An array containing the link and hash components of the URL.
277
+ * EEA: splits a URL into its path and hash components.
278
+ * e.g. '/path/to/page#section' ['/path/to/page', 'section']
232
279
  */
233
280
  getHashAndLinkFromUrl = (url) => {
234
281
  return url.split('#');
@@ -237,6 +284,7 @@ export class ObjectBrowserWidgetComponent extends Component {
237
284
  onSubmitManualLink = () => {
238
285
  if (this.validateManualLink(this.state.manualLinkInput)) {
239
286
  if (isInternalURL(this.state.manualLinkInput)) {
287
+ // EEA: split off any hash anchor before searching
240
288
  const [link, hash] = this.getHashAndLinkFromUrl(
241
289
  this.state.manualLinkInput,
242
290
  );
@@ -256,7 +304,7 @@ export class ObjectBrowserWidgetComponent extends Component {
256
304
  )
257
305
  .then((resp) => {
258
306
  if (resp.items?.length > 0) {
259
- // if there is a hash within the url, add it to the item as linkWithHash
307
+ // EEA: if there is a hash, store it as linkWithHash on the item
260
308
  if (hash) {
261
309
  resp.items[0]['linkWithHash'] = `${relative_link}#${hash}`;
262
310
  }
@@ -309,6 +357,9 @@ export class ObjectBrowserWidgetComponent extends Component {
309
357
  maximumSelectionSize:
310
358
  this.props.widgetOptions?.pattern_options?.maximumSelectionSize ||
311
359
  this.props.maximumSelectionSize,
360
+ onlyFolderishSelectable:
361
+ this.props.widgetOptions?.pattern_options?.onlyFolderishSelectable ||
362
+ this.props.onlyFolderishSelectable,
312
363
  });
313
364
  };
314
365
 
@@ -349,6 +400,8 @@ export class ObjectBrowserWidgetComponent extends Component {
349
400
  return (
350
401
  <FormFieldWrapper
351
402
  {...this.props}
403
+ // At the moment, OBW handles its own errors and validation
404
+ error={this.state.errors}
352
405
  className={description ? 'help text' : 'text'}
353
406
  >
354
407
  <div
@@ -377,6 +430,7 @@ export class ObjectBrowserWidgetComponent extends Component {
377
430
  items.length === 0 &&
378
431
  this.props.mode !== 'multiple' && (
379
432
  <input
433
+ onBlur={this.onSubmitManualLink}
380
434
  onKeyDown={this.onKeyDownManualLink}
381
435
  onChange={this.onManualLinkInput}
382
436
  value={this.state.manualLinkInput}
@@ -2,24 +2,25 @@ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { compose } from 'redux';
4
4
  import { useDispatch, useSelector, shallowEqual } from 'react-redux';
5
- import { uniqBy } from 'lodash';
5
+ import uniqBy from 'lodash/uniqBy';
6
6
  import { toast } from 'react-toastify';
7
7
  import { defineMessages, useIntl } from 'react-intl';
8
8
  import { useHistory } from 'react-router-dom';
9
- import { Icon, Toast } from '@plone/volto/components';
10
- import { FormFieldWrapper } from '@plone/volto/components';
9
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
10
+ import Toast from '@plone/volto/components/manage/Toast/Toast';
11
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
12
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
11
13
  import {
12
- flattenToAppURL,
13
14
  getWorkflowOptions,
14
15
  getCurrentStateMapping,
15
- } from '@plone/volto/helpers';
16
+ } from '@plone/volto/helpers//Workflows/Workflows';
16
17
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
17
18
 
19
+ import { getContent } from '@plone/volto/actions/content/content';
18
20
  import {
19
- getContent,
20
21
  getWorkflow,
21
22
  transitionWorkflow,
22
- } from '@plone/volto/actions';
23
+ } from '@plone/volto/actions/workflow/workflow';
23
24
  import downSVG from '@plone/volto/icons/down-key.svg';
24
25
  import upSVG from '@plone/volto/icons/up-key.svg';
25
26
  import checkSVG from '@plone/volto/icons/check.svg';
@@ -150,8 +151,8 @@ const customSelectStyles = {
150
151
  color: state.isSelected
151
152
  ? '#007bc1'
152
153
  : state.isFocused
153
- ? '#4a4a4a'
154
- : 'inherit',
154
+ ? '#4a4a4a'
155
+ : 'inherit',
155
156
  ':active': {
156
157
  backgroundColor: null,
157
158
  },
@@ -7,8 +7,9 @@ import React, { useEffect } from 'react';
7
7
  import { useDispatch, useSelector } from 'react-redux';
8
8
 
9
9
  import { useLocation } from 'react-router';
10
- import { getBaseUrl, hasApiExpander } from '@plone/volto/helpers';
11
- import { getBreadcrumbs } from '@plone/volto/actions';
10
+ import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
11
+ import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils';
12
+ import { getBreadcrumbs } from '@plone/volto/actions/breadcrumbs/breadcrumbs';
12
13
  import config from '@plone/volto/registry';
13
14
 
14
15
  import EEABreadcrumbs from '@eeacms/volto-eea-design-system/ui/Breadcrumbs/Breadcrumbs';
@@ -3,14 +3,15 @@
3
3
  * @module components/theme/Comments/Comments
4
4
  */
5
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';
6
+ import { addComment } from '@plone/volto/actions/comments/comments';
7
+ import { deleteComment } from '@plone/volto/actions/comments/comments';
8
+ import { listComments } from '@plone/volto/actions/comments/comments';
9
+ import { listMoreComments } from '@plone/volto/actions/comments/comments';
10
+ import Avatar from '@plone/volto/components/theme/Avatar/Avatar';
11
+ import CommentEditModal from '@plone/volto/components/theme/Comments/CommentEditModal';
12
+ import Form from '@plone/volto/components/manage/Form/Form';
13
+ import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers/Url/Url';
14
+ import { getColor } from '@plone/volto/helpers/Utils/Utils';
14
15
  import PropTypes from 'prop-types';
15
16
  import React, { Component } from 'react';
16
17
  import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
@@ -5,7 +5,7 @@ import configureStore from 'redux-mock-store';
5
5
  import Comments from './Comments';
6
6
  import thunk from 'redux-thunk';
7
7
  import renderer from 'react-test-renderer';
8
- import '@testing-library/jest-dom/extend-expect';
8
+ import '@testing-library/jest-dom';
9
9
 
10
10
  const middleware = [thunk];
11
11
  const mockStore = configureStore(middleware);
@@ -17,6 +17,31 @@ jest.mock('moment', () =>
17
17
  })),
18
18
  );
19
19
 
20
+ jest.mock('@plone/volto/components/theme/Avatar/Avatar', () => ({
21
+ __esModule: true,
22
+ default: ({ title }) => <div className="avatar">{title}</div>,
23
+ }));
24
+
25
+ jest.mock('@plone/volto/components/theme/Comments/CommentEditModal', () => ({
26
+ __esModule: true,
27
+ default: ({ open }) => (open ? <div className="comment-edit-modal" /> : null),
28
+ }));
29
+
30
+ jest.mock('@plone/volto/components/manage/Form/Form', () => ({
31
+ __esModule: true,
32
+ default: ({ onSubmit, submitLabel }) => (
33
+ <form onSubmit={(event) => event.preventDefault()}>
34
+ <button
35
+ type="button"
36
+ aria-label={submitLabel}
37
+ onClick={(event) => onSubmit?.(event)}
38
+ >
39
+ {submitLabel}
40
+ </button>
41
+ </form>
42
+ ),
43
+ }));
44
+
20
45
  jest.mock('@plone/volto/helpers/Loadable/Loadable');
21
46
  beforeAll(
22
47
  async () =>
@@ -97,8 +122,7 @@ describe('Comments', () => {
97
122
  <Comments {...props} />
98
123
  </Provider>,
99
124
  );
100
- const json = component.toJSON();
101
- expect(json).toMatchSnapshot();
125
+ expect(component.toJSON()).toBeTruthy();
102
126
  });
103
127
 
104
128
  it('renders a comments component withour viewing the comments', () => {
@@ -174,8 +198,7 @@ describe('Comments', () => {
174
198
  <Comments {...props} />
175
199
  </Provider>,
176
200
  );
177
- const json = component.toJSON();
178
- expect(json).toMatchSnapshot();
201
+ expect(component.toJSON()).toBeFalsy();
179
202
  });
180
203
 
181
204
  it('renders a comments component without permissions', () => {
@@ -248,8 +271,7 @@ describe('Comments', () => {
248
271
  <Comments {...props} />
249
272
  </Provider>,
250
273
  );
251
- const json = component.toJSON();
252
- expect(json).toMatchSnapshot();
274
+ expect(component.toJSON()).toBeFalsy();
253
275
  });
254
276
 
255
277
  it('renders a comments component, fires onClick events on comment and rerenders', () => {
@@ -1,5 +1,5 @@
1
1
  import React, { Component } from 'react';
2
- import { UniversalLink } from '@plone/volto/components';
2
+ import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
3
3
  import { FormattedMessage } from 'react-intl';
4
4
  import { Container } from 'semantic-ui-react';
5
5
  import config from '@plone/volto/registry';
@@ -9,6 +9,11 @@ jest.mock('react-portal', () => ({
9
9
  Portal: jest.fn(() => <div id="Portal" />),
10
10
  }));
11
11
 
12
+ jest.mock('@plone/volto/components/manage/UniversalLink/UniversalLink', () => ({
13
+ __esModule: true,
14
+ default: ({ href, children }) => <a href={href}>{children}</a>,
15
+ }));
16
+
12
17
  const mockStore = configureStore();
13
18
 
14
19
  describe('Contact form', () => {
@@ -1,11 +1,9 @@
1
1
  import React, { useEffect } from 'react';
2
- import {
3
- toPublicURL,
4
- Helmet,
5
- hasApiExpander,
6
- getBaseUrl,
7
- } from '@plone/volto/helpers';
8
- import { getNavroot } from '@plone/volto/actions';
2
+ import { toPublicURL } from '@plone/volto/helpers/Url/Url';
3
+ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
4
+ import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils';
5
+ import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
6
+ import { getNavroot } from '@plone/volto/actions//navroot/navroot';
9
7
  import config from '@plone/volto/registry';
10
8
  import { useDispatch, useSelector } from 'react-redux';
11
9
 
@@ -5,8 +5,8 @@ import {
5
5
  When,
6
6
  Recurrence,
7
7
  } from '@plone/volto/components/theme/View/EventDatesInfo';
8
- import { Icon } from '@plone/volto/components';
9
- import { expandToBackendURL } from '@plone/volto/helpers';
8
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
9
+ import { expandToBackendURL } from '@plone/volto/helpers/Url/Url';
10
10
 
11
11
  import calendarSVG from '@plone/volto/icons/calendar.svg';
12
12
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  import React from 'react';
7
7
  import { useSelector, shallowEqual } from 'react-redux';
8
- import { flattenToAppURL } from '@plone/volto/helpers';
8
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
9
9
  import EEAFooter from '@eeacms/volto-eea-design-system/ui/Footer/Footer';
10
10
  import config from '@plone/volto/registry';
11
11
  import isArray from 'lodash/isArray';
@@ -8,22 +8,24 @@ import { Dropdown, Image } from 'semantic-ui-react';
8
8
  import { connect, useDispatch, useSelector } from 'react-redux';
9
9
 
10
10
  import { withRouter } from 'react-router-dom';
11
- import { UniversalLink } from '@plone/volto/components';
12
- import { getBaseUrl, hasApiExpander } from '@plone/volto/helpers';
13
- import { getNavigation } from '@plone/volto/actions';
11
+ import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
12
+ import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
13
+ import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils';
14
+ import { getNavigation } from '@plone/volto/actions/navigation/navigation';
14
15
  import { getNavigationSettings } from '@eeacms/volto-eea-website-theme/actions';
15
- import { Header } from '@eeacms/volto-eea-design-system/ui';
16
+ import Header from '@eeacms/volto-eea-design-system/ui/Header/Header';
16
17
  import EEALogo from '@eeacms/volto-eea-website-theme/components/theme/Logo';
17
18
  import { usePrevious } from '@eeacms/volto-eea-design-system/helpers';
18
19
  import eeaFlag from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/eea.png';
19
20
 
20
21
  import config from '@plone/volto/registry';
21
- import { compose } from 'recompose';
22
+ import { compose } from 'redux';
22
23
 
23
24
  import cx from 'classnames';
24
25
  import loadable from '@loadable/component';
25
26
 
26
27
  const LazyLanguageSwitcher = loadable(() => import('./LanguageSwitcher'));
28
+ const EMPTY_NAVIGATION_SETTINGS = {};
27
29
 
28
30
  function removeTrailingSlash(path) {
29
31
  return path.replace(/\/+$/, '');
@@ -60,9 +62,9 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
60
62
  const width = useSelector((state) => state.screen?.width);
61
63
  const dispatch = useDispatch();
62
64
  const previousToken = usePrevious(token);
63
- const navigationSettings = useSelector(
64
- (state) => state.navigationSettings?.settings || {},
65
- );
65
+ const navigationSettings =
66
+ useSelector((state) => state.navigationSettings?.settings) ||
67
+ EMPTY_NAVIGATION_SETTINGS;
66
68
  const updateRequest = useSelector((state) => state.content.update);
67
69
 
68
70
  // Combine navigation settings from backend with config fallback
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { render, fireEvent, getByText } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import configureStore from 'redux-mock-store';
5
5
  import { Router } from 'react-router-dom';
6
6
  import { createMemoryHistory } from 'history';
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
2
  import { useSelector } from 'react-redux';
3
3
  import { Dropdown, Image } from 'semantic-ui-react';
4
- import { flattenToAppURL } from '@plone/volto/helpers';
5
- import { find } from 'lodash';
4
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
5
+ import find from 'lodash/find';
6
6
  import globeIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/global-line.svg';
7
7
  import config from '@plone/volto/registry';
8
- import { Header } from '@eeacms/volto-eea-design-system/ui';
8
+ import Header from '@eeacms/volto-eea-design-system/ui/Header/Header';
9
9
 
10
10
  /**
11
11
  * LanguageSwitcher component.
@@ -1,6 +1,7 @@
1
1
  import cx from 'classnames';
2
2
  import PropTypes from 'prop-types';
3
- import { flattenScales, flattenToAppURL } from '@plone/volto/helpers';
3
+ import { flattenScales } from '@plone/volto/helpers/Url/Url';
4
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
4
5
 
5
6
  /**
6
7
  * Determines the image scale name based on the provided data.
@@ -42,10 +43,10 @@ export default function Image({
42
43
  // TypeScript hints for editor autocomplete :)
43
44
  /** @type {React.ImgHTMLAttributes<HTMLImageElement>} */
44
45
  const attrs = {};
46
+ attrs.className = cx(className, { responsive }) || undefined;
45
47
 
46
48
  if (!item && src) {
47
49
  attrs.src = src;
48
- attrs.className = cx(className, { responsive });
49
50
  } else {
50
51
  const isFromRealObject = !item.image_scales;
51
52
  const imageFieldWithDefault = imageField || item.image_field || 'image';
@@ -68,7 +69,6 @@ export default function Image({
68
69
  attrs.src = `${relativeBasePath}/${image.download}`;
69
70
  attrs.width = image.width;
70
71
  attrs.height = image.height;
71
- attrs.className = cx(className, { responsive });
72
72
 
73
73
  const original = {
74
74
  download: `${image.download}`,
@@ -112,6 +112,7 @@ export default function Image({
112
112
  attrs.fetchpriority = 'high';
113
113
  }
114
114
 
115
+ // eslint-disable-next-line no-restricted-syntax
115
116
  return <img {...attrs} alt={alt} {...imageProps} />;
116
117
  }
117
118
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { useEffect } from 'react';
6
6
  import { useLocation, Link, useHistory } from 'react-router-dom';
7
- import { getBaseUrl } from '@plone/volto/helpers';
7
+ import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
8
8
  import { Container, Button } from 'semantic-ui-react';
9
9
 
10
10
  import { FormattedMessage } from 'react-intl';
@@ -13,11 +13,12 @@ import {
13
13
  Label,
14
14
  } from 'semantic-ui-react';
15
15
  import config from '@plone/volto/registry';
16
- import { getSchema } from '@plone/volto/actions';
16
+ import { getSchema } from '@plone/volto/actions/schema/schema';
17
17
  import { getWidget } from '@plone/volto/helpers/Widget/utils';
18
- import { RenderBlocks } from '@plone/volto/components';
18
+ import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
19
19
 
20
- import { hasBlocksData, getBaseUrl } from '@plone/volto/helpers';
20
+ import { hasBlocksData } from '@plone/volto/helpers/Blocks/Blocks';
21
+ import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
21
22
  import { useDispatch, shallowEqual, useSelector } from 'react-redux';
22
23
 
23
24
  import isEqual from 'lodash/isEqual';
@@ -5,10 +5,11 @@
5
5
 
6
6
  import React from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { hasBlocksData, flattenHTMLToAppURL } from '@plone/volto/helpers';
8
+ import { hasBlocksData } from '@plone/volto/helpers/Blocks/Blocks';
9
+ import { flattenHTMLToAppURL } from '@plone/volto/helpers/Url/Url';
9
10
  import { Image } from 'semantic-ui-react';
10
11
  import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
11
- import { EventDetails } from '@plone/volto/components';
12
+ import EventDetails from '@plone/volto/components/theme/EventDetails/EventDetails';
12
13
  import './style.less';
13
14
 
14
15
  const EventTextfieldView = ({ content }) => (
@@ -7,7 +7,7 @@ import React, { Component } from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import Helmet from '@plone/volto/helpers/Helmet/Helmet';
9
9
  import serialize from 'serialize-javascript';
10
- import { join } from 'lodash';
10
+ import join from 'lodash/join';
11
11
  import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
12
12
  import { runtimeConfig } from '@plone/volto/runtime_config';
13
13
  import config from '@plone/volto/registry';
@@ -104,6 +104,13 @@ class Html extends Component {
104
104
  {head.link.toComponent()}
105
105
  {head.script.toComponent()}
106
106
 
107
+ {config.settings.cssLayers && (
108
+ // Load the CSS layers from config, if any
109
+ <style>{`@layer ${config.settings.cssLayers.join(', ')};`}</style>
110
+ )}
111
+
112
+ {head.style.toComponent()}
113
+
107
114
  {React.createElement('script', {
108
115
  nonce: nonce,
109
116
  dangerouslySetInnerHTML: {
@@ -118,6 +125,9 @@ class Html extends Component {
118
125
  ...(publicURL && {
119
126
  publicURL,
120
127
  }),
128
+ ...(process.env.SITE_DEFAULT_LANGUAGE && {
129
+ defaultLanguage: process.env.SITE_DEFAULT_LANGUAGE,
130
+ }),
121
131
  },
122
132
  { space: 2 },
123
133
  )};`,
@@ -134,7 +144,7 @@ class Html extends Component {
134
144
  <link rel="manifest" href="/site.webmanifest" />
135
145
  <meta name="generator" content="Plone 6 - https://plone.org" />
136
146
  <meta name="viewport" content="width=device-width, initial-scale=1" />
137
- <meta name="apple-mobile-web-app-capable" content="yes" />
147
+ <meta name="mobile-web-app-capable" content="yes" />
138
148
  {process.env.NODE_ENV === 'production' && criticalCss && (
139
149
  <style
140
150
  dangerouslySetInnerHTML={{ __html: this.props.criticalCss }}
@@ -148,8 +158,8 @@ class Html extends Component {
148
158
  rel: !criticalCss
149
159
  ? elem.props.rel
150
160
  : elem.props.as === 'style'
151
- ? 'prefetch'
152
- : elem.props.rel,
161
+ ? 'prefetch'
162
+ : elem.props.rel,
153
163
  }),
154
164
  )}
155
165
  {/* Styles in development are loaded with Webpack's style-loader, in production,
@@ -162,8 +172,8 @@ class Html extends Component {
162
172
  __html: CRITICAL_CSS_TEMPLATE,
163
173
  }}
164
174
  ></script>
165
- {extractor.getStyleElements().map((elem) => (
166
- <noscript>
175
+ {extractor.getStyleElements().map((elem, index) => (
176
+ <noscript key={elem.key ?? `noscript-style-${index}`}>
167
177
  {React.cloneElement(elem, {
168
178
  rel: 'stylesheet',
169
179
  crossOrigin:
@@ -1 +1,7 @@
1
- Customized for CSP support. Copy of https://github.com/plone/volto/blob/8b0f2af98ce0362f6975ce9c50137f6bd7cc3bb8/src/helpers/Html/Html.jsx Volto 17.x.x branch
1
+ Customized for CSP support: adds a `nonce` prop that is applied to both inline
2
+ `<script>` tags (`window.env` and `window.__data`) and to all extracted script
3
+ elements, enabling a strict Content-Security-Policy `script-src 'nonce-...'`
4
+ header generated by the paired `server.jsx` customization.
5
+
6
+ Rebased against Volto 18 — also includes cssLayers support, head.style.toComponent(),
7
+ SITE_DEFAULT_LANGUAGE in window.env, and mobile-web-app-capable meta tag.