@plone/volto 16.30.2 → 16.31.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.draft CHANGED
@@ -1,11 +1,8 @@
1
- ## 16.30.2 (2024-01-24)
1
+ ## 16.31.0 (2024-02-26)
2
2
 
3
- ### Bugfix
3
+ ### Feature
4
4
 
5
- - Removed git merge conflicts from french, english and brazilian volto.po locale files. @ichim-david [#5682](https://github.com/plone/volto/issues/5682)
6
-
7
- ### Internal
8
-
9
- - Update versions of deps for Plone 5 @sneridagh [#5686](https://github.com/plone/volto/issues/5686)
5
+ - Allow editor to edit metadata during bulk upload. @iFlameing [#5549](https://github.com/plone/volto/issues/5549)
6
+ - Add global form state. @robgietema [#5721](https://github.com/plone/volto/issues/5721)
10
7
 
11
8
 
Binary file
package/CHANGELOG.md CHANGED
@@ -8,6 +8,21 @@
8
8
 
9
9
  <!-- towncrier release notes start -->
10
10
 
11
+ ## 16.31.0 (2024-02-26)
12
+
13
+ ### Feature
14
+
15
+ - Allow editor to edit metadata during bulk upload. @iFlameing [#5549](https://github.com/plone/volto/issues/5549)
16
+ - Add global form state. @robgietema [#5721](https://github.com/plone/volto/issues/5721)
17
+
18
+ ## 16.30.3 (2024-02-15)
19
+
20
+ ### Bugfix
21
+
22
+ - Fix `links-to-item` should be a protected route. @iFlameing [#5666](https://github.com/plone/volto/issues/5666)
23
+ - Fixed listing SSR rendering by sending `subrequestId` instead of `id` only within `getAsyncData`, similar to calling `getQueryStringResults` directly. @ichim-david [#5688](https://github.com/plone/volto/issues/5688)
24
+ - Add extra wait calls to listing block tests to avoid sporadic failures. @ichim-david [#5753](https://github.com/plone/volto/issues/5753)
25
+
11
26
  ## 16.30.2 (2024-01-24)
12
27
 
13
28
  ### Bugfix
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  import '@testing-library/cypress/add-commands';
2
3
  import { getIfExists } from '../helpers';
3
4
  import { ploneAuth } from './constants';
@@ -38,6 +39,30 @@ Cypress.Commands.add('isInViewport', (element) => {
38
39
  });
39
40
  });
40
41
 
42
+ // --- isInHTML ----------------------------------------------------------
43
+ Cypress.Commands.add('isInHTML', ({ parent = 'body', content }) => {
44
+ cy.url().then((currentUrl) => {
45
+ // sometimes the cy command is called when the url is still at content/edit
46
+ // we want to query the html markup of the content, not the edit form
47
+ const url =
48
+ currentUrl.indexOf('/edit') !== -1
49
+ ? currentUrl.split('/edit')[0]
50
+ : currentUrl;
51
+ cy.request({
52
+ method: 'GET',
53
+ url: url,
54
+ }).then((response) => {
55
+ const html = Cypress.$(response.body);
56
+ if (content.startsWith('.') || content.startsWith('#')) {
57
+ return expect(html.find(parent)).to.have.descendants(content);
58
+ } else {
59
+ // check if parent contains the content text string in its HTML output
60
+ return expect(html.find(parent)).to.contain(content);
61
+ }
62
+ });
63
+ });
64
+ });
65
+
41
66
  // --- AUTOLOGIN -------------------------------------------------------------
42
67
  Cypress.Commands.add('autologin', (usr, pass) => {
43
68
  let api_url, user, password;
@@ -698,7 +723,7 @@ Cypress.Commands.add('clearSlate', (selector) => {
698
723
  return cy
699
724
  .get(selector)
700
725
  .focus()
701
- .click()
726
+ .click({ force: true }) // fix sporadic failure this element is currently animating
702
727
  .wait(1000)
703
728
  .type('{selectAll}')
704
729
  .wait(1000)
@@ -801,7 +826,6 @@ function getTextNode(el, match) {
801
826
  return walk.nextNode();
802
827
  }
803
828
 
804
- const nodes = [];
805
829
  let node;
806
830
  while ((node = walk.nextNode())) {
807
831
  if (node.wholeText.includes(match)) {
@@ -830,7 +854,7 @@ function createHtmlPasteEvent(htmlContent) {
830
854
 
831
855
  Cypress.Commands.add('addNewBlock', (blockName, createNewSlate = false) => {
832
856
  let block;
833
- block = cy.getSlate(createNewSlate).type(`/${blockName}{enter}`);
857
+ block = cy.getSlate(createNewSlate).click().type(`/${blockName}{enter}`);
834
858
  return block;
835
859
  });
836
860
 
@@ -884,5 +908,38 @@ Cypress.Commands.add('configureListingWith', (contentType) => {
884
908
  '.querystring-widget .fields:first-of-type > .field .react-select__menu .react-select__option',
885
909
  )
886
910
  .contains(contentType)
911
+
887
912
  .click();
888
913
  });
914
+
915
+ Cypress.Commands.add(
916
+ 'addLocationQuerystring',
917
+ (option = 'Relative path', value) => {
918
+ cy.get('.block-editor-listing').click();
919
+ cy.get('.querystring-widget .fields').contains('Add criteria').click();
920
+ cy.get('.querystring-widget .react-select__menu .react-select__option')
921
+ .contains('Location')
922
+ .click();
923
+
924
+ cy.get('.querystring-widget .fields').contains('Absolute path').click();
925
+ cy.get(
926
+ '.querystring-widget .fields .react-select__menu .react-select__option',
927
+ )
928
+ .contains(option)
929
+ .click();
930
+ if (value) {
931
+ cy.get('.querystring-widget .fields .input')
932
+ .click()
933
+ .type(`${value}{enter}`);
934
+ }
935
+ },
936
+ );
937
+
938
+ Cypress.Commands.add('queryCounter', (path, steps, number = 1) => {
939
+ cy.intercept(path, cy.spy().as('counterName'));
940
+ steps.forEach((element) => {
941
+ element();
942
+ });
943
+
944
+ cy.get('@counterName').its('callCount').should('equal', number);
945
+ });
@@ -1,6 +1,3 @@
1
- # Translation of plone.pot to Catalan
2
- # Ramon Navarro Bosch <bloodbare a plone.org>, 2021
3
- # Víctor Fernández de Alba <victor a plone.org>, 2021
4
1
  msgid ""
5
2
  msgstr ""
6
3
  "Project-Id-Version: Plone\n"
@@ -1,4 +1,3 @@
1
- # Translation of plone.pot to German
2
1
  msgid ""
3
2
  msgstr ""
4
3
  "Project-Id-Version: Plone\n"
@@ -1,7 +1,3 @@
1
- # Gettext Message File for Plone
2
- # Translators:
3
- # Leonardo J. Caballero G. <leonardocaballero@gmail.com>, 2019, 2022, 2023.
4
- # Mikel Larreategi <mlarreategi@codesyntax.com>, 2021, 2022.
5
1
  msgid ""
6
2
  msgstr ""
7
3
  "Project-Id-Version: Plone\n"
@@ -1,9 +1,3 @@
1
- #
2
- # Translators:
3
- # Petri Savolainen <petri.savolainen@iki.fi>, 2020
4
- # Asko Soukka <asko.soukka@iki.fi>, 2020
5
- # Rikupekka Oksanen <rioksane@gmail.com>, 2022
6
- #
7
1
  msgid ""
8
2
  msgstr ""
9
3
  "Project-Id-Version: Plone\n"
@@ -1,13 +1,3 @@
1
- # Translation of plone.pot to French
2
- # French Translation Team <plone-i18n@lists.sourceforge.net>, 2003-2006.
3
- # Sebastien Douche <sdouche@gmail.com>, 2005-2007.
4
- # Encolpe Degoute <encolpe.degoute@ingeniweb.com>, 2006, 2007, 2008.
5
- # Gilles Lenfant <gilles.lenfant@ingeniweb.com>, 2008.
6
- # Encolpe Degoute <encolpe@gmail.com>, 2008.
7
- # Vincent Fretin <vincent.fretin@gmail.com>, 2009.
8
- # Kevin Deldycke <kevin@deldycke.com>, 2009.
9
- # JeanMichel FRANCOIS <toutpt@gmail.com>, 2010.
10
- # Denis Bitouzé <denis.bitouze@univ-littoral.fr>, 2019.
11
1
  msgid ""
12
2
  msgstr ""
13
3
  "Project-Id-Version: volto-fr\n"
@@ -1,6 +1,3 @@
1
- # Manabu TERADA <terada@cmscom.jp> 2019
2
- # Peacock <peacock0803sz@gmail.com> 2019
3
- # zenich 2021
4
1
  msgid ""
5
2
  msgstr ""
6
3
  "Project-Id-Version: Plone\n"
@@ -1,6 +1,3 @@
1
- # Translators:
2
- # Rob Gietema, 2017
3
- # Kim Paulissen, 2022
4
1
  msgid ""
5
2
  msgstr ""
6
3
  "Project-Id-Version: PlonenPOT-Creation-Date: 2017-04-27T19:30:59.079Z\n"
@@ -1,6 +1,3 @@
1
- # Translators:
2
- # Emanuel de Jesus <emanuel.angelo@gmail.com>, 2019
3
- #
4
1
  msgid ""
5
2
  msgstr ""
6
3
  "Project-Id-Version: Plone\n"
@@ -1,7 +1,3 @@
1
- # Translators:
2
- # Léu Almeida <leo@webid.net.br>, 2019
3
- # Emanuel Angelo, emansije <emanuel.angelo@gmail.com>, 2019
4
- # Érico Andrei <ericof@plone.org>, 2021
5
1
  msgid ""
6
2
  msgstr ""
7
3
  "Project-Id-Version: Plone\n"
package/locales/volto.pot CHANGED
@@ -1,7 +1,7 @@
1
1
  msgid ""
2
2
  msgstr ""
3
3
  "Project-Id-Version: Plone\n"
4
- "POT-Creation-Date: 2023-11-13T11:51:31.002Z\n"
4
+ "POT-Creation-Date: 2024-02-23T10:42:17.489Z\n"
5
5
  "Last-Translator: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
6
6
  "Language-Team: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
7
7
  "MIME-Version: 1.0\n"
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "16.30.2",
12
+ "version": "16.31.0",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -0,0 +1 @@
1
+ Fix sidebar form update. @robgietema
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plone/volto-slate",
3
- "version": "16.30.2",
3
+ "version": "16.31.0",
4
4
  "description": "Slate.js integration with Volto",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -5,7 +5,7 @@
5
5
 
6
6
  import React, { Component } from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { map, remove } from 'lodash';
8
+ import { isEmpty, map, remove } from 'lodash';
9
9
  import { Button, Table } from 'semantic-ui-react';
10
10
  import cx from 'classnames';
11
11
  import { defineMessages, injectIntl } from 'react-intl';
@@ -227,7 +227,7 @@ class Edit extends Component {
227
227
  * @returns {undefined}
228
228
  */
229
229
  componentDidMount() {
230
- if (!this.props.data.table) {
230
+ if (!this.props.data.table || isEmpty(this.props.data.table)) {
231
231
  this.props.onChangeBlock(this.props.block, {
232
232
  ...this.props.data,
233
233
  table: initialTable,
@@ -243,7 +243,7 @@ class Edit extends Component {
243
243
  * @returns {undefined}
244
244
  */
245
245
  UNSAFE_componentWillReceiveProps(nextProps) {
246
- if (!nextProps.data.table) {
246
+ if (!nextProps.data.table || isEmpty(nextProps.data.table)) {
247
247
  this.props.onChangeBlock(nextProps.block, {
248
248
  ...nextProps.data,
249
249
  table: initialTable,
@@ -528,10 +528,7 @@ class Edit extends Component {
528
528
  icon
529
529
  basic
530
530
  onClick={this.onDeleteRow}
531
- disabled={
532
- this.props.data.table &&
533
- this.props.data.table.rows.length === 1
534
- }
531
+ disabled={this.props.data.table?.rows?.length === 1}
535
532
  title={this.props.intl.formatMessage(messages.deleteRow)}
536
533
  aria-label={this.props.intl.formatMessage(messages.deleteRow)}
537
534
  >
@@ -569,10 +566,7 @@ class Edit extends Component {
569
566
  icon
570
567
  basic
571
568
  onClick={this.onDeleteCol}
572
- disabled={
573
- this.props.data.table &&
574
- this.props.data.table.rows[0].cells.length === 1
575
- }
569
+ disabled={this.props.data.table?.rows?.[0].cells.length === 1}
576
570
  title={this.props.intl.formatMessage(messages.deleteCol)}
577
571
  aria-label={this.props.intl.formatMessage(messages.deleteCol)}
578
572
  >
@@ -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
+ });
@@ -150,6 +150,7 @@ export {
150
150
  export { getQuerystring } from '@plone/volto/actions/querystring/querystring';
151
151
  export { getQueryStringResults } from '@plone/volto/actions/querystringsearch/querystringsearch';
152
152
  export { setSidebarTab } from '@plone/volto/actions/sidebar/sidebar';
153
+ export { setFormData } from '@plone/volto/actions/form/form';
153
154
  export {
154
155
  deleteLinkTranslation,
155
156
  getTranslationLocator,
@@ -359,6 +359,7 @@ class Add extends Component {
359
359
  onSelectForm={() => {
360
360
  this.setState({ formSelected: 'addForm' });
361
361
  }}
362
+ global
362
363
  />
363
364
  {this.state.isClient && (
364
365
  <Portal node={document.getElementById('toolbar')}>
@@ -1,14 +1,29 @@
1
1
  import { getQueryStringResults } from '@plone/volto/actions';
2
2
  import { resolveBlockExtensions } from '@plone/volto/helpers';
3
+ import qs from 'query-string';
4
+ import { slugify } from '@plone/volto/helpers/Utils/Utils';
5
+
6
+ const getCurrentPage = (location, id) => {
7
+ const pageQueryParam = qs.parse(location.search);
8
+ switch (Object.keys(pageQueryParam).length) {
9
+ case 0:
10
+ return 1;
11
+ case 1:
12
+ // when there is only one query param, it could be the simple page number or the sluggified block id
13
+ return pageQueryParam['page'] || pageQueryParam[slugify(`page-${id}`)];
14
+ default:
15
+ return pageQueryParam[slugify(`page-${id}`)];
16
+ }
17
+ };
18
+
19
+ export default function getListingBlockAsyncData(props) {
20
+ const { data, path, location, id, dispatch, blocksConfig, content } = props;
3
21
 
4
- export default function getListingBlockAsyncData({
5
- dispatch,
6
- data,
7
- path,
8
- blocksConfig,
9
- }) {
10
22
  const { resolvedExtensions } = resolveBlockExtensions(data, blocksConfig);
11
23
 
24
+ const subrequestID = content?.UID ? `${content?.UID}-${id}` : id;
25
+ const currentPage = getCurrentPage(location, id);
26
+
12
27
  return [
13
28
  dispatch(
14
29
  getQueryStringResults(
@@ -19,7 +34,8 @@ export default function getListingBlockAsyncData({
19
34
  ? { fullobjects: 1 }
20
35
  : { metadata_fields: '_all' }),
21
36
  },
22
- data.block,
37
+ subrequestID,
38
+ currentPage,
23
39
  ),
24
40
  ),
25
41
  ];
@@ -17,6 +17,7 @@ import {
17
17
  Modal,
18
18
  Table,
19
19
  Segment,
20
+ Input,
20
21
  } from 'semantic-ui-react';
21
22
  import loadable from '@loadable/component';
22
23
  import { concat, filter, map } from 'lodash';
@@ -150,7 +151,27 @@ class ContentsUploadModal extends Component {
150
151
  }
151
152
 
152
153
  /**
153
- * Submit handler
154
+ * Name change handler
155
+ * @method onChangeFileName
156
+ * @returns {undefined}
157
+ */
158
+
159
+ onChangeFileName(e, index) {
160
+ let copyOfFiles = [...this.state.files];
161
+ let originalFile = this.state.files[index];
162
+ let newFile = new File([originalFile], e.target.value, {
163
+ type: originalFile.type,
164
+ });
165
+ newFile.preview = originalFile.preview;
166
+ newFile.path = e.target.value;
167
+ copyOfFiles[index] = newFile;
168
+ this.setState({
169
+ files: copyOfFiles,
170
+ });
171
+ }
172
+
173
+ /**
174
+ * Submit handlers
154
175
  * @method onSubmit
155
176
  * @returns {undefined}
156
177
  */
@@ -187,7 +208,7 @@ class ContentsUploadModal extends Component {
187
208
  render() {
188
209
  return (
189
210
  this.props.open && (
190
- <Modal open={this.props.open}>
211
+ <Modal className="contents-upload-modal" open={this.props.open}>
191
212
  <Header>
192
213
  <FormattedMessage id="Upload files" defaultMessage="Upload files" />
193
214
  </Header>
@@ -269,8 +290,14 @@ class ContentsUploadModal extends Component {
269
290
  </Table.Header>
270
291
  <Table.Body>
271
292
  {map(this.state.files, (file, index) => (
272
- <Table.Row className="upload-row" key={file.name}>
273
- <Table.Cell>{file.name}</Table.Cell>
293
+ <Table.Row className="upload-row" key={index}>
294
+ <Table.Cell>
295
+ <Input
296
+ className="file-name"
297
+ value={file.name}
298
+ onChange={(e) => this.onChangeFileName(e, index)}
299
+ />
300
+ </Table.Cell>
274
301
  <Table.Cell>
275
302
  {file.lastModifiedDate && (
276
303
  <FormattedRelativeDate date={file.lastModifiedDate} />
@@ -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
  /**
@@ -198,6 +202,12 @@ class Form extends Component {
198
202
  }
199
203
  }
200
204
 
205
+ // Sync state to global state
206
+ if (this.props.global) {
207
+ this.props.setFormData(formData);
208
+ }
209
+
210
+ // Set initial state
201
211
  this.state = {
202
212
  formData,
203
213
  initialFormData: cloneDeep(formData),
@@ -244,14 +254,18 @@ class Form extends Component {
244
254
  }
245
255
 
246
256
  if (this.props.onChangeFormData) {
247
- if (
248
- // TODO: use fast-deep-equal
249
- JSON.stringify(prevState?.formData) !==
250
- JSON.stringify(this.state.formData)
251
- ) {
257
+ if (!isEqual(prevState?.formData, this.state.formData)) {
252
258
  this.props.onChangeFormData(this.state.formData);
253
259
  }
254
260
  }
261
+ if (
262
+ this.props.global &&
263
+ !isEqual(this.props.globalData, prevProps.globalData)
264
+ ) {
265
+ this.setState({
266
+ formData: this.props.globalData,
267
+ });
268
+ }
255
269
  }
256
270
 
257
271
  /**
@@ -325,15 +339,18 @@ class Form extends Component {
325
339
  onChangeField(id, value) {
326
340
  this.setState((prevState) => {
327
341
  const { errors, formData } = prevState;
342
+ const newFormData = {
343
+ ...formData,
344
+ // We need to catch also when the value equals false this fixes #888
345
+ [id]: value || (value !== undefined && isBoolean(value)) ? value : null,
346
+ };
328
347
  delete errors[id];
348
+ if (this.props.global) {
349
+ this.props.setFormData(newFormData);
350
+ }
329
351
  return {
330
352
  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
- },
353
+ formData: newFormData,
337
354
  // Changing the form data re-renders the select widget which causes the
338
355
  // focus to get lost. To circumvent this, we set the focus back to
339
356
  // the input.
@@ -355,14 +372,13 @@ class Form extends Component {
355
372
  onSelectBlock(id, isMultipleSelection, event) {
356
373
  let multiSelected = [];
357
374
  let selected = id;
375
+ const formData = this.state.formData;
358
376
 
359
377
  if (isMultipleSelection) {
360
378
  selected = null;
361
- const blocksLayoutFieldname = getBlocksLayoutFieldname(
362
- this.state.formData,
363
- );
379
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
364
380
 
365
- const blocks_layout = this.state.formData[blocksLayoutFieldname].items;
381
+ const blocks_layout = formData[blocksLayoutFieldname].items;
366
382
 
367
383
  if (event.shiftKey) {
368
384
  const anchor =
@@ -422,6 +438,9 @@ class Form extends Component {
422
438
  this.setState({
423
439
  formData: this.props.formData,
424
440
  });
441
+ if (this.props.global) {
442
+ this.props.setFormData(this.props.formData);
443
+ }
425
444
  }
426
445
  this.props.onCancel(event);
427
446
  }
@@ -433,6 +452,8 @@ class Form extends Component {
433
452
  * @returns {undefined}
434
453
  */
435
454
  onSubmit(event) {
455
+ const formData = this.state.formData;
456
+
436
457
  if (event) {
437
458
  event.preventDefault();
438
459
  }
@@ -440,7 +461,7 @@ class Form extends Component {
440
461
  const errors = this.props.schema
441
462
  ? FormValidation.validateFieldsPerFieldset({
442
463
  schema: this.props.schema,
443
- formData: this.state.formData,
464
+ formData,
444
465
  formatMessage: this.props.intl.formatMessage,
445
466
  })
446
467
  : {};
@@ -475,12 +496,15 @@ class Form extends Component {
475
496
  if (this.props.isEditForm) {
476
497
  this.props.onSubmit(this.getOnlyFormModifiedValues());
477
498
  } else {
478
- this.props.onSubmit(this.state.formData);
499
+ this.props.onSubmit(formData);
479
500
  }
480
501
  if (this.props.resetAfterSubmit) {
481
502
  this.setState({
482
503
  formData: this.props.formData,
483
504
  });
505
+ if (this.props.global) {
506
+ this.props.setFormData(this.props.formData);
507
+ }
484
508
  }
485
509
  }
486
510
  }
@@ -495,15 +519,15 @@ class Form extends Component {
495
519
  * @returns {undefined}
496
520
  */
497
521
  getOnlyFormModifiedValues = () => {
522
+ const formData = this.state.formData;
523
+
498
524
  const fieldsModified = Object.keys(
499
- difference(this.state.formData, this.state.initialFormData),
525
+ difference(formData, this.state.initialFormData),
500
526
  );
501
527
  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'],
528
+ ...pickBy(formData, (value, key) => fieldsModified.includes(key)),
529
+ ...(formData['@static_behaviors'] && {
530
+ '@static_behaviors': formData['@static_behaviors'],
507
531
  }),
508
532
  };
509
533
  };
@@ -549,7 +573,7 @@ class Form extends Component {
549
573
  navRoot,
550
574
  type,
551
575
  } = this.props;
552
- const { formData } = this.state;
576
+ const formData = this.state.formData;
553
577
  const schema = this.removeBlocksLayoutFields(originalSchema);
554
578
  const Container =
555
579
  config.getComponent({ name: 'Container' }).component || SemanticContainer;
@@ -560,17 +584,21 @@ class Form extends Component {
560
584
  this.state.isClient && (
561
585
  <Container>
562
586
  <BlocksToolbar
563
- formData={this.state.formData}
587
+ formData={formData}
564
588
  selectedBlock={this.state.selected}
565
589
  selectedBlocks={this.state.multiSelected}
566
- onChangeBlocks={(newBlockData) =>
590
+ onChangeBlocks={(newBlockData) => {
591
+ const newFormData = {
592
+ ...formData,
593
+ ...newBlockData,
594
+ };
567
595
  this.setState({
568
- formData: {
569
- ...formData,
570
- ...newBlockData,
571
- },
572
- })
573
- }
596
+ formData: newFormData,
597
+ });
598
+ if (this.props.global) {
599
+ this.props.setFormData(newFormData);
600
+ }
601
+ }}
574
602
  onSetSelectedBlocks={(blockIds) =>
575
603
  this.setState({ multiSelected: blockIds })
576
604
  }
@@ -578,22 +606,31 @@ class Form extends Component {
578
606
  />
579
607
  <UndoToolbar
580
608
  state={{
581
- formData: this.state.formData,
609
+ formData,
582
610
  selected: this.state.selected,
583
611
  multiSelected: this.state.multiSelected,
584
612
  }}
585
613
  enableHotKeys
586
- onUndoRedo={({ state }) => this.setState(state)}
614
+ onUndoRedo={({ state }) => {
615
+ if (this.props.global) {
616
+ this.props.setFormData(state.formData);
617
+ }
618
+ return this.setState(state);
619
+ }}
587
620
  />
588
621
  <BlocksForm
589
- onChangeFormData={(newFormData) =>
622
+ onChangeFormData={(newData) => {
623
+ const newFormData = {
624
+ ...formData,
625
+ ...newData,
626
+ };
590
627
  this.setState({
591
- formData: {
592
- ...formData,
593
- ...newFormData,
594
- },
595
- })
596
- }
628
+ formData: newFormData,
629
+ });
630
+ if (this.props.global) {
631
+ this.props.setFormData(newFormData);
632
+ }
633
+ }}
597
634
  onChangeField={this.onChangeField}
598
635
  onSelectBlock={this.onSelectBlock}
599
636
  properties={formData}
@@ -633,9 +670,9 @@ class Form extends Component {
633
670
  {...schema.properties[field]}
634
671
  id={field}
635
672
  fieldSet={item.title.toLowerCase()}
636
- formData={this.state.formData}
673
+ formData={formData}
637
674
  focus={this.state.inFocus[field]}
638
- value={this.state.formData?.[field]}
675
+ value={formData?.[field]}
639
676
  required={schema.required.indexOf(field) !== -1}
640
677
  onChange={this.onChangeField}
641
678
  onBlur={this.onBlurField}
@@ -698,10 +735,10 @@ class Form extends Component {
698
735
  {...schema.properties[field]}
699
736
  isDisabled={!this.props.editable}
700
737
  id={field}
701
- formData={this.state.formData}
738
+ formData={formData}
702
739
  fieldSet={item.title.toLowerCase()}
703
740
  focus={this.state.inFocus[field]}
704
- value={this.state.formData?.[field]}
741
+ value={formData?.[field]}
705
742
  required={schema.required.indexOf(field) !== -1}
706
743
  onChange={this.onChangeField}
707
744
  onBlur={this.onBlurField}
@@ -749,7 +786,7 @@ class Form extends Component {
749
786
  <Field
750
787
  {...schema.properties[field]}
751
788
  id={field}
752
- value={this.state.formData?.[field]}
789
+ value={formData?.[field]}
753
790
  required={schema.required.indexOf(field) !== -1}
754
791
  onChange={this.onChangeField}
755
792
  onBlur={this.onBlurField}
@@ -810,5 +847,12 @@ class Form extends Component {
810
847
  const FormIntl = injectIntl(Form, { forwardRef: true });
811
848
 
812
849
  export default compose(
813
- connect(null, { setSidebarTab }, null, { forwardRef: true }),
850
+ connect(
851
+ (state, props) => ({
852
+ globalData: state.form?.global,
853
+ }),
854
+ { setSidebarTab, setFormData },
855
+ null,
856
+ { forwardRef: true },
857
+ ),
814
858
  )(FormIntl);
@@ -242,6 +242,7 @@ export const fetchContent = async ({ store, location }) => {
242
242
  id,
243
243
  data,
244
244
  blocksConfig,
245
+ content,
245
246
  });
246
247
  if (!p?.length) {
247
248
  throw new Error(
@@ -138,3 +138,4 @@ export const REMOVE_ALIASES = 'REMOVE_ALIASES';
138
138
  export const GET_USERSCHEMA = 'GET_USERSCHEMA';
139
139
  export const GET_UPGRADE = 'GET_UPGRADE';
140
140
  export const POST_UPGRADE = 'POST_UPGRADE';
141
+ 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
  });
@@ -209,3 +209,14 @@
209
209
  align-items: center;
210
210
  margin-top: 2rem;
211
211
  }
212
+
213
+ /* Contents Upload Modal */
214
+ .contents-upload-modal {
215
+ .file-name {
216
+ width: 75%;
217
+
218
+ input {
219
+ padding: 0.67857143em 1em;
220
+ }
221
+ }
222
+ }