@plone/volto 17.1.1 → 17.3.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 +40 -0
- package/news/5298 bugfix +1 -0
- package/package.json +1 -1
- package/packages/volto-slate/package.json +1 -1
- package/src/components/manage/Blocks/Search/components/SelectStyling.jsx +1 -2
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +47 -13
- package/src/components/manage/Edit/Edit.jsx +11 -2
- package/src/components/manage/Widgets/DatetimeWidget.jsx +2 -0
- package/src/components/manage/Widgets/QueryWidget.jsx +6 -2
- package/src/components/theme/Footer/Footer.jsx +1 -0
- package/src/components/theme/Navigation/Navigation.jsx +1 -1
- package/src/components/theme/SkipLinks/SkipLinks.jsx +3 -1
- package/src/components/theme/SkipLinks/SkipLinks.test.jsx +6 -3
- package/src/helpers/Blocks/Blocks.js +120 -48
- package/src/helpers/Blocks/Blocks.test.js +75 -0
- package/src/helpers/Html/Html.jsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,46 @@
|
|
|
8
8
|
|
|
9
9
|
<!-- towncrier release notes start -->
|
|
10
10
|
|
|
11
|
+
## 17.3.0 (2023-10-27)
|
|
12
|
+
|
|
13
|
+
### Feature
|
|
14
|
+
|
|
15
|
+
- Updated aria-label for landmarks @ichim-david
|
|
16
|
+
Added landmark on sidebar @ichim-david
|
|
17
|
+
Added Pluggable section for skiplinks @ichim-david [#5290](https://github.com/plone/volto/issues/5290)
|
|
18
|
+
|
|
19
|
+
### Bugfix
|
|
20
|
+
|
|
21
|
+
- (FIX): put padding so the text is not clipped #5305 @dobri1408 [#5305](https://github.com/plone/volto/issues/5305)
|
|
22
|
+
- Fix compare translations view @sneridagh [#5327](https://github.com/plone/volto/issues/5327)
|
|
23
|
+
- Fix DatetimeWidget on FF, the button default if no type is set is sending the form. @sneridagh
|
|
24
|
+
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#formmethod [#5343](https://github.com/plone/volto/issues/5343)
|
|
25
|
+
|
|
26
|
+
### Internal
|
|
27
|
+
|
|
28
|
+
- For blocks that define their `blockSchema`, call `applyBlockDefaults` when creating the initial data for the blocks form.
|
|
29
|
+
It is now possible to define a block configuration function, `initialValue` that returns the initial value for a block. This is useful in use cases such as container blocks that want to create a complex initial data structure, to avoid the need to call `React.useEffect` on their initial block rendering and thus, avoid complex async "concurent" state mutations.
|
|
30
|
+
The `addBlock`, `mutateBlock`, `insertBlock` now allow passing a `blocksConfig` configuration object
|
|
31
|
+
|
|
32
|
+
@tiberiuichim [#5320](https://github.com/plone/volto/issues/5320)
|
|
33
|
+
- Add a new set of acceptance tests with the multilingual fixture using seamless mode. @sneridagh [#5332](https://github.com/plone/volto/issues/5332)
|
|
34
|
+
|
|
35
|
+
### Documentation
|
|
36
|
+
|
|
37
|
+
- Fix reference link to installation. @stevepiercy [#5328](https://github.com/plone/volto/issues/5328)
|
|
38
|
+
- Add upgrade docs for users of `@kitconcept/volto-blocks-grid` addon @sneridagh [#5333](https://github.com/plone/volto/issues/5333)
|
|
39
|
+
|
|
40
|
+
## 17.2.0 (2023-10-16)
|
|
41
|
+
|
|
42
|
+
### Feature
|
|
43
|
+
|
|
44
|
+
- add cypress test for search block via url - @ionlizarazu [#5298](https://github.com/plone/volto/issues/5298)
|
|
45
|
+
|
|
46
|
+
### Bugfix
|
|
47
|
+
|
|
48
|
+
- Fix adding multiple path criteria in search and listing blocks. @davisagli [#5317](https://github.com/plone/volto/issues/5317)
|
|
49
|
+
|
|
50
|
+
|
|
11
51
|
## 17.1.1 (2023-10-13)
|
|
12
52
|
|
|
13
53
|
### Bugfix
|
package/news/5298 bugfix
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fix the search block for search via url - @ionlizarazu
|
package/package.json
CHANGED
|
@@ -28,7 +28,14 @@ const PAQO = 'plone.app.querystring.operation';
|
|
|
28
28
|
* @function getInitialState
|
|
29
29
|
*
|
|
30
30
|
*/
|
|
31
|
-
function getInitialState(
|
|
31
|
+
function getInitialState(
|
|
32
|
+
data,
|
|
33
|
+
facets,
|
|
34
|
+
urlSearchText,
|
|
35
|
+
id,
|
|
36
|
+
sortOnParam,
|
|
37
|
+
sortOrderParam,
|
|
38
|
+
) {
|
|
32
39
|
const { types: facetWidgetTypes } =
|
|
33
40
|
config.blocks.blocksConfig.search.extensions.facetWidgets;
|
|
34
41
|
const facetSettings = data?.facets || [];
|
|
@@ -62,8 +69,8 @@ function getInitialState(data, facets, urlSearchText, id) {
|
|
|
62
69
|
]
|
|
63
70
|
: []),
|
|
64
71
|
],
|
|
65
|
-
sort_on: data.query?.sort_on,
|
|
66
|
-
sort_order: data.query?.sort_order,
|
|
72
|
+
sort_on: sortOnParam || data.query?.sort_on,
|
|
73
|
+
sort_order: sortOrderParam || data.query?.sort_order,
|
|
67
74
|
b_size: data.query?.b_size,
|
|
68
75
|
limit: data.query?.limit,
|
|
69
76
|
block: id,
|
|
@@ -257,7 +264,28 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
257
264
|
const multiFacets = data.facets
|
|
258
265
|
?.filter((facet) => facet?.multiple)
|
|
259
266
|
.map((facet) => facet?.field?.value);
|
|
260
|
-
const [facets, setFacets] = React.useState(
|
|
267
|
+
const [facets, setFacets] = React.useState(
|
|
268
|
+
Object.assign(
|
|
269
|
+
{},
|
|
270
|
+
...urlQuery.map(({ i, v }) => ({ [i]: v })), // TODO: the 'o' should be kept. This would be a major refactoring of the facets
|
|
271
|
+
|
|
272
|
+
// support for simple filters like ?Subject=something
|
|
273
|
+
// TODO: since the move to hash params this is no longer working.
|
|
274
|
+
// We'd have to treat the location.search and manage it just like the
|
|
275
|
+
// hash, to support it. We can read it, but we'd have to reset it as
|
|
276
|
+
// well, so at that point what's the difference to the hash?
|
|
277
|
+
...configuredFacets.map((f) =>
|
|
278
|
+
locationSearchData[f]
|
|
279
|
+
? {
|
|
280
|
+
[f]:
|
|
281
|
+
multiFacets.indexOf(f) > -1
|
|
282
|
+
? [locationSearchData[f]]
|
|
283
|
+
: locationSearchData[f],
|
|
284
|
+
}
|
|
285
|
+
: {},
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
261
289
|
const previousUrlQuery = usePrevious(urlQuery);
|
|
262
290
|
|
|
263
291
|
React.useEffect(() => {
|
|
@@ -296,11 +324,16 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
296
324
|
const [sortOn, setSortOn] = React.useState(data?.query?.sort_on);
|
|
297
325
|
const [sortOrder, setSortOrder] = React.useState(data?.query?.sort_order);
|
|
298
326
|
|
|
299
|
-
const [searchData, setSearchData] = React.useState(
|
|
327
|
+
const [searchData, setSearchData] = React.useState(
|
|
328
|
+
getInitialState(data, facets, urlSearchText, id),
|
|
329
|
+
);
|
|
300
330
|
|
|
331
|
+
const deepFacets = JSON.stringify(facets);
|
|
301
332
|
React.useEffect(() => {
|
|
302
|
-
setSearchData(
|
|
303
|
-
|
|
333
|
+
setSearchData(
|
|
334
|
+
getInitialState(data, facets, urlSearchText, id, sortOn, sortOrder),
|
|
335
|
+
);
|
|
336
|
+
}, [deepFacets, facets, data, urlSearchText, id, sortOn, sortOrder]);
|
|
304
337
|
|
|
305
338
|
const timeoutRef = React.useRef();
|
|
306
339
|
const facetSettings = data?.facets;
|
|
@@ -316,7 +349,7 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
316
349
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
317
350
|
timeoutRef.current = setTimeout(
|
|
318
351
|
() => {
|
|
319
|
-
const
|
|
352
|
+
const newSearchData = normalizeState({
|
|
320
353
|
id,
|
|
321
354
|
query: data.query || {},
|
|
322
355
|
facets: toSearchFacets || facets,
|
|
@@ -328,8 +361,8 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
328
361
|
if (toSearchFacets) setFacets(toSearchFacets);
|
|
329
362
|
if (toSortOn) setSortOn(toSortOn);
|
|
330
363
|
if (toSortOrder) setSortOrder(toSortOrder);
|
|
331
|
-
setSearchData(
|
|
332
|
-
setLocationSearchData(getSearchFields(
|
|
364
|
+
setSearchData(newSearchData);
|
|
365
|
+
setLocationSearchData(getSearchFields(newSearchData));
|
|
333
366
|
},
|
|
334
367
|
toSearchFacets ? inputDelay / 3 : inputDelay,
|
|
335
368
|
);
|
|
@@ -349,13 +382,14 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
349
382
|
);
|
|
350
383
|
|
|
351
384
|
const removeSearchQuery = () => {
|
|
352
|
-
|
|
385
|
+
let newSearchData = { ...searchData };
|
|
386
|
+
newSearchData.query = searchData.query.reduce(
|
|
353
387
|
// Remove SearchableText from query
|
|
354
388
|
(acc, kvp) => (kvp.i === 'SearchableText' ? acc : [...acc, kvp]),
|
|
355
389
|
[],
|
|
356
390
|
);
|
|
357
|
-
setSearchData(
|
|
358
|
-
setLocationSearchData(getSearchFields(
|
|
391
|
+
setSearchData(newSearchData);
|
|
392
|
+
setLocationSearchData(getSearchFields(newSearchData));
|
|
359
393
|
};
|
|
360
394
|
|
|
361
395
|
const querystringResults = useSelector(
|
|
@@ -35,7 +35,11 @@ import {
|
|
|
35
35
|
getSchema,
|
|
36
36
|
listActions,
|
|
37
37
|
} from '@plone/volto/actions';
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
flattenToAppURL,
|
|
40
|
+
getBaseUrl,
|
|
41
|
+
hasBlocksData,
|
|
42
|
+
} from '@plone/volto/helpers';
|
|
39
43
|
import { preloadLazyLibs } from '@plone/volto/helpers/Loadable';
|
|
40
44
|
|
|
41
45
|
import saveSVG from '@plone/volto/icons/save.svg';
|
|
@@ -260,7 +264,12 @@ class Edit extends Component {
|
|
|
260
264
|
|
|
261
265
|
setComparingLanguage(lang, content_id) {
|
|
262
266
|
this.setState({ comparingLanguage: lang });
|
|
263
|
-
this.props.getContent(
|
|
267
|
+
this.props.getContent(
|
|
268
|
+
flattenToAppURL(content_id),
|
|
269
|
+
null,
|
|
270
|
+
'compare_to',
|
|
271
|
+
null,
|
|
272
|
+
);
|
|
264
273
|
}
|
|
265
274
|
|
|
266
275
|
form = React.createRef();
|
|
@@ -240,6 +240,8 @@ export class DatetimeWidgetComponent extends Component {
|
|
|
240
240
|
)}
|
|
241
241
|
{resettable && (
|
|
242
242
|
<button
|
|
243
|
+
// FF needs that the type is "button" in order to not POST the form
|
|
244
|
+
type="button"
|
|
243
245
|
disabled={this.props.isDisabled || !datetime}
|
|
244
246
|
onClick={() => this.onResetDates()}
|
|
245
247
|
className="item ui noborder button"
|
|
@@ -314,7 +314,8 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
314
314
|
label: field[1].title,
|
|
315
315
|
value: field[0],
|
|
316
316
|
isDisabled: (value || []).some(
|
|
317
|
-
(v) =>
|
|
317
|
+
(v) =>
|
|
318
|
+
v['i'] !== 'path' && v['i'] === field[0],
|
|
318
319
|
),
|
|
319
320
|
}),
|
|
320
321
|
),
|
|
@@ -444,8 +445,11 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
444
445
|
(field) => ({
|
|
445
446
|
label: field[1].title,
|
|
446
447
|
value: field[0],
|
|
448
|
+
// disable selecting indexes that are already used,
|
|
449
|
+
// except for path, which has explicit support
|
|
450
|
+
// in the backend for multipath queries
|
|
447
451
|
isDisabled: (value || []).some(
|
|
448
|
-
(v) => v['i'] === field[0],
|
|
452
|
+
(v) => v['i'] !== 'path' && v['i'] === field[0],
|
|
449
453
|
),
|
|
450
454
|
}),
|
|
451
455
|
),
|
|
@@ -50,7 +50,7 @@ const Navigation = (props) => {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
|
-
<nav className="navigation" id="navigation" aria-label="
|
|
53
|
+
<nav className="navigation" id="navigation" aria-label="Site">
|
|
54
54
|
<div className="hamburger-wrapper mobile tablet only">
|
|
55
55
|
<button
|
|
56
56
|
className={cx('hamburger hamburger--spin', {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useIntl, defineMessages } from 'react-intl';
|
|
3
|
+
import { Pluggable } from '@plone/volto/components/manage/Pluggable';
|
|
3
4
|
|
|
4
5
|
const messages = defineMessages({
|
|
5
6
|
mainView: {
|
|
@@ -23,7 +24,7 @@ const SkipLinks = () => {
|
|
|
23
24
|
<div
|
|
24
25
|
className="skiplinks-wrapper"
|
|
25
26
|
role="complementary"
|
|
26
|
-
aria-label="
|
|
27
|
+
aria-label="Skiplinks"
|
|
27
28
|
>
|
|
28
29
|
<a className="skiplink" href="#view">
|
|
29
30
|
{intl.formatMessage(messages.mainView)}
|
|
@@ -34,6 +35,7 @@ const SkipLinks = () => {
|
|
|
34
35
|
<a className="skiplink" href="#footer">
|
|
35
36
|
{intl.formatMessage(messages.footer)}
|
|
36
37
|
</a>
|
|
38
|
+
<Pluggable name="main.skiplinks" />
|
|
37
39
|
</div>
|
|
38
40
|
);
|
|
39
41
|
};
|
|
@@ -3,6 +3,7 @@ import renderer from 'react-test-renderer';
|
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
+
import { PluggablesProvider } from '@plone/volto/components/manage/Pluggable';
|
|
6
7
|
|
|
7
8
|
import SkipLinks from './SkipLinks';
|
|
8
9
|
|
|
@@ -18,9 +19,11 @@ describe('SkipLinks', () => {
|
|
|
18
19
|
});
|
|
19
20
|
const component = renderer.create(
|
|
20
21
|
<Provider store={store}>
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
22
|
+
<PluggablesProvider>
|
|
23
|
+
<MemoryRouter>
|
|
24
|
+
<SkipLinks />
|
|
25
|
+
</MemoryRouter>
|
|
26
|
+
</PluggablesProvider>
|
|
24
27
|
</Provider>,
|
|
25
28
|
);
|
|
26
29
|
const json = component.toJSON();
|
|
@@ -132,14 +132,14 @@ export function deleteBlock(formData, blockId) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
|
-
*
|
|
135
|
+
* Adds a block to the blocks form
|
|
136
136
|
* @function addBlock
|
|
137
137
|
* @param {Object} formData Form data
|
|
138
138
|
* @param {string} type Block type
|
|
139
139
|
* @param {number} index Destination index
|
|
140
140
|
* @return {Array} New block id, New form data
|
|
141
141
|
*/
|
|
142
|
-
export function addBlock(formData, type, index) {
|
|
142
|
+
export function addBlock(formData, type, index, blocksConfig) {
|
|
143
143
|
const { settings } = config;
|
|
144
144
|
const id = uuid();
|
|
145
145
|
const idTrailingBlock = uuid();
|
|
@@ -148,81 +148,133 @@ export function addBlock(formData, type, index) {
|
|
|
148
148
|
const totalItems = formData[blocksLayoutFieldname].items.length;
|
|
149
149
|
const insert = index === -1 ? totalItems : index;
|
|
150
150
|
|
|
151
|
+
let value = applyBlockDefaults({
|
|
152
|
+
data: {
|
|
153
|
+
'@type': type,
|
|
154
|
+
},
|
|
155
|
+
intl: _dummyIntl,
|
|
156
|
+
});
|
|
157
|
+
|
|
151
158
|
return [
|
|
152
159
|
id,
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
160
|
+
_applyBlockInitialValue({
|
|
161
|
+
id,
|
|
162
|
+
value,
|
|
163
|
+
blocksConfig,
|
|
164
|
+
formData: {
|
|
165
|
+
...formData,
|
|
166
|
+
[blocksLayoutFieldname]: {
|
|
167
|
+
items: [
|
|
168
|
+
...formData[blocksLayoutFieldname].items.slice(0, insert),
|
|
169
|
+
id,
|
|
170
|
+
...(type !== settings.defaultBlockType ? [idTrailingBlock] : []),
|
|
171
|
+
...formData[blocksLayoutFieldname].items.slice(insert),
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
[blocksFieldname]: {
|
|
175
|
+
...formData[blocksFieldname],
|
|
176
|
+
[id]: value,
|
|
177
|
+
...(type !== settings.defaultBlockType && {
|
|
178
|
+
[idTrailingBlock]: {
|
|
179
|
+
'@type': settings.defaultBlockType,
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
167
182
|
},
|
|
168
|
-
|
|
169
|
-
[idTrailingBlock]: {
|
|
170
|
-
'@type': settings.defaultBlockType,
|
|
171
|
-
},
|
|
172
|
-
}),
|
|
183
|
+
selected: id,
|
|
173
184
|
},
|
|
174
|
-
|
|
175
|
-
},
|
|
185
|
+
}),
|
|
176
186
|
];
|
|
177
187
|
}
|
|
178
188
|
|
|
179
189
|
/**
|
|
180
|
-
*
|
|
190
|
+
* Gets an initial value for a block, based on configuration
|
|
191
|
+
*
|
|
192
|
+
* This allows blocks that need complex initial data structures to avoid having
|
|
193
|
+
* to call `onChangeBlock` at their creation time, as this is prone to racing
|
|
194
|
+
* issue on block data storage.
|
|
195
|
+
*/
|
|
196
|
+
const _applyBlockInitialValue = ({ id, value, blocksConfig, formData }) => {
|
|
197
|
+
const blocksFieldname = getBlocksFieldname(formData);
|
|
198
|
+
const type = value['@type'];
|
|
199
|
+
blocksConfig = blocksConfig || config.blocks.blocksConfig;
|
|
200
|
+
|
|
201
|
+
if (blocksConfig[type]?.initialValue) {
|
|
202
|
+
value = blocksConfig[type].initialValue({
|
|
203
|
+
id,
|
|
204
|
+
value,
|
|
205
|
+
formData,
|
|
206
|
+
});
|
|
207
|
+
formData[blocksFieldname][id] = value;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return formData;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Mutate block, changes the block @type
|
|
181
215
|
* @function mutateBlock
|
|
182
216
|
* @param {Object} formData Form data
|
|
183
217
|
* @param {string} id Block uid to mutate
|
|
184
218
|
* @param {number} value Block's new value
|
|
185
219
|
* @return {Object} New form data
|
|
186
220
|
*/
|
|
187
|
-
export function mutateBlock(formData, id, value) {
|
|
221
|
+
export function mutateBlock(formData, id, value, blocksConfig) {
|
|
188
222
|
const { settings } = config;
|
|
189
223
|
const blocksFieldname = getBlocksFieldname(formData);
|
|
190
224
|
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
|
|
191
225
|
const index = formData[blocksLayoutFieldname].items.indexOf(id) + 1;
|
|
192
226
|
|
|
227
|
+
value = applyBlockDefaults({
|
|
228
|
+
data: value,
|
|
229
|
+
intl: _dummyIntl,
|
|
230
|
+
});
|
|
231
|
+
let newFormData;
|
|
232
|
+
|
|
193
233
|
// Test if block at index is already a placeholder (trailing) block
|
|
194
234
|
const trailId = formData[blocksLayoutFieldname].items[index];
|
|
195
235
|
if (trailId) {
|
|
196
236
|
const block = formData[blocksFieldname][trailId];
|
|
197
|
-
|
|
198
|
-
|
|
237
|
+
newFormData = _applyBlockInitialValue({
|
|
238
|
+
id,
|
|
239
|
+
value,
|
|
240
|
+
blocksConfig,
|
|
241
|
+
formData: {
|
|
199
242
|
...formData,
|
|
200
243
|
[blocksFieldname]: {
|
|
201
244
|
...formData[blocksFieldname],
|
|
202
245
|
[id]: value || null,
|
|
203
246
|
},
|
|
204
|
-
}
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
if (!blockHasValue(block)) {
|
|
250
|
+
return newFormData;
|
|
205
251
|
}
|
|
206
252
|
}
|
|
207
253
|
|
|
208
254
|
const idTrailingBlock = uuid();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
255
|
+
newFormData = _applyBlockInitialValue({
|
|
256
|
+
id,
|
|
257
|
+
value,
|
|
258
|
+
blocksConfig,
|
|
259
|
+
formData: {
|
|
260
|
+
...formData,
|
|
261
|
+
[blocksFieldname]: {
|
|
262
|
+
...formData[blocksFieldname],
|
|
263
|
+
[id]: value || null,
|
|
264
|
+
[idTrailingBlock]: {
|
|
265
|
+
'@type': settings.defaultBlockType,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
[blocksLayoutFieldname]: {
|
|
269
|
+
items: [
|
|
270
|
+
...formData[blocksLayoutFieldname].items.slice(0, index),
|
|
271
|
+
idTrailingBlock,
|
|
272
|
+
...formData[blocksLayoutFieldname].items.slice(index),
|
|
273
|
+
],
|
|
216
274
|
},
|
|
217
275
|
},
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
...formData[blocksLayoutFieldname].items.slice(0, index),
|
|
221
|
-
idTrailingBlock,
|
|
222
|
-
...formData[blocksLayoutFieldname].items.slice(index),
|
|
223
|
-
],
|
|
224
|
-
},
|
|
225
|
-
};
|
|
276
|
+
});
|
|
277
|
+
return newFormData;
|
|
226
278
|
}
|
|
227
279
|
|
|
228
280
|
/**
|
|
@@ -233,15 +285,29 @@ export function mutateBlock(formData, id, value) {
|
|
|
233
285
|
* @param {number} value New block's value
|
|
234
286
|
* @return {Array} New block id, New form data
|
|
235
287
|
*/
|
|
236
|
-
export function insertBlock(
|
|
288
|
+
export function insertBlock(
|
|
289
|
+
formData,
|
|
290
|
+
id,
|
|
291
|
+
value,
|
|
292
|
+
current = {},
|
|
293
|
+
offset = 0,
|
|
294
|
+
blocksConfig,
|
|
295
|
+
) {
|
|
237
296
|
const blocksFieldname = getBlocksFieldname(formData);
|
|
238
297
|
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
|
|
239
298
|
const index = formData[blocksLayoutFieldname].items.indexOf(id);
|
|
240
299
|
|
|
300
|
+
value = applyBlockDefaults({
|
|
301
|
+
data: value,
|
|
302
|
+
intl: _dummyIntl,
|
|
303
|
+
});
|
|
304
|
+
|
|
241
305
|
const newBlockId = uuid();
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
306
|
+
const newFormData = _applyBlockInitialValue({
|
|
307
|
+
id,
|
|
308
|
+
value,
|
|
309
|
+
blocksConfig,
|
|
310
|
+
formData: {
|
|
245
311
|
...formData,
|
|
246
312
|
[blocksFieldname]: {
|
|
247
313
|
...formData[blocksFieldname],
|
|
@@ -259,7 +325,9 @@ export function insertBlock(formData, id, value, current = {}, offset = 0) {
|
|
|
259
325
|
],
|
|
260
326
|
},
|
|
261
327
|
},
|
|
262
|
-
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return [newBlockId, newFormData];
|
|
263
331
|
}
|
|
264
332
|
|
|
265
333
|
/**
|
|
@@ -570,3 +638,7 @@ export function findBlocks(blocks, types, result = []) {
|
|
|
570
638
|
|
|
571
639
|
return result;
|
|
572
640
|
}
|
|
641
|
+
|
|
642
|
+
const _dummyIntl = {
|
|
643
|
+
formatMessage() {},
|
|
644
|
+
};
|
|
@@ -64,6 +64,24 @@ config.blocks.blocksConfig.text = {
|
|
|
64
64
|
}),
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
config.blocks.blocksConfig.dummyText = {
|
|
68
|
+
id: 'dummyText',
|
|
69
|
+
title: 'Text',
|
|
70
|
+
group: 'text',
|
|
71
|
+
restricted: false,
|
|
72
|
+
mostUsed: false,
|
|
73
|
+
blockHasOwnFocusManagement: true,
|
|
74
|
+
blockHasValue: (data) => {
|
|
75
|
+
const isEmpty =
|
|
76
|
+
!data.text ||
|
|
77
|
+
(data.text?.blocks?.length === 1 && data.text.blocks[0].text === '');
|
|
78
|
+
return !isEmpty;
|
|
79
|
+
},
|
|
80
|
+
initialValue: ({ value, id, formData }) => {
|
|
81
|
+
return { ...value, marker: true };
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
67
85
|
config.blocks.blocksConfig.enhancedBlock = {
|
|
68
86
|
id: 'enhancedBlock',
|
|
69
87
|
title: 'Text',
|
|
@@ -474,6 +492,63 @@ describe('Blocks', () => {
|
|
|
474
492
|
);
|
|
475
493
|
expect(form.blocks_layout.items).toStrictEqual(['a', newId, 'b']);
|
|
476
494
|
});
|
|
495
|
+
|
|
496
|
+
it('initializes data for new block with initialValue', () => {
|
|
497
|
+
const [newId, form] = addBlock(
|
|
498
|
+
{
|
|
499
|
+
blocks: { a: { value: 1 }, b: { value: 2 } },
|
|
500
|
+
blocks_layout: { items: ['a', 'b'] },
|
|
501
|
+
},
|
|
502
|
+
'dummyText',
|
|
503
|
+
1,
|
|
504
|
+
);
|
|
505
|
+
expect(form.blocks[newId]).toStrictEqual({
|
|
506
|
+
'@type': 'dummyText',
|
|
507
|
+
marker: true,
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('initializes data for new block based on schema defaults', () => {
|
|
512
|
+
const [newId, form] = addBlock(
|
|
513
|
+
{
|
|
514
|
+
blocks: { a: { value: 1 }, b: { value: 2 } },
|
|
515
|
+
blocks_layout: { items: ['a', 'b'] },
|
|
516
|
+
},
|
|
517
|
+
'text',
|
|
518
|
+
1,
|
|
519
|
+
);
|
|
520
|
+
expect(form.blocks[newId]).toStrictEqual({
|
|
521
|
+
'@type': 'text',
|
|
522
|
+
booleanField: false,
|
|
523
|
+
description: 'Default description',
|
|
524
|
+
title: 'Default title',
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('initializes data for new block based on schema defaults and initialValue', () => {
|
|
529
|
+
config.blocks.blocksConfig.text.initialValue = ({ value }) => ({
|
|
530
|
+
...value,
|
|
531
|
+
marker: true,
|
|
532
|
+
});
|
|
533
|
+
const [newId, form] = addBlock(
|
|
534
|
+
{
|
|
535
|
+
blocks: { a: { value: 1 }, b: { value: 2 } },
|
|
536
|
+
blocks_layout: { items: ['a', 'b'] },
|
|
537
|
+
},
|
|
538
|
+
'text',
|
|
539
|
+
1,
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
delete config.blocks.blocksConfig.text.initialValue;
|
|
543
|
+
|
|
544
|
+
expect(form.blocks[newId]).toStrictEqual({
|
|
545
|
+
'@type': 'text',
|
|
546
|
+
booleanField: false,
|
|
547
|
+
description: 'Default description',
|
|
548
|
+
title: 'Default title',
|
|
549
|
+
marker: true,
|
|
550
|
+
});
|
|
551
|
+
});
|
|
477
552
|
});
|
|
478
553
|
|
|
479
554
|
describe('moveBlock', () => {
|
|
@@ -177,7 +177,7 @@ class Html extends Component {
|
|
|
177
177
|
<body className={bodyClass}>
|
|
178
178
|
<div role="navigation" aria-label="Toolbar" id="toolbar" />
|
|
179
179
|
<div id="main" dangerouslySetInnerHTML={{ __html: markup }} />
|
|
180
|
-
<div id="sidebar" />
|
|
180
|
+
<div role="complementary" aria-label="Sidebar" id="sidebar" />
|
|
181
181
|
<script
|
|
182
182
|
dangerouslySetInnerHTML={{
|
|
183
183
|
__html: `window.__data=${serialize(
|