@plone/volto 17.0.0-alpha.15 → 17.0.0-alpha.17

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 (94) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +30 -0
  3. package/cypress/support/commands.js +18 -0
  4. package/locales/ca/LC_MESSAGES/volto.po +41 -0
  5. package/locales/ca.json +1 -1
  6. package/locales/de/LC_MESSAGES/volto.po +41 -0
  7. package/locales/de.json +1 -1
  8. package/locales/en/LC_MESSAGES/volto.po +41 -0
  9. package/locales/en.json +1 -1
  10. package/locales/es/LC_MESSAGES/volto.po +41 -0
  11. package/locales/es.json +1 -1
  12. package/locales/eu/LC_MESSAGES/volto.po +41 -0
  13. package/locales/eu.json +1 -1
  14. package/locales/fi/LC_MESSAGES/volto.po +41 -0
  15. package/locales/fi.json +1 -1
  16. package/locales/fr/LC_MESSAGES/volto.po +41 -0
  17. package/locales/fr.json +1 -1
  18. package/locales/it/LC_MESSAGES/volto.po +41 -0
  19. package/locales/it.json +1 -1
  20. package/locales/ja/LC_MESSAGES/volto.po +41 -0
  21. package/locales/ja.json +1 -1
  22. package/locales/nl/LC_MESSAGES/volto.po +41 -0
  23. package/locales/nl.json +1 -1
  24. package/locales/pt/LC_MESSAGES/volto.po +41 -0
  25. package/locales/pt.json +1 -1
  26. package/locales/pt_BR/LC_MESSAGES/volto.po +41 -0
  27. package/locales/pt_BR.json +1 -1
  28. package/locales/ro/LC_MESSAGES/volto.po +41 -0
  29. package/locales/ro.json +1 -1
  30. package/locales/volto.pot +42 -1
  31. package/locales/zh_CN/LC_MESSAGES/volto.po +41 -0
  32. package/locales/zh_CN.json +1 -1
  33. package/news/4351.bugfix +1 -0
  34. package/news/4725.bugfix +1 -0
  35. package/news/4932.bugfix +1 -0
  36. package/news/4941.documentation +1 -0
  37. package/news/4951.breaking +1 -0
  38. package/news/4962.feature +1 -0
  39. package/news/4964.bugfix +1 -0
  40. package/package.json +1 -1
  41. package/packages/volto-slate/package.json +1 -1
  42. package/packages/volto-slate/src/blocks/Text/index.js +8 -0
  43. package/src/components/manage/Blocks/Block/BlocksForm.jsx +19 -2
  44. package/src/components/manage/Blocks/Block/Edit.jsx +1 -1
  45. package/src/components/manage/Blocks/Container/Data.jsx +32 -0
  46. package/src/components/manage/Blocks/Container/Edit.jsx +174 -0
  47. package/src/components/manage/Blocks/Container/EditBlockWrapper.jsx +120 -0
  48. package/src/components/manage/Blocks/Container/NewBlockAddButton.jsx +84 -0
  49. package/src/components/manage/Blocks/Container/SimpleContainerToolbar.jsx +54 -0
  50. package/src/components/manage/Blocks/Grid/Edit.jsx +33 -0
  51. package/src/components/manage/Blocks/Grid/View.jsx +43 -0
  52. package/src/components/manage/Blocks/Grid/adapter.js +14 -0
  53. package/src/components/manage/Blocks/Grid/grid-1.svg +6 -0
  54. package/src/components/manage/Blocks/Grid/grid-2.svg +9 -0
  55. package/src/components/manage/Blocks/Grid/grid-3.svg +10 -0
  56. package/src/components/manage/Blocks/Grid/grid-4.svg +11 -0
  57. package/src/components/manage/Blocks/Grid/schema.js +35 -0
  58. package/src/components/manage/Blocks/Grid/templates.js +47 -0
  59. package/src/components/manage/Blocks/Image/ImageSidebar.jsx +2 -1
  60. package/src/components/manage/Blocks/Image/schema.js +11 -0
  61. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +3 -4
  62. package/src/components/manage/Blocks/Teaser/Body.jsx +0 -1
  63. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +20 -15
  64. package/src/components/manage/Blocks/Teaser/schema.js +5 -0
  65. package/src/components/manage/DragDropList/DragDropList.jsx +18 -13
  66. package/src/components/manage/TemplateChooser/TemplateChooser.jsx +38 -0
  67. package/src/components/manage/TemplateChooser/TemplateChooser.test.jsx +34 -0
  68. package/src/components/manage/TemplateChooser/template.svg +10 -0
  69. package/src/components/manage/UniversalLink/UniversalLink.jsx +2 -6
  70. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +36 -0
  71. package/src/components/theme/Anontools/Anontools.jsx +3 -4
  72. package/src/components/theme/Component/Component.jsx +1 -1
  73. package/src/components/theme/Header/Header.jsx +2 -2
  74. package/src/components/theme/View/AlbumView.jsx +9 -1
  75. package/src/components/theme/View/EventView.jsx +6 -2
  76. package/src/components/theme/View/FileView.jsx +23 -18
  77. package/src/components/theme/View/ImageView.jsx +37 -32
  78. package/src/components/theme/View/LinkView.jsx +4 -1
  79. package/src/components/theme/View/ListingView.jsx +33 -27
  80. package/src/components/theme/View/RenderBlocks.jsx +56 -27
  81. package/src/components/theme/View/RenderEmptyBlock.jsx +5 -0
  82. package/src/components/theme/View/SummaryView.jsx +47 -38
  83. package/src/components/theme/View/TabularView.jsx +59 -53
  84. package/src/config/Blocks.jsx +44 -0
  85. package/src/helpers/Blocks/Blocks.js +26 -0
  86. package/src/helpers/Blocks/Blocks.test.js +21 -0
  87. package/src/helpers/Utils/Utils.js +25 -0
  88. package/src/helpers/index.js +2 -0
  89. package/src/hooks/index.js +0 -1
  90. package/src/icons/grid-block.svg +11 -0
  91. package/theme/themes/pastanaga/extras/grid.less +426 -0
  92. package/theme/themes/pastanaga/extras/main.less +1 -0
  93. package/src/hooks/content/useContent.js +0 -31
  94. package/src/hooks/userSession/useToken.js +0 -5
