@eeacms/volto-bise-policy 1.0.0 → 1.0.2

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 (105) hide show
  1. package/CHANGELOG.md +21 -2
  2. package/package.json +3 -1
  3. package/src/components/manage/Blocks/NewsletterSignup/Edit.jsx +30 -0
  4. package/src/components/manage/Blocks/NewsletterSignup/View.jsx +13 -0
  5. package/src/components/manage/Blocks/NewsletterSignup/index.js +49 -0
  6. package/src/components/manage/Blocks/NewsletterSignup/schema.js +25 -0
  7. package/src/components/manage/Blocks/index.js +2 -0
  8. package/src/customizations/@eeacms/volto-eea-design-system/ui/Header/HeaderMenuPopUp.js +354 -0
  9. package/src/customizations/@eeacms/volto-eea-website-theme/components/theme/SubsiteClass.jsx +70 -0
  10. package/src/customizations/volto/components/manage/Edit/Edit.jsx +519 -0
  11. package/src/customizations/volto/components/manage/Form/BlockDataForm.jsx +28 -0
  12. package/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx +214 -0
  13. package/src/customizations/volto/components/manage/Form/Field.jsx +269 -0
  14. package/src/customizations/volto/components/manage/Form/Form.jsx +804 -0
  15. package/src/customizations/volto/components/theme/Header/Header.jsx +151 -131
  16. package/src/index.js +12 -5
  17. package/theme/collections/message.overrides +10 -0
  18. package/theme/elements/container.overrides +156 -0
  19. package/theme/elements/segment.variables +1 -0
  20. package/theme/extras/header.overrides +107 -13
  21. package/theme/extras/header.variables +12 -0
  22. package/theme/extras/keyfacts.less +3 -3
  23. package/theme/extras/pluggables.less +17 -16
  24. package/theme/fonts/OpenSans-Bold.ttf +0 -0
  25. package/theme/fonts/OpenSans-Regular.ttf +0 -0
  26. package/theme/fonts/OpenSans-Semibold.ttf +0 -0
  27. package/theme/fonts/Rajdhani-Bold.ttf +0 -0
  28. package/theme/fonts/Rajdhani-Regular.ttf +0 -0
  29. package/theme/globals/helpers.less +75 -0
  30. package/theme/globals/natura2000.less +53 -0
  31. package/theme/globals/site.overrides +204 -4
  32. package/theme/globals/site.variables +5 -0
  33. package/theme/modules/accordion.overrides +3 -0
  34. package/theme/modules/accordion.variables +7 -0
  35. package/theme/modules/tab.overrides +176 -0
  36. package/theme/theme.config +1 -0
  37. package/theme/views/card.variables +2 -0
  38. package/locales/bg/LC_MESSAGES/volto.po +0 -14
  39. package/locales/bg.json +0 -1
  40. package/locales/bs/LC_MESSAGES/volto.po +0 -14
  41. package/locales/bs.json +0 -1
  42. package/locales/cs/LC_MESSAGES/volto.po +0 -14
  43. package/locales/cs.json +0 -1
  44. package/locales/da/LC_MESSAGES/volto.po +0 -14
  45. package/locales/da.json +0 -1
  46. package/locales/de/LC_MESSAGES/volto.po +0 -29
  47. package/locales/de.json +0 -1
  48. package/locales/el/LC_MESSAGES/volto.po +0 -14
  49. package/locales/el.json +0 -1
  50. package/locales/en/LC_MESSAGES/volto.po +0 -14
  51. package/locales/en.json +0 -1
  52. package/locales/es/LC_MESSAGES/volto.po +0 -24
  53. package/locales/es.json +0 -1
  54. package/locales/et/LC_MESSAGES/volto.po +0 -14
  55. package/locales/et.json +0 -1
  56. package/locales/eu/LC_MESSAGES/volto.po +0 -19
  57. package/locales/eu.json +0 -1
  58. package/locales/fi/LC_MESSAGES/volto.po +0 -14
  59. package/locales/fi.json +0 -1
  60. package/locales/fr/LC_MESSAGES/volto.po +0 -29
  61. package/locales/fr.json +0 -1
  62. package/locales/ga/LC_MESSAGES/volto.po +0 -14
  63. package/locales/ga.json +0 -1
  64. package/locales/hr/LC_MESSAGES/volto.po +0 -14
  65. package/locales/hr.json +0 -1
  66. package/locales/hu/LC_MESSAGES/volto.po +0 -14
  67. package/locales/hu.json +0 -1
  68. package/locales/is/LC_MESSAGES/volto.po +0 -14
  69. package/locales/is.json +0 -1
  70. package/locales/it/LC_MESSAGES/volto.po +0 -14
  71. package/locales/it.json +0 -1
  72. package/locales/ja/LC_MESSAGES/volto.po +0 -21
  73. package/locales/ja.json +0 -1
  74. package/locales/lt/LC_MESSAGES/volto.po +0 -14
  75. package/locales/lt.json +0 -1
  76. package/locales/lv/LC_MESSAGES/volto.po +0 -14
  77. package/locales/lv.json +0 -1
  78. package/locales/mk/LC_MESSAGES/volto.po +0 -14
  79. package/locales/mk.json +0 -1
  80. package/locales/mt/LC_MESSAGES/volto.po +0 -14
  81. package/locales/mt.json +0 -1
  82. package/locales/nl/LC_MESSAGES/volto.po +0 -33
  83. package/locales/nl.json +0 -1
  84. package/locales/no/LC_MESSAGES/volto.po +0 -14
  85. package/locales/no.json +0 -1
  86. package/locales/pl/LC_MESSAGES/volto.po +0 -14
  87. package/locales/pl.json +0 -1
  88. package/locales/pt/LC_MESSAGES/volto.po +0 -22
  89. package/locales/pt.json +0 -1
  90. package/locales/pt_BR/LC_MESSAGES/volto.po +0 -20
  91. package/locales/pt_BR.json +0 -1
  92. package/locales/ro/LC_MESSAGES/volto.po +0 -20
  93. package/locales/ro.json +0 -1
  94. package/locales/sh/LC_MESSAGES/volto.po +0 -14
  95. package/locales/sh.json +0 -1
  96. package/locales/sk/LC_MESSAGES/volto.po +0 -14
  97. package/locales/sk.json +0 -1
  98. package/locales/sl/LC_MESSAGES/volto.po +0 -14
  99. package/locales/sl.json +0 -1
  100. package/locales/sq/LC_MESSAGES/volto.po +0 -14
  101. package/locales/sq.json +0 -1
  102. package/locales/sv/LC_MESSAGES/volto.po +0 -14
  103. package/locales/sv.json +0 -1
  104. package/locales/tr/LC_MESSAGES/volto.po +0 -14
  105. package/locales/tr.json +0 -1
