@plone/volto 17.12.1 → 17.13.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.
package/CHANGELOG.md CHANGED
@@ -17,6 +17,12 @@ myst:
17
17
 
18
18
  <!-- towncrier release notes start -->
19
19
 
20
+ ## 17.13.0 (2024-02-09)
21
+
22
+ ### Feature
23
+
24
+ - Add global form state. @robgietema [#5721](https://github.com/plone/volto/issues/5721)
25
+
20
26
  ## 17.12.1 (2024-02-06)
21
27
 
22
28
  ### Bugfix
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "17.12.1",
12
+ "version": "17.13.0",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plone/volto-slate",
3
- "version": "17.12.1",
3
+ "version": "17.13.0",
4
4
  "description": "Slate.js integration with Volto",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Form actions.
3
+ * @module actions/form/form
4
+ */
5
+
6
+ import { SET_FORM_DATA } from '@plone/volto/constants/ActionTypes';
7
+
8
+ /**
9
+ * Set form data function.
10
+ * @function setFormData
11
+ * @param {Object} data New form data.
12
+ * @returns {Object} Set sidebar action.
13
+ */
14
+ export function setFormData(data) {
15
+ return {
16
+ type: SET_FORM_DATA,
17
+ data,
18
+ };
19
+ }
@@ -0,0 +1,14 @@
1
+ import { setFormData } from './form';
2
+ import { SET_FORM_DATA } from '@plone/volto/constants/ActionTypes';
3
+
4
+ describe('Form action', () => {
5
+ describe('setFormData', () => {
6
+ it('should create an action to set the form data', () => {
7
+ const data = { foo: 'bar' };
8
+ const action = setFormData(data);
9
+
10
+ expect(action.type).toEqual(SET_FORM_DATA);
11
+ expect(action.data).toEqual(data);
12
+ });
13
+ });
14
+ });
@@ -151,6 +151,7 @@ export {
151
151
  export { getQuerystring } from '@plone/volto/actions/querystring/querystring';
152
152
  export { getQueryStringResults } from '@plone/volto/actions/querystringsearch/querystringsearch';
153
153
  export { setSidebarTab } from '@plone/volto/actions/sidebar/sidebar';
154
+ export { setFormData } from '@plone/volto/actions/form/form';
154
155
  export {
155
156
  deleteLinkTranslation,
156
157
  getTranslationLocator,
@@ -364,6 +364,7 @@ class Add extends Component {
364
364
  onSelectForm={() => {
365
365
  this.setState({ formSelected: 'addForm' });
366
366
  }}
367
+ global
367
368
  />
368
369
  {this.state.isClient && (
369
370
  <Portal node={document.getElementById('toolbar')}>
@@ -307,6 +307,7 @@ class Edit extends Component {
307
307
  onSelectForm={() => {
308
308
  this.setState({ formSelected: 'editForm' });
309
309
  }}
310
+ global
310
311
  />
311
312
  );
312
313
 
@@ -16,6 +16,7 @@ import clearSVG from '@plone/volto/icons/clear.svg';
16
16
  import {
17
17
  findIndex,
18
18
  isEmpty,
19
+ isEqual,
19
20
  keys,
20
21
  map,
21
22
  mapValues,
@@ -40,7 +41,7 @@ import {
40
41
  import { v4 as uuid } from 'uuid';
41
42
  import { toast } from 'react-toastify';
42
43
  import { BlocksToolbar, UndoToolbar } from '@plone/volto/components';
43
- import { setSidebarTab } from '@plone/volto/actions';
44
+ import { setSidebarTab, setFormData } from '@plone/volto/actions';
44
45
  import { compose } from 'redux';
45
46
  import config from '@plone/volto/registry';
46
47
 
@@ -69,6 +70,7 @@ class Form extends Component {
69
70
  required: PropTypes.arrayOf(PropTypes.string),
70
71
  }),
71
72
  formData: PropTypes.objectOf(PropTypes.any),
73
+ globalData: PropTypes.objectOf(PropTypes.any),
72
74
  pathname: PropTypes.string,
73
75
  onSubmit: PropTypes.func,
74
76
  onCancel: PropTypes.func,
@@ -93,6 +95,7 @@ class Form extends Component {
93
95
  requestError: PropTypes.string,
94
96
  allowedBlocks: PropTypes.arrayOf(PropTypes.string),
95
97
  showRestricted: PropTypes.bool,
98
+ global: PropTypes.bool,
96
99
  };
97
100
 
98
101
  /**
@@ -123,6 +126,7 @@ class Form extends Component {
123
126
  editable: true,
124
127
  requestError: null,
125
128
  allowedBlocks: null,
129
+ global: false,
126
130
  };
127
131
 
128
132
  /**
@@ -201,6 +205,12 @@ class Form extends Component {
201
205
  }
202
206
  }
203
207
 
208
+ // Sync state to global state
209
+ if (this.props.global) {
210
+ this.props.setFormData(formData);
211
+ }
212
+
213
+ // Set initial state
204
214
  this.state = {
205
215
  formData,
206
216
  initialFormData,
@@ -246,14 +256,18 @@ class Form extends Component {
246
256
  }
247
257
 
248
258
  if (this.props.onChangeFormData) {
249
- if (
250
- // TODO: use fast-deep-equal
251
- JSON.stringify(prevState?.formData) !==
252
- JSON.stringify(this.state.formData)
253
- ) {
259
+ if (!isEqual(prevState?.formData, this.state.formData)) {
254
260
  this.props.onChangeFormData(this.state.formData);
255
261
  }
256
262
  }
263
+ if (
264
+ this.props.global &&
265
+ !isEqual(this.props.globalData, this.state.formData)
266
+ ) {
267
+ this.setState({
268
+ formData: this.props.globalData,
269
+ });
270
+ }
257
271
  }
258
272
 
259
273
  /**
@@ -327,15 +341,18 @@ class Form extends Component {
327
341
  onChangeField(id, value) {
328
342
  this.setState((prevState) => {
329
343
  const { errors, formData } = prevState;
344
+ const newFormData = {
345
+ ...formData,
346
+ // We need to catch also when the value equals false this fixes #888
347
+ [id]: value || (value !== undefined && isBoolean(value)) ? value : null,
348
+ };
330
349
  delete errors[id];
350
+ if (this.props.global) {
351
+ this.props.setFormData(newFormData);
352
+ }
331
353
  return {
332
354
  errors,
333
- formData: {
334
- ...formData,
335
- // We need to catch also when the value equals false this fixes #888
336
- [id]:
337
- value || (value !== undefined && isBoolean(value)) ? value : null,
338
- },
355
+ formData: newFormData,
339
356
  // Changing the form data re-renders the select widget which causes the
340
357
  // focus to get lost. To circumvent this, we set the focus back to
341
358
  // the input.
@@ -357,14 +374,13 @@ class Form extends Component {
357
374
  onSelectBlock(id, isMultipleSelection, event) {
358
375
  let multiSelected = [];
359
376
  let selected = id;
377
+ const formData = this.state.formData;
360
378
 
361
379
  if (isMultipleSelection) {
362
380
  selected = null;
363
- const blocksLayoutFieldname = getBlocksLayoutFieldname(
364
- this.state.formData,
365
- );
381
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
366
382
 
367
- const blocks_layout = this.state.formData[blocksLayoutFieldname].items;
383
+ const blocks_layout = formData[blocksLayoutFieldname].items;
368
384
 
369
385
  if (event.shiftKey) {
370
386
  const anchor =
@@ -424,6 +440,9 @@ class Form extends Component {
424
440
  this.setState({
425
441
  formData: this.props.formData,
426
442
  });
443
+ if (this.props.global) {
444
+ this.props.setFormData(this.props.formData);
445
+ }
427
446
  }
428
447
  this.props.onCancel(event);
429
448
  }
@@ -435,6 +454,8 @@ class Form extends Component {
435
454
  * @returns {undefined}
436
455
  */
437
456
  onSubmit(event) {
457
+ const formData = this.state.formData;
458
+
438
459
  if (event) {
439
460
  event.preventDefault();
440
461
  }
@@ -442,7 +463,7 @@ class Form extends Component {
442
463
  const errors = this.props.schema
443
464
  ? FormValidation.validateFieldsPerFieldset({
444
465
  schema: this.props.schema,
445
- formData: this.state.formData,
466
+ formData,
446
467
  formatMessage: this.props.intl.formatMessage,
447
468
  })
448
469
  : {};
@@ -477,12 +498,15 @@ class Form extends Component {
477
498
  if (this.props.isEditForm) {
478
499
  this.props.onSubmit(this.getOnlyFormModifiedValues());
479
500
  } else {
480
- this.props.onSubmit(this.state.formData);
501
+ this.props.onSubmit(formData);
481
502
  }
482
503
  if (this.props.resetAfterSubmit) {
483
504
  this.setState({
484
505
  formData: this.props.formData,
485
506
  });
507
+ if (this.props.global) {
508
+ this.props.setFormData(this.props.formData);
509
+ }
486
510
  }
487
511
  }
488
512
  }
@@ -497,15 +521,15 @@ class Form extends Component {
497
521
  * @returns {undefined}
498
522
  */
499
523
  getOnlyFormModifiedValues = () => {
524
+ const formData = this.state.formData;
525
+
500
526
  const fieldsModified = Object.keys(
501
- difference(this.state.formData, this.state.initialFormData),
527
+ difference(formData, this.state.initialFormData),
502
528
  );
503
529
  return {
504
- ...pickBy(this.state.formData, (value, key) =>
505
- fieldsModified.includes(key),
506
- ),
507
- ...(this.state.formData['@static_behaviors'] && {
508
- '@static_behaviors': this.state.formData['@static_behaviors'],
530
+ ...pickBy(formData, (value, key) => fieldsModified.includes(key)),
531
+ ...(formData['@static_behaviors'] && {
532
+ '@static_behaviors': formData['@static_behaviors'],
509
533
  }),
510
534
  };
511
535
  };
@@ -551,7 +575,7 @@ class Form extends Component {
551
575
  navRoot,
552
576
  type,
553
577
  } = this.props;
554
- const { formData } = this.state;
578
+ const formData = this.state.formData;
555
579
  const schema = this.removeBlocksLayoutFields(originalSchema);
556
580
  const Container =
557
581
  config.getComponent({ name: 'Container' }).component || SemanticContainer;
@@ -562,17 +586,21 @@ class Form extends Component {
562
586
  this.state.isClient && (
563
587
  <Container>
564
588
  <BlocksToolbar
565
- formData={this.state.formData}
589
+ formData={formData}
566
590
  selectedBlock={this.state.selected}
567
591
  selectedBlocks={this.state.multiSelected}
568
- onChangeBlocks={(newBlockData) =>
592
+ onChangeBlocks={(newBlockData) => {
593
+ const newFormData = {
594
+ ...formData,
595
+ ...newBlockData,
596
+ };
569
597
  this.setState({
570
- formData: {
571
- ...formData,
572
- ...newBlockData,
573
- },
574
- })
575
- }
598
+ formData: newFormData,
599
+ });
600
+ if (this.props.global) {
601
+ this.props.setFormData(newFormData);
602
+ }
603
+ }}
576
604
  onSetSelectedBlocks={(blockIds) =>
577
605
  this.setState({ multiSelected: blockIds })
578
606
  }
@@ -580,22 +608,31 @@ class Form extends Component {
580
608
  />
581
609
  <UndoToolbar
582
610
  state={{
583
- formData: this.state.formData,
611
+ formData,
584
612
  selected: this.state.selected,
585
613
  multiSelected: this.state.multiSelected,
586
614
  }}
587
615
  enableHotKeys
588
- onUndoRedo={({ state }) => this.setState(state)}
616
+ onUndoRedo={({ state }) => {
617
+ if (this.props.global) {
618
+ this.props.setFormData(state.formData);
619
+ }
620
+ return this.setState(state);
621
+ }}
589
622
  />
590
623
  <BlocksForm
591
- onChangeFormData={(newFormData) =>
624
+ onChangeFormData={(newData) => {
625
+ const newFormData = {
626
+ ...formData,
627
+ ...newData,
628
+ };
592
629
  this.setState({
593
- formData: {
594
- ...formData,
595
- ...newFormData,
596
- },
597
- })
598
- }
630
+ formData: newFormData,
631
+ });
632
+ if (this.props.global) {
633
+ this.props.setFormData(newFormData);
634
+ }
635
+ }}
599
636
  onChangeField={this.onChangeField}
600
637
  onSelectBlock={this.onSelectBlock}
601
638
  properties={formData}
@@ -635,9 +672,9 @@ class Form extends Component {
635
672
  {...schema.properties[field]}
636
673
  id={field}
637
674
  fieldSet={item.title.toLowerCase()}
638
- formData={this.state.formData}
675
+ formData={formData}
639
676
  focus={this.state.inFocus[field]}
640
- value={this.state.formData?.[field]}
677
+ value={formData?.[field]}
641
678
  required={schema.required.indexOf(field) !== -1}
642
679
  onChange={this.onChangeField}
643
680
  onBlur={this.onBlurField}
@@ -700,10 +737,10 @@ class Form extends Component {
700
737
  {...schema.properties[field]}
701
738
  isDisabled={!this.props.editable}
702
739
  id={field}
703
- formData={this.state.formData}
740
+ formData={formData}
704
741
  fieldSet={item.title.toLowerCase()}
705
742
  focus={this.state.inFocus[field]}
706
- value={this.state.formData?.[field]}
743
+ value={formData?.[field]}
707
744
  required={schema.required.indexOf(field) !== -1}
708
745
  onChange={this.onChangeField}
709
746
  onBlur={this.onBlurField}
@@ -751,7 +788,7 @@ class Form extends Component {
751
788
  <Field
752
789
  {...schema.properties[field]}
753
790
  id={field}
754
- value={this.state.formData?.[field]}
791
+ value={formData?.[field]}
755
792
  required={schema.required.indexOf(field) !== -1}
756
793
  onChange={this.onChangeField}
757
794
  onBlur={this.onBlurField}
@@ -812,5 +849,12 @@ class Form extends Component {
812
849
  const FormIntl = injectIntl(Form, { forwardRef: true });
813
850
 
814
851
  export default compose(
815
- connect(null, { setSidebarTab }, null, { forwardRef: true }),
852
+ connect(
853
+ (state, props) => ({
854
+ globalData: state.form?.global,
855
+ }),
856
+ { setSidebarTab, setFormData },
857
+ null,
858
+ { forwardRef: true },
859
+ ),
816
860
  )(FormIntl);
@@ -141,3 +141,4 @@ export const POST_UPGRADE = 'POST_UPGRADE';
141
141
  export const RESET_LOGIN_REQUEST = 'RESET_LOGIN_REQUEST';
142
142
  export const GET_SITE = 'GET_SITE';
143
143
  export const GET_NAVROOT = 'GET_NAVROOT';
144
+ export const SET_FORM_DATA = 'SET_FORM_DATA';
@@ -4,7 +4,11 @@
4
4
  * @module reducers/form/form
5
5
  */
6
6
 
7
- const initialState = {};
7
+ import { SET_FORM_DATA } from '@plone/volto/constants/ActionTypes';
8
+
9
+ const initialState = {
10
+ global: {},
11
+ };
8
12
 
9
13
  /**
10
14
  * Form reducer.
@@ -12,6 +16,14 @@ const initialState = {};
12
16
  * @param {Object} state Current state.
13
17
  * @returns {Object} New state.
14
18
  */
15
- export default function form(state = initialState) {
16
- return state;
19
+ export default function form(state = initialState, action = {}) {
20
+ switch (action.type) {
21
+ case SET_FORM_DATA:
22
+ return {
23
+ ...state,
24
+ global: action.data,
25
+ };
26
+ default:
27
+ return state;
28
+ }
17
29
  }
@@ -1,7 +1,19 @@
1
1
  import form from './form';
2
+ import { SET_FORM_DATA } from '@plone/volto/constants/ActionTypes';
2
3
 
3
4
  describe('Form reducer', () => {
4
5
  it('should return the initial state', () => {
5
- expect(form()).toEqual({});
6
+ expect(form()).toEqual({ global: {} });
7
+ });
8
+
9
+ it('should handle SET_FORM_DATA', () => {
10
+ expect(
11
+ form(undefined, {
12
+ type: SET_FORM_DATA,
13
+ data: { foo: 'bar' },
14
+ }),
15
+ ).toEqual({
16
+ global: { foo: 'bar' },
17
+ });
6
18
  });
7
19
  });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Set form data function.
3
+ * @function setFormData
4
+ * @param {Object} data New form data.
5
+ * @returns {Object} Set sidebar action.
6
+ */
7
+ export function setFormData(data: any): any;
@@ -0,0 +1 @@
1
+ export {};
@@ -11,6 +11,7 @@ export { getTypes } from "@plone/volto/actions/types/types";
11
11
  export { getQuerystring } from "@plone/volto/actions/querystring/querystring";
12
12
  export { getQueryStringResults } from "@plone/volto/actions/querystringsearch/querystringsearch";
13
13
  export { setSidebarTab } from "@plone/volto/actions/sidebar/sidebar";
14
+ export { setFormData } from "@plone/volto/actions/form/form";
14
15
  export { loadLazyLibrary } from "@plone/volto/actions/lazyLibraries/lazyLibraries";
15
16
  export { getContextNavigation } from "@plone/volto/actions/contextNavigation/contextNavigation";
16
17
  export { authenticatedRole } from "@plone/volto/actions/authRole/authRole";
@@ -1,5 +1,5 @@
1
1
  declare const _default: import("react-redux").ConnectedComponent<React.ForwardRefExoticComponent<Pick<import("react-intl").WithIntlProps<any>, string | number | symbol> & React.RefAttributes<any>> & {
2
2
  WrappedComponent: React.ComponentType<any>;
3
- }, import("react-redux").Omit<Pick<import("react-intl").WithIntlProps<any>, string | number | symbol> & React.RefAttributes<any>, "setSidebarTab">>;
3
+ }, import("react-redux").Omit<Pick<import("react-intl").WithIntlProps<any>, string | number | symbol> & React.RefAttributes<any>, "setSidebarTab" | "setFormData" | "globalData">>;
4
4
  export default _default;
5
5
  import React from 'react';
@@ -137,3 +137,4 @@ export const POST_UPGRADE: "POST_UPGRADE";
137
137
  export const RESET_LOGIN_REQUEST: "RESET_LOGIN_REQUEST";
138
138
  export const GET_SITE: "GET_SITE";
139
139
  export const GET_NAVROOT: "GET_NAVROOT";
140
+ export const SET_FORM_DATA: "SET_FORM_DATA";
@@ -4,4 +4,4 @@
4
4
  * @param {Object} state Current state.
5
5
  * @returns {Object} New state.
6
6
  */
7
- export default function form(state?: any): any;
7
+ export default function form(state?: any, action?: {}): any;