@@ -0,0 +1,38 @@
1
+ import PropTypes from 'prop-types';
2
+ import { useIntl } from 'react-intl';
3
+ import { Button, Grid, Message } from 'semantic-ui-react';
4
+
5
+ const TemplateChooser = ({ templates, onSelectTemplate }) => {
6
+ const intl = useIntl();
7
+ return (
8
+ <div className="template-chooser">
9
+ <Message>
10
+ <Grid columns={templates(intl).length}>
11
+ {templates(intl).map((template, index) => (
12
+ <Grid.Column key={template.id}>
13
+ <Button
14
+ className="template-chooser-item"
15
+ onClick={() => onSelectTemplate(index)}
16
+ >
17
+ <img src={template.image} alt="" />
18
+ <div className="template-chooser-title">
19
+ {intl.formatMessage({
20
+ id: template.id,
21
+ defaultMessage: template.title,
22
+ })}
23
+ </div>
24
+ </Button>
25
+ </Grid.Column>
26
+ ))}
27
+ </Grid>
28
+ </Message>
29
+ </div>
30
+ );
31
+ };
32
+
33
+ TemplateChooser.propTypes = {
34
+ templates: PropTypes.func.isRequired,
35
+ onSelectTemplate: PropTypes.func.isRequired,
36
+ };
37
+
38
+ export default TemplateChooser;
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import renderer from 'react-test-renderer';
3
+ import configureStore from 'redux-mock-store';
4
+ import { Provider } from 'react-intl-redux';
5
+ import TemplateChooser from './TemplateChooser';
6
+ import templateSVG from './template.svg';
7
+
8
+ const mockStore = configureStore();
9
+
10
+ test('renders a TemplateChooser component', () => {
11
+ const store = mockStore({
12
+ intl: {
13
+ locale: 'en',
14
+ messages: { templateid: 'Template default translation' },
15
+ },
16
+ });
17
+
18
+ const component = renderer.create(
19
+ <Provider store={store}>
20
+ <TemplateChooser
21
+ templates={() => [
22
+ {
23
+ image: templateSVG,
24
+ id: 'templateid',
25
+ defaultMessage: 'Template default translation',
26
+ },
27
+ ]}
28
+ onSelectTemplate={() => {}}
29
+ />
30
+ </Provider>,
31
+ );
32
+ const json = component.toJSON();
33
+ expect(json).toMatchSnapshot();
34
+ });
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
2
+ <g fill="none" fill-rule="evenodd">
3
+ <rect width="96" height="96" fill="#9FD1E5" rx="3"/>
4
+ <g fill="#FFF" opacity=".9" transform="translate(9 21)">
5
+ <rect width="78" height="29"/>
6
+ <rect width="36" height="8" y="33"/>
7
+ <rect width="77" height="10" y="45"/>
8
+ </g>
9
+ </g>
10
+ </svg>
@@ -12,7 +12,6 @@ import {
12
12
  isInternalURL,
13
13
  URLUtils,
14
14
  } from '@plone/volto/helpers/Url/Url';
