@plone/volto 17.0.0-alpha.5 → 17.0.0-alpha.7

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 (71) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +91 -13
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +4 -4
  5. package/locales/de/LC_MESSAGES/volto.po +17 -17
  6. package/locales/de.json +1 -1
  7. package/package.json +3 -2
  8. package/packages/volto-slate/package.json +1 -1
  9. package/src/actions/language/language.js +8 -8
  10. package/src/components/manage/Add/Add.jsx +2 -2
  11. package/src/components/manage/Blocks/Listing/Edit.jsx +0 -14
  12. package/src/components/manage/Blocks/Listing/getAsyncData.js +10 -2
  13. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +18 -13
  14. package/src/components/manage/Blocks/Search/SearchBlockView.jsx +2 -1
  15. package/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx +4 -1
  16. package/src/components/manage/Contents/Contents.jsx +16 -14
  17. package/src/components/manage/Controlpanels/Controlpanels.jsx +190 -224
  18. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +46 -7
  19. package/src/components/manage/Form/InlineForm.jsx +39 -9
  20. package/src/components/manage/Form/InlineFormState.js +8 -0
  21. package/src/components/manage/Multilingual/CreateTranslation.jsx +2 -2
  22. package/src/components/manage/Multilingual/TranslationObject.jsx +4 -3
  23. package/src/components/manage/Preferences/PersonalPreferences.jsx +2 -2
  24. package/src/components/manage/Toolbar/Types.jsx +2 -2
  25. package/src/components/manage/Widgets/DatetimeWidget.jsx +9 -5
  26. package/src/components/manage/Widgets/ObjectListWidget.jsx +3 -8
  27. package/src/components/manage/Widgets/RecurrenceWidget/ByDayField.jsx +2 -1
  28. package/src/components/manage/Widgets/RecurrenceWidget/MonthOfTheYearField.jsx +2 -1
  29. package/src/components/manage/Widgets/RecurrenceWidget/Occurences.jsx +2 -1
  30. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +7 -2
  31. package/src/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthField.jsx +2 -1
  32. package/src/components/theme/Footer/Footer.jsx +2 -13
  33. package/src/components/theme/Icon/Icon.jsx +2 -2
  34. package/src/components/theme/LanguageSelector/LanguageSelector.js +8 -3
  35. package/src/components/theme/Login/Login.jsx +1 -0
  36. package/src/components/theme/Logo/Logo.jsx +2 -1
  37. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +2 -2
  38. package/src/components/theme/Navigation/NavItem.jsx +4 -2
  39. package/src/components/theme/Sitemap/Sitemap.jsx +5 -3
  40. package/src/components/theme/View/EventDatesInfo.jsx +2 -1
  41. package/src/components/theme/Widgets/DateWidget.jsx +2 -1
  42. package/src/components/theme/Widgets/DatetimeWidget.jsx +2 -1
  43. package/src/config/index.js +1 -0
  44. package/src/helpers/Robots/Robots.js +16 -1
  45. package/src/helpers/Url/Url.js +8 -3
  46. package/src/helpers/Url/Url.test.js +14 -0
  47. package/src/helpers/Utils/Utils.js +38 -13
  48. package/src/helpers/Utils/Utils.test.js +4 -4
  49. package/src/helpers/index.js +7 -2
  50. package/src/middleware/Api.test.js +54 -0
  51. package/src/middleware/api.js +8 -4
  52. package/src/server.jsx +28 -23
  53. package/test-setup-config.js +1 -0
  54. package/theme/themes/pastanaga/extras/sidebar.less +4 -0
  55. package/.changelog.draft +0 -13
  56. package/.editorconfig +0 -36
  57. package/.storybook/main.js +0 -127
  58. package/.storybook/manager.js +0 -15
  59. package/.storybook/preview.js +0 -21
  60. package/.storybook/static/previewImage.svg +0 -48
  61. package/.vale.ini +0 -10
  62. package/.yarnrc.yml +0 -5
  63. package/jsdoc.json +0 -16
  64. package/netlify.toml +0 -5
  65. package/pyvenv.cfg +0 -3
  66. package/share/man/man1/ttx.1 +0 -225
  67. package/styles/Vocab/Base/accept.txt +0 -0
  68. package/styles/Vocab/Base/reject.txt +0 -0
  69. package/styles/Vocab/Plone/accept.txt +0 -10
  70. package/styles/Vocab/Plone/reject.txt +0 -5
  71. package/towncrier.toml +0 -33
