@plone/volto 17.0.0-alpha.19 → 17.0.0-alpha.20
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 +11 -0
- package/cypress/support/commands.js +1 -4
- package/cypress/support/volto-slate.js +4 -5
- package/package.json +1 -1
- package/packages/volto-slate/package.json +1 -1
- package/src/config/Widgets.jsx +1 -0
- package/src/config/index.js +19 -0
- package/src/helpers/Blocks/Blocks.js +22 -0
- package/src/helpers/Blocks/Blocks.test.js +59 -1
- package/src/helpers/Utils/usePagination.js +2 -7
- package/src/helpers/index.js +2 -0
- package/src/middleware/api.js +16 -2
- package/src/reducers/actions/actions.js +7 -5
- package/src/reducers/actions/actions.test.js +70 -0
- package/test-setup-config.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
<!-- towncrier release notes start -->
|
|
10
10
|
|
|
11
|
+
## 17.0.0-alpha.20 (2023-07-18)
|
|
12
|
+
|
|
13
|
+
### Feature
|
|
14
|
+
|
|
15
|
+
- Use all the apiExpanders in use, so we perform a single request for getting all the required data. @sneridagh [#4946](https://github.com/plone/volto/issues/4946)
|
|
16
|
+
|
|
17
|
+
### Bugfix
|
|
18
|
+
|
|
19
|
+
- Fix the condition deciding on listing pagination format so it takes into account container blocks as well @sneridagh [#4978](https://github.com/plone/volto/issues/4978)
|
|
20
|
+
|
|
21
|
+
|
|
11
22
|
## 17.0.0-alpha.19 (2023-07-18)
|
|
12
23
|
|
|
13
24
|
### Feature
|
|
@@ -688,10 +688,7 @@ Cypress.Commands.add(
|
|
|
688
688
|
Cypress.Commands.add('toolbarSave', () => {
|
|
689
689
|
// Save
|
|
690
690
|
cy.get('#toolbar-save', { timeout: 10000 }).click();
|
|
691
|
-
cy.waitForResourceToLoad('
|
|
692
|
-
cy.waitForResourceToLoad('@breadcrumbs');
|
|
693
|
-
cy.waitForResourceToLoad('@actions');
|
|
694
|
-
cy.waitForResourceToLoad('@types');
|
|
691
|
+
cy.waitForResourceToLoad('');
|
|
695
692
|
});
|
|
696
693
|
|
|
697
694
|
Cypress.Commands.add('clearSlate', (selector) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const slateBeforeEach = (contentType = 'Document') => {
|
|
2
|
+
cy.intercept('GET', `/**/*?expand*`).as('content');
|
|
2
3
|
cy.autologin();
|
|
3
4
|
cy.createContent({
|
|
4
5
|
contentType: contentType,
|
|
@@ -6,12 +7,10 @@ export const slateBeforeEach = (contentType = 'Document') => {
|
|
|
6
7
|
contentTitle: 'My Page',
|
|
7
8
|
});
|
|
8
9
|
cy.visit('/my-page');
|
|
9
|
-
cy.
|
|
10
|
-
|
|
11
|
-
cy.waitForResourceToLoad('@actions');
|
|
12
|
-
cy.waitForResourceToLoad('@types');
|
|
13
|
-
cy.waitForResourceToLoad('my-page');
|
|
10
|
+
cy.wait('@content');
|
|
11
|
+
|
|
14
12
|
cy.navigate('/my-page/edit');
|
|
13
|
+
cy.wait('@content');
|
|
15
14
|
};
|
|
16
15
|
|
|
17
16
|
export const getSelectedSlateEditor = () => {
|
package/package.json
CHANGED
package/src/config/Widgets.jsx
CHANGED
package/src/config/index.js
CHANGED
|
@@ -77,6 +77,8 @@ let config = {
|
|
|
77
77
|
okRoute: '/ok',
|
|
78
78
|
apiPath,
|
|
79
79
|
apiExpanders: [
|
|
80
|
+
// Added here for documentation purposes, addded at the end because it
|
|
81
|
+
// depends on a value of this object.
|
|
80
82
|
// Add the following expanders for only issuing a single request.
|
|
81
83
|
// https://6.docs.plone.org/volto/configuration/settings-reference.html#term-apiExpanders
|
|
82
84
|
// {
|
|
@@ -185,6 +187,7 @@ let config = {
|
|
|
185
187
|
querystringSearchGet: false,
|
|
186
188
|
blockSettingsTabFieldsetsInitialStateOpen: true,
|
|
187
189
|
excludeLinksAndReferencesMenuItem: false,
|
|
190
|
+
containerBlockTypes: ['gridBlock'],
|
|
188
191
|
},
|
|
189
192
|
experimental: {
|
|
190
193
|
addBlockButton: {
|
|
@@ -215,6 +218,22 @@ let config = {
|
|
|
215
218
|
components,
|
|
216
219
|
};
|
|
217
220
|
|
|
221
|
+
// The apiExpanders depends on a config of the object, so it's done here
|
|
222
|
+
config.settings.apiExpanders = [
|
|
223
|
+
...config.settings.apiExpanders,
|
|
224
|
+
{
|
|
225
|
+
match: '',
|
|
226
|
+
GET_CONTENT: ['breadcrumbs', 'actions', 'types'],
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
match: '',
|
|
230
|
+
GET_CONTENT: ['navigation'],
|
|
231
|
+
querystring: (config) => ({
|
|
232
|
+
'expand.navigation.depth': config.settings.navDepth,
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
|
|
218
237
|
ConfigRegistry.settings = config.settings;
|
|
219
238
|
ConfigRegistry.experimental = config.experimental;
|
|
220
239
|
ConfigRegistry.blocks = config.blocks;
|
|
@@ -550,3 +550,25 @@ export const getPreviousNextBlock = ({ content, block }) => {
|
|
|
550
550
|
|
|
551
551
|
return [previousBlock, nextBlock];
|
|
552
552
|
};
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Given a `block` object and a list of block types, return a list of block ids matching the types
|
|
556
|
+
*
|
|
557
|
+
* @function findBlocks
|
|
558
|
+
* @param {Object} types A list with the list of types to be matched
|
|
559
|
+
* @return {Array} An array of block ids
|
|
560
|
+
*/
|
|
561
|
+
export function findBlocks(blocks, types, result = []) {
|
|
562
|
+
const containerBlockTypes = config.settings.containerBlockTypes;
|
|
563
|
+
|
|
564
|
+
Object.keys(blocks).forEach((blockId) => {
|
|
565
|
+
const block = blocks[blockId];
|
|
566
|
+
if (types.includes(block['@type'])) {
|
|
567
|
+
result.push(blockId);
|
|
568
|
+
} else if (containerBlockTypes.includes(block['@type']) || block.blocks) {
|
|
569
|
+
findBlocks(block.blocks, types, result);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
buildStyleClassNamesExtenders,
|
|
21
21
|
getPreviousNextBlock,
|
|
22
22
|
blocksFormGenerator,
|
|
23
|
+
findBlocks,
|
|
23
24
|
} from './Blocks';
|
|
24
25
|
|
|
25
26
|
import config from '@plone/volto/registry';
|
|
@@ -1284,7 +1285,7 @@ describe('Blocks', () => {
|
|
|
1284
1285
|
blocks_layout: { items: [] },
|
|
1285
1286
|
});
|
|
1286
1287
|
});
|
|
1287
|
-
it
|
|
1288
|
+
it('Returns a filled blocks/blocks_layout pair with type block', () => {
|
|
1288
1289
|
const result = blocksFormGenerator(2, 'teaser');
|
|
1289
1290
|
expect(Object.keys(result.blocks).length).toEqual(2);
|
|
1290
1291
|
expect(result.blocks_layout.items.length).toEqual(2);
|
|
@@ -1297,3 +1298,60 @@ describe('Blocks', () => {
|
|
|
1297
1298
|
});
|
|
1298
1299
|
});
|
|
1299
1300
|
});
|
|
1301
|
+
|
|
1302
|
+
describe('findBlocks', () => {
|
|
1303
|
+
it('Get all blocks in the first level (main block container)', () => {
|
|
1304
|
+
const blocks = {
|
|
1305
|
+
'1': { title: 'title', '@type': 'title' },
|
|
1306
|
+
'2': { title: 'an image', '@type': 'image' },
|
|
1307
|
+
'3': { title: 'description', '@type': 'description' },
|
|
1308
|
+
'4': { title: 'a text', '@type': 'slate' },
|
|
1309
|
+
};
|
|
1310
|
+
const types = ['description'];
|
|
1311
|
+
expect(findBlocks(blocks, types)).toStrictEqual(['3']);
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
it('Get all blocks in the first level (main block container) given a list', () => {
|
|
1315
|
+
const blocks = {
|
|
1316
|
+
'1': { title: 'title', '@type': 'title' },
|
|
1317
|
+
'2': { title: 'an image', '@type': 'image' },
|
|
1318
|
+
'3': { title: 'description', '@type': 'description' },
|
|
1319
|
+
'4': { title: 'a text', '@type': 'slate' },
|
|
1320
|
+
};
|
|
1321
|
+
const types = ['description', 'slate'];
|
|
1322
|
+
expect(findBlocks(blocks, types)).toStrictEqual(['3', '4']);
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it('Get all blocks in the first level (main block container) given a list', () => {
|
|
1326
|
+
const blocks = {
|
|
1327
|
+
'1': { title: 'title', '@type': 'title' },
|
|
1328
|
+
'2': { title: 'an image', '@type': 'image' },
|
|
1329
|
+
'3': { title: 'description', '@type': 'description' },
|
|
1330
|
+
'4': { title: 'a text', '@type': 'slate' },
|
|
1331
|
+
'5': { title: 'a text', '@type': 'slate' },
|
|
1332
|
+
};
|
|
1333
|
+
const types = ['description', 'slate'];
|
|
1334
|
+
expect(findBlocks(blocks, types)).toStrictEqual(['3', '4', '5']);
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
it('Get all blocks, including containers, given a list', () => {
|
|
1338
|
+
const blocks = {
|
|
1339
|
+
'1': { title: 'title', '@type': 'title' },
|
|
1340
|
+
'2': { title: 'an image', '@type': 'image' },
|
|
1341
|
+
'3': { title: 'description', '@type': 'description' },
|
|
1342
|
+
'4': { title: 'a text', '@type': 'slate' },
|
|
1343
|
+
'5': {
|
|
1344
|
+
title: 'a container',
|
|
1345
|
+
'@type': 'gridBlock',
|
|
1346
|
+
blocks: {
|
|
1347
|
+
'6': { title: 'title', '@type': 'title' },
|
|
1348
|
+
'7': { title: 'an image', '@type': 'image' },
|
|
1349
|
+
'8': { title: 'description', '@type': 'description' },
|
|
1350
|
+
'9': { title: 'a text', '@type': 'slate' },
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
};
|
|
1354
|
+
const types = ['description', 'slate'];
|
|
1355
|
+
expect(findBlocks(blocks, types)).toStrictEqual(['3', '4', '8', '9']);
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
@@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react';
|
|
|
2
2
|
import { useHistory, useLocation } from 'react-router-dom';
|
|
3
3
|
import qs from 'query-string';
|
|
4
4
|
import { useSelector } from 'react-redux';
|
|
5
|
-
import { slugify } from '@plone/volto/helpers
|
|
5
|
+
import { findBlocks, slugify } from '@plone/volto/helpers';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @function useCreatePageQueryStringKey
|
|
@@ -12,13 +12,8 @@ import { slugify } from '@plone/volto/helpers/Utils/Utils';
|
|
|
12
12
|
const useCreatePageQueryStringKey = (id) => {
|
|
13
13
|
const blockTypesWithPagination = ['search', 'listing'];
|
|
14
14
|
const blocks = useSelector((state) => state?.content?.data?.blocks) || [];
|
|
15
|
-
const blocksLayout =
|
|
16
|
-
useSelector((state) => state?.content?.data?.blocks_layout?.items) || [];
|
|
17
|
-
const displayedBlocks = blocksLayout?.map((item) => blocks[item]);
|
|
18
15
|
const hasMultiplePaginations =
|
|
19
|
-
|
|
20
|
-
blockTypesWithPagination.includes(item['@type']),
|
|
21
|
-
).length > 1 || false;
|
|
16
|
+
findBlocks(blocks, blockTypesWithPagination).length > 1;
|
|
22
17
|
|
|
23
18
|
return hasMultiplePaginations ? slugify(`page-${id}`) : 'page';
|
|
24
19
|
};
|
package/src/helpers/index.js
CHANGED
|
@@ -58,6 +58,7 @@ export {
|
|
|
58
58
|
buildStyleClassNamesFromData,
|
|
59
59
|
buildStyleClassNamesExtenders,
|
|
60
60
|
getPreviousNextBlock,
|
|
61
|
+
findBlocks,
|
|
61
62
|
} from '@plone/volto/helpers/Blocks/Blocks';
|
|
62
63
|
export { default as BodyClass } from '@plone/volto/helpers/BodyClass/BodyClass';
|
|
63
64
|
export { default as ScrollToTop } from '@plone/volto/helpers/ScrollToTop/ScrollToTop';
|
|
@@ -93,6 +94,7 @@ export {
|
|
|
93
94
|
arrayRange,
|
|
94
95
|
reorderArray,
|
|
95
96
|
isInteractiveElement,
|
|
97
|
+
slugify,
|
|
96
98
|
} from '@plone/volto/helpers/Utils/Utils';
|
|
97
99
|
export { messages } from './MessageLabels/MessageLabels';
|
|
98
100
|
export {
|
package/src/middleware/api.js
CHANGED
|
@@ -67,7 +67,15 @@ export function addExpandersToPath(path, type, isAnonymous) {
|
|
|
67
67
|
|
|
68
68
|
const querystringFromConfig = apiExpanders
|
|
69
69
|
.filter((expand) => matchPath(url, expand.match) && expand[type])
|
|
70
|
-
.reduce((acc, expand) =>
|
|
70
|
+
.reduce((acc, expand) => {
|
|
71
|
+
let querystring = expand?.['querystring'];
|
|
72
|
+
// The querystring accepts being a function to be able to take other
|
|
73
|
+
// config parameters
|
|
74
|
+
if (typeof querystring === 'function') {
|
|
75
|
+
querystring = querystring(config);
|
|
76
|
+
}
|
|
77
|
+
return { ...acc, ...querystring };
|
|
78
|
+
}, {});
|
|
71
79
|
|
|
72
80
|
const queryMerge = { ...query, ...querystringFromConfig };
|
|
73
81
|
|
|
@@ -122,7 +130,13 @@ const apiMiddlewareFactory = (api) => ({ dispatch, getState }) => (next) => (
|
|
|
122
130
|
) => {
|
|
123
131
|
const { settings } = config;
|
|
124
132
|
|
|
125
|
-
const
|
|
133
|
+
const token = getState().userSession.token;
|
|
134
|
+
let isAnonymous = true;
|
|
135
|
+
if (token) {
|
|
136
|
+
const tokenExpiration = jwtDecode(token).exp;
|
|
137
|
+
const currentTime = new Date().getTime() / 1000;
|
|
138
|
+
isAnonymous = !token || currentTime > tokenExpiration;
|
|
139
|
+
}
|
|
126
140
|
|
|
127
141
|
if (typeof action === 'function') {
|
|
128
142
|
return action(dispatch, getState);
|
|
@@ -57,11 +57,13 @@ export default function actions(state = initialState, action = {}) {
|
|
|
57
57
|
}
|
|
58
58
|
return state;
|
|
59
59
|
case `${LIST_ACTIONS}_SUCCESS`:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
|
|
60
|
+
// Even if the expander is set or not, if the LIST_ACTIONS is
|
|
61
|
+
// called, we want it to store the data if the actions data is
|
|
62
|
+
// not set in the expander data (['@components']) but in the "normal"
|
|
63
|
+
// action result (we look for the object property returned by the endpoint)
|
|
64
|
+
// Unfortunately, this endpoint returns all the actions in a plain object
|
|
65
|
+
// with no structure :(
|
|
66
|
+
if (action.result.object) {
|
|
65
67
|
return {
|
|
66
68
|
...state,
|
|
67
69
|
error: null,
|
|
@@ -207,4 +207,74 @@ describe('Actions reducer - (ACTIONS)GET_CONTENT', () => {
|
|
|
207
207
|
loading: false,
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
|
+
|
|
211
|
+
it('should handle (ACTIONS)LIST_ACTIONS (standalone with apiExpander enabled)', () => {
|
|
212
|
+
expect(
|
|
213
|
+
actions(undefined, {
|
|
214
|
+
type: `${LIST_ACTIONS}_SUCCESS`,
|
|
215
|
+
result: {
|
|
216
|
+
object: [],
|
|
217
|
+
object_buttons: [],
|
|
218
|
+
site_actions: [],
|
|
219
|
+
user: [
|
|
220
|
+
{
|
|
221
|
+
icon: '',
|
|
222
|
+
id: 'preferences',
|
|
223
|
+
title: 'Preferences',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
icon: '',
|
|
227
|
+
id: 'dashboard',
|
|
228
|
+
title: 'Dashboard',
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
icon: '',
|
|
232
|
+
id: 'plone_setup',
|
|
233
|
+
title: 'Site Setup',
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
icon: '',
|
|
237
|
+
id: 'logout',
|
|
238
|
+
title: 'Log out',
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
document_actions: [],
|
|
242
|
+
portal_tabs: [],
|
|
243
|
+
},
|
|
244
|
+
}),
|
|
245
|
+
).toEqual({
|
|
246
|
+
error: null,
|
|
247
|
+
actions: {
|
|
248
|
+
object: [],
|
|
249
|
+
object_buttons: [],
|
|
250
|
+
site_actions: [],
|
|
251
|
+
user: [
|
|
252
|
+
{
|
|
253
|
+
icon: '',
|
|
254
|
+
id: 'preferences',
|
|
255
|
+
title: 'Preferences',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
icon: '',
|
|
259
|
+
id: 'dashboard',
|
|
260
|
+
title: 'Dashboard',
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
icon: '',
|
|
264
|
+
id: 'plone_setup',
|
|
265
|
+
title: 'Site Setup',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
icon: '',
|
|
269
|
+
id: 'logout',
|
|
270
|
+
title: 'Log out',
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
document_actions: [],
|
|
274
|
+
portal_tabs: [],
|
|
275
|
+
},
|
|
276
|
+
loaded: true,
|
|
277
|
+
loading: false,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
210
280
|
});
|