@eeacms/volto-eea-website-theme 1.34.0 → 2.0.0

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 (54) hide show
  1. package/.eslintrc.js +7 -2
  2. package/CHANGELOG.md +44 -20
  3. package/docker-compose.yml +1 -1
  4. package/jest-addon.config.js +3 -0
  5. package/package.json +2 -1
  6. package/src/components/manage/Blocks/LayoutSettings/index.js +3 -1
  7. package/src/components/manage/Blocks/Title/index.js +3 -1
  8. package/src/components/manage/Blocks/Title/schema.js +3 -1
  9. package/src/components/theme/Banner/View.jsx +12 -5
  10. package/src/components/theme/DraftBackground/DraftBackground.jsx +1 -0
  11. package/src/components/theme/DraftBackground/DraftBackground.test.jsx +85 -0
  12. package/src/config.js +2 -0
  13. package/src/customizations/@plone/volto-slate/blocks/Text/TextBlockView.jsx +32 -0
  14. package/src/customizations/@plone/volto-slate/editor/render.jsx +75 -0
  15. package/src/customizations/@plone/volto-slate/elementEditor/utils.js +76 -75
  16. package/src/customizations/volto/components/manage/Blocks/Grid/Edit.jsx +70 -0
  17. package/src/customizations/volto/components/manage/Blocks/Grid/View.jsx +61 -0
  18. package/src/customizations/volto/components/manage/Blocks/Grid/readme.md +1 -0
  19. package/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx +82 -23
  20. package/src/customizations/volto/components/manage/Blocks/Image/Edit.test.jsx +10 -3
  21. package/src/customizations/volto/components/manage/Blocks/Image/View.jsx +110 -111
  22. package/src/customizations/volto/components/manage/Blocks/Image/schema.js +17 -2
  23. package/src/customizations/volto/components/manage/Blocks/LeadImage/Edit.jsx +35 -14
  24. package/src/customizations/volto/components/manage/Blocks/LeadImage/View.jsx +65 -79
  25. package/src/customizations/volto/components/manage/Display/Display.jsx +306 -0
  26. package/src/customizations/volto/components/manage/Display/Readme.md +1 -0
  27. package/src/customizations/volto/components/manage/Sidebar/SidebarPopup copy.jsx +82 -0
  28. package/src/customizations/volto/components/manage/Toolbar/More.jsx +541 -0
  29. package/src/customizations/volto/components/manage/UniversalLink/UniversalLink.jsx +3 -1
  30. package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.jsx +24 -14
  31. package/src/customizations/volto/components/manage/Widgets/README.md +1 -0
  32. package/src/customizations/volto/components/manage/Workflow/README.txt +1 -0
  33. package/src/customizations/volto/components/manage/Workflow/Workflow.jsx +324 -0
  34. package/src/customizations/volto/components/manage/Workflow/Workflow.test.jsx +81 -0
  35. package/src/customizations/volto/components/theme/Comments/Comments.jsx +1 -2
  36. package/src/customizations/volto/components/theme/ContactForm/ContactForm.jsx +1 -1
  37. package/src/customizations/volto/components/theme/EventDetails/EventDetails.jsx +1 -0
  38. package/src/index.js +21 -16
  39. package/src/middleware/ok.js +4 -2
  40. package/src/middleware/voltoCustom.js +4 -2
  41. package/src/slate.js +10 -8
  42. package/src/customizations/@plone/volto-slate/editor/plugins/StyleMenu/README.txt +0 -1
  43. package/src/customizations/@plone/volto-slate/editor/plugins/StyleMenu/StyleMenu.jsx +0 -157
  44. package/src/customizations/@plone/volto-slate/editor/plugins/StyleMenu/utils.js +0 -168
  45. package/src/customizations/volto/components/manage/Add/Add.jsx +0 -498
  46. package/src/customizations/volto/components/manage/Add/readme.md +0 -1
  47. package/src/customizations/volto/components/manage/Contents/ContentsPropertiesModal.jsx +0 -232
  48. package/src/customizations/volto/components/manage/Form/Form.jsx +0 -810
  49. package/src/customizations/volto/components/manage/Form/Form.test.jsx +0 -1124
  50. package/src/customizations/volto/components/manage/Form/ModalForm.jsx +0 -326
  51. package/src/customizations/volto/components/manage/Sharing/Sharing.jsx +0 -528
  52. package/src/customizations/volto/components/manage/Sharing/Sharing.test.jsx +0 -72
  53. package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.test.jsx +0 -193
  54. package/src/customizations/volto/components/theme/AppExtras/AppExtras.jsx +0 -27