@@ -3,16 +3,16 @@
3
3
  * @module components/manage/Controlpanels/Controlpanels
4
4
  */
5
5
 
6
- import React, { Component } from 'react';
7
6
  import PropTypes from 'prop-types';
7
+ import { useState, useEffect } from 'react';
8
8
  import { connect } from 'react-redux';
9
9
  import { compose } from 'redux';
10
10
  import { Link } from 'react-router-dom';
11
11
  import { concat, filter, last, map, uniqBy } from 'lodash';
12
12
  import { Portal } from 'react-portal';
13
- import { Helmet } from '@plone/volto/helpers';
13
+ import { asyncConnect, Helmet } from '@plone/volto/helpers';
14
14
  import { Container, Grid, Header, Message, Segment } from 'semantic-ui-react';
15
- import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
15
+ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
16
16
 
17
17
  import { listControlpanels, getSystemInformation } from '@plone/volto/actions';
18
18
  import { Error, Icon, Toolbar, VersionOverview } from '@plone/volto/components';
@@ -94,185 +94,126 @@ const messages = defineMessages({
94
94
 
95
95
  /**
96
96
  * Controlpanels container class.
97
- * @class Controlpanels
98
- * @extends Component
99
97
  */
100
- class Controlpanels extends Component {
101
- /**
102
- * Property types.
103
- * @property {Object} propTypes Property types.
104
- * @static
105
- */
106
- static propTypes = {
107
- listControlpanels: PropTypes.func.isRequired,
108
- controlpanels: PropTypes.arrayOf(
109
- PropTypes.shape({
110
- '@id': PropTypes.string,
111
- group: PropTypes.string,
112
- title: PropTypes.string,
113
- }),
114
- ).isRequired,
115
- pathname: PropTypes.string.isRequired,
116
- };
98
+ function Controlpanels({
99
+ controlpanels,
100
+ controlpanelsRequest,
101
+ systemInformation,
102
+ pathname,
103
+ }) {
104
+ const intl = useIntl();
105
+ const [isClient, setIsClient] = useState(false);
117
106
 
118
- /**
119
- * Constructor
120
- * @method constructor
121
- * @param {Object} props Component properties
122
- * @constructs EditComponent
123
- */
124
- constructor(props) {
125
- super(props);
126
- this.state = {
127
- error: null,
128
- isClient: false,
129
- };
130
- }
107
+ useEffect(() => {
108
+ setIsClient(true);
109
+ }, []);
131
110
 
132
- /**
133
- * Component did mount
134
- * @method componentDidMount
135
- * @returns {undefined}
136
- */
137
- componentDidMount() {
138
- this.props.listControlpanels();
139
- this.props.getSystemInformation();
140
- this.setState({ isClient: true });
141
- }
111
+ const error = controlpanelsRequest?.error;
142
112
 
143
- UNSAFE_componentWillReceiveProps(nextProps) {
144
- // Error
145
- if (
146
- this.props.controlpanelsRequest.loading &&
147
- nextProps.controlpanelsRequest.error
148
- ) {
149
- this.setState({
150
- error: nextProps.controlpanelsRequest.error,
151
- });
152
- }
113
+ if (error) {
114
+ return <Error error={error} />;
153
115
  }
154
116
 
155
- /**
156
- * Render method.
157
- * @method render
158
- * @returns {string} Markup for the component.
159
- */
160
- render() {
161
- // Error
162
- if (this.state.error) {
163
- return <Error error={this.state.error} />;
164
- }
117
+ let customcontrolpanels = config.settings.controlpanels
118
+ ? config.settings.controlpanels.map((el) => {
119
+ el.group =
120
+ intl.formatMessage({
121
+ id: el.group,
122
+ defaultMessage: el.group,
123
+ }) || el.group;
124
+ return el;
125
+ })
126
+ : [];
127
+ const { filterControlPanels } = config.settings;
165
128
 
166
- let customcontrolpanels = config.settings.controlpanels
167
- ? config.settings.controlpanels.map((el) => {
168
- el.group =
169
- this.props.intl.formatMessage({
170
- id: el.group,
171
- defaultMessage: el.group,
172
- }) || el.group;
173
- return el;
174
- })
175
- : [];
176
- const { filterControlPanels } = config.settings;
177
- const controlpanels = map(
178
- concat(
179
- filterControlPanels(this.props.controlpanels),
180
- customcontrolpanels,
181
- [
182
- {
183
- '@id': '/addons',
184
- group: this.props.intl.formatMessage(messages.general),
185
- title: this.props.intl.formatMessage(messages.addons),
186
- },
187
- {
188
- '@id': '/database',
189
- group: this.props.intl.formatMessage(messages.general),
190
- title: this.props.intl.formatMessage(messages.database),
191
- },
192
- {
193
- '@id': '/rules',
194
- group: this.props.intl.formatMessage(messages.content),
195
- title: this.props.intl.formatMessage(messages.contentRules),
196
- },
197
- {
198
- '@id': '/undo',
199
- group: this.props.intl.formatMessage(messages.general),
200
- title: this.props.intl.formatMessage(messages.undo),
201
- },
202
- {
203
- '@id': '/aliases',
204
- group: this.props.intl.formatMessage(messages.general),
205
- title: this.props.intl.formatMessage(messages.urlmanagement),
206
- },
207
- {
208
- '@id': '/moderate-comments',
209
- group: this.props.intl.formatMessage(messages.content),
210
- title: this.props.intl.formatMessage(messages.moderatecomments),
211
- },
212
- {
213
- '@id': '/users',
214
- group: this.props.intl.formatMessage(
215
- messages.usersControlPanelCategory,
216
- ),
217
- title: this.props.intl.formatMessage(messages.users),
218
- },
219
- {
220
- '@id': '/usergroupmembership',
221
- group: this.props.intl.formatMessage(
222
- messages.usersControlPanelCategory,
223
- ),
224
- title: this.props.intl.formatMessage(
225
- messages.usergroupmemberbership,
226
- ),
227
- },
228
- {
229
- '@id': '/groups',
230
- group: this.props.intl.formatMessage(
231
- messages.usersControlPanelCategory,
232
- ),
233
- title: this.props.intl.formatMessage(messages.groups),
234
- },
235
- ],
236
- ),
237
- (controlpanel) => ({
238
- ...controlpanel,
239
- id: last(controlpanel['@id'].split('/')),
240
- }),
241
- );
242
- const groups = map(uniqBy(controlpanels, 'group'), 'group');
243
- const { controlPanelsIcons: icons } = config.settings;
129
+ const filteredControlPanels = map(
130
+ concat(filterControlPanels(controlpanels), customcontrolpanels, [
131
+ {
132
+ '@id': '/addons',
133
+ group: intl.formatMessage(messages.general),
134
+ title: intl.formatMessage(messages.addons),
135
+ },
136
+ {
137
+ '@id': '/database',
138
+ group: intl.formatMessage(messages.general),
139
+ title: intl.formatMessage(messages.database),
140
+ },
141
+ {
142
+ '@id': '/rules',
143
+ group: intl.formatMessage(messages.content),
144
+ title: intl.formatMessage(messages.contentRules),
145
+ },
146
+ {
147
+ '@id': '/undo',
148
+ group: intl.formatMessage(messages.general),
149
+ title: intl.formatMessage(messages.undo),
150
+ },
151
+ {
152
+ '@id': '/aliases',
153
+ group: intl.formatMessage(messages.general),
154
+ title: intl.formatMessage(messages.urlmanagement),
155
+ },
156
+ {
157
+ '@id': '/moderate-comments',
158
+ group: intl.formatMessage(messages.content),
159
+ title: intl.formatMessage(messages.moderatecomments),
160
+ },
161
+ {
162
+ '@id': '/users',
163
+ group: intl.formatMessage(messages.usersControlPanelCategory),
164
+ title: intl.formatMessage(messages.users),
165
+ },
166
+ {
167
+ '@id': '/usergroupmembership',
168
+ group: intl.formatMessage(messages.usersControlPanelCategory),
169
+ title: intl.formatMessage(messages.usergroupmemberbership),
170
+ },
171
+ {
172
+ '@id': '/groups',
173
+ group: intl.formatMessage(messages.usersControlPanelCategory),
174
+ title: intl.formatMessage(messages.groups),
175
+ },
176
+ ]),
177
+ (controlpanel) => ({
178
+ ...controlpanel,
179
+ id: last(controlpanel['@id'].split('/')),
180
+ }),
181
+ );
182
+ const groups = map(uniqBy(filteredControlPanels, 'group'), 'group');
183
+ const { controlPanelsIcons: icons } = config.settings;
244
184
 
245
- return (
246
- <div className="view-wrapper">
247
- <Helmet title={this.props.intl.formatMessage(messages.sitesetup)} />
248
- <Container className="controlpanel">
249
- <Segment.Group raised>
250
- <Segment className="primary">
251
- <FormattedMessage id="Site Setup" defaultMessage="Site Setup" />
252
- </Segment>
253
- {this.props.systemInformation &&
254
- this.props.systemInformation.upgrade && (
255
- <Message attached warning>
256
- <FormattedMessage
257
- id="The site configuration is outdated and needs to be upgraded."
258
- defaultMessage="The site configuration is outdated and needs to be upgraded."
259
- />{' '}
260
- <Link to={`/controlpanel/plone-upgrade`}>
261
- <FormattedMessage
262
- id="Please continue with the upgrade."
263
- defaultMessage="Please continue with the upgrade."
264
- />
265
- </Link>
266
- </Message>
267
- )}
268
- {map(groups, (group) => [
269
- <Segment key={`header-${group}`} secondary>
270
- {group}
271
- </Segment>,
272
- <Segment key={`body-${group}`} attached>
273
- <Grid doubling columns={6}>
274
- <Grid.Row>
275
- {map(filter(controlpanels, { group }), (controlpanel) => (
185
+ return (
186
+ <div className="view-wrapper">
187
+ <Helmet title={intl.formatMessage(messages.sitesetup)} />
188
+ <Container className="controlpanel">
189
+ <Segment.Group raised>
190
+ <Segment className="primary">
191
+ <FormattedMessage id="Site Setup" defaultMessage="Site Setup" />
192
+ </Segment>
193
+ {systemInformation && systemInformation.upgrade && (
194
+ <Message attached warning>
195
+ <FormattedMessage
196
+ id="The site configuration is outdated and needs to be upgraded."
197
+ defaultMessage="The site configuration is outdated and needs to be upgraded."
198
+ />{' '}
199
+ <Link to={`/controlpanel/plone-upgrade`}>
200
+ <FormattedMessage
201
+ id="Please continue with the upgrade."
202
+ defaultMessage="Please continue with the upgrade."
203
+ />
204
+ </Link>
205
+ </Message>
206
+ )}
207
+ {map(groups, (group) => [
208
+ <Segment key={`header-${group}`} secondary>
209
+ {group}
210
+ </Segment>,
211
+ <Segment key={`body-${group}`} attached>
212
+ <Grid doubling columns={6}>
213
+ <Grid.Row>
214
+ {map(
215
+ filter(filteredControlPanels, { group }),
216
+ (controlpanel) => (
276
217
  <Grid.Column key={controlpanel.id}>
277
218
  <Link to={`/controlpanel/${controlpanel.id}`}>
278
219
  <Header as="h3" icon textAlign="center">
@@ -286,58 +227,83 @@ class Controlpanels extends Component {
286
227
  </Header>
287
228
  </Link>
288
229
  </Grid.Column>
289
- ))}
290
- </Grid.Row>
291
- </Grid>
292
- </Segment>,
293
- ])}
294
- </Segment.Group>
295
- <Segment.Group raised>
296
- <Segment className="primary">
297
- <FormattedMessage
298
- id="Version Overview"
299
- defaultMessage="Version Overview"
300
- />
301
- </Segment>
302
- <Segment attached>
303
- {this.props.systemInformation ? (
304
- <VersionOverview {...this.props.systemInformation} />
305
- ) : null}
306
- </Segment>
307
- </Segment.Group>
308
- </Container>
309
- {this.state.isClient && (
310
- <Portal node={document.getElementById('toolbar')}>
311
- <Toolbar
312
- pathname={this.props.pathname}
313
- hideDefaultViewButtons
314
- inner={
315
- <Link to="/" className="item">
316
- <Icon
317
- name={backSVG}
318
- className="contents circled"
319
- size="30px"
320
- title={this.props.intl.formatMessage(messages.back)}
321
- />
322
- </Link>
323
- }
230
+ ),
231
+ )}
232
+ </Grid.Row>
233
+ </Grid>
234
+ </Segment>,
235
+ ])}
236
+ </Segment.Group>
237
+ <Segment.Group raised>
238
+ <Segment className="primary">
239
+ <FormattedMessage
240
+ id="Version Overview"
241
+ defaultMessage="Version Overview"
324
242
  />
325
- </Portal>
326
- )}
327
- </div>
328
- );
329
- }
243
+ </Segment>
244
+ <Segment attached>
245
+ {systemInformation ? (
246
+ <VersionOverview {...systemInformation} />
247
+ ) : null}
248
+ </Segment>
249
+ </Segment.Group>
250
+ </Container>
251
+ {isClient && (
252
+ <Portal node={document.getElementById('toolbar')}>
253
+ <Toolbar
254
+ pathname={pathname}
255
+ hideDefaultViewButtons
256
+ inner={
257
+ <Link to="/" className="item">
258
+ <Icon
259
+ name={backSVG}
260
+ className="contents circled"
261
+ size="30px"
262
+ title={intl.formatMessage(messages.back)}
263
+ />
264
+ </Link>
265
+ }
266
+ />
267
+ </Portal>
268
+ )}
269
+ </div>
270
+ );
330
271
  }
331
272
 
332
- export default compose(
333
- injectIntl,
334
- connect(
335
- (state, props) => ({
336
- controlpanels: state.controlpanels.controlpanels,
337
- controlpanelsRequest: state.controlpanels.list,
338
- pathname: props.location.pathname,
339
- systemInformation: state.controlpanels.systeminformation,
273
+ /**
274
+ * Property types.
275
+ * @property {Object} propTypes Property types.
276
+ * @static
277
+ */
278
+ Controlpanels.propTypes = {
279
+ controlpanels: PropTypes.arrayOf(
280
+ PropTypes.shape({
281
+ '@id': PropTypes.string,
282
+ group: PropTypes.string,
283
+ title: PropTypes.string,
340
284
  }),
341
- { listControlpanels, getSystemInformation },
342
- ),
285
+ ).isRequired,
286
+ pathname: PropTypes.string.isRequired,
287
+ };
288
+
289
+ export default compose(
290
+ connect((state, props) => ({
291
+ controlpanels: state.controlpanels.controlpanels,
292
+ controlpanelsRequest: state.controlpanels.list,
293
+ pathname: props.location.pathname,
294
+ systemInformation: state.controlpanels.systeminformation,
295
+ })),
296
+
297
+ asyncConnect([
298
+ {
299
+ key: 'controlpanels',
300
+ promise: async ({ location, store: { dispatch } }) =>
301
+ await dispatch(listControlpanels()),
302
+ },
303
+ {
304
+ key: 'systemInformation',
305
+ promise: async ({ location, store: { dispatch } }) =>
306
+ await dispatch(getSystemInformation()),
307
+ },
308
+ ]),
343
309
  )(Controlpanels);
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import renderer from 'react-test-renderer';
3
- import configureStore from 'redux-mock-store';
4
2
  import { Provider } from 'react-intl-redux';
5
3
  import { MemoryRouter } from 'react-router-dom';
4
+ import renderer from 'react-test-renderer';
5
+ import configureStore from 'redux-mock-store';
6
6
 
7
- import Controlpanels from './Controlpanels';
8
7
  import config from '@plone/volto/registry';
8
+ import Controlpanels from './Controlpanels';
9
9
 
10
10
  const mockStore = configureStore();
11
11
 
@@ -20,7 +20,30 @@ jest.mock('./VersionOverview', () =>
20
20
  describe('Controlpanels', () => {
21
21
  it('renders a controlpanels component', () => {
22
22
  const store = mockStore({
23
- controlpanels: {
23
+ controlpanels: [
24
+ {
25
+ '@id': 'http://localhost:8080/Plone/@controlpanels/date-and-time',
26
+ group: 'General',
27
+ title: 'Date and Time',
28
+ },
29
+ {
30
+ '@id': 'http://localhost:8080/Plone/@controlpanels/lang',
31
+ group: 'General',
32
+ title: 'Language',
33
+ },
34
+ {
35
+ '@id': 'http://localhost:8080/Plone/@controlpanels/editing',
36
+ group: 'Content',
37
+ title: 'Editing',
38
+ },
39
+ {
40
+ '@id': 'http://localhost:8080/Plone/@controlpanels/security',
41
+ group: 'Security',
42
+ title: 'test',
43
+ },
44
+ ],
45
+ reduxAsyncConnect: {
46
+ // Mocked in redux async connect as it isn't fetch client-side.
24
47
  controlpanels: [
25
48
  {
26
49
  '@id': 'http://localhost:8080/Plone/@controlpanels/date-and-time',
@@ -40,10 +63,11 @@ describe('Controlpanels', () => {
40
63
  {
41
64
  '@id': 'http://localhost:8080/Plone/@controlpanels/security',
42
65
  group: 'Security',
43
- title: 'Security',
66
+ title: 'test',
44
67
  },
45
68
  ],
46
69
  },
70
+ router: { location: '/blog' },
47
71
  intl: {
48
72
  locale: 'en',
49
73
  messages: {},
@@ -62,9 +86,24 @@ describe('Controlpanels', () => {
62
86
 
63
87
  it('renders an additional control panel', () => {
64
88
  const store = mockStore({
65
- controlpanels: {
66
- controlpanels: [],
89
+ controlpanels: [
90
+ {
91
+ '@id': 'http://localhost:8080/Plone/@controlpanels/security',
92
+ group: 'Security',
93
+ title: 'test',
94
+ },
95
+ ],
96
+ reduxAsyncConnect: {
97
+ // Mocked in redux async connect as it isn't fetch client-side.
98
+ controlpanels: [
99
+ {
100
+ '@id': 'http://localhost:8080/Plone/@controlpanels/security',
101
+ group: 'Security',
102
+ title: 'test',
103
+ },
104
+ ],
67
105
  },
106
+ router: { location: '/blog' },
68
107
  intl: {
69
108
  locale: 'en',
70
109
  messages: {},
@@ -4,13 +4,21 @@ import { Accordion, Segment, Message } from 'semantic-ui-react';
4
4
  import { defineMessages, injectIntl } from 'react-intl';
5
5
  import AnimateHeight from 'react-animate-height';
6
6
  import { keys, map, isEqual } from 'lodash';
7
-
7
+ import { useAtom } from 'jotai';
8
+ import { inlineFormFieldsetsState } from './InlineFormState';
9
+ import {
10
+ insertInArray,
11
+ removeFromArray,
12
+ arrayRange,
13
+ } from '@plone/volto/helpers/Utils/Utils';
8
14
  import { Field, Icon } from '@plone/volto/components';
9
15
  import { applySchemaDefaults } from '@plone/volto/helpers';
10
16
 
11
17
  import upSVG from '@plone/volto/icons/up-key.svg';
12
18
  import downSVG from '@plone/volto/icons/down-key.svg';
13
19
 
20
+ import config from '@plone/volto/registry';
21
+
14
22
  const messages = defineMessages({
15
23
  editValues: {
16
24
  id: 'Edit values',
@@ -70,12 +78,34 @@ const InlineForm = (props) => {
70
78
  // eslint-disable-next-line react-hooks/exhaustive-deps
71
79
  }, []);
72
80
 
73
- const [currentActiveFieldset, setCurrentActiveFieldset] = React.useState(0);
81
+ const [currentActiveFieldset, setCurrentActiveFieldset] = useAtom(
82
+ inlineFormFieldsetsState({
83
+ name: block,
84
+ fielsetList: other,
85
+ initialState: config.settings.blockSettingsTabFieldsetsInitialStateOpen
86
+ ? arrayRange(0, other.length - 1, 1)
87
+ : [],
88
+ }),
89
+ );
90
+
74
91
  function handleCurrentActiveFieldset(e, blockProps) {
75
92
  const { index } = blockProps;
76
- const newIndex = currentActiveFieldset === index ? -1 : index;
77
-
78
- setCurrentActiveFieldset(newIndex);
93
+ if (currentActiveFieldset.includes(index)) {
94
+ setCurrentActiveFieldset(
95
+ removeFromArray(
96
+ currentActiveFieldset,
97
+ currentActiveFieldset.indexOf(index),
98
+ ),
99
+ );
100
+ } else {
101
+ setCurrentActiveFieldset(
102
+ insertInArray(
103
+ currentActiveFieldset,
104
+ index,
105
+ currentActiveFieldset.length + 1,
106
+ ),
107
+ );
108
+ }
79
109
  }
80
110
 
81
111
  return (
@@ -136,22 +166,22 @@ const InlineForm = (props) => {
136
166
  <Accordion fluid styled className="form" key={fieldset.id}>
137
167
  <div key={fieldset.id} id={`blockform-fieldset-${fieldset.id}`}>
138
168
  <Accordion.Title
139
- active={currentActiveFieldset === index}
169
+ active={currentActiveFieldset.includes(index)}
140
170
  index={index}
141
171
  onClick={handleCurrentActiveFieldset}
142
172
  >
143
173
  {fieldset.title && <>{fieldset.title}</>}
144
- {currentActiveFieldset === index ? (
174
+ {currentActiveFieldset.includes(index) ? (
145
175
  <Icon name={upSVG} size="20px" />
146
176
  ) : (
147
177
  <Icon name={downSVG} size="20px" />
148
178
  )}
149
179
  </Accordion.Title>
150
- <Accordion.Content active={currentActiveFieldset === index}>
180
+ <Accordion.Content active={currentActiveFieldset.includes(index)}>
151
181
  <AnimateHeight
152
182
  animateOpacity
153
183
  duration={500}
154
- height={currentActiveFieldset === index ? 'auto' : 0}
184
+ height={currentActiveFieldset.includes(index) ? 'auto' : 0}
155
185
  >
156
186
  <Segment className="attached">
157
187
  {map(fieldset.fields, (field) => (
@@ -0,0 +1,8 @@
1
+ import { atom } from 'jotai';
2
+ import { atomFamily } from 'jotai/utils';
3
+ import { isEqual } from 'lodash';
4
+
5
+ export const inlineFormFieldsetsState = atomFamily(
6
+ ({ name, initialState }) => atom(initialState),
7
+ (a, b) => a.name === b.name && isEqual(a.fielsetList, b.fielsetList),
8
+ );
@@ -6,7 +6,7 @@ import {
6
6
  getTranslationLocator,
7
7
  getContent,
8
8
  } from '@plone/volto/actions';
9
- import { flattenToAppURL, normalizeLanguageName } from '@plone/volto/helpers';
9
+ import { flattenToAppURL, toGettextLang } from '@plone/volto/helpers';
10
10
  import config from '@plone/volto/registry';
11
11
 
12
12
  const CreateTranslation = (props) => {
@@ -33,7 +33,7 @@ const CreateTranslation = (props) => {
33
33
  return () => {
34
34
  // We change the interface language
35
35
  if (config.settings.supportedLanguages.includes(language)) {
36
- const langFileName = normalizeLanguageName(language);
36
+ const langFileName = toGettextLang(language);
37
37
  import('@root/../locales/' + langFileName + '.json').then((locale) => {
38
38
  dispatch(changeLanguage(language, locale.default));
39
39
  });