15
- import { matchPath } from 'react-router';
16
15
 
17
16
  import config from '@plone/volto/registry';
18
17
 
@@ -66,11 +65,8 @@ const UniversalLink = ({
66
65
  }
67
66
  }
68
67
 
69
- const isBlacklisted =
70
- (config.settings.externalRoutes ?? []).find((route) =>
71
- matchPath(flattenToAppURL(url), route.match),
72
- )?.length > 0;
73
- const isExternal = !isInternalURL(url) || isBlacklisted;
68
+ const isExternal = !isInternalURL(url);
69
+
74
70
  const isDownload = (!isExternal && url.includes('@@download')) || download;
75
71
  const isDisplayFile =
76
72
  (!isExternal && url.includes('@@display-file')) || false;
@@ -157,6 +157,42 @@ describe('UniversalLink', () => {
157
157
  );
158
158
  });
159
159
 
160
+ it('UniversalLink renders external link where link is blacklisted', () => {
161
+ const notInEN = /^(?!.*(#|\/en|\/static|\/controlpanel|\/cypress|\/login|\/logout|\/contact-form)).*$/;
162
+ config.settings.externalRoutes = [
163
+ {
164
+ match: {
165
+ path: notInEN,
166
+ exact: false,
167
+ strict: false,
168
+ },
169
+ url(payload) {
170
+ return payload.location.pathname;
171
+ },
172
+ },
173
+ ];
174
+
175
+ const { getByTitle } = render(
176
+ <Provider store={store}>
177
+ <MemoryRouter>
178
+ <UniversalLink
179
+ href="http://localhost:3000/blacklisted-app"
180
+ title="External blacklisted app"
181
+ >
182
+ <h1>Title</h1>
183
+ </UniversalLink>
184
+ </MemoryRouter>
185
+ </Provider>,
186
+ );
187
+
188
+ expect(getByTitle('External blacklisted app').getAttribute('target')).toBe(
189
+ '_blank',
190
+ );
191
+ expect(getByTitle('External blacklisted app').getAttribute('rel')).toBe(
192
+ 'noopener noreferrer',
193
+ );
194
+ });
195
+
160
196
  it('check UniversalLink does not break with error in item', () => {
161
197
  const component = renderer.create(
162
198
  <Provider store={store}>
@@ -3,13 +3,12 @@ import { Link } from 'react-router-dom';
3
3
  import { Menu } from 'semantic-ui-react';
4
4
  import { FormattedMessage } from 'react-intl';
5
5
  import { flattenToAppURL } from '@plone/volto/helpers';
6
- import { useToken } from '@plone/volto/hooks/userSession/useToken';
7
- import { useContent } from '@plone/volto/hooks/content/useContent';
8
6
  import config from '@plone/volto/registry';
7
+ import { useSelector, shallowEqual } from 'react-redux';
9
8
 
10
9
  const Anontools = () => {
11
- const token = useToken();
12
- const { data: content } = useContent();
10
+ const token = useSelector((state) => state.userSession.token, shallowEqual);
11
+ const content = useSelector((state) => state.content.data, shallowEqual);
13
12
 
14
13
  const { settings } = config;
15
14
  return (
@@ -1,7 +1,7 @@
1
1
  import registry from '@plone/volto/registry';
2
2
 
3
3
  /**
4
- * A component that can autommatically look up its implementation from the
4
+ * A component that can automatically look up its implementation from the
5
5
  * registry based on the provided component `componentName`
6
6
  */
7
7
  const Component = ({ componentName, dependencies, ...rest }) => {
@@ -1,6 +1,6 @@
1
1
  import { Container, Segment } from 'semantic-ui-react';
2
2
  import PropTypes from 'prop-types';
3
- import { useToken } from '@plone/volto/hooks/userSession/useToken';
3
+ import { useSelector, shallowEqual } from 'react-redux';
4
4
 
5
5
  import {
6
6
  Anontools,
@@ -11,7 +11,7 @@ import {
11
11
  } from '@plone/volto/components';
12
12
 
13
13
  const Header = ({ pathname }) => {
14
- const token = useToken();
14
+ const token = useSelector((state) => state.userSession.token, shallowEqual);
15
15
 
16
16
  return (
17
17
  <Segment basic className="header-wrapper" role="banner">
@@ -5,9 +5,14 @@
5
5
 
6
6
  import React, { Component } from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { Container, GridColumn, Segment } from 'semantic-ui-react';
8
+ import {
9
+ Container as SemanticContainer,
10
+ GridColumn,
11
+ Segment,
12
+ } from 'semantic-ui-react';
9
13
  import { Button, Modal, Grid } from 'semantic-ui-react';
10
14
  import { Icon, UniversalLink, PreviewImage } from '@plone/volto/components';
15
+ import config from '@plone/volto/registry';
11
16
 
12
17
  import openSVG from '@plone/volto/icons/open.svg';
13
18
  import aheadSVG from '@plone/volto/icons/ahead.svg';
@@ -56,6 +61,9 @@ class AlbumView extends Component {
56
61
 
57
62
  render() {
58
63
  const { content } = this.props;
64
+ const Container =
65
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
66
+
59
67
  return (
60
68
  <Container className="view-wrapper">
61
69
  <article id="content">
@@ -9,6 +9,8 @@ import { hasBlocksData, flattenHTMLToAppURL } from '@plone/volto/helpers';
9
9
  import { Image, Grid } from 'semantic-ui-react';
10
10
  import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
11
11
  import { EventDetails } from '@plone/volto/components';
12
+ import { Container as SemanticContainer } from 'semantic-ui-react';
13
+ import config from '@plone/volto/registry';
12
14
 
13
15
  const EventTextfieldView = ({ content }) => (
14
16
  <React.Fragment>
@@ -41,9 +43,11 @@ const EventTextfieldView = ({ content }) => (
41
43
  */
42
44
  const EventView = (props) => {
43
45
  const { content } = props;
46
+ const Container =
47
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
44
48
 
45
49
  return (
46
- <div id="page-document" className="ui container view-wrapper event-view">
50
+ <Container id="page-document" className="view-wrapper event-view">
47
51
  <Grid>
48
52
  <Grid.Column width={7} className="mobile hidden">
49
53
  {hasBlocksData(content) ? (
@@ -83,7 +87,7 @@ const EventView = (props) => {
83
87
  )}
84
88
  </Grid.Column>
85
89
  </Grid>
86
- </div>
90
+ </Container>
87
91
  );
88
92
  };
89
93
 
@@ -5,9 +5,9 @@
5
5
 
6
6
  import React from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { Container } from 'semantic-ui-react';
9
-
8
+ import { Container as SemanticContainer } from 'semantic-ui-react';
10
9
  import { flattenToAppURL } from '@plone/volto/helpers';
10
+ import config from '@plone/volto/registry';
11
11
 
12
12
  /**
13
13
  * File view component class.
@@ -15,22 +15,27 @@ import { flattenToAppURL } from '@plone/volto/helpers';
15
15
  * @params {object} content Content object.
16
16
  * @returns {string} Markup of the component.
17
17
  */
18
- const FileView = ({ content }) => (
19
- <Container className="view-wrapper">
20
- <h1 className="documentFirstHeading">
21
- {content.title}
22
- {content.subtitle && ` - ${content.subtitle}`}
23
- </h1>
24
- {content.description && (
25
- <p className="documentDescription">{content.description}</p>
26
- )}
27
- {content.file?.download && (
28
- <a href={flattenToAppURL(content.file.download)}>
29
- {content.file.filename}
30
- </a>
31
- )}
32
- </Container>
33
- );
18
+ const FileView = ({ content }) => {
19
+ const Container =
20
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
21
+
22
+ return (
23
+ <Container className="view-wrapper">
24
+ <h1 className="documentFirstHeading">
25
+ {content.title}
26
+ {content.subtitle && ` - ${content.subtitle}`}
27
+ </h1>
28
+ {content.description && (
29
+ <p className="documentDescription">{content.description}</p>
30
+ )}
31
+ {content.file?.download && (
32
+ <a href={flattenToAppURL(content.file.download)}>
33
+ {content.file.filename}
34
+ </a>
35
+ )}
36
+ </Container>
37
+ );
38
+ };
34
39
 
35
40
  /**
36
41
  * Property types.
@@ -5,11 +5,11 @@
5
5
 
6
6
  import React from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { Container } from 'semantic-ui-react';
8
+ import { Container as SemanticContainer } from 'semantic-ui-react';
9
9
  import { FormattedMessage } from 'react-intl';
10
10
  import prettybytes from 'pretty-bytes';
11
-
12
11
  import { flattenToAppURL } from '@plone/volto/helpers';
12
+ import config from '@plone/volto/registry';
13
13
 
14
14
  /**
15
15
  * Image view component class.
@@ -17,37 +17,42 @@ import { flattenToAppURL } from '@plone/volto/helpers';
17
17
  * @params {object} content Content object.
18
18
  * @returns {string} Markup of the component.
19
19
  */
20
- const ImageView = ({ content }) => (
21
- <Container className="view-wrapper">
22
- <h1 className="documentFirstHeading">
23
- {content.title}
24
- {content.subtitle && ` - ${content.subtitle}`}
25
- </h1>
26
- {content.description && (
27
- <p className="documentDescription">{content.description}</p>
28
- )}
29
- {content?.image?.download && (
30
- <a href={flattenToAppURL(content.image.download)}>
31
- <img
32
- alt={content.title}
33
- src={flattenToAppURL(content.image.scales.preview.download)}
34
- />
35
- <figcaption>
36
- <FormattedMessage
37
- id="Size: {size}"
38
- defaultMessage="Size: {size}"
39
- values={{ size: prettybytes(content.image.size) }}
40
- />
41
- &nbsp; &mdash; &nbsp;
42
- <FormattedMessage
43
- id="Click to download full sized image"
44
- defaultMessage="Click to download full sized image"
20
+ const ImageView = ({ content }) => {
21
+ const Container =
22
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
23
+
24
+ return (
25
+ <Container className="view-wrapper">
26
+ <h1 className="documentFirstHeading">
27
+ {content.title}
28
+ {content.subtitle && ` - ${content.subtitle}`}
29
+ </h1>
30
+ {content.description && (
31
+ <p className="documentDescription">{content.description}</p>
32
+ )}
33
+ {content?.image?.download && (
34
+ <a href={flattenToAppURL(content.image.download)}>
35
+ <img
36
+ alt={content.title}
37
+ src={flattenToAppURL(content.image.scales.preview.download)}
45
38
  />
46
- </figcaption>
47
- </a>
48
- )}
49
- </Container>
50
- );
39
+ <figcaption>
40
+ <FormattedMessage
41
+ id="Size: {size}"
42
+ defaultMessage="Size: {size}"
43
+ values={{ size: prettybytes(content.image.size) }}
44
+ />
45
+ &nbsp; &mdash; &nbsp;
46
+ <FormattedMessage
47
+ id="Click to download full sized image"
48
+ defaultMessage="Click to download full sized image"
49
+ />
50
+ </figcaption>
51
+ </a>
52
+ )}
53
+ </Container>
54
+ );
55
+ };
51
56
 
52
57
  /**
53
58
  * Property types.
@@ -6,7 +6,7 @@
6
6
  import React, { Component } from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers';
9
- import { Container } from 'semantic-ui-react';
9
+ import { Container as SemanticContainer } from 'semantic-ui-react';
10
10
  import { UniversalLink } from '@plone/volto/components';
11
11
  import { FormattedMessage } from 'react-intl';
12
12
  import config from '@plone/volto/registry';
@@ -60,6 +60,9 @@ class LinkView extends Component {
60
60
  render() {
61
61
  const { remoteUrl } = this.props.content;
62
62
  const { openExternalLinkInNewTab } = config.settings;
63
+ const Container =
64
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
65
+
63
66
  return (
64
67
  <Container id="page-document">
65
68
  <h1 className="documentFirstHeading">{this.props.content.title}</h1>
@@ -5,8 +5,9 @@
5
5
 
6
6
  import React from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { Segment, Container } from 'semantic-ui-react';
8
+ import { Segment, Container as SemanticContainer } from 'semantic-ui-react';
9
9
  import { UniversalLink, PreviewImage } from '@plone/volto/components';
10
+ import config from '@plone/volto/registry';
10
11
 
11
12
  /**
12
13
  * List view component class.
@@ -14,32 +15,37 @@ import { UniversalLink, PreviewImage } from '@plone/volto/components';
14
15
  * @params {object} content Content object.
15
16
  * @returns {string} Markup of the component.
16
17
  */
17
- const ListingView = ({ content }) => (
18
- <Container id="page-home">
19
- <section id="content-core">
20
- {content.items.map((item) => (
21
- <Segment key={item.url} className="listing-item">
22
- <Container>
23
- <h2>
24
- <UniversalLink item={item} title={item['@type']}>
25
- {item.title}
26
- </UniversalLink>
27
- </h2>
28
- {item.description && <p>{item.description}</p>}
29
- </Container>
30
- {item.image_field && (
31
- <PreviewImage
32
- item={item}
33
- size="thumb"
34
- alt={item.image_caption ? item.image_caption : item.title}
35
- className="ui image"
36
- />
37
- )}
38
- </Segment>
39
- ))}
40
- </section>
41
- </Container>
42
- );
18
+ const ListingView = ({ content }) => {
19
+ const Container =
20
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
21
+
22
+ return (
23
+ <Container id="page-home">
24
+ <section id="content-core">
25
+ {content.items.map((item) => (
26
+ <Segment key={item.url} className="listing-item">
27
+ <Container>
28
+ <h2>
29
+ <UniversalLink item={item} title={item['@type']}>
30
+ {item.title}
31
+ </UniversalLink>
32
+ </h2>
33
+ {item.description && <p>{item.description}</p>}
34
+ </Container>
35
+ {item.image_field && (
36
+ <PreviewImage
37
+ item={item}
38
+ size="thumb"
39
+ alt={item.image_caption ? item.image_caption : item.title}
40
+ className="ui image"
41
+ />
42
+ )}
43
+ </Segment>
44
+ ))}
45
+ </section>
46
+ </Container>
47
+ );
48
+ };
43
49
 
44
50
  /**
45
51
  * Property types.
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { getBaseUrl, applyBlockDefaults } from '@plone/volto/helpers';
3
- import { defineMessages, injectIntl } from 'react-intl';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
4
  import { map } from 'lodash';
5
+ import { MaybeWrap } from '@plone/volto/components';
5
6
  import {
6
7
  getBlocksFieldname,
7
8
  getBlocksLayoutFieldname,
@@ -10,6 +11,7 @@ import {
10
11
  import StyleWrapper from '@plone/volto/components/manage/Blocks/Block/StyleWrapper';
11
12
  import config from '@plone/volto/registry';
12
13
  import { ViewDefaultBlock } from '@plone/volto/components';
14
+ import RenderEmptyBlock from './RenderEmptyBlock';
13
15
 
14
16
  const messages = defineMessages({
15
17
  unknownBlock: {
@@ -23,7 +25,8 @@ const messages = defineMessages({
23
25
  });
24
26
 
25
27
  const RenderBlocks = (props) => {
26
- const { content, intl, location, metadata } = props;
28
+ const { content, location, metadata, blockWrapperTag } = props;
29
+ const intl = useIntl();
27
30
  const blocksFieldname = getBlocksFieldname(content);
28
31
  const blocksLayoutFieldname = getBlocksLayoutFieldname(content);
29
32
  const blocksConfig = props.blocksConfig || config.blocks.blocksConfig;
@@ -43,30 +46,56 @@ const RenderBlocks = (props) => {
43
46
  properties: content,
44
47
  });
45
48
 
46
- return Block ? (
47
- <StyleWrapper
48
- key={block}
49
- {...props}
50
- id={block}
51
- block={block}
52
- data={blockData}
53
- >
54
- <Block
55
- id={block}
56
- metadata={metadata}
57
- properties={content}
58
- data={blockData}
59
- path={getBaseUrl(location?.pathname || '')}
60
- blocksConfig={blocksConfig}
61
- />
62
- </StyleWrapper>
63
- ) : blockData ? (
64
- <div key={block}>
65
- {intl.formatMessage(messages.unknownBlock, {
66
- block: content[blocksFieldname]?.[block]?.['@type'],
67
- })}
68
- </div>
69
- ) : (
49
+ if (content[blocksFieldname]?.[block]?.['@type'] === 'empty') {
50
+ return (
51
+ <MaybeWrap
52
+ key={block}
53
+ condition={blockWrapperTag}
54
+ as={blockWrapperTag}
55
+ >
56
+ <RenderEmptyBlock />
57
+ </MaybeWrap>
58
+ );
59
+ }
60
+
61
+ if (Block) {
62
+ return (
63
+ <MaybeWrap
64
+ key={block}
65
+ condition={blockWrapperTag}
66
+ as={blockWrapperTag}
67
+ >
68
+ <StyleWrapper
69
+ key={block}
70
+ {...props}
71
+ id={block}
72
+ block={block}
73
+ data={blockData}
74
+ >
75
+ <Block
76
+ id={block}
77
+ metadata={metadata}
78
+ properties={content}
79
+ data={blockData}
80
+ path={getBaseUrl(location?.pathname || '')}
81
+ blocksConfig={blocksConfig}
82
+ />
83
+ </StyleWrapper>
84
+ </MaybeWrap>
85
+ );
86
+ }
87
+
88
+ if (blockData) {
89
+ return (
90
+ <div key={block}>
91
+ {intl.formatMessage(messages.unknownBlock, {
92
+ block: content[blocksFieldname]?.[block]?.['@type'],
93
+ })}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ return (
70
99
  <div key={block}>{intl.formatMessage(messages.invalidBlock)}</div>
71
100
  );
72
101
  })}
@@ -76,4 +105,4 @@ const RenderBlocks = (props) => {
76
105
  );
77
106
  };
78
107
 
79
- export default injectIntl(RenderBlocks);
108
+ export default RenderBlocks;
@@ -0,0 +1,5 @@
1
+ const RenderEmptyBlock = () => {
2
+ return null;
3
+ };
4
+
5
+ export default RenderEmptyBlock;