@@ -158,88 +158,89 @@ export const _isActiveElement = (elementType) => (editor) => {
158
158
  * @param {string|Object[]} elementType - this can be a string or an array of strings
159
159
  * @returns {Object|null} - found node
160
160
  */
161
- export const _getActiveElement = (elementType) => (
162
- editor,
163
- direction = 'any',
164
- ) => {
165
- const selection = editor.selection || editor.getSavedSelection();
166
- let found = [];
167
-
168
- try {
169
- found = Array.from(
170
- Editor.nodes(editor, {
171
- match: (n) =>
172
- Array.isArray(elementType)
173
- ? elementType.includes(n.type)
174
- : n.type === elementType,
175
- at: selection,
176
- }),
177
- );
178
- } catch (e) {
179
- return null;
180
- }
181
-
182
- if (found.length) return found[0];
183
-
184
- if (!selection) return null;
161
+ export const _getActiveElement =
162
+ (elementType) =>
163
+ (editor, direction = 'any') => {
164
+ const selection = editor.selection || editor.getSavedSelection();
165
+ let found = [];
185
166
 
186
- if (direction === 'any' || direction === 'backward') {
187
- const { path } = selection.anchor;
188
- const isAtStart =
189
- selection.anchor.offset === 0 && selection.focus.offset === 0;
167
+ try {
168
+ found = Array.from(
169
+ Editor.nodes(editor, {
170
+ match: (n) =>
171
+ Array.isArray(elementType)
172
+ ? elementType.includes(n.type)
173
+ : n.type === elementType,
174
+ at: selection,
175
+ }),
176
+ );
177
+ } catch (e) {
178
+ return null;
179
+ }
190
180
 
191
- if (isAtStart) {
192
- let found;
193
- try {
194
- found = Editor.previous(editor, {
195
- at: path,
196
- });
197
- } catch (ex) {
198
- // eslint-disable-next-line no-console
199
- console.warn('Unable to find previous node', editor, path);
200
- return;
201
- }
202
- if (found && found[0] && found[0].type === elementType) {
203
- if (
204
- (Array.isArray(elementType) && elementType.includes(found[0].type)) ||
205
- found[0].type === elementType
206
- ) {
207
- return found;
181
+ if (found.length) return found[0];
182
+
183
+ if (!selection) return null;
184
+
185
+ if (direction === 'any' || direction === 'backward') {
186
+ const { path } = selection.anchor;
187
+ const isAtStart =
188
+ selection.anchor.offset === 0 && selection.focus.offset === 0;
189
+
190
+ if (isAtStart) {
191
+ let found;
192
+ try {
193
+ found = Editor.previous(editor, {
194
+ at: path,
195
+ });
196
+ } catch (ex) {
197
+ // eslint-disable-next-line no-console
198
+ console.warn('Unable to find previous node', editor, path);
199
+ return;
200
+ }
201
+ if (found && found[0] && found[0].type === elementType) {
202
+ if (
203
+ (Array.isArray(elementType) &&
204
+ elementType.includes(found[0].type)) ||
205
+ found[0].type === elementType
206
+ ) {
207
+ return found;
208
+ }
209
+ } else {
210
+ return null;
208
211
  }
209
- } else {
210
- return null;
211
212
  }
212
213
  }
213
- }
214
-
215
- if (direction === 'any' || direction === 'forward') {
216
- const { path } = selection.anchor;
217
- const isAtStart =
218
- selection.anchor.offset === 0 && selection.focus.offset === 0;
219
214
 
220
- if (isAtStart) {
221
- let found;
222
- try {
223
- found = Editor.next(editor, {
224
- at: path,
225
- });
226
- } catch (e) {
227
- // eslint-disable-next-line
228
- console.warn('Unable to find next node', editor, path);
229
- return;
230
- }
231
- if (found && found[0] && found[0].type === elementType) {
232
- if (
233
- (Array.isArray(elementType) && elementType.includes(found[0].type)) ||
234
- found[0].type === elementType
235
- ) {
236
- return found;
215
+ if (direction === 'any' || direction === 'forward') {
216
+ const { path } = selection.anchor;
217
+ const isAtStart =
218
+ selection.anchor.offset === 0 && selection.focus.offset === 0;
219
+
220
+ if (isAtStart) {
221
+ let found;
222
+ try {
223
+ found = Editor.next(editor, {
224
+ at: path,
225
+ });
226
+ } catch (e) {
227
+ // eslint-disable-next-line
228
+ console.warn('Unable to find next node', editor, path);
229
+ return;
230
+ }
231
+ if (found && found[0] && found[0].type === elementType) {
232
+ if (
233
+ (Array.isArray(elementType) &&
234
+ elementType.includes(found[0].type)) ||
235
+ found[0].type === elementType
236
+ ) {
237
+ return found;
238
+ }
239
+ } else {
240
+ return null;
237
241
  }
238
- } else {
239
- return null;
240
242
  }
241
243
  }
242
- }
243
244
 
244
- return null;
245
- };
245
+ return null;
246
+ };
@@ -0,0 +1,70 @@
1
+ import PropTypes from 'prop-types';
2
+ import cx from 'classnames';
3
+ import { useState } from 'react';
4
+ import ContainerEdit from '@plone/volto/components/manage/Blocks/Container/Edit';
5
+
6
+ const convertTeaserToGridIfNecessary = (data) => {
7
+ if (data?.['@type'] === 'teaserGrid')
8
+ return {
9
+ ...data,
10
+ '@type': 'gridBlock',
11
+ blocks_layout: { items: data?.columns.map((c) => c.id) },
12
+ blocks: data?.columns?.reduce((acc, current) => {
13
+ return {
14
+ ...acc,
15
+ [current?.id]: current,
16
+ };
17
+ }, {}),
18
+ };
19
+ return data;
20
+ };
21
+
22
+ const GridBlockEdit = (props) => {
23
+ const { data } = props;
24
+
25
+ const columnsLength =
26
+ data?.blocks_layout?.items?.length || data?.columns?.length || 0;
27
+
28
+ const [selectedBlock, setSelectedBlock] = useState(null);
29
+
30
+ //convert to gridBlock if necessary
31
+ if (data?.['@type'] === 'teaserGrid') {
32
+ props.onChangeBlock(props.block, convertTeaserToGridIfNecessary(data));
33
+ }
34
+
35
+ return (
36
+ <div
37
+ className={cx({
38
+ one: columnsLength === 1,
39
+ two: columnsLength === 2,
40
+ three: columnsLength === 3,
41
+ four: columnsLength >= 4,
42
+ 'grid-items': true,
43
+ })}
44
+ // This is required to enabling a small "in-between" clickable area
45
+ // for bringing the Grid sidebar alive once you have selected an inner block
46
+ onClick={(e) => {
47
+ if (!e.block) setSelectedBlock(null);
48
+ }}
49
+ role="presentation"
50
+ >
51
+ <ContainerEdit
52
+ {...props}
53
+ data={data}
54
+ selectedBlock={selectedBlock}
55
+ setSelectedBlock={setSelectedBlock}
56
+ direction="horizontal"
57
+ />
58
+ </div>
59
+ );
60
+ };
61
+
62
+ GridBlockEdit.propTypes = {
63
+ block: PropTypes.string.isRequired,
64
+ onChangeBlock: PropTypes.func.isRequired,
65
+ pathname: PropTypes.string.isRequired,
66
+ selected: PropTypes.bool.isRequired,
67
+ manage: PropTypes.bool.isRequired,
68
+ };
69
+
70
+ export default GridBlockEdit;
@@ -0,0 +1,61 @@
1
+ import { Grid } from 'semantic-ui-react';
2
+ import cx from 'classnames';
3
+ import { RenderBlocks } from '@plone/volto/components';
4
+ import { withBlockExtensions } from '@plone/volto/helpers';
5
+ import config from '@plone/volto/registry';
6
+
7
+ const convertTeaserToGridIfNecessary = (data) => {
8
+ if (data?.['@type'] === 'teaserGrid')
9
+ return {
10
+ ...data,
11
+ '@type': 'gridBlock',
12
+ blocks_layout: { items: data?.columns.map((c) => c.id) },
13
+ blocks: data?.columns?.reduce((acc, current) => {
14
+ return {
15
+ ...acc,
16
+ [current?.id]: current,
17
+ };
18
+ }, {}),
19
+ };
20
+ return data;
21
+ };
22
+
23
+ const GridBlockView = (props) => {
24
+ const { data, path, className, style } = props;
25
+ const metadata = props.metadata || props.properties;
26
+ const columns = data?.blocks_layout?.items || data?.columns;
27
+ const blocksConfig =
28
+ config.blocks.blocksConfig[data['@type']].blocksConfig ||
29
+ props.blocksConfig;
30
+ const location = {
31
+ pathname: path,
32
+ };
33
+
34
+ return (
35
+ <div
36
+ className={cx('block', data['@type'], className, {
37
+ one: columns?.length === 1,
38
+ two: columns?.length === 2,
39
+ three: columns?.length === 3,
40
+ four: columns?.length === 4,
41
+ })}
42
+ style={style}
43
+ >
44
+ {data.headline && <h2 className="headline">{data.headline}</h2>}
45
+
46
+ <Grid stackable stretched columns={columns?.length}>
47
+ <RenderBlocks
48
+ {...props}
49
+ blockWrapperTag={Grid.Column}
50
+ metadata={metadata}
51
+ content={convertTeaserToGridIfNecessary(data)}
52
+ location={location}
53
+ blocksConfig={blocksConfig}
54
+ isContainer
55
+ />
56
+ </Grid>
57
+ </div>
58
+ );
59
+ };
60
+
61
+ export default withBlockExtensions(GridBlockView);
@@ -0,0 +1 @@
1
+ These two customizations ensure backward compatibility with the legacy @kitconcept/volto-blocks-grid. For more details, refer to the ticket here: https://taskman.eionet.europa.eu/issues/265726.
@@ -16,7 +16,6 @@ import { isEqual } from 'lodash';
16
16
 
17
17
  import { Icon, ImageSidebar, SidebarPortal } from '@plone/volto/components';
18
18
  import { Icon as IconSemantic } from 'semantic-ui-react';
19
- import { withBlockExtensions } from '@plone/volto/helpers';
20
19
  import { createContent } from '@plone/volto/actions';
21
20
  import { Copyright } from '@eeacms/volto-eea-design-system/ui';
22
21
 
@@ -24,13 +23,33 @@ import {
24
23
  flattenToAppURL,
25
24
  getBaseUrl,
26
25
  isInternalURL,
26
+ withBlockExtensions,
27
+ validateFileUploadSize,
27
28
  } from '@plone/volto/helpers';
29
+ import config from '@plone/volto/registry';
28
30
 
29
31
  import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
30
32
  import clearSVG from '@plone/volto/icons/clear.svg';
31
33
  import navTreeSVG from '@plone/volto/icons/nav.svg';
32
34
  import aheadSVG from '@plone/volto/icons/ahead.svg';
33
35
  import uploadSVG from '@plone/volto/icons/upload.svg';
36
+
37
+ // please Volto 16 test
38
+ export const getImageBlockSizes = function (data) {
39
+ if (data.align === 'full') return '100vw';
40
+ if (data.align === 'center') {
41
+ if (data.size === 'l') return '100vw';
42
+ if (data.size === 'm') return '50vw';
43
+ if (data.size === 's') return '25vw';
44
+ }
45
+ if (data.align === 'left' || data.align === 'right') {
46
+ if (data.size === 'l') return '50vw';
47
+ if (data.size === 'm') return '25vw';
48
+ if (data.size === 's') return '15vw';
49
+ }
50
+ return undefined;
51
+ };
52
+
34
53
  const Dropzone = loadable(() => import('react-dropzone'));
35
54
 
36
55
  const messages = defineMessages({
@@ -38,6 +57,10 @@ const messages = defineMessages({
38
57
  id: 'Browse the site, drop an image, or type an URL',
39
58
  defaultMessage: 'Browse the site, drop an image, or type an URL',
40
59
  },
60
+ uploadingImage: {
61
+ id: 'Uploading image',
62
+ defaultMessage: 'Uploading image',
63
+ },
41
64
  });
42
65
 
43
66
  /**
@@ -56,7 +79,7 @@ class Edit extends Component {
56
79
  block: PropTypes.string.isRequired,
57
80
  index: PropTypes.number.isRequired,
58
81
  data: PropTypes.objectOf(PropTypes.any).isRequired,
59
- content: PropTypes.objectOf(PropTypes.any).isRequired,
82
+ content: PropTypes.objectOf(PropTypes.any),
60
83
  request: PropTypes.shape({
61
84
  loading: PropTypes.bool,
62
85
  loaded: PropTypes.bool,
@@ -85,6 +108,7 @@ class Edit extends Component {
85
108
  * @returns {undefined}
86
109
  */
87
110
  UNSAFE_componentWillReceiveProps(nextProps) {
111
+ // Update block data after upload finished
88
112
  if (
89
113
  this.props.request.loading &&
90
114
  nextProps.request.loaded &&
@@ -96,6 +120,8 @@ class Edit extends Component {
96
120
  this.props.onChangeBlock(this.props.block, {
97
121
  ...this.props.data,
98
122
  url: nextProps.content['@id'],
123
+ image_field: 'image',
124
+ image_scales: { image: [nextProps.content.image] },
99
125
  alt: '',
100
126
  });
101
127
  }
@@ -123,6 +149,7 @@ class Edit extends Component {
123
149
  onUploadImage = (e) => {
124
150
  e.stopPropagation();
125
151
  const file = e.target.files[0];
152
+ if (!validateFileUploadSize(file, this.props.intl.formatMessage)) return;
126
153
  this.setState({
127
154
  uploading: true,
128
155
  });
@@ -167,6 +194,8 @@ class Edit extends Component {
167
194
  this.props.onChangeBlock(this.props.block, {
168
195
  ...this.props.data,
169
196
  url: flattenToAppURL(this.state.url),
197
+ image_field: undefined,
198
+ image_scales: undefined,
170
199
  });
171
200
  };
172
201
 
@@ -176,23 +205,25 @@ class Edit extends Component {
176
205
  * @param {array} files File objects
177
206
  * @returns {undefined}
178
207
  */
179
- onDrop = (file) => {
180
- this.setState({
181
- uploading: true,
182
- });
208
+ onDrop = (files) => {
209
+ if (!validateFileUploadSize(files[0], this.props.intl.formatMessage)) {
210
+ this.setState({ dragging: false });
211
+ return;
212
+ }
213
+ this.setState({ uploading: true });
183
214
 
184
- readAsDataURL(file[0]).then((data) => {
215
+ readAsDataURL(files[0]).then((data) => {
185
216
  const fields = data.match(/^data:(.*);(.*),(.*)$/);
186
217
  this.props.createContent(
187
218
  getBaseUrl(this.props.pathname),
188
219
  {
189
220
  '@type': 'Image',
190
- title: file[0].name,
221
+ title: files[0].name,
191
222
  image: {
192
223
  data: fields[3],
193
224
  encoding: fields[2],
194
225
  'content-type': fields[1],
195
- filename: file[0].name,
226
+ filename: files[0].name,
196
227
  },
197
228
  },
198
229
  this.props.block,
@@ -234,6 +265,7 @@ class Edit extends Component {
234
265
  * @returns {string} Markup for the component.
235
266
  */
236
267
  render() {
268
+ const Image = config.getComponent({ name: 'Image' }).component;
237
269
  const { data } = this.props;
238
270
  const placeholder =
239
271
  this.props.data.placeholder ||
@@ -265,7 +297,7 @@ class Edit extends Component {
265
297
  >
266
298
  {data.url ? (
267
299
  <>
268
- <img
300
+ <Image
269
301
  className={cx(
270
302
  {
271
303
  'full-width': data.align === 'full',
@@ -275,18 +307,23 @@ class Edit extends Component {
275
307
  },
276
308
  data?.styles?.objectPosition,
277
309
  )}
310
+ item={
311
+ data.image_scales
312
+ ? {
313
+ '@id': data.url,
314
+ image_field: data.image_field,
315
+ image_scales: data.image_scales,
316
+ }
317
+ : undefined
318
+ }
278
319
  src={
279
- isInternalURL(data.url)
320
+ data.image_scales
321
+ ? undefined
322
+ : isInternalURL(data.url)
280
323
  ? // Backwards compat in the case that the block is storing the full server URL
281
324
  (() => {
282
- if (data.align === 'full')
283
- return `${flattenToAppURL(
284
- data.url,
285
- )}/@@images/image/huge`;
286
325
  if (data.size === 'l')
287
- return `${flattenToAppURL(
288
- data.url,
289
- )}/@@images/image/great`;
326
+ return `${flattenToAppURL(data.url)}/@@images/image`;
290
327
  if (data.size === 'm')
291
328
  return `${flattenToAppURL(
292
329
  data.url,
@@ -295,13 +332,14 @@ class Edit extends Component {
295
332
  return `${flattenToAppURL(
296
333
  data.url,
297
334
  )}/@@images/image/mini`;
298
- return `${flattenToAppURL(
299
- data.url,
300
- )}/@@images/image/great`;
335
+ return `${flattenToAppURL(data.url)}/@@images/image`;
301
336
  })()
302
337
  : data.url
303
338
  }
339
+ sizes={config.blocks.blocksConfig.image.getSizes(data)}
304
340
  alt={data.alt || ''}
341
+ loading="lazy"
342
+ responsive={true}
305
343
  />
306
344
  <div className={`copyright-wrapper ${copyrightPosition}`}>
307
345
  {copyright && showCopyright ? (
@@ -332,7 +370,11 @@ class Edit extends Component {
332
370
  {this.state.dragging && <Dimmer active></Dimmer>}
333
371
  {this.state.uploading && (
334
372
  <Dimmer active>
335
- <Loader indeterminate>Uploading image</Loader>
373
+ <Loader indeterminate>
374
+ {this.props.intl.formatMessage(
375
+ messages.uploadingImage,
376
+ )}
377
+ </Loader>
336
378
  </Dimmer>
337
379
  )}
338
380
  <div className="no-image-wrapper">
@@ -345,7 +387,24 @@ class Edit extends Component {
345
387
  onClick={(e) => {
346
388
  e.stopPropagation();
347
389
  e.preventDefault();
348
- this.props.openObjectBrowser();
390
+ this.props.openObjectBrowser({
391
+ onSelectItem: (
392
+ url,
393
+ { title, image_field, image_scales },
394
+ ) => {
395
+ this.props.onChangeBlock(
396
+ this.props.block,
397
+ {
398
+ ...this.props.data,
399
+ url,
400
+ image_field,
401
+ image_scales,
402
+ alt:
403
+ this.props.data.alt || title || '',
404
+ },
405
+ );
406
+ },
407
+ });
349
408
  }}
350
409
  >
351
410
  <Icon name={navTreeSVG} size="24px" />
@@ -1,10 +1,16 @@
1
- import React from 'react';
1
+ import config from '@plone/volto/registry';
2
+ import '@testing-library/jest-dom/extend-expect';
2
3
  import { render } from '@testing-library/react';
4
+ import React from 'react';
3
5
  import { Provider } from 'react-intl-redux';
4
6
  import configureMockStore from 'redux-mock-store';
5
7
  import Edit from './Edit';
6
- import config from '@plone/volto/registry';
7
- import '@testing-library/jest-dom/extend-expect';
8
+ import { Image } from '@plone/volto/components';
9
+ import { getImageBlockSizes } from './Edit';
10
+
11
+ config.set('components', {
12
+ Image: { component: Image },
13
+ });
8
14
 
9
15
  const mockStore = configureMockStore();
10
16
  const { settings } = config;
@@ -25,6 +31,7 @@ config.blocks.blocksConfig = {
25
31
  addPermission: [],
26
32
  view: [],
27
33
  },
34
+ getSizes: getImageBlockSizes,
28
35
  },
29
36
  };
30
37
  const blockId = '1234';