@plone/volto 16.20.7 → 16.21.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 (61) hide show
  1. package/.changelog.draft +12 -12
  2. package/.yarn/install-state.gz +0 -0
  3. package/CHANGELOG.md +31 -0
  4. package/locales/ca/LC_MESSAGES/volto.po +94 -3
  5. package/locales/ca.json +1 -1
  6. package/locales/de/LC_MESSAGES/volto.po +94 -3
  7. package/locales/de.json +1 -1
  8. package/locales/en/LC_MESSAGES/volto.po +93 -2
  9. package/locales/en.json +1 -1
  10. package/locales/es/LC_MESSAGES/volto.po +94 -3
  11. package/locales/es.json +1 -1
  12. package/locales/eu/LC_MESSAGES/volto.po +94 -3
  13. package/locales/eu.json +1 -1
  14. package/locales/fi/LC_MESSAGES/volto.po +94 -3
  15. package/locales/fi.json +1 -1
  16. package/locales/fr/LC_MESSAGES/volto.po +94 -3
  17. package/locales/fr.json +1 -1
  18. package/locales/it/LC_MESSAGES/volto.po +94 -3
  19. package/locales/it.json +1 -1
  20. package/locales/ja/LC_MESSAGES/volto.po +94 -3
  21. package/locales/ja.json +1 -1
  22. package/locales/nl/LC_MESSAGES/volto.po +94 -3
  23. package/locales/nl.json +1 -1
  24. package/locales/pt/LC_MESSAGES/volto.po +94 -3
  25. package/locales/pt.json +1 -1
  26. package/locales/pt_BR/LC_MESSAGES/volto.po +94 -3
  27. package/locales/pt_BR.json +1 -1
  28. package/locales/ro/LC_MESSAGES/volto.po +94 -3
  29. package/locales/ro.json +1 -1
  30. package/locales/volto.pot +94 -3
  31. package/locales/zh_CN/LC_MESSAGES/volto.po +94 -3
  32. package/locales/zh_CN.json +1 -1
  33. package/package.json +1 -1
  34. package/packages/volto-slate/package.json +1 -1
  35. package/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +8 -3
  36. package/packages/volto-slate/src/blocks/Text/extensions/withDeserializers.js +3 -1
  37. package/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx +14 -4
  38. package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +6 -1
  39. package/src/components/manage/Blocks/Image/Edit.jsx +11 -7
  40. package/src/components/manage/Contents/Contents.jsx +5 -1
  41. package/src/components/manage/Contents/ContentsUploadModal.jsx +10 -5
  42. package/src/components/manage/Form/Form.jsx +5 -3
  43. package/src/components/manage/History/History.jsx +11 -1
  44. package/src/components/manage/Preferences/ChangePassword.jsx +2 -2
  45. package/src/components/manage/Sharing/Sharing.jsx +5 -1
  46. package/src/components/manage/Toast/Toast.jsx +1 -1
  47. package/src/components/manage/Widgets/ColorPickerWidget.jsx +6 -1
  48. package/src/components/manage/Widgets/FileWidget.jsx +2 -1
  49. package/src/components/theme/NotFound/NotFound.jsx +55 -41
  50. package/src/components/theme/PasswordReset/PasswordReset.jsx +6 -3
  51. package/src/components/theme/View/NewsItemView.jsx +10 -5
  52. package/src/components/theme/View/RenderBlocks.jsx +7 -1
  53. package/src/config/index.js +1 -0
  54. package/src/helpers/Api/Api.js +1 -1
  55. package/src/helpers/Extensions/withBlockSchemaEnhancer.js +15 -11
  56. package/src/helpers/Extensions/withBlockSchemaEnhancer.test.js +145 -0
  57. package/src/helpers/FormValidation/FormValidation.js +30 -1
  58. package/src/helpers/FormValidation/FormValidation.test.js +32 -0
  59. package/src/helpers/MessageLabels/MessageLabels.js +76 -0
  60. package/src/helpers/index.js +3 -1
  61. package/theme/themes/pastanaga/extras/contents.less +1 -0
