@plone/volto 16.8.0 → 16.9.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 +12 -6
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +30 -0
- package/cypress/support/commands.js +6 -0
- package/news/4104.internal +1 -0
- package/news/4165.feature +1 -0
- package/news/4189.bugfix +1 -0
- package/news/4194.bugfix +1 -0
- package/news/4223.bugfix +1 -0
- package/news/4231.feature +1 -0
- package/news/4282.feature +1 -0
- package/news/4285.feature +1 -0
- package/news/4311.bugfix +1 -0
- package/news/4313.feature +1 -0
- package/package.json +3 -2
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/blocks/Text/DetachedTextBlockEditor.jsx +1 -0
- package/packages/volto-slate/src/editor/SlateEditor.jsx +5 -1
- package/packages/volto-slate/src/editor/ui/InlineToolbar.jsx +3 -1
- package/packages/volto-slate/src/editor/ui/SlateToolbar.jsx +3 -1
- package/src/components/manage/Blocks/Block/Edit.jsx +1 -0
- package/src/components/manage/Blocks/Listing/ListingData.jsx +5 -2
- package/src/components/manage/Blocks/Search/widgets/SelectMetadataField.jsx +1 -1
- package/src/components/manage/Edit/Edit.jsx +5 -2
- package/src/components/manage/Toolbar/Toolbar.jsx +22 -5
- package/src/components/manage/UniversalLink/UniversalLink.jsx +2 -2
- package/src/components/manage/Widgets/ArrayWidget.jsx +1 -0
- package/src/components/manage/Widgets/SelectWidget.jsx +1 -0
- package/src/components/manage/Widgets/TokenWidget.jsx +1 -0
- package/src/components/theme/App/App.jsx +39 -13
- package/src/components/theme/View/View.jsx +9 -2
- package/src/config/index.js +10 -2
- package/src/helpers/Blocks/Blocks.js +7 -2
- package/src/helpers/Blocks/Blocks.test.js +44 -5
- package/src/helpers/FormValidation/FormValidation.js +11 -9
- package/src/helpers/ScrollToTop/ScrollToTop.jsx +4 -1
- package/src/helpers/Utils/Utils.js +5 -3
- package/src/middleware/api.js +22 -15
- package/src/reducers/actions/actions.js +36 -8
- package/src/reducers/actions/actions.test.js +87 -1
- package/src/reducers/breadcrumbs/breadcrumbs.js +50 -13
- package/src/reducers/breadcrumbs/breadcrumbs.test.js +47 -1
- package/src/reducers/navigation/navigation.js +41 -9
- package/src/reducers/navigation/navigation.test.js +49 -1
- package/src/reducers/types/types.js +36 -8
- package/src/reducers/types/types.test.js +31 -1
- package/src/registry.js +18 -0
- package/src/registry.test.js +34 -0
- package/test-setup-config.js +3 -0
|
@@ -4,27 +4,29 @@ import { messages } from '../MessageLabels/MessageLabels';
|
|
|
4
4
|
/**
|
|
5
5
|
* Will return the intl message if invalid
|
|
6
6
|
* @param {boolean} isValid
|
|
7
|
-
* @param {string}
|
|
7
|
+
* @param {string} criterion
|
|
8
8
|
* @param {string | number} valueToCompare can compare '47' < 50
|
|
9
9
|
* @param {Function} intlFunc
|
|
10
10
|
*/
|
|
11
|
-
const validationMessage = (isValid,
|
|
11
|
+
const validationMessage = (isValid, criterion, valueToCompare, intlFunc) =>
|
|
12
12
|
!isValid
|
|
13
|
-
? intlFunc(messages[
|
|
13
|
+
? intlFunc(messages[criterion], {
|
|
14
14
|
len: valueToCompare,
|
|
15
15
|
})
|
|
16
16
|
: null;
|
|
17
|
+
|
|
17
18
|
/**
|
|
18
19
|
* Returns if based on the criterion the value is lower or equal
|
|
19
20
|
* @param {string | number} value can compare '47' < 50
|
|
20
21
|
* @param {string | number} valueToCompare can compare '47' < 50
|
|
21
|
-
* @param {string}
|
|
22
|
+
* @param {string} maxCriterion
|
|
22
23
|
* @param {Function} intlFunc
|
|
23
24
|
*/
|
|
24
|
-
const isMaxPropertyValid = (value, valueToCompare,
|
|
25
|
+
const isMaxPropertyValid = (value, valueToCompare, maxCriterion, intlFunc) => {
|
|
25
26
|
const isValid = valueToCompare !== undefined ? value <= valueToCompare : true;
|
|
26
|
-
return validationMessage(isValid,
|
|
27
|
+
return validationMessage(isValid, maxCriterion, valueToCompare, intlFunc);
|
|
27
28
|
};
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
31
|
* Returns if based on the criterion the value is higher or equal
|
|
30
32
|
* @param {string | number} value can compare '47' < 50
|
|
@@ -32,9 +34,9 @@ const isMaxPropertyValid = (value, valueToCompare, minCriterion, intlFunc) => {
|
|
|
32
34
|
* @param {string} minCriterion
|
|
33
35
|
* @param {Function} intlFunc
|
|
34
36
|
*/
|
|
35
|
-
const isMinPropertyValid = (value, valueToCompare,
|
|
37
|
+
const isMinPropertyValid = (value, valueToCompare, minCriterion, intlFunc) => {
|
|
36
38
|
const isValid = valueToCompare !== undefined ? value >= valueToCompare : true;
|
|
37
|
-
return validationMessage(isValid,
|
|
39
|
+
return validationMessage(isValid, minCriterion, valueToCompare, intlFunc);
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
const widgetValidation = {
|
|
@@ -109,7 +111,7 @@ const widgetValidation = {
|
|
|
109
111
|
maxLength: (value, itemObj, intlFunc) =>
|
|
110
112
|
isMaxPropertyValid(
|
|
111
113
|
value.length,
|
|
112
|
-
itemObj.
|
|
114
|
+
itemObj.maxLength,
|
|
113
115
|
'maxLength',
|
|
114
116
|
intlFunc,
|
|
115
117
|
),
|
|
@@ -27,7 +27,10 @@ class ScrollToTop extends React.Component {
|
|
|
27
27
|
* @memberof ScrollToTop
|
|
28
28
|
*/
|
|
29
29
|
componentDidUpdate(prevProps) {
|
|
30
|
-
if (
|
|
30
|
+
if (
|
|
31
|
+
!this.props.location?.pathname.hash &&
|
|
32
|
+
this.props.location?.pathname !== prevProps.location?.pathname
|
|
33
|
+
) {
|
|
31
34
|
window.scrollTo(0, 0);
|
|
32
35
|
}
|
|
33
36
|
}
|
|
@@ -208,9 +208,11 @@ export const toLangUnderscoreRegion = (language) => {
|
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* Lookup if a given expander is set in apiExpanders
|
|
212
|
-
* @param {string}
|
|
213
|
-
* @
|
|
211
|
+
* Lookup if a given expander is set in apiExpanders for the given path and action type
|
|
212
|
+
* @param {string} expander The id literal of the expander eg. `navigation`
|
|
213
|
+
* @param {string} path The path (no URL) to check if the expander has effect
|
|
214
|
+
* @param {string} type The Redux action type
|
|
215
|
+
* @returns {boolean} Return if the expander is present for the path and the type given
|
|
214
216
|
*/
|
|
215
217
|
export const hasApiExpander = (expander, path = '', type = 'GET_CONTENT') => {
|
|
216
218
|
return flatten(
|
package/src/middleware/api.js
CHANGED
|
@@ -36,7 +36,7 @@ let socket = null;
|
|
|
36
36
|
* @param {*} type The action type
|
|
37
37
|
* @returns {string} The url/path with the configured expanders added to the query string
|
|
38
38
|
*/
|
|
39
|
-
export function addExpandersToPath(path, type) {
|
|
39
|
+
export function addExpandersToPath(path, type, isAnonymous) {
|
|
40
40
|
const { settings } = config;
|
|
41
41
|
const { apiExpanders = [] } = settings;
|
|
42
42
|
|
|
@@ -49,7 +49,9 @@ export function addExpandersToPath(path, type) {
|
|
|
49
49
|
.filter((expand) => matchPath(url, expand.match) && expand[type])
|
|
50
50
|
.map((expand) => expand[type]);
|
|
51
51
|
|
|
52
|
-
const expandMerge = compact(
|
|
52
|
+
const expandMerge = compact(
|
|
53
|
+
union([expand, ...flatten(expandersFromConfig)]),
|
|
54
|
+
).filter((item) => !(item === 'types' && isAnonymous)); // Remove types expander if isAnonymous
|
|
53
55
|
|
|
54
56
|
const stringifiedExpand = qs.stringify(
|
|
55
57
|
{ expand: expandMerge },
|
|
@@ -116,6 +118,8 @@ const apiMiddlewareFactory = (api) => ({ dispatch, getState }) => (next) => (
|
|
|
116
118
|
) => {
|
|
117
119
|
const { settings } = config;
|
|
118
120
|
|
|
121
|
+
const isAnonymous = !getState().userSession.token;
|
|
122
|
+
|
|
119
123
|
if (typeof action === 'function') {
|
|
120
124
|
return action(dispatch, getState);
|
|
121
125
|
}
|
|
@@ -137,14 +141,14 @@ const apiMiddlewareFactory = (api) => ({ dispatch, getState }) => (next) => (
|
|
|
137
141
|
request.map((item) =>
|
|
138
142
|
sendOnSocket({
|
|
139
143
|
...item,
|
|
140
|
-
path: addExpandersToPath(item.path, type),
|
|
144
|
+
path: addExpandersToPath(item.path, type, isAnonymous),
|
|
141
145
|
id: type,
|
|
142
146
|
}),
|
|
143
147
|
),
|
|
144
148
|
)
|
|
145
149
|
: sendOnSocket({
|
|
146
150
|
...request,
|
|
147
|
-
path: addExpandersToPath(request.path, type),
|
|
151
|
+
path: addExpandersToPath(request.path, type, isAnonymous),
|
|
148
152
|
id: type,
|
|
149
153
|
});
|
|
150
154
|
} else {
|
|
@@ -152,22 +156,25 @@ const apiMiddlewareFactory = (api) => ({ dispatch, getState }) => (next) => (
|
|
|
152
156
|
? mode === 'serial'
|
|
153
157
|
? request.reduce((prevPromise, item) => {
|
|
154
158
|
return prevPromise.then((acc) => {
|
|
155
|
-
return api[item.op](
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
return api[item.op](
|
|
160
|
+
addExpandersToPath(item.path, type, isAnonymous),
|
|
161
|
+
{
|
|
162
|
+
data: item.data,
|
|
163
|
+
type: item.type,
|
|
164
|
+
headers: item.headers,
|
|
165
|
+
params: request.params,
|
|
166
|
+
checkUrl: settings.actions_raising_api_errors.includes(
|
|
167
|
+
action.type,
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
).then((reqres) => {
|
|
164
171
|
return [...acc, reqres];
|
|
165
172
|
});
|
|
166
173
|
});
|
|
167
174
|
}, Promise.resolve([]))
|
|
168
175
|
: Promise.all(
|
|
169
176
|
request.map((item) =>
|
|
170
|
-
api[item.op](addExpandersToPath(item.path, type), {
|
|
177
|
+
api[item.op](addExpandersToPath(item.path, type, isAnonymous), {
|
|
171
178
|
data: item.data,
|
|
172
179
|
type: item.type,
|
|
173
180
|
headers: item.headers,
|
|
@@ -178,7 +185,7 @@ const apiMiddlewareFactory = (api) => ({ dispatch, getState }) => (next) => (
|
|
|
178
185
|
}),
|
|
179
186
|
),
|
|
180
187
|
)
|
|
181
|
-
: api[request.op](addExpandersToPath(request.path, type), {
|
|
188
|
+
: api[request.op](addExpandersToPath(request.path, type, isAnonymous), {
|
|
182
189
|
data: request.data,
|
|
183
190
|
type: request.type,
|
|
184
191
|
headers: request.headers,
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
* @module reducers/actions/actions
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { LIST_ACTIONS } from '@plone/volto/constants/ActionTypes';
|
|
6
|
+
import { GET_CONTENT, LIST_ACTIONS } from '@plone/volto/constants/ActionTypes';
|
|
7
|
+
import {
|
|
8
|
+
flattenToAppURL,
|
|
9
|
+
getBaseUrl,
|
|
10
|
+
hasApiExpander,
|
|
11
|
+
} from '@plone/volto/helpers';
|
|
7
12
|
|
|
8
13
|
const initialState = {
|
|
9
14
|
error: null,
|
|
@@ -27,6 +32,7 @@ const initialState = {
|
|
|
27
32
|
* @returns {Object} New state.
|
|
28
33
|
*/
|
|
29
34
|
export default function actions(state = initialState, action = {}) {
|
|
35
|
+
let hasExpander;
|
|
30
36
|
switch (action.type) {
|
|
31
37
|
case `${LIST_ACTIONS}_PENDING`:
|
|
32
38
|
return {
|
|
@@ -35,14 +41,36 @@ export default function actions(state = initialState, action = {}) {
|
|
|
35
41
|
loaded: false,
|
|
36
42
|
loading: true,
|
|
37
43
|
};
|
|
44
|
+
case `${GET_CONTENT}_SUCCESS`:
|
|
45
|
+
hasExpander = hasApiExpander(
|
|
46
|
+
'actions',
|
|
47
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
48
|
+
);
|
|
49
|
+
if (hasExpander) {
|
|
50
|
+
return {
|
|
51
|
+
...state,
|
|
52
|
+
error: null,
|
|
53
|
+
actions: action.result['@components'].actions,
|
|
54
|
+
loaded: true,
|
|
55
|
+
loading: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return state;
|
|
38
59
|
case `${LIST_ACTIONS}_SUCCESS`:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
hasExpander = hasApiExpander(
|
|
61
|
+
'actions',
|
|
62
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
63
|
+
);
|
|
64
|
+
if (!hasExpander) {
|
|
65
|
+
return {
|
|
66
|
+
...state,
|
|
67
|
+
error: null,
|
|
68
|
+
actions: action.result,
|
|
69
|
+
loaded: true,
|
|
70
|
+
loading: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return state;
|
|
46
74
|
case `${LIST_ACTIONS}_FAIL`:
|
|
47
75
|
return {
|
|
48
76
|
...state,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import actions from './actions';
|
|
2
|
-
import { LIST_ACTIONS } from '@plone/volto/constants/ActionTypes';
|
|
2
|
+
import { GET_CONTENT, LIST_ACTIONS } from '@plone/volto/constants/ActionTypes';
|
|
3
|
+
import config from '@plone/volto/registry';
|
|
3
4
|
|
|
4
5
|
describe('Actions reducer', () => {
|
|
5
6
|
it('should return the initial state', () => {
|
|
@@ -122,3 +123,88 @@ describe('Actions reducer', () => {
|
|
|
122
123
|
});
|
|
123
124
|
});
|
|
124
125
|
});
|
|
126
|
+
|
|
127
|
+
describe('Actions reducer - (ACTIONS)GET_CONTENT', () => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
config.settings.apiExpanders = [
|
|
130
|
+
{
|
|
131
|
+
match: '',
|
|
132
|
+
GET_CONTENT: ['actions'],
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle (ACTIONS)GET_CONTENT', () => {
|
|
138
|
+
expect(
|
|
139
|
+
actions(undefined, {
|
|
140
|
+
type: `${GET_CONTENT}_SUCCESS`,
|
|
141
|
+
result: {
|
|
142
|
+
'@components': {
|
|
143
|
+
actions: {
|
|
144
|
+
object: [],
|
|
145
|
+
object_buttons: [],
|
|
146
|
+
site_actions: [],
|
|
147
|
+
user: [
|
|
148
|
+
{
|
|
149
|
+
icon: '',
|
|
150
|
+
id: 'preferences',
|
|
151
|
+
title: 'Preferences',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
icon: '',
|
|
155
|
+
id: 'dashboard',
|
|
156
|
+
title: 'Dashboard',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
icon: '',
|
|
160
|
+
id: 'plone_setup',
|
|
161
|
+
title: 'Site Setup',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
icon: '',
|
|
165
|
+
id: 'logout',
|
|
166
|
+
title: 'Log out',
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
document_actions: [],
|
|
170
|
+
portal_tabs: [],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
).toEqual({
|
|
176
|
+
error: null,
|
|
177
|
+
actions: {
|
|
178
|
+
object: [],
|
|
179
|
+
object_buttons: [],
|
|
180
|
+
site_actions: [],
|
|
181
|
+
user: [
|
|
182
|
+
{
|
|
183
|
+
icon: '',
|
|
184
|
+
id: 'preferences',
|
|
185
|
+
title: 'Preferences',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
icon: '',
|
|
189
|
+
id: 'dashboard',
|
|
190
|
+
title: 'Dashboard',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
icon: '',
|
|
194
|
+
id: 'plone_setup',
|
|
195
|
+
title: 'Site Setup',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
icon: '',
|
|
199
|
+
id: 'logout',
|
|
200
|
+
title: 'Log out',
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
document_actions: [],
|
|
204
|
+
portal_tabs: [],
|
|
205
|
+
},
|
|
206
|
+
loaded: true,
|
|
207
|
+
loading: false,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -4,9 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { map } from 'lodash';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
flattenToAppURL,
|
|
9
|
+
getBaseUrl,
|
|
10
|
+
hasApiExpander,
|
|
11
|
+
} from '@plone/volto/helpers';
|
|
8
12
|
|
|
9
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
GET_BREADCRUMBS,
|
|
15
|
+
GET_CONTENT,
|
|
16
|
+
} from '@plone/volto/constants/ActionTypes';
|
|
10
17
|
|
|
11
18
|
const initialState = {
|
|
12
19
|
error: null,
|
|
@@ -24,6 +31,7 @@ const initialState = {
|
|
|
24
31
|
* @returns {Object} New state.
|
|
25
32
|
*/
|
|
26
33
|
export default function breadcrumbs(state = initialState, action = {}) {
|
|
34
|
+
let hasExpander;
|
|
27
35
|
switch (action.type) {
|
|
28
36
|
case `${GET_BREADCRUMBS}_PENDING`:
|
|
29
37
|
return {
|
|
@@ -32,18 +40,47 @@ export default function breadcrumbs(state = initialState, action = {}) {
|
|
|
32
40
|
loaded: false,
|
|
33
41
|
loading: true,
|
|
34
42
|
};
|
|
43
|
+
case `${GET_CONTENT}_SUCCESS`:
|
|
44
|
+
hasExpander = hasApiExpander(
|
|
45
|
+
'breadcrumbs',
|
|
46
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
47
|
+
);
|
|
48
|
+
if (hasExpander) {
|
|
49
|
+
return {
|
|
50
|
+
...state,
|
|
51
|
+
error: null,
|
|
52
|
+
items: map(
|
|
53
|
+
action.result['@components'].breadcrumbs.items,
|
|
54
|
+
(item) => ({
|
|
55
|
+
title: item.title,
|
|
56
|
+
url: flattenToAppURL(item['@id']),
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
root: flattenToAppURL(action.result['@components'].breadcrumbs.root),
|
|
60
|
+
loaded: true,
|
|
61
|
+
loading: false,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return state;
|
|
35
65
|
case `${GET_BREADCRUMBS}_SUCCESS`:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
66
|
+
hasExpander = hasApiExpander(
|
|
67
|
+
'breadcrumbs',
|
|
68
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
69
|
+
);
|
|
70
|
+
if (!hasExpander) {
|
|
71
|
+
return {
|
|
72
|
+
...state,
|
|
73
|
+
error: null,
|
|
74
|
+
items: map(action.result.items, (item) => ({
|
|
75
|
+
title: item.title,
|
|
76
|
+
url: flattenToAppURL(item['@id']),
|
|
77
|
+
})),
|
|
78
|
+
root: flattenToAppURL(action.result.root),
|
|
79
|
+
loaded: true,
|
|
80
|
+
loading: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return state;
|
|
47
84
|
case `${GET_BREADCRUMBS}_FAIL`:
|
|
48
85
|
return {
|
|
49
86
|
...state,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import config from '@plone/volto/registry';
|
|
2
2
|
import breadcrumbs from './breadcrumbs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
GET_BREADCRUMBS,
|
|
5
|
+
GET_CONTENT,
|
|
6
|
+
} from '@plone/volto/constants/ActionTypes';
|
|
4
7
|
|
|
5
8
|
const { settings } = config;
|
|
6
9
|
|
|
@@ -72,3 +75,46 @@ describe('Breadcrumbs reducer', () => {
|
|
|
72
75
|
});
|
|
73
76
|
});
|
|
74
77
|
});
|
|
78
|
+
|
|
79
|
+
describe('Breadcrumbs reducer - (BREADCRUMBS)GET_CONTENT', () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
config.settings.apiExpanders = [
|
|
82
|
+
{
|
|
83
|
+
match: '',
|
|
84
|
+
GET_CONTENT: ['breadcrumbs'],
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle (BREADCRUMBS)GET_CONTENT_SUCCESS', () => {
|
|
90
|
+
expect(
|
|
91
|
+
breadcrumbs(undefined, {
|
|
92
|
+
type: `${GET_CONTENT}_SUCCESS`,
|
|
93
|
+
result: {
|
|
94
|
+
'@components': {
|
|
95
|
+
breadcrumbs: {
|
|
96
|
+
items: [
|
|
97
|
+
{
|
|
98
|
+
title: 'Welcome to Plone!',
|
|
99
|
+
'@id': `${settings.apiPath}/front-page`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
root: settings.apiPath,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
).toEqual({
|
|
108
|
+
error: null,
|
|
109
|
+
items: [
|
|
110
|
+
{
|
|
111
|
+
title: 'Welcome to Plone!',
|
|
112
|
+
url: '/front-page',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
root: '',
|
|
116
|
+
loaded: true,
|
|
117
|
+
loading: false,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -4,9 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { map } from 'lodash';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
flattenToAppURL,
|
|
9
|
+
getBaseUrl,
|
|
10
|
+
hasApiExpander,
|
|
11
|
+
} from '@plone/volto/helpers';
|
|
8
12
|
|
|
9
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
GET_CONTENT,
|
|
15
|
+
GET_NAVIGATION,
|
|
16
|
+
} from '@plone/volto/constants/ActionTypes';
|
|
10
17
|
|
|
11
18
|
const initialState = {
|
|
12
19
|
error: null,
|
|
@@ -39,6 +46,7 @@ function getRecursiveItems(items) {
|
|
|
39
46
|
* @returns {Object} New state.
|
|
40
47
|
*/
|
|
41
48
|
export default function navigation(state = initialState, action = {}) {
|
|
49
|
+
let hasExpander;
|
|
42
50
|
switch (action.type) {
|
|
43
51
|
case `${GET_NAVIGATION}_PENDING`:
|
|
44
52
|
return {
|
|
@@ -47,14 +55,38 @@ export default function navigation(state = initialState, action = {}) {
|
|
|
47
55
|
loaded: false,
|
|
48
56
|
loading: true,
|
|
49
57
|
};
|
|
58
|
+
case `${GET_CONTENT}_SUCCESS`:
|
|
59
|
+
hasExpander = hasApiExpander(
|
|
60
|
+
'navigation',
|
|
61
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
62
|
+
);
|
|
63
|
+
if (hasExpander) {
|
|
64
|
+
return {
|
|
65
|
+
...state,
|
|
66
|
+
error: null,
|
|
67
|
+
items: getRecursiveItems(
|
|
68
|
+
action.result['@components'].navigation.items,
|
|
69
|
+
),
|
|
70
|
+
loaded: true,
|
|
71
|
+
loading: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return state;
|
|
50
75
|
case `${GET_NAVIGATION}_SUCCESS`:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
hasExpander = hasApiExpander(
|
|
77
|
+
'navigation',
|
|
78
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
79
|
+
);
|
|
80
|
+
if (!hasExpander) {
|
|
81
|
+
return {
|
|
82
|
+
...state,
|
|
83
|
+
error: null,
|
|
84
|
+
items: getRecursiveItems(action.result.items),
|
|
85
|
+
loaded: true,
|
|
86
|
+
loading: false,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return state;
|
|
58
90
|
case `${GET_NAVIGATION}_FAIL`:
|
|
59
91
|
return {
|
|
60
92
|
...state,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GET_CONTENT,
|
|
3
|
+
GET_NAVIGATION,
|
|
4
|
+
} from '@plone/volto/constants/ActionTypes';
|
|
2
5
|
import config from '@plone/volto/registry';
|
|
3
6
|
import navigation from './navigation';
|
|
4
7
|
|
|
@@ -125,3 +128,48 @@ describe('Navigation reducer', () => {
|
|
|
125
128
|
});
|
|
126
129
|
});
|
|
127
130
|
});
|
|
131
|
+
|
|
132
|
+
describe('Navigation reducer (NAVIGATION)GET_CONTENT', () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
config.settings.apiExpanders = [
|
|
135
|
+
{
|
|
136
|
+
match: '',
|
|
137
|
+
GET_CONTENT: ['navigation'],
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should handle (NAVIGATION)GET_CONTENT_SUCCESS', () => {
|
|
143
|
+
expect(
|
|
144
|
+
navigation(undefined, {
|
|
145
|
+
type: `${GET_CONTENT}_SUCCESS`,
|
|
146
|
+
result: {
|
|
147
|
+
'@components': {
|
|
148
|
+
navigation: {
|
|
149
|
+
items: [
|
|
150
|
+
{
|
|
151
|
+
title: 'Welcome to Plone!',
|
|
152
|
+
description:
|
|
153
|
+
'Congratulations! You have successfully installed Plone.',
|
|
154
|
+
'@id': `${settings.apiPath}/front-page`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
).toEqual({
|
|
162
|
+
error: null,
|
|
163
|
+
items: [
|
|
164
|
+
{
|
|
165
|
+
title: 'Welcome to Plone!',
|
|
166
|
+
description:
|
|
167
|
+
'Congratulations! You have successfully installed Plone.',
|
|
168
|
+
url: '/front-page',
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
loaded: true,
|
|
172
|
+
loading: false,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
* @module reducers/types/types
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { GET_TYPES } from '@plone/volto/constants/ActionTypes';
|
|
6
|
+
import { GET_CONTENT, GET_TYPES } from '@plone/volto/constants/ActionTypes';
|
|
7
|
+
import {
|
|
8
|
+
flattenToAppURL,
|
|
9
|
+
getBaseUrl,
|
|
10
|
+
hasApiExpander,
|
|
11
|
+
} from '@plone/volto/helpers';
|
|
7
12
|
|
|
8
13
|
const initialState = {
|
|
9
14
|
error: null,
|
|
@@ -20,6 +25,7 @@ const initialState = {
|
|
|
20
25
|
* @returns {Object} New state.
|
|
21
26
|
*/
|
|
22
27
|
export default function types(state = initialState, action = {}) {
|
|
28
|
+
let hasExpander;
|
|
23
29
|
switch (action.type) {
|
|
24
30
|
case `${GET_TYPES}_PENDING`:
|
|
25
31
|
return {
|
|
@@ -28,14 +34,36 @@ export default function types(state = initialState, action = {}) {
|
|
|
28
34
|
loading: true,
|
|
29
35
|
loaded: false,
|
|
30
36
|
};
|
|
37
|
+
case `${GET_CONTENT}_SUCCESS`:
|
|
38
|
+
hasExpander = hasApiExpander(
|
|
39
|
+
'types',
|
|
40
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
41
|
+
);
|
|
42
|
+
if (hasExpander) {
|
|
43
|
+
return {
|
|
44
|
+
...state,
|
|
45
|
+
error: null,
|
|
46
|
+
loading: false,
|
|
47
|
+
loaded: true,
|
|
48
|
+
types: action.result['@components'].types,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return state;
|
|
31
52
|
case `${GET_TYPES}_SUCCESS`:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
hasExpander = hasApiExpander(
|
|
54
|
+
'types',
|
|
55
|
+
getBaseUrl(flattenToAppURL(action.result['@id'])),
|
|
56
|
+
);
|
|
57
|
+
if (!hasExpander) {
|
|
58
|
+
return {
|
|
59
|
+
...state,
|
|
60
|
+
error: null,
|
|
61
|
+
loading: false,
|
|
62
|
+
loaded: true,
|
|
63
|
+
types: action.result,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return state;
|
|
39
67
|
case `${GET_TYPES}_FAIL`:
|
|
40
68
|
return {
|
|
41
69
|
...state,
|