@@ -0,0 +1,804 @@
1
+ /**
2
+ * Form component.
3
+ * @module components/manage/Form/Form
4
+ */
5
+
6
+ import { BlocksForm, Field, Icon, Toast } from '@plone/volto/components';
7
+ import {
8
+ difference,
9
+ FormValidation,
10
+ getBlocksFieldname,
11
+ getBlocksLayoutFieldname,
12
+ messages,
13
+ } from '@plone/volto/helpers';
14
+ import aheadSVG from '@plone/volto/icons/ahead.svg';
15
+ import clearSVG from '@plone/volto/icons/clear.svg';
16
+ import {
17
+ findIndex,
18
+ isEmpty,
19
+ keys,
20
+ map,
21
+ mapValues,
22
+ pickBy,
23
+ without,
24
+ cloneDeep,
25
+ } from 'lodash';
26
+ import isBoolean from 'lodash/isBoolean';
27
+ import PropTypes from 'prop-types';
28
+ import React, { Component } from 'react';
29
+ import { injectIntl } from 'react-intl';
30
+ import { Portal } from 'react-portal';
31
+ import { connect } from 'react-redux';
32
+ import {
33
+ Button,
34
+ Container,
35
+ Form as UiForm,
36
+ Message,
37
+ Segment,
38
+ Tab,
39
+ } from 'semantic-ui-react';
40
+ import { v4 as uuid } from 'uuid';
41
+ import { toast } from 'react-toastify';
42
+ import { BlocksToolbar, UndoToolbar } from '@plone/volto/components';
43
+ import { setSidebarTab } from '@plone/volto/actions';
44
+ import { compose } from 'redux';
45
+ import config from '@plone/volto/registry';
46
+
47
+ /**
48
+ * Form container class.
49
+ * @class Form
50
+ * @extends Component
51
+ */
52
+ class Form extends Component {
53
+ /**
54
+ * Property types.
55
+ * @property {Object} propTypes Property types.
56
+ * @static
57
+ */
58
+ static propTypes = {
59
+ schema: PropTypes.shape({
60
+ fieldsets: PropTypes.arrayOf(
61
+ PropTypes.shape({
62
+ fields: PropTypes.arrayOf(PropTypes.string),
63
+ id: PropTypes.string,
64
+ title: PropTypes.string,
65
+ }),
66
+ ),
67
+ properties: PropTypes.objectOf(PropTypes.any),
68
+ definitions: PropTypes.objectOf(PropTypes.any),
69
+ required: PropTypes.arrayOf(PropTypes.string),
70
+ }),
71
+ formData: PropTypes.objectOf(PropTypes.any),
72
+ pathname: PropTypes.string,
73
+ onSubmit: PropTypes.func,
74
+ onCancel: PropTypes.func,
75
+ submitLabel: PropTypes.string,
76
+ resetAfterSubmit: PropTypes.bool,
77
+ resetOnCancel: PropTypes.bool,
78
+ isEditForm: PropTypes.bool,
79
+ isAdminForm: PropTypes.bool,
80
+ title: PropTypes.string,
81
+ error: PropTypes.shape({
82
+ message: PropTypes.string,
83
+ }),
84
+ loading: PropTypes.bool,
85
+ hideActions: PropTypes.bool,
86
+ description: PropTypes.string,
87
+ visual: PropTypes.bool,
88
+ blocks: PropTypes.arrayOf(PropTypes.object),
89
+ isFormSelected: PropTypes.bool,
90
+ onSelectForm: PropTypes.func,
91
+ editable: PropTypes.bool,
92
+ onChangeFormData: PropTypes.func,
93
+ requestError: PropTypes.string,
94
+ allowedBlocks: PropTypes.arrayOf(PropTypes.string),
95
+ showRestricted: PropTypes.bool,
96
+ };
97
+
98
+ /**
99
+ * Default properties.
100
+ * @property {Object} defaultProps Default properties.
101
+ * @static
102
+ */
103
+ static defaultProps = {
104
+ formData: null,
105
+ onSubmit: null,
106
+ onCancel: null,
107
+ submitLabel: null,
108
+ resetAfterSubmit: false,
109
+ resetOnCancel: false,
110
+ isEditForm: false,
111
+ isAdminForm: false,
112
+ title: null,
113
+ description: null,
114
+ error: null,
115
+ loading: null,
116
+ hideActions: false,
117
+ visual: false,
118
+ blocks: [],
119
+ pathname: '',
120
+ schema: {},
121
+ isFormSelected: true,
122
+ onSelectForm: null,
123
+ editable: true,
124
+ requestError: null,
125
+ allowedBlocks: null,
126
+ };
127
+
128
+ /**
129
+ * Constructor
130
+ * @method constructor
131
+ * @param {Object} props Component properties
132
+ * @constructs Form
133
+ */
134
+ constructor(props) {
135
+ super(props);
136
+ const ids = {
137
+ title: uuid(),
138
+ text: uuid(),
139
+ };
140
+ let { formData } = props;
141
+ const blocksFieldname = getBlocksFieldname(formData);
142
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
143
+
144
+ if (!props.isEditForm) {
145
+ // It's a normal (add form), get defaults from schema
146
+ formData = {
147
+ ...mapValues(props.schema.properties, 'default'),
148
+ ...formData,
149
+ };
150
+ }
151
+ // defaults for block editor; should be moved to schema on server side
152
+ // Adding fallback in case the fields are empty, so we are sure that the edit form
153
+ // shows at least the default blocks
154
+ if (
155
+ formData.hasOwnProperty(blocksFieldname) &&
156
+ formData.hasOwnProperty(blocksLayoutFieldname)
157
+ ) {
158
+ if (
159
+ !formData[blocksLayoutFieldname] ||
160
+ isEmpty(formData[blocksLayoutFieldname].items)
161
+ ) {
162
+ formData[blocksLayoutFieldname] = {
163
+ items: [ids.title, ids.text],
164
+ };
165
+ }
166
+ if (!formData[blocksFieldname] || isEmpty(formData[blocksFieldname])) {
167
+ formData[blocksFieldname] = {
168
+ [ids.title]: {
169
+ '@type': 'title',
170
+ },
171
+ [ids.text]: {
172
+ '@type': config.settings.defaultBlockType,
173
+ },
174
+ };
175
+ }
176
+ }
177
+
178
+ let selectedBlock = null;
179
+ if (
180
+ formData.hasOwnProperty(blocksLayoutFieldname) &&
181
+ formData[blocksLayoutFieldname].items.length > 0
182
+ ) {
183
+ if (config.blocks?.initialBlocksFocus === null) {
184
+ selectedBlock = null;
185
+ } else if (this.props.type in config.blocks?.initialBlocksFocus) {
186
+ // Default selected is not the first block, but the one from config.
187
+ // TODO Select first block and not an arbitrary one.
188
+ Object.keys(formData[blocksFieldname]).forEach((b_key) => {
189
+ if (
190
+ formData[blocksFieldname][b_key]['@type'] ===
191
+ config.blocks?.initialBlocksFocus?.[this.props.type]
192
+ ) {
193
+ selectedBlock = b_key;
194
+ }
195
+ });
196
+ } else {
197
+ selectedBlock = formData[blocksLayoutFieldname].items[0];
198
+ }
199
+ }
200
+
201
+ this.state = {
202
+ formData,
203
+ initialFormData: cloneDeep(formData),
204
+ errors: {},
205
+ selected: selectedBlock,
206
+ multiSelected: [],
207
+ isClient: false,
208
+ // Ensure focus remain in field after change
209
+ inFocus: {},
210
+ };
211
+ this.onChangeField = this.onChangeField.bind(this);
212
+ this.onSelectBlock = this.onSelectBlock.bind(this);
213
+ this.onSubmit = this.onSubmit.bind(this);
214
+ this.onCancel = this.onCancel.bind(this);
215
+ this.onTabChange = this.onTabChange.bind(this);
216
+ this.onBlurField = this.onBlurField.bind(this);
217
+ this.onClickInput = this.onClickInput.bind(this);
218
+ }
219
+
220
+ /**
221
+ * On updates caused by props change
222
+ * if errors from Backend come, these will be shown to their corresponding Fields
223
+ * also the first Tab to have any errors will be selected
224
+ * @param {Object} prevProps
225
+ */
226
+ async componentDidUpdate(prevProps, prevState) {
227
+ let { requestError } = this.props;
228
+ let errors = {};
229
+ let activeIndex = 0;
230
+
231
+ if (requestError && prevProps.requestError !== requestError) {
232
+ errors = FormValidation.giveServerErrorsToCorrespondingFields(
233
+ requestError,
234
+ );
235
+ activeIndex = FormValidation.showFirstTabWithErrors({
236
+ errors,
237
+ schema: this.props.schema,
238
+ });
239
+
240
+ this.setState({
241
+ errors,
242
+ activeIndex,
243
+ });
244
+ }
245
+
246
+ if (this.props.onChangeFormData) {
247
+ if (
248
+ // TODO: use fast-deep-equal
249
+ JSON.stringify(prevState?.formData) !==
250
+ JSON.stringify(this.state.formData)
251
+ ) {
252
+ this.props.onChangeFormData(this.state.formData);
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Tab selection is done only by setting activeIndex in state
259
+ */
260
+ onTabChange(e, { activeIndex }) {
261
+ const defaultFocus = this.props.schema.fieldsets[activeIndex].fields[0];
262
+ this.setState({
263
+ activeIndex,
264
+ ...(defaultFocus ? { inFocus: { [defaultFocus]: true } } : {}),
265
+ });
266
+ }
267
+
268
+ /**
269
+ * If user clicks on input, the form will be not considered pristine
270
+ * this will avoid onBlur effects without interraction with the form
271
+ * @param {Object} e event
272
+ */
273
+ onClickInput(e) {
274
+ this.setState({ isFormPristine: false });
275
+ }
276
+
277
+ /**
278
+ * Validate fields on blur
279
+ * @method onBlurField
280
+ * @param {string} id Id of the field
281
+ * @param {*} value Value of the field
282
+ * @returns {undefined}
283
+ */
284
+ onBlurField(id, value) {
285
+ if (!this.state.isFormPristine) {
286
+ const errors = FormValidation.validateFieldsPerFieldset({
287
+ schema: this.props.schema,
288
+ formData: this.state.formData,
289
+ formatMessage: this.props.intl.formatMessage,
290
+ touchedField: { [id]: value },
291
+ });
292
+
293
+ this.setState({
294
+ errors,
295
+ });
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Component did mount
301
+ * @method componentDidMount
302
+ * @returns {undefined}
303
+ */
304
+ componentDidMount() {
305
+ this.setState({ isClient: true });
306
+ }
307
+
308
+ static getDerivedStateFromProps(props, state) {
309
+ let newState = { ...state };
310
+ if (!props.isFormSelected) {
311
+ newState.selected = null;
312
+ }
313
+
314
+ return newState;
315
+ }
316
+
317
+ /**
318
+ * Change field handler
319
+ * Remove errors for changed field
320
+ * @method onChangeField
321
+ * @param {string} id Id of the field
322
+ * @param {*} value Value of the field
323
+ * @returns {undefined}
324
+ */
325
+ onChangeField(id, value) {
326
+ this.setState((prevState) => {
327
+ const { errors, formData } = prevState;
328
+ delete errors[id];
329
+ return {
330
+ errors,
331
+ formData: {
332
+ ...formData,
333
+ // We need to catch also when the value equals false this fixes #888
334
+ [id]:
335
+ value || (value !== undefined && isBoolean(value)) ? value : null,
336
+ },
337
+ // Changing the form data re-renders the select widget which causes the
338
+ // focus to get lost. To circumvent this, we set the focus back to
339
+ // the input.
340
+ // This could fix other widgets too but currently targeted
341
+ // against the select widget only.
342
+ // Ensure field to be in focus after the change
343
+ inFocus: { [id]: true },
344
+ };
345
+ });
346
+ }
347
+
348
+ /**
349
+ * Select block handler
350
+ * @method onSelectBlock
351
+ * @param {string} id Id of the field
352
+ * @param {string} isMultipleSelection true if multiple blocks are selected
353
+ * @returns {undefined}
354
+ */
355
+ onSelectBlock(id, isMultipleSelection, event) {
356
+ let multiSelected = [];
357
+ let selected = id;
358
+
359
+ if (isMultipleSelection) {
360
+ selected = null;
361
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(
362
+ this.state.formData,
363
+ );
364
+
365
+ const blocks_layout = this.state.formData[blocksLayoutFieldname].items;
366
+
367
+ if (event.shiftKey) {
368
+ const anchor =
369
+ this.state.multiSelected.length > 0
370
+ ? blocks_layout.indexOf(this.state.multiSelected[0])
371
+ : blocks_layout.indexOf(this.state.selected);
372
+ const focus = blocks_layout.indexOf(id);
373
+
374
+ if (anchor === focus) {
375
+ multiSelected = [id];
376
+ } else if (focus > anchor) {
377
+ multiSelected = [...blocks_layout.slice(anchor, focus + 1)];
378
+ } else {
379
+ multiSelected = [...blocks_layout.slice(focus, anchor + 1)];
380
+ }
381
+ }
382
+
383
+ if ((event.ctrlKey || event.metaKey) && !event.shiftKey) {
384
+ multiSelected = this.state.multiSelected || [];
385
+ if (!this.state.multiSelected.includes(this.state.selected)) {
386
+ multiSelected = [...multiSelected, this.state.selected];
387
+ selected = null;
388
+ }
389
+ if (this.state.multiSelected.includes(id)) {
390
+ selected = null;
391
+ multiSelected = without(multiSelected, id);
392
+ } else {
393
+ multiSelected = [...multiSelected, id];
394
+ }
395
+ }
396
+ }
397
+
398
+ this.setState({
399
+ selected,
400
+ multiSelected,
401
+ });
402
+
403
+ if (this.props.onSelectForm) {
404
+ if (event) event.nativeEvent.stopImmediatePropagation();
405
+ this.props.onSelectForm();
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Cancel handler
411
+ * It prevents event from triggering submit, reset form if props.resetAfterSubmit
412
+ * and calls this.props.onCancel
413
+ * @method onCancel
414
+ * @param {Object} event Event object.
415
+ * @returns {undefined}
416
+ */
417
+ onCancel(event) {
418
+ if (event) {
419
+ event.preventDefault();
420
+ }
421
+ if (this.props.resetOnCancel || this.props.resetAfterSubmit) {
422
+ this.setState({
423
+ formData: this.props.formData,
424
+ });
425
+ }
426
+ this.props.onCancel(event);
427
+ }
428
+
429
+ /**
430
+ * Submit handler also validate form and collect errors
431
+ * @method onSubmit
432
+ * @param {Object} event Event object.
433
+ * @returns {undefined}
434
+ */
435
+ onSubmit(event) {
436
+ if (event) {
437
+ event.preventDefault();
438
+ }
439
+
440
+ const errors = this.props.schema
441
+ ? FormValidation.validateFieldsPerFieldset({
442
+ schema: this.props.schema,
443
+ formData: this.state.formData,
444
+ formatMessage: this.props.intl.formatMessage,
445
+ })
446
+ : {};
447
+
448
+ if (keys(errors).length > 0) {
449
+ const activeIndex = FormValidation.showFirstTabWithErrors({
450
+ errors,
451
+ schema: this.props.schema,
452
+ });
453
+ this.setState(
454
+ {
455
+ errors,
456
+ activeIndex,
457
+ },
458
+ () => {
459
+ Object.keys(errors).forEach((err) =>
460
+ toast.error(
461
+ <Toast
462
+ error
463
+ title={this.props.schema.properties[err].title || err}
464
+ content={errors[err].join(', ')}
465
+ />,
466
+ ),
467
+ );
468
+ },
469
+ );
470
+ // Changes the focus to the metadata tab in the sidebar if error
471
+ this.props.setSidebarTab(0);
472
+ } else {
473
+ // Get only the values that have been modified (Edit forms), send all in case that
474
+ // it's an add form
475
+ if (this.props.isEditForm) {
476
+ this.props.onSubmit(this.getOnlyFormModifiedValues());
477
+ } else {
478
+ this.props.onSubmit(this.state.formData);
479
+ }
480
+ if (this.props.resetAfterSubmit) {
481
+ this.setState({
482
+ formData: this.props.formData,
483
+ });
484
+ }
485
+ }
486
+ }
487
+
488
+ /**
489
+ * getOnlyFormModifiedValues handler
490
+ * It returns only the values of the fields that are have really changed since the
491
+ * form was loaded. Useful for edit forms and PATCH operations, when we only want to
492
+ * send the changed data.
493
+ * @method getOnlyFormModifiedValues
494
+ * @param {Object} event Event object.
495
+ * @returns {undefined}
496
+ */
497
+ getOnlyFormModifiedValues = () => {
498
+ const fieldsModified = Object.keys(
499
+ difference(this.state.formData, this.state.initialFormData),
500
+ );
501
+ return {
502
+ ...pickBy(this.state.formData, (value, key) =>
503
+ fieldsModified.includes(key),
504
+ ),
505
+ ...(this.state.formData['@static_behaviors'] && {
506
+ '@static_behaviors': this.state.formData['@static_behaviors'],
507
+ }),
508
+ };
509
+ };
510
+
511
+ /**
512
+ * Removed blocks and blocks_layout fields from the form.
513
+ * @method removeBlocksLayoutFields
514
+ * @param {object} schema The schema definition of the form.
515
+ * @returns A modified copy of the given schema.
516
+ */
517
+ removeBlocksLayoutFields = (schema) => {
518
+ const newSchema = { ...schema };
519
+ const layoutFieldsetIndex = findIndex(
520
+ newSchema.fieldsets,
521
+ (fieldset) => fieldset.id === 'layout',
522
+ );
523
+ if (layoutFieldsetIndex > -1) {
524
+ const layoutFields = newSchema.fieldsets[layoutFieldsetIndex].fields;
525
+ newSchema.fieldsets[layoutFieldsetIndex].fields = layoutFields.filter(
526
+ (field) => field !== 'blocks' && field !== 'blocks_layout',
527
+ );
528
+ if (newSchema.fieldsets[layoutFieldsetIndex].fields.length === 0) {
529
+ newSchema.fieldsets = [
530
+ ...newSchema.fieldsets.slice(0, layoutFieldsetIndex),
531
+ ...newSchema.fieldsets.slice(layoutFieldsetIndex + 1),
532
+ ];
533
+ }
534
+ }
535
+ return newSchema;
536
+ };
537
+
538
+ /**
539
+ * Render method.
540
+ * @method render
541
+ * @returns {string} Markup for the component.
542
+ */
543
+ render() {
544
+ const { settings } = config;
545
+ const { schema: originalSchema, onCancel, onSubmit } = this.props;
546
+ const { formData } = this.state;
547
+ const schema = this.removeBlocksLayoutFields(originalSchema);
548
+
549
+ return this.props.visual ? (
550
+ // Removing this from SSR is important, since react-beautiful-dnd supports SSR,
551
+ // but draftJS don't like it much and the hydration gets messed up
552
+ this.state.isClient && (
553
+ <div className="ui container">
554
+ <BlocksToolbar
555
+ formData={this.state.formData}
556
+ selectedBlock={this.state.selected}
557
+ selectedBlocks={this.state.multiSelected}
558
+ onChangeBlocks={(newBlockData) =>
559
+ this.setState({
560
+ formData: {
561
+ ...formData,
562
+ ...newBlockData,
563
+ },
564
+ })
565
+ }
566
+ onSetSelectedBlocks={(blockIds) =>
567
+ this.setState({ multiSelected: blockIds })
568
+ }
569
+ onSelectBlock={this.onSelectBlock}
570
+ />
571
+ <UndoToolbar
572
+ state={{
573
+ formData: this.state.formData,
574
+ selected: this.state.selected,
575
+ multiSelected: this.state.multiSelected,
576
+ }}
577
+ enableHotKeys
578
+ onUndoRedo={({ state }) => this.setState(state)}
579
+ />
580
+ <BlocksForm
581
+ onChangeFormData={(newFormData) =>
582
+ this.setState({
583
+ formData: {
584
+ ...formData,
585
+ ...newFormData,
586
+ },
587
+ })
588
+ }
589
+ onChangeField={this.onChangeField}
590
+ onSelectBlock={this.onSelectBlock}
591
+ properties={formData}
592
+ pathname={this.props.pathname}
593
+ selectedBlock={this.state.selected}
594
+ multiSelected={this.state.multiSelected}
595
+ manage={this.props.isAdminForm}
596
+ allowedBlocks={this.props.allowedBlocks}
597
+ showRestricted={this.props.showRestricted}
598
+ editable={this.props.editable}
599
+ isMainForm={this.props.editable}
600
+ />
601
+ {this.state.isClient && this.props.editable && (
602
+ <Portal
603
+ node={__CLIENT__ && document.getElementById('sidebar-metadata')}
604
+ >
605
+ <UiForm
606
+ method="post"
607
+ onSubmit={this.onSubmit}
608
+ error={keys(this.state.errors).length > 0}
609
+ >
610
+ {schema &&
611
+ map(schema.fieldsets, (item) => [
612
+ <Segment
613
+ secondary
614
+ attached
615
+ className={`fieldset-${item.id}`}
616
+ key={item.title}
617
+ >
618
+ {item.title}
619
+ </Segment>,
620
+ <Segment attached key={`fieldset-contents-${item.title}`}>
621
+ {map(item.fields, (field, index) => (
622
+ <Field
623
+ {...schema.properties[field]}
624
+ id={field}
625
+ fieldSet={item.title.toLowerCase()}
626
+ formData={this.state.formData}
627
+ focus={this.state.inFocus[field]}
628
+ value={this.state.formData?.[field]}
629
+ required={schema.required.indexOf(field) !== -1}
630
+ onChange={this.onChangeField}
631
+ onBlur={this.onBlurField}
632
+ onClick={this.onClickInput}
633
+ key={field}
634
+ error={this.state.errors[field]}
635
+ />
636
+ ))}
637
+ </Segment>,
638
+ ])}
639
+ </UiForm>
640
+ </Portal>
641
+ )}
642
+ </div>
643
+ )
644
+ ) : (
645
+ <Container>
646
+ <UiForm
647
+ method="post"
648
+ onSubmit={this.onSubmit}
649
+ error={keys(this.state.errors).length > 0}
650
+ className={settings.verticalFormTabs ? 'vertical-form' : ''}
651
+ >
652
+ <fieldset className="invisible">
653
+ <Segment.Group raised>
654
+ {schema && schema.fieldsets.length > 1 && (
655
+ <>
656
+ {settings.verticalFormTabs && this.props.title && (
657
+ <Segment secondary attached key={this.props.title}>
658
+ {this.props.title}
659
+ </Segment>
660
+ )}
661
+ <Tab
662
+ menu={{
663
+ secondary: true,
664
+ pointing: true,
665
+ attached: true,
666
+ tabular: true,
667
+ className: 'formtabs',
668
+ vertical: settings.verticalFormTabs,
669
+ }}
670
+ grid={{ paneWidth: 9, tabWidth: 3, stackable: true }}
671
+ onTabChange={this.onTabChange}
672
+ activeIndex={this.state.activeIndex}
673
+ panes={map(schema.fieldsets, (item) => ({
674
+ menuItem: item.title,
675
+ render: () => [
676
+ !settings.verticalFormTabs && this.props.title && (
677
+ <Segment secondary attached key={this.props.title}>
678
+ {this.props.title}
679
+ </Segment>
680
+ ),
681
+ item.description && (
682
+ <Message attached="bottom">
683
+ {item.description}
684
+ </Message>
685
+ ),
686
+ ...map(item.fields, (field, index) => (
687
+ <Field
688
+ {...schema.properties[field]}
689
+ isDisabled={!this.props.editable}
690
+ id={field}
691
+ formData={this.state.formData}
692
+ fieldSet={item.title.toLowerCase()}
693
+ focus={this.state.inFocus[field]}
694
+ value={this.state.formData?.[field]}
695
+ required={schema.required.indexOf(field) !== -1}
696
+ onChange={this.onChangeField}
697
+ onBlur={this.onBlurField}
698
+ onClick={this.onClickInput}
699
+ key={field}
700
+ error={this.state.errors[field]}
701
+ />
702
+ )),
703
+ ],
704
+ }))}
705
+ />
706
+ </>
707
+ )}
708
+ {schema && schema.fieldsets.length === 1 && (
709
+ <Segment>
710
+ {this.props.title && (
711
+ <Segment className="primary">
712
+ <h1 style={{ fontSize: '16px' }}> {this.props.title}</h1>
713
+ </Segment>
714
+ )}
715
+ {this.props.description && (
716
+ <Segment secondary>{this.props.description}</Segment>
717
+ )}
718
+ {keys(this.state.errors).length > 0 && (
719
+ <Message
720
+ icon="warning"
721
+ negative
722
+ attached
723
+ header={this.props.intl.formatMessage(messages.error)}
724
+ content={this.props.intl.formatMessage(
725
+ messages.thereWereSomeErrors,
726
+ )}
727
+ />
728
+ )}
729
+ {this.props.error && (
730
+ <Message
731
+ icon="warning"
732
+ negative
733
+ attached
734
+ header={this.props.intl.formatMessage(messages.error)}
735
+ content={this.props.error.message}
736
+ />
737
+ )}
738
+ {map(schema.fieldsets[0].fields, (field) => (
739
+ <Field
740
+ {...schema.properties[field]}
741
+ id={field}
742
+ value={this.state.formData?.[field]}
743
+ required={schema.required.indexOf(field) !== -1}
744
+ onChange={this.onChangeField}
745
+ onBlur={this.onBlurField}
746
+ onClick={this.onClickInput}
747
+ key={field}
748
+ error={this.state.errors[field]}
749
+ />
750
+ ))}
751
+ </Segment>
752
+ )}
753
+ {!this.props.hideActions && (
754
+ <Segment className="actions" clearing>
755
+ {onSubmit && (
756
+ <Button
757
+ basic
758
+ primary
759
+ floated="right"
760
+ type="submit"
761
+ aria-label={
762
+ this.props.submitLabel
763
+ ? this.props.submitLabel
764
+ : this.props.intl.formatMessage(messages.save)
765
+ }
766
+ title={
767
+ this.props.submitLabel
768
+ ? this.props.submitLabel
769
+ : this.props.intl.formatMessage(messages.save)
770
+ }
771
+ loading={this.props.loading}
772
+ >
773
+ <Icon className="circled" name={aheadSVG} size="30px" />
774
+ </Button>
775
+ )}
776
+ {onCancel && (
777
+ <Button
778
+ basic
779
+ secondary
780
+ aria-label={this.props.intl.formatMessage(
781
+ messages.cancel,
782
+ )}
783
+ title={this.props.intl.formatMessage(messages.cancel)}
784
+ floated="right"
785
+ onClick={this.onCancel}
786
+ >
787
+ <Icon className="circled" name={clearSVG} size="30px" />
788
+ </Button>
789
+ )}
790
+ </Segment>
791
+ )}
792
+ </Segment.Group>
793
+ </fieldset>
794
+ </UiForm>
795
+ </Container>
796
+ );
797
+ }
798
+ }
799
+
800
+ const FormIntl = injectIntl(Form, { forwardRef: true });
801
+
802
+ export default compose(
803
+ connect(null, { setSidebarTab }, null, { forwardRef: true }),
804
+ )(FormIntl);