@@ -21,6 +21,7 @@ import {
21
21
  flattenToAppURL,
22
22
  getBaseUrl,
23
23
  isInternalURL,
24
+ validateFileUploadSize,
24
25
  } from '@plone/volto/helpers';
25
26
 
26
27
  import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
@@ -125,6 +126,7 @@ class Edit extends Component {
125
126
  onUploadImage = (e) => {
126
127
  e.stopPropagation();
127
128
  const file = e.target.files[0];
129
+ if (!validateFileUploadSize(file, this.props.intl.formatMessage)) return;
128
130
  this.setState({
129
131
  uploading: true,
130
132
  });
@@ -178,23 +180,25 @@ class Edit extends Component {
178
180
  * @param {array} files File objects
179
181
  * @returns {undefined}
180
182
  */
181
- onDrop = (file) => {
182
- this.setState({
183
- uploading: true,
184
- });
183
+ onDrop = (files) => {
184
+ if (!validateFileUploadSize(files[0], this.props.intl.formatMessage)) {
185
+ this.setState({ dragging: false });
186
+ return;
187
+ }
188
+ this.setState({ uploading: true });
185
189
 
186
- readAsDataURL(file[0]).then((data) => {
190
+ readAsDataURL(files[0]).then((data) => {
187
191
  const fields = data.match(/^data:(.*);(.*),(.*)$/);
188
192
  this.props.createContent(
189
193
  getBaseUrl(this.props.pathname),
190
194
  {
191
195
  '@type': 'Image',
192
- title: file[0].name,
196
+ title: files[0].name,
193
197
  image: {
194
198
  data: fields[3],
195
199
  encoding: fields[2],
196
200
  'content-type': fields[1],
197
- filename: file[0].name,
201
+ filename: files[0].name,
198
202
  },
199
203
  },
200
204
  this.props.block,
@@ -12,7 +12,7 @@ import { Link } from 'react-router-dom';
12
12
  import {
13
13
  Button,
14
14
  Confirm,
15
- Container,
15
+ Container as SemanticContainer,
16
16
  Divider,
17
17
  Dropdown,
18
18
  Menu,
@@ -70,6 +70,7 @@ import {
70
70
 
71
71
  import { Helmet, getBaseUrl } from '@plone/volto/helpers';
72
72
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
73
+ import config from '@plone/volto/registry';
73
74
 
74
75
  import backSVG from '@plone/volto/icons/back.svg';
75
76
  import cutSVG from '@plone/volto/icons/cut.svg';
@@ -1177,6 +1178,9 @@ class Contents extends Component {
1177
1178
  (this.props.orderRequest?.loading && !this.props.orderRequest?.error) ||
1178
1179
  (this.props.searchRequest?.loading && !this.props.searchRequest?.error);
1179
1180
 
1181
+ const Container =
1182
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
1183
+
1180
1184
  return this.props.token && this.props.objectActions?.length > 0 ? (
1181
1185
  <>
1182
1186
  {folderContentsAction ? (
@@ -25,6 +25,7 @@ import { readAsDataURL } from 'promise-file-reader';
25
25
  import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
26
26
  import { FormattedRelativeDate } from '@plone/volto/components';
27
27
  import { createContent } from '@plone/volto/actions';
28
+ import { validateFileUploadSize } from '@plone/volto/helpers';
28
29
 
29
30
  const Dropzone = loadable(() => import('react-dropzone'));
30
31
 
@@ -121,14 +122,18 @@ class ContentsUploadModal extends Component {
121
122
  * @returns {undefined}
122
123
  */
123
124
  onDrop = async (files) => {
125
+ const validFiles = [];
124
126
  for (let i = 0; i < files.length; i++) {
125
- await readAsDataURL(files[i]).then((data) => {
126
- const fields = data.match(/^data:(.*);(.*),(.*)$/);
127
- files[i].preview = fields[0];
128
- });
127
+ if (validateFileUploadSize(files[i], this.props.intl.formatMessage)) {
128
+ await readAsDataURL(files[i]).then((data) => {
129
+ const fields = data.match(/^data:(.*);(.*),(.*)$/);
130
+ files[i].preview = fields[0];
131
+ });
132
+ validFiles.push(files[i]);
133
+ }
129
134
  }
130
135
  this.setState({
131
- files: concat(this.state.files, files),
136
+ files: concat(this.state.files, validFiles),
132
137
  });
133
138
  };
134
139
 
@@ -31,7 +31,7 @@ import { Portal } from 'react-portal';
31
31
  import { connect } from 'react-redux';
32
32
  import {
33
33
  Button,
34
- Container,
34
+ Container as SemanticContainer,
35
35
  Form as UiForm,
36
36
  Message,
37
37
  Segment,
@@ -545,12 +545,14 @@ class Form extends Component {
545
545
  const { schema: originalSchema, onCancel, onSubmit } = this.props;
546
546
  const { formData } = this.state;
547
547
  const schema = this.removeBlocksLayoutFields(originalSchema);
548
+ const Container =
549
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
548
550
 
549
551
  return this.props.visual ? (
550
552
  // Removing this from SSR is important, since react-beautiful-dnd supports SSR,
551
553
  // but draftJS don't like it much and the hydration gets messed up
552
554
  this.state.isClient && (
553
- <div className="ui container">
555
+ <Container>
554
556
  <BlocksToolbar
555
557
  formData={this.state.formData}
556
558
  selectedBlock={this.state.selected}
@@ -639,7 +641,7 @@ class Form extends Component {
639
641
  </UiForm>
640
642
  </Portal>
641
643
  )}
642
- </div>
644
+ </Container>
643
645
  )
644
646
  ) : (
645
647
  <Container>
@@ -9,7 +9,13 @@ import { Helmet } from '@plone/volto/helpers';
9
9
  import { Link } from 'react-router-dom';
10
10
  import { connect } from 'react-redux';
11
11
  import { compose } from 'redux';
12
- import { Container, Dropdown, Icon, Segment, Table } from 'semantic-ui-react';
12
+ import {
13
+ Container as SemanticContainer,
14
+ Dropdown,
15
+ Icon,
16
+ Segment,
17
+ Table,
18
+ } from 'semantic-ui-react';
13
19
  import { concat, map, reverse, find } from 'lodash';
14
20
  import { Portal } from 'react-portal';
15
21
  import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
@@ -24,6 +30,7 @@ import {
24
30
  } from '@plone/volto/components';
25
31
  import { getHistory, revertHistory, listActions } from '@plone/volto/actions';
26
32
  import { getBaseUrl } from '@plone/volto/helpers';
33
+ import config from '@plone/volto/registry';
27
34
 
28
35
  import backSVG from '@plone/volto/icons/back.svg';
29
36
 
@@ -147,6 +154,9 @@ class History extends Component {
147
154
  });
148
155
  const entries = this.processHistoryEntries();
149
156
 
157
+ const Container =
158
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
159
+
150
160
  return !historyAction ? (
151
161
  <>
152
162
  {this.props.token ? (
@@ -43,8 +43,8 @@ const messages = defineMessages({
43
43
  defaultMessage: 'New password',
44
44
  },
45
45
  newPasswordDescription: {
46
- id: 'Enter your new password. Minimum 5 characters.',
47
- defaultMessage: 'Enter your new password. Minimum 5 characters.',
46
+ id: 'Enter your new password. Minimum 8 characters.',
47
+ defaultMessage: 'Enter your new password. Minimum 8 characters.',
48
48
  },
49
49
  newPasswordRepeatTitle: {
50
50
  id: 'Confirm password',
@@ -14,7 +14,7 @@ import { Portal } from 'react-portal';
14
14
  import {
15
15
  Button,
16
16
  Checkbox,
17
- Container,
17
+ Container as SemanticContainer,
18
18
  Form,
19
19
  Icon as IconOld,
20
20
  Input,
@@ -28,6 +28,7 @@ import { updateSharing, getSharing } from '@plone/volto/actions';
28
28
  import { getBaseUrl } from '@plone/volto/helpers';
29
29
  import { Icon, Toolbar, Toast } from '@plone/volto/components';
30
30
  import { toast } from 'react-toastify';
31
+ import config from '@plone/volto/registry';
31
32
 
32
33
  import aheadSVG from '@plone/volto/icons/ahead.svg';
33
34
  import clearSVG from '@plone/volto/icons/clear.svg';
@@ -288,6 +289,9 @@ class SharingComponent extends Component {
288
289
  * @returns {string} Markup for the component.
289
290
  */
290
291
  render() {
292
+ const Container =
293
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
294
+
291
295
  return (
292
296
  <Container id="page-sharing">
293
297
  <Helmet title={this.props.intl.formatMessage(messages.sharing)} />
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Icon } from '@plone/volto/components';
3
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
4
4
 
5
5
  import successSVG from '@plone/volto/icons/ready.svg';
6
6
  import infoSVG from '@plone/volto/icons/info.svg';
@@ -51,7 +51,12 @@ const ColorPickerWidget = (props) => {
51
51
  onClick={(e) => {
52
52
  e.preventDefault();
53
53
  e.stopPropagation();
54
- onChange(id, color.name);
54
+ onChange(
55
+ id,
56
+ value === color.name
57
+ ? props.missing_value
58
+ : color.name,
59
+ );
55
60
  }}
56
61
  active={value === color.name}
57
62
  circular
@@ -11,7 +11,7 @@ import { injectIntl } from 'react-intl';
11
11
  import deleteSVG from '@plone/volto/icons/delete.svg';
12
12
  import { Icon, FormFieldWrapper } from '@plone/volto/components';
13
13
  import loadable from '@loadable/component';
14
- import { flattenToAppURL } from '@plone/volto/helpers';
14
+ import { flattenToAppURL, validateFileUploadSize } from '@plone/volto/helpers';
15
15
  import { defineMessages, useIntl } from 'react-intl';
16
16
 
17
17
  const imageMimetypes = [
@@ -95,6 +95,7 @@ const FileWidget = (props) => {
95
95
  */
96
96
  const onDrop = (files) => {
97
97
  const file = files[0];
98
+ if (!validateFileUploadSize(file, intl.formatMessage)) return;
98
99
  readAsDataURL(file).then((data) => {
99
100
  const fields = data.match(/^data:(.*);(.*),(.*)$/);
100
101
  onChange(id, {
@@ -1,53 +1,67 @@
1
- /**
2
- * Home container.
3
- * @module components/theme/NotFound/NotFound
4
- */
5
-
6
- import React from 'react';
1
+ import { useEffect } from 'react';
2
+ import { BodyClass, toBackendLang } from '@plone/volto/helpers';
7
3
  import { FormattedMessage } from 'react-intl';
8
4
  import { Link } from 'react-router-dom';
9
5
  import { Container } from 'semantic-ui-react';
10
6
  import { withServerErrorCode } from '@plone/volto/helpers/Utils/Utils';
7
+ import { useDispatch, useSelector } from 'react-redux';
8
+ import { getNavigation } from '@plone/volto/actions';
9
+ import config from '@plone/volto/registry';
11
10
 
12
11
  /**
13
12
  * Not found function.
14
13
  * @function NotFound
15
14
  * @returns {string} Markup of the not found page.
16
15
  */
17
- const NotFound = () => (
18
- <Container className="view-wrapper">
19
- <h1>
20
- <FormattedMessage
21
- id="This page does not seem to exist…"
22
- defaultMessage="This page does not seem to exist…"
23
- />
24
- </h1>
25
- <p className="description">
26
- <FormattedMessage
27
- id="We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the links below to help you find what you are looking for."
28
- defaultMessage="We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the links below to help you find what you are looking for."
29
- />
30
- </p>
31
- <p>
32
- <FormattedMessage
33
- id="If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}."
34
- defaultMessage="If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}."
35
- values={{
36
- site_admin: (
37
- <Link to="/contact-form">
38
- <FormattedMessage
39
- id="Site Administration"
40
- defaultMessage="Site Administration"
41
- />
42
- </Link>
43
- ),
44
- }}
45
- />
46
- </p>
47
- <p>
48
- <FormattedMessage id="Thank you." defaultMessage="Thank you." />
49
- </p>
50
- </Container>
51
- );
16
+ const NotFound = () => {
17
+ const dispatch = useDispatch();
18
+ const lang = useSelector((state) => state.intl.locale);
19
+
20
+ useEffect(() => {
21
+ dispatch(
22
+ getNavigation(
23
+ config.settings.isMultilingual ? `/${toBackendLang(lang)}` : '/',
24
+ config.settings.navDepth,
25
+ ),
26
+ );
27
+ }, [dispatch, lang]);
28
+
29
+ return (
30
+ <Container className="view-wrapper">
31
+ <BodyClass className="page-not-found" />
32
+ <h1>
33
+ <FormattedMessage
34
+ id="This page does not seem to exist…"
35
+ defaultMessage="This page does not seem to exist…"
36
+ />
37
+ </h1>
38
+ <p className="description">
39
+ <FormattedMessage
40
+ id="We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the links below to help you find what you are looking for."
41
+ defaultMessage="We apologize for the inconvenience, but the page you were trying to access is not at this address. You can use the links below to help you find what you are looking for."
42
+ />
43
+ </p>
44
+ <p>
45
+ <FormattedMessage
46
+ id="If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}."
47
+ defaultMessage="If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}."
48
+ values={{
49
+ site_admin: (
50
+ <Link to="/contact-form">
51
+ <FormattedMessage
52
+ id="Site Administration"
53
+ defaultMessage="Site Administration"
54
+ />
55
+ </Link>
56
+ ),
57
+ }}
58
+ />
59
+ </p>
60
+ <p>
61
+ <FormattedMessage id="Thank you." defaultMessage="Thank you." />
62
+ </p>
63
+ </Container>
64
+ );
65
+ };
52
66
 
53
67
  export default withServerErrorCode(404)(NotFound);
@@ -50,8 +50,8 @@ const messages = defineMessages({
50
50
  defaultMessage: 'New password',
51
51
  },
52
52
  passwordDescription: {
53
- id: 'Enter your new password. Minimum 5 characters.',
54
- defaultMessage: 'Enter your new password. Minimum 5 characters.',
53
+ id: 'Enter your new password. Minimum 8 characters.',
54
+ defaultMessage: 'Enter your new password. Minimum 8 characters.',
55
55
  },
56
56
  passwordRepeatTitle: {
57
57
  id: 'Confirm password',
@@ -227,6 +227,9 @@ class PasswordReset extends Component {
227
227
  );
228
228
  }
229
229
  if (this.props.token) {
230
+ const errmsg = this.props.error
231
+ ? this.props.error.response.body.error
232
+ : null;
230
233
  return (
231
234
  <div id="page-password-reset">
232
235
  <Helmet
@@ -238,7 +241,7 @@ class PasswordReset extends Component {
238
241
  description={this.props.intl.formatMessage(messages.description)}
239
242
  onSubmit={this.onSubmit}
240
243
  onCancel={this.onCancel}
241
- error={this.state.error || this.props.error}
244
+ error={this.state.error || errmsg}
242
245
  schema={{
243
246
  fieldsets: [
244
247
  {
@@ -5,13 +5,14 @@
5
5
 
6
6
  import React from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { Container, Image } from 'semantic-ui-react';
8
+ import { Container as SemanticContainer, Image } from 'semantic-ui-react';
9
9
  import {
10
10
  hasBlocksData,
11
11
  flattenToAppURL,
12
12
  flattenHTMLToAppURL,
13
13
  } from '@plone/volto/helpers';
14
14
  import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
15
+ import config from '@plone/volto/registry';
15
16
 
16
17
  /**
17
18
  * NewsItemView view component class.
@@ -19,11 +20,14 @@ import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
19
20
  * @params {object} content Content object.
20
21
  * @returns {string} Markup of the component.
21
22
  */
22
- const NewsItemView = ({ content }) =>
23
- hasBlocksData(content) ? (
24
- <div id="page-document" className="ui container view-wrapper newsitem-view">
23
+ const NewsItemView = ({ content }) => {
24
+ const Container =
25
+ config.getComponent({ name: 'Container' }).component || SemanticContainer;
26
+
27
+ return hasBlocksData(content) ? (
28
+ <Container id="page-document" className="view-wrapper newsitem-view">
25
29
  <RenderBlocks content={content} />
26
- </div>
30
+ </Container>
27
31
  ) : (
28
32
  <Container className="view-wrapper">
29
33
  {content.title && (
@@ -57,6 +61,7 @@ const NewsItemView = ({ content }) =>
57
61
  )}
58
62
  </Container>
59
63
  );
64
+ };
60
65
 
61
66
  /**
62
67
  * Property types.
@@ -44,7 +44,13 @@ const RenderBlocks = (props) => {
44
44
  });
45
45
 
46
46
  return Block ? (
47
- <StyleWrapper key={block} {...props} id={block} data={blockData}>
47
+ <StyleWrapper
48
+ key={block}
49
+ {...props}
50
+ id={block}
51
+ block={block}
52
+ data={blockData}
53
+ >
48
54
  <Block
49
55
  id={block}
50
56
  metadata={metadata}
@@ -150,6 +150,7 @@ let config = {
150
150
  },
151
151
  appExtras: [],
152
152
  maxResponseSize: 2000000000, // This is superagent default (200 mb)
153
+ maxFileUploadSize: null,
153
154
  serverConfig,
154
155
  storeExtenders: [],
155
156
  showTags: true,
@@ -89,7 +89,7 @@ class Api {
89
89
  checkUrl &&
90
90
  request.url &&
91
91
  request.xhr &&
92
- stripQuerystring(request.url) !==
92
+ encodeURI(stripQuerystring(request.url)) !==
93
93
  stripQuerystring(request.xhr.responseURL)
94
94
  ) {
95
95
  if (request.xhr.responseURL?.length === 0) {
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { defineMessages } from 'react-intl';
3
3
  import { useIntl } from 'react-intl';
4
+ import { find, isEmpty } from 'lodash';
4
5
  import config from '@plone/volto/registry';
5
6
  import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils';
6
7
 
@@ -291,20 +292,23 @@ export const EMPTY_STYLES_SCHEMA = {
291
292
  };
292
293
 
293
294
  /**
294
- * Creates the `styles` field and fieldset in a schema
295
+ * Adds the `styles` field and 'styling' fieldset in a given schema
295
296
  */
296
297
  export const addStyling = ({ schema, formData, intl }) => {
297
- schema.fieldsets.push({
298
- id: 'styling',
299
- title: intl.formatMessage(messages.styling),
300
- fields: ['styles'],
301
- });
298
+ if (isEmpty(find(schema.fieldsets, { id: 'styling' }))) {
299
+ schema.fieldsets.push({
300
+ id: 'styling',
301
+ title: intl.formatMessage(messages.styling),
302
+ fields: ['styles'],
303
+ });
304
+
305
+ schema.properties.styles = {
306
+ widget: 'object',
307
+ title: intl.formatMessage(messages.styling),
308
+ schema: cloneDeepSchema(EMPTY_STYLES_SCHEMA),
309
+ };
310
+ }
302
311
 
303
- schema.properties.styles = {
304
- widget: 'object',
305
- title: intl.formatMessage(messages.styling),
306
- schema: EMPTY_STYLES_SCHEMA,
307
- };
308
312
  return schema;
309
313
  };
310
314
 
@@ -2,6 +2,7 @@ import {
2
2
  addExtensionFieldToSchema,
3
3
  applySchemaEnhancer,
4
4
  composeSchema,
5
+ addStyling,
5
6
  } from './withBlockSchemaEnhancer';
6
7
 
7
8
  import config from '@plone/volto/registry';
@@ -246,3 +247,147 @@ describe('composeSchema', () => {
246
247
  expect(res).toStrictEqual([6, 9]);
247
248
  });
248
249
  });
250
+
251
+ describe('addStyling', () => {
252
+ it('returns an enhanced schema with the styling wrapper object on it', () => {
253
+ const intl = { formatMessage: () => 'Styling' };
254
+
255
+ const schema = {
256
+ fieldsets: [
257
+ {
258
+ id: 'default',
259
+ title: 'Default',
260
+ fields: [],
261
+ },
262
+ ],
263
+ properties: {},
264
+ required: [],
265
+ };
266
+
267
+ const result = addStyling({ schema, intl });
268
+
269
+ expect(result).toStrictEqual({
270
+ fieldsets: [
271
+ { id: 'default', title: 'Default', fields: [] },
272
+ { id: 'styling', title: 'Styling', fields: ['styles'] },
273
+ ],
274
+ properties: {
275
+ styles: {
276
+ widget: 'object',
277
+ title: 'Styling',
278
+ schema: {
279
+ fieldsets: [
280
+ {
281
+ fields: [],
282
+ id: 'default',
283
+ title: 'Default',
284
+ },
285
+ ],
286
+ properties: {},
287
+ required: [],
288
+ },
289
+ },
290
+ },
291
+ required: [],
292
+ });
293
+ });
294
+
295
+ it('multiple schema enhancers', () => {
296
+ const intl = { formatMessage: () => 'Styling' };
297
+
298
+ const schema1 = {
299
+ fieldsets: [
300
+ {
301
+ id: 'default',
302
+ title: 'Default',
303
+ fields: [],
304
+ },
305
+ ],
306
+ properties: {},
307
+ required: [],
308
+ };
309
+
310
+ const schema2 = {
311
+ fieldsets: [
312
+ {
313
+ id: 'default',
314
+ title: 'Default',
315
+ fields: [],
316
+ },
317
+ ],
318
+ properties: {},
319
+ required: [],
320
+ };
321
+
322
+ const result = addStyling({ schema: schema1, intl });
323
+
324
+ // We add some fields to the styling schema
325
+ result.properties.styles.schema.properties.align = {
326
+ widget: 'align',
327
+ title: 'align',
328
+ actions: ['left', 'right', 'center'],
329
+ default: 'left',
330
+ };
331
+
332
+ result.properties.styles.schema.fieldsets[0].fields = ['align'];
333
+
334
+ const result2 = addStyling({ schema: schema2, intl });
335
+
336
+ expect(result).toStrictEqual({
337
+ fieldsets: [
338
+ { id: 'default', title: 'Default', fields: [] },
339
+ { id: 'styling', title: 'Styling', fields: ['styles'] },
340
+ ],
341
+ properties: {
342
+ styles: {
343
+ widget: 'object',
344
+ title: 'Styling',
345
+ schema: {
346
+ fieldsets: [
347
+ {
348
+ fields: ['align'],
349
+ id: 'default',
350
+ title: 'Default',
351
+ },
352
+ ],
353
+ properties: {
354
+ align: {
355
+ widget: 'align',
356
+ title: 'align',
357
+ actions: ['left', 'right', 'center'],
358
+ default: 'left',
359
+ },
360
+ },
361
+ required: [],
362
+ },
363
+ },
364
+ },
365
+ required: [],
366
+ });
367
+
368
+ expect(result2).toStrictEqual({
369
+ fieldsets: [
370
+ { id: 'default', title: 'Default', fields: [] },
371
+ { id: 'styling', title: 'Styling', fields: ['styles'] },
372
+ ],
373
+ properties: {
374
+ styles: {
375
+ widget: 'object',
376
+ title: 'Styling',
377
+ schema: {
378
+ fieldsets: [
379
+ {
380
+ fields: [],
381
+ id: 'default',
382
+ title: 'Default',
383
+ },
384
+ ],
385
+ properties: {},
386
+ required: [],
387
+ },
388
+ },
389
+ },
390
+ required: [],
391
+ });
392
+ });
393
+ });