@plone/volto 18.9.1 → 18.10.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 (85) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +0 -2
  3. package/locales/ca/LC_MESSAGES/volto.po +5 -0
  4. package/locales/ca.json +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +5 -0
  6. package/locales/de.json +1 -1
  7. package/locales/en/LC_MESSAGES/volto.po +5 -0
  8. package/locales/en.json +1 -1
  9. package/locales/es/LC_MESSAGES/volto.po +5 -0
  10. package/locales/es.json +1 -1
  11. package/locales/eu/LC_MESSAGES/volto.po +5 -0
  12. package/locales/eu.json +1 -1
  13. package/locales/fi/LC_MESSAGES/volto.po +5 -0
  14. package/locales/fi.json +1 -1
  15. package/locales/fr/LC_MESSAGES/volto.po +5 -0
  16. package/locales/fr.json +1 -1
  17. package/locales/hi/LC_MESSAGES/volto.po +5 -0
  18. package/locales/hi.json +1 -1
  19. package/locales/it/LC_MESSAGES/volto.po +5 -0
  20. package/locales/it.json +1 -1
  21. package/locales/ja/LC_MESSAGES/volto.po +5 -0
  22. package/locales/ja.json +1 -1
  23. package/locales/nl/LC_MESSAGES/volto.po +5 -0
  24. package/locales/nl.json +1 -1
  25. package/locales/pt/LC_MESSAGES/volto.po +5 -0
  26. package/locales/pt.json +1 -1
  27. package/locales/pt_BR/LC_MESSAGES/volto.po +5 -0
  28. package/locales/pt_BR.json +1 -1
  29. package/locales/ro/LC_MESSAGES/volto.po +5 -0
  30. package/locales/ro.json +1 -1
  31. package/locales/volto.pot +6 -1
  32. package/locales/zh_CN/LC_MESSAGES/volto.po +5 -0
  33. package/locales/zh_CN.json +1 -1
  34. package/package.json +2 -2
  35. package/src/components/manage/Add/Add.jsx +5 -1
  36. package/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx +2 -0
  37. package/src/components/manage/BlockChooser/BlockChooser.jsx +1 -0
  38. package/src/components/manage/BlockChooser/BlockChooserButton.jsx +1 -0
  39. package/src/components/manage/BlockChooser/BlockChooserSearch.jsx +1 -0
  40. package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +1 -0
  41. package/src/components/manage/Blocks/Block/StyleWrapper.jsx +8 -2
  42. package/src/components/manage/Blocks/Container/EditBlockWrapper.jsx +11 -1
  43. package/src/components/manage/Blocks/Container/NewBlockAddButton.jsx +1 -0
  44. package/src/components/manage/Blocks/Container/SimpleContainerToolbar.jsx +2 -0
  45. package/src/components/manage/Blocks/HTML/Edit.jsx +9 -1
  46. package/src/components/manage/Blocks/Image/ImageSidebar.jsx +1 -0
  47. package/src/components/manage/Blocks/Listing/ImageGallery.jsx +2 -0
  48. package/src/components/manage/Blocks/Maps/Edit.jsx +2 -0
  49. package/src/components/manage/Blocks/Search/components/Facets.jsx +1 -0
  50. package/src/components/manage/Blocks/Search/components/FilterList.jsx +1 -0
  51. package/src/components/manage/Blocks/Search/components/SearchInput.jsx +1 -0
  52. package/src/components/manage/Blocks/Search/components/SortOn.jsx +2 -0
  53. package/src/components/manage/Blocks/Teaser/Data.jsx +2 -0
  54. package/src/components/manage/Blocks/Video/Edit.jsx +2 -0
  55. package/src/components/manage/Delete/Delete.jsx +1 -0
  56. package/src/components/manage/Diff/Diff.jsx +1 -0
  57. package/src/components/manage/Edit/Edit.jsx +1 -0
  58. package/src/components/manage/Form/Form.jsx +1 -0
  59. package/src/components/manage/Form/ModalForm.jsx +1 -0
  60. package/src/components/manage/Form/UndoToolbar.jsx +2 -0
  61. package/src/components/manage/Sidebar/AlignBlock.jsx +1 -0
  62. package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +1 -0
  63. package/src/components/manage/Sidebar/Sidebar.jsx +2 -0
  64. package/src/components/manage/TemplateChooser/TemplateChooser.jsx +1 -0
  65. package/src/components/manage/Widgets/CheckboxWidget.jsx +1 -0
  66. package/src/components/manage/Widgets/SelectWidget.jsx +1 -0
  67. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +2 -1
  68. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +67 -10
  69. package/src/components/theme/Image/Image.jsx +1 -2
  70. package/src/components/theme/Image/Image.test.jsx +8 -0
  71. package/src/components/theme/View/RenderBlocks.jsx +2 -1
  72. package/src/config/index.js +11 -1
  73. package/src/config/validation.ts +8 -0
  74. package/src/helpers/Blocks/Blocks.js +6 -2
  75. package/src/helpers/Blocks/Blocks.test.js +1 -1
  76. package/src/helpers/FormValidation/FormValidation.jsx +1 -0
  77. package/src/helpers/FormValidation/FormValidation.test.js +72 -4
  78. package/src/helpers/FormValidation/validators.ts +14 -0
  79. package/src/helpers/Html/Html.jsx +3 -0
  80. package/src/helpers/MessageLabels/MessageLabels.js +5 -0
  81. package/src/start-client.jsx +4 -0
  82. package/src/storybook.jsx +2 -0
  83. package/types/helpers/Blocks/Blocks.d.ts +1 -1
  84. package/types/helpers/FormValidation/validators.d.ts +1 -0
  85. package/types/helpers/MessageLabels/MessageLabels.d.ts +6 -0
@@ -101,6 +101,7 @@ const Delete = () => {
101
101
  onClick={onSubmit}
102
102
  />
103
103
  <Button
104
+ type="button"
104
105
  basic
105
106
  circular
106
107
  secondary
@@ -249,6 +249,7 @@ class Diff extends Component {
249
249
  ],
250
250
  (view) => (
251
251
  <Button
252
+ type="button"
252
253
  key={view.id}
253
254
  value={view.id}
254
255
  active={this.props.view === view.id}
@@ -447,6 +447,7 @@ class Edit extends Component {
447
447
  />
448
448
  </Button>
449
449
  <Button
450
+ type="button"
450
451
  className="cancel"
451
452
  aria-label={this.props.intl.formatMessage(messages.cancel)}
452
453
  onClick={() => this.onCancel()}
@@ -1020,6 +1020,7 @@ class Form extends Component {
1020
1020
  )}
1021
1021
  {onCancel && (
1022
1022
  <Button
1023
+ type="button"
1023
1024
  basic
1024
1025
  secondary
1025
1026
  aria-label={this.props.intl.formatMessage(
@@ -308,6 +308,7 @@ class ModalForm extends Component {
308
308
  </Button>
309
309
  {onCancel && (
310
310
  <Button
311
+ type="button"
311
312
  basic
312
313
  circular
313
314
  secondary
@@ -39,6 +39,7 @@ const UndoToolbar = ({ state, onUndoRedo, maxUndoLevels, enableHotKeys }) => {
39
39
  dependencies={[canUndo, canRedo]}
40
40
  >
41
41
  <Button
42
+ type="button"
42
43
  className="undo"
43
44
  onClick={() => doUndo()}
44
45
  aria-label={intl.formatMessage(messages.undo)}
@@ -58,6 +59,7 @@ const UndoToolbar = ({ state, onUndoRedo, maxUndoLevels, enableHotKeys }) => {
58
59
  dependencies={[canUndo, canRedo]}
59
60
  >
60
61
  <Button
62
+ type="button"
61
63
  className="redo"
62
64
  onClick={() => doRedo()}
63
65
  aria-label={intl.formatMessage(messages.redo)}
@@ -59,6 +59,7 @@ const AlignBlock = ({
59
59
  {actions.map((action) => (
60
60
  <Button.Group key={action}>
61
61
  <Button
62
+ type="button"
62
63
  icon
63
64
  basic
64
65
  aria-label={intl.formatMessage(messages[action])}
@@ -164,6 +164,7 @@ const ObjectBrowserNav = ({
164
164
  >
165
165
  <Button.Group>
166
166
  <Button
167
+ type="button"
167
168
  basic
168
169
  icon
169
170
  aria-label={`${intl.formatMessage(messages.browse)} ${
@@ -113,6 +113,7 @@ const Sidebar = (props) => {
113
113
  style={size > 0 ? { width: size } : null}
114
114
  >
115
115
  <Button
116
+ type="button"
116
117
  aria-label={
117
118
  expanded
118
119
  ? intl.formatMessage(messages.shrinkSidebar)
@@ -126,6 +127,7 @@ const Sidebar = (props) => {
126
127
  onClick={onToggleExpanded}
127
128
  />
128
129
  <Button
130
+ type="button"
129
131
  className="full-size-sidenav-btn"
130
132
  onClick={onToggleFullSize}
131
133
  aria-label="full-screen-sidenav"
@@ -11,6 +11,7 @@ const TemplateChooser = ({ templates, onSelectTemplate }) => {
11
11
  {templates(intl).map((template, index) => (
12
12
  <Grid.Column key={template.id}>
13
13
  <Button
14
+ type="button"
14
15
  className="template-chooser-item"
15
16
  onClick={() => onSelectTemplate(index)}
16
17
  >
@@ -31,6 +31,7 @@ const CheckboxWidget = (props) => {
31
31
  <FormFieldWrapper {...props} columns={1}>
32
32
  <div className="wrapper">
33
33
  <Checkbox
34
+ id={`field-${id}`}
34
35
  name={`field-${id}`}
35
36
  checked={value || false}
36
37
  disabled={isDisabled}
@@ -251,6 +251,7 @@ class SelectWidget extends Component {
251
251
  this.props.placeholder ??
252
252
  this.props.intl.formatMessage(messages.select)
253
253
  }
254
+ onBlur={() => this.props.onBlur(id, value)}
254
255
  onChange={(selectedOption) => {
255
256
  if (isMulti) {
256
257
  return onChange(
@@ -1,5 +1,6 @@
1
1
  import config from '@plone/volto/registry';
2
2
  import Helmet from '@plone/volto/helpers/Helmet/Helmet';
3
+ import { flattenToAppURL, toPublicURL } from '@plone/volto/helpers/Url/Url';
3
4
 
4
5
  const AlternateHrefLangs = (props) => {
5
6
  const { content } = props;
@@ -16,7 +17,7 @@ const AlternateHrefLangs = (props) => {
16
17
  key={key}
17
18
  rel="alternate"
18
19
  hrefLang={item.language}
19
- href={item['@id']}
20
+ href={toPublicURL(flattenToAppURL(item['@id']))}
20
21
  />
21
22
  );
22
23
  })}
@@ -35,16 +35,18 @@ describe('AlternateHrefLangs', () => {
35
35
  const helmetLinks = Helmet.peek().linkTags;
36
36
  expect(helmetLinks.length).toBe(0);
37
37
  });
38
+
38
39
  it('multilingual site, with some translations', () => {
40
+ config.settings.publicURL = 'https://plone.org';
39
41
  config.settings.isMultilingual = true;
40
42
  config.settings.supportedLanguages = ['en', 'es', 'eu'];
41
43
 
42
44
  const content = {
43
- '@id': '/en',
45
+ '@id': 'http://localhost:8080/Plone/en',
44
46
  language: { token: 'en', title: 'English' },
45
47
  '@components': {
46
48
  translations: {
47
- items: [{ '@id': '/es', language: 'es' }],
49
+ items: [{ '@id': 'http://localhost:8080/Plone/es', language: 'es' }],
48
50
  },
49
51
  },
50
52
  };
@@ -71,17 +73,72 @@ describe('AlternateHrefLangs', () => {
71
73
 
72
74
  expect(helmetLinks).toContainEqual({
73
75
  rel: 'alternate',
74
- href: '/es',
76
+ href: 'https://plone.org/es',
75
77
  hrefLang: 'es',
76
78
  });
77
79
  expect(helmetLinks).toContainEqual({
78
80
  rel: 'alternate',
79
- href: '/en',
81
+ href: 'https://plone.org/en',
80
82
  hrefLang: 'en',
81
83
  });
82
84
  });
83
85
 
84
86
  it('multilingual site, with all available translations', () => {
87
+ config.settings.publicURL = 'https://plone.org';
88
+ config.settings.isMultilingual = true;
89
+ config.settings.supportedLanguages = ['en', 'es', 'eu'];
90
+ const store = mockStore({
91
+ intl: {
92
+ locale: 'en',
93
+ messages: {},
94
+ },
95
+ });
96
+
97
+ const content = {
98
+ '@id': 'http://localhost:8080/Plone/en',
99
+ language: { token: 'en', title: 'English' },
100
+ '@components': {
101
+ translations: {
102
+ items: [
103
+ { '@id': 'http://localhost:8080/Plone/eu', language: 'eu' },
104
+ { '@id': 'http://localhost:8080/Plone/es', language: 'es' },
105
+ ],
106
+ },
107
+ },
108
+ };
109
+
110
+ // We need to force the component rendering
111
+ // to fill the Helmet
112
+ renderer.create(
113
+ <Provider store={store}>
114
+ <AlternateHrefLangs content={content} />
115
+ </Provider>,
116
+ );
117
+
118
+ const helmetLinks = Helmet.peek().linkTags;
119
+
120
+ // We expect having 3 links
121
+ expect(helmetLinks.length).toBe(3);
122
+
123
+ expect(helmetLinks).toContainEqual({
124
+ rel: 'alternate',
125
+ href: 'https://plone.org/eu',
126
+ hrefLang: 'eu',
127
+ });
128
+ expect(helmetLinks).toContainEqual({
129
+ rel: 'alternate',
130
+ href: 'https://plone.org/es',
131
+ hrefLang: 'es',
132
+ });
133
+ expect(helmetLinks).toContainEqual({
134
+ rel: 'alternate',
135
+ href: 'https://plone.org/en',
136
+ hrefLang: 'en',
137
+ });
138
+ });
139
+
140
+ it('multilingual site, with all available translations - with server URL', () => {
141
+ config.settings.publicURL = 'https://plone.org';
85
142
  config.settings.isMultilingual = true;
86
143
  config.settings.supportedLanguages = ['en', 'es', 'eu'];
87
144
  const store = mockStore({
@@ -92,13 +149,13 @@ describe('AlternateHrefLangs', () => {
92
149
  });
93
150
 
94
151
  const content = {
95
- '@id': '/en',
152
+ '@id': 'http://localhost:8080/Plone/en',
96
153
  language: { token: 'en', title: 'English' },
97
154
  '@components': {
98
155
  translations: {
99
156
  items: [
100
- { '@id': '/eu', language: 'eu' },
101
- { '@id': '/es', language: 'es' },
157
+ { '@id': 'http://localhost:8080/Plone/eu', language: 'eu' },
158
+ { '@id': 'http://localhost:8080/Plone/es', language: 'es' },
102
159
  ],
103
160
  },
104
161
  },
@@ -119,17 +176,17 @@ describe('AlternateHrefLangs', () => {
119
176
 
120
177
  expect(helmetLinks).toContainEqual({
121
178
  rel: 'alternate',
122
- href: '/eu',
179
+ href: 'https://plone.org/eu',
123
180
  hrefLang: 'eu',
124
181
  });
125
182
  expect(helmetLinks).toContainEqual({
126
183
  rel: 'alternate',
127
- href: '/es',
184
+ href: 'https://plone.org/es',
128
185
  hrefLang: 'es',
129
186
  });
130
187
  expect(helmetLinks).toContainEqual({
131
188
  rel: 'alternate',
132
- href: '/en',
189
+ href: 'https://plone.org/en',
133
190
  hrefLang: 'en',
134
191
  });
135
192
  });
@@ -27,10 +27,10 @@ export default function Image({
27
27
  // TypeScript hints for editor autocomplete :)
28
28
  /** @type {React.ImgHTMLAttributes<HTMLImageElement>} */
29
29
  const attrs = {};
30
+ attrs.className = cx(className, { responsive }) || undefined;
30
31
 
31
32
  if (!item && src) {
32
33
  attrs.src = src;
33
- attrs.className = cx(className, { responsive });
34
34
  } else {
35
35
  const isFromRealObject = !item.image_scales;
36
36
  const imageFieldWithDefault = imageField || item.image_field || 'image';
@@ -51,7 +51,6 @@ export default function Image({
51
51
  attrs.src = `${flattenToAppURL(basePath)}/${image.download}`;
52
52
  attrs.width = image.width;
53
53
  attrs.height = image.height;
54
- attrs.className = cx(className, { responsive });
55
54
 
56
55
  if (!isSvg && image.scales && Object.keys(image.scales).length > 0) {
57
56
  const sortedScales = Object.values({
@@ -155,3 +155,11 @@ test('renders an image component from a string src', () => {
155
155
  const json = component.toJSON();
156
156
  expect(json).toMatchSnapshot();
157
157
  });
158
+
159
+ test('should not render empty class attribute in img tag', () => {
160
+ const component = renderer.create(
161
+ <Image src="/image.png" alt="no class attribute" />,
162
+ );
163
+ const json = component.toJSON();
164
+ expect(json).toMatchSnapshot();
165
+ });
@@ -26,7 +26,7 @@ const messages = defineMessages({
26
26
  });
27
27
 
28
28
  const RenderBlocks = (props) => {
29
- const { content, location, metadata, blockWrapperTag } = props;
29
+ const { blockWrapperTag, content, location, isContainer, metadata } = props;
30
30
  const intl = useIntl();
31
31
  const blocksFieldname = getBlocksFieldname(content);
32
32
  const blocksLayoutFieldname = getBlocksLayoutFieldname(content);
@@ -72,6 +72,7 @@ const RenderBlocks = (props) => {
72
72
  id={block}
73
73
  block={block}
74
74
  data={blockData}
75
+ isContainer={isContainer}
75
76
  >
76
77
  <Block
77
78
  id={block}
@@ -113,7 +113,7 @@ let config = {
113
113
  defaultPageSize: 25,
114
114
  isMultilingual: false,
115
115
  supportedLanguages: ['en'],
116
- defaultLanguage: 'en',
116
+ defaultLanguage: process.env.SITE_DEFAULT_LANGUAGE || 'en',
117
117
  navDepth: 1,
118
118
  expressMiddleware: serverConfig.expressMiddleware, // BBB
119
119
  defaultBlockType: 'slate',
@@ -232,6 +232,16 @@ Object.entries(slots).forEach(([slotName, components]) => {
232
232
  });
233
233
  });
234
234
 
235
+ // Make sure that process.env.SITE_DEFAULT_LANGUAGE is set in availableLanguages
236
+ if (
237
+ process.env.SITE_DEFAULT_LANGUAGE &&
238
+ !config.settings.supportedLanguages.includes(
239
+ process.env.SITE_DEFAULT_LANGUAGE,
240
+ )
241
+ ) {
242
+ config.settings.supportedLanguages.push(process.env.SITE_DEFAULT_LANGUAGE);
243
+ }
244
+
235
245
  registerValidators(ConfigRegistry);
236
246
  installDefaultComponents(ConfigRegistry);
237
247
  installDefaultWidgets(ConfigRegistry);
@@ -15,6 +15,7 @@ import {
15
15
  startEventDateRangeValidator,
16
16
  endEventDateRangeValidator,
17
17
  patternValidator,
18
+ defaultLanguageControlPanelValidator,
18
19
  } from '@plone/volto/helpers/FormValidation/validators';
19
20
 
20
21
  const registerValidators = (config: ConfigType) => {
@@ -150,6 +151,13 @@ const registerValidators = (config: ConfigType) => {
150
151
  dependencies: { behaviorName: 'plone.eventbasic', fieldName: 'end' },
151
152
  method: endEventDateRangeValidator,
152
153
  });
154
+
155
+ config.registerUtility({
156
+ name: 'default_language',
157
+ type: 'validator',
158
+ dependencies: { format: 'default_language' },
159
+ method: defaultLanguageControlPanelValidator,
160
+ });
153
161
  };
154
162
 
155
163
  export { registerValidators };
@@ -661,7 +661,11 @@ export const styleDataToStyleObject = (key, value, prefix = '') => {
661
661
  * @param {string} prefix The prefix (could be dragged from a recursive call, initially empty)
662
662
  * @return {Object} The style object ready to be passed as prop
663
663
  */
664
- export const buildStyleObjectFromData = (data = {}, prefix = '') => {
664
+ export const buildStyleObjectFromData = (
665
+ data = {},
666
+ prefix = '',
667
+ container = {},
668
+ ) => {
665
669
  // style wrapper object has the form:
666
670
  // const styles = {
667
671
  // color: 'red',
@@ -720,7 +724,7 @@ export const buildStyleObjectFromData = (data = {}, prefix = '') => {
720
724
  enhancers.forEach(({ method }) => {
721
725
  stylesFromObjectStyleEnhancers = {
722
726
  ...stylesFromObjectStyleEnhancers,
723
- ...method(data),
727
+ ...method({ data, container }),
724
728
  };
725
729
  });
726
730
 
@@ -1151,7 +1151,7 @@ describe('Blocks', () => {
1151
1151
 
1152
1152
  describe('buildStyleObjectFromData', () => {
1153
1153
  beforeEach(() => {
1154
- function blockThemesEnhancer(data) {
1154
+ function blockThemesEnhancer({ data }) {
1155
1155
  const blockConfig = config.blocks.blocksConfig[data['@type']];
1156
1156
  const blockStyleDefinitions =
1157
1157
  // We look up for the blockThemes in the block's data, then in the global config
@@ -133,6 +133,7 @@ const validateFieldsPerFieldset = (
133
133
 
134
134
  Object.entries(schema.properties).forEach(([fieldId, field]) => {
135
135
  let fieldData = formData[fieldId];
136
+ field = { ...field, ...field.widgetOptions?.frontendOptions?.widgetProps };
136
137
 
137
138
  // Validation per specific validator set (format property)
138
139
  const hasSpecificValidator =
@@ -257,7 +257,7 @@ describe('FormValidation', () => {
257
257
  });
258
258
  });
259
259
 
260
- it('string - min lenght', () => {
260
+ it('string - min length', () => {
261
261
  let newSchema = {
262
262
  properties: {
263
263
  ...schema.properties,
@@ -283,7 +283,7 @@ describe('FormValidation', () => {
283
283
  });
284
284
  });
285
285
 
286
- it('string - max lenght', () => {
286
+ it('string - max length', () => {
287
287
  let newSchema = {
288
288
  properties: {
289
289
  ...schema.properties,
@@ -570,7 +570,7 @@ describe('FormValidation', () => {
570
570
  });
571
571
  });
572
572
 
573
- it('password - min lenght', () => {
573
+ it('password - min length', () => {
574
574
  let newSchema = {
575
575
  ...schema,
576
576
  properties: {
@@ -595,7 +595,41 @@ describe('FormValidation', () => {
595
595
  });
596
596
  });
597
597
 
598
- it('password - max lenght', () => {
598
+ it('description - min length from server side', () => {
599
+ let newSchema = {
600
+ ...schema,
601
+ properties: {
602
+ ...schema.properties,
603
+ description: {
604
+ title: 'description',
605
+ type: 'string',
606
+ description: '',
607
+ widgetOptions: {
608
+ frontendOptions: {
609
+ widgetProps: {
610
+ minLength: 8,
611
+ },
612
+ },
613
+ },
614
+ },
615
+ },
616
+ required: [],
617
+ };
618
+ expect(
619
+ FormValidation.validateFieldsPerFieldset({
620
+ schema: newSchema,
621
+ formData: {
622
+ username: 'test username',
623
+ description: 'asd',
624
+ },
625
+ formatMessage,
626
+ }),
627
+ ).toEqual({
628
+ description: [messages.minLength.defaultMessage],
629
+ });
630
+ });
631
+
632
+ it('password - max length', () => {
599
633
  let newSchema = {
600
634
  ...schema,
601
635
  properties: {
@@ -620,6 +654,40 @@ describe('FormValidation', () => {
620
654
  });
621
655
  });
622
656
 
657
+ it('description - max length from server side', () => {
658
+ let newSchema = {
659
+ ...schema,
660
+ properties: {
661
+ ...schema.properties,
662
+ description: {
663
+ title: 'description',
664
+ type: 'string',
665
+ description: '',
666
+ widgetOptions: {
667
+ frontendOptions: {
668
+ widgetProps: {
669
+ maxLength: 8,
670
+ },
671
+ },
672
+ },
673
+ },
674
+ },
675
+ required: [],
676
+ };
677
+ expect(
678
+ FormValidation.validateFieldsPerFieldset({
679
+ schema: newSchema,
680
+ formData: {
681
+ username: 'test username',
682
+ description: 'asdasdasdasdasd',
683
+ },
684
+ formatMessage,
685
+ }),
686
+ ).toEqual({
687
+ description: [messages.maxLength.defaultMessage],
688
+ });
689
+ });
690
+
623
691
  it('array - maxItems', () => {
624
692
  let newSchema = {
625
693
  properties: {
@@ -203,3 +203,17 @@ export const minItemsValidator = ({
203
203
  ? formatMessage(messages.minItems, { minItems: field.minItems })
204
204
  : null;
205
205
  };
206
+
207
+ export const defaultLanguageControlPanelValidator = ({
208
+ value,
209
+ formData,
210
+ formatMessage,
211
+ }: Validator) => {
212
+ const isValid =
213
+ value &&
214
+ (formData.available_languages.find(
215
+ (lang: { token: string }) => lang.token === value,
216
+ ) ||
217
+ formData.available_languages.includes(value));
218
+ return !isValid ? formatMessage(messages.defaultLanguage) : null;
219
+ };
@@ -122,6 +122,9 @@ class Html extends Component {
122
122
  ...(publicURL && {
123
123
  publicURL,
124
124
  }),
125
+ ...(process.env.SITE_DEFAULT_LANGUAGE && {
126
+ defaultLanguage: process.env.SITE_DEFAULT_LANGUAGE,
127
+ }),
125
128
  })};`,
126
129
  }}
127
130
  />
@@ -403,4 +403,9 @@ export const messages = defineMessages({
403
403
  defaultMessage:
404
404
  'The number of items must be greater than or equal to {minItems}',
405
405
  },
406
+ defaultLanguage: {
407
+ id: "The selected default language must be in the list of the field 'Available languages'",
408
+ defaultMessage:
409
+ "The selected default language must be in the list of the field 'Available languages'",
410
+ },
406
411
  });
@@ -58,6 +58,10 @@ export default function client() {
58
58
  if (window.env.RAZZLE_LEGACY_TRAVERSE) {
59
59
  config.settings.legacyTraverse = true;
60
60
  }
61
+ // Support for setting the default language from a server environment variable
62
+ if (window.env.defaultLanguage) {
63
+ config.settings.defaultLanguage = window.env.defaultLanguage;
64
+ }
61
65
 
62
66
  loadableReady(() => {
63
67
  hydrateRoot(
package/src/storybook.jsx CHANGED
@@ -1490,6 +1490,7 @@ export const FormUndoWrapper = ({
1490
1490
  onClick={() => doUndo()}
1491
1491
  aria-label="Undo"
1492
1492
  disabled={!canUndo}
1493
+ type="button"
1493
1494
  >
1494
1495
  Undo
1495
1496
  </Button>
@@ -1500,6 +1501,7 @@ export const FormUndoWrapper = ({
1500
1501
  onClick={() => doRedo()}
1501
1502
  aria-label="Redo"
1502
1503
  disabled={!canRedo}
1504
+ type="button"
1503
1505
  >
1504
1506
  Redo
1505
1507
  </Button>
@@ -187,7 +187,7 @@ export function styleToClassName(key: any, value: any, prefix?: string): any;
187
187
  export function buildStyleClassNamesFromData(obj?: {}, prefix?: string): any;
188
188
  export function buildStyleClassNamesExtenders({ block, content, data, classNames, }: any): any[];
189
189
  export function styleDataToStyleObject(key: any, value: any, prefix?: string): any[];
190
- export function buildStyleObjectFromData(data?: any, prefix?: string): any;
190
+ export function buildStyleObjectFromData(data?: any, prefix?: string, container?: {}): any;
191
191
  export function getPreviousNextBlock({ content, block }: any): any[];
192
192
  export function getBlocksHierarchy(properties: any): any;
193
193
  export function findContainer(formData: object, { containerId }: {
@@ -26,4 +26,5 @@ export declare const endEventDateRangeValidator: ({ value, field, formData, form
26
26
  export declare const patternValidator: ({ value, field, formatMessage, }: Validator) => any;
27
27
  export declare const maxItemsValidator: ({ value, field, formatMessage, }: Validator) => any;
28
28
  export declare const minItemsValidator: ({ value, field, formatMessage, }: Validator) => any;
29
+ export declare const defaultLanguageControlPanelValidator: ({ value, formData, formatMessage, }: Validator) => any;
29
30
  export {};
@@ -585,4 +585,10 @@ export namespace messages {
585
585
  let defaultMessage_97: string;
586
586
  export { defaultMessage_97 as defaultMessage };
587
587
  }
588
+ namespace defaultLanguage {
589
+ let id_98: string;
590
+ export { id_98 as id };
591
+ let defaultMessage_98: string;
592
+ export { defaultMessage_98 as defaultMessage };
593
+ }
588
594
  }