@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.
Files changed (49) hide show
  1. package/.changelog.draft +12 -6
  2. package/.yarn/install-state.gz +0 -0
  3. package/CHANGELOG.md +30 -0
  4. package/cypress/support/commands.js +6 -0
  5. package/news/4104.internal +1 -0
  6. package/news/4165.feature +1 -0
  7. package/news/4189.bugfix +1 -0
  8. package/news/4194.bugfix +1 -0
  9. package/news/4223.bugfix +1 -0
  10. package/news/4231.feature +1 -0
  11. package/news/4282.feature +1 -0
  12. package/news/4285.feature +1 -0
  13. package/news/4311.bugfix +1 -0
  14. package/news/4313.feature +1 -0
  15. package/package.json +3 -2
  16. package/packages/volto-slate/package.json +1 -1
  17. package/packages/volto-slate/src/blocks/Text/DetachedTextBlockEditor.jsx +1 -0
  18. package/packages/volto-slate/src/editor/SlateEditor.jsx +5 -1
  19. package/packages/volto-slate/src/editor/ui/InlineToolbar.jsx +3 -1
  20. package/packages/volto-slate/src/editor/ui/SlateToolbar.jsx +3 -1
  21. package/src/components/manage/Blocks/Block/Edit.jsx +1 -0
  22. package/src/components/manage/Blocks/Listing/ListingData.jsx +5 -2
  23. package/src/components/manage/Blocks/Search/widgets/SelectMetadataField.jsx +1 -1
  24. package/src/components/manage/Edit/Edit.jsx +5 -2
  25. package/src/components/manage/Toolbar/Toolbar.jsx +22 -5
  26. package/src/components/manage/UniversalLink/UniversalLink.jsx +2 -2
  27. package/src/components/manage/Widgets/ArrayWidget.jsx +1 -0
  28. package/src/components/manage/Widgets/SelectWidget.jsx +1 -0
  29. package/src/components/manage/Widgets/TokenWidget.jsx +1 -0
  30. package/src/components/theme/App/App.jsx +39 -13
  31. package/src/components/theme/View/View.jsx +9 -2
  32. package/src/config/index.js +10 -2
  33. package/src/helpers/Blocks/Blocks.js +7 -2
  34. package/src/helpers/Blocks/Blocks.test.js +44 -5
  35. package/src/helpers/FormValidation/FormValidation.js +11 -9
  36. package/src/helpers/ScrollToTop/ScrollToTop.jsx +4 -1
  37. package/src/helpers/Utils/Utils.js +5 -3
  38. package/src/middleware/api.js +22 -15
  39. package/src/reducers/actions/actions.js +36 -8
  40. package/src/reducers/actions/actions.test.js +87 -1
  41. package/src/reducers/breadcrumbs/breadcrumbs.js +50 -13
  42. package/src/reducers/breadcrumbs/breadcrumbs.test.js +47 -1
  43. package/src/reducers/navigation/navigation.js +41 -9
  44. package/src/reducers/navigation/navigation.test.js +49 -1
  45. package/src/reducers/types/types.js +36 -8
  46. package/src/reducers/types/types.test.js +31 -1
  47. package/src/registry.js +18 -0
  48. package/src/registry.test.js +34 -0
  49. 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} maxCriterion
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, maxCriterion, valueToCompare, intlFunc) =>
11
+ const validationMessage = (isValid, criterion, valueToCompare, intlFunc) =>
12
12
  !isValid
13
- ? intlFunc(messages[maxCriterion], {
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} minCriterion
22
+ * @param {string} maxCriterion
22
23
  * @param {Function} intlFunc
23
24
  */
24
- const isMaxPropertyValid = (value, valueToCompare, minCriterion, intlFunc) => {
25
+ const isMaxPropertyValid = (value, valueToCompare, maxCriterion, intlFunc) => {
25
26
  const isValid = valueToCompare !== undefined ? value <= valueToCompare : true;
26
- return validationMessage(isValid, minCriterion, valueToCompare, intlFunc);
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, maxCriterion, intlFunc) => {
37
+ const isMinPropertyValid = (value, valueToCompare, minCriterion, intlFunc) => {
36
38
  const isValid = valueToCompare !== undefined ? value >= valueToCompare : true;
37
- return validationMessage(isValid, maxCriterion, valueToCompare, intlFunc);
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.maxLengthj,
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 (this.props.location?.pathname !== prevProps.location?.pathname) {
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} language Language to be normalized
213
- * @returns {string} Language normalized
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(
@@ -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(union([expand, ...flatten(expandersFromConfig)]));
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](addExpandersToPath(item.path, type), {
156
- data: item.data,
157
- type: item.type,
158
- headers: item.headers,
159
- params: request.params,
160
- checkUrl: settings.actions_raising_api_errors.includes(
161
- action.type,
162
- ),
163
- }).then((reqres) => {
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
- return {
40
- ...state,
41
- error: null,
42
- actions: action.result,
43
- loaded: true,
44
- loading: false,
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 { flattenToAppURL } from '@plone/volto/helpers';
7
+ import {
8
+ flattenToAppURL,
9
+ getBaseUrl,
10
+ hasApiExpander,
11
+ } from '@plone/volto/helpers';
8
12
 
9
- import { GET_BREADCRUMBS } from '@plone/volto/constants/ActionTypes';
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
- return {
37
- ...state,
38
- error: null,
39
- items: map(action.result.items, (item) => ({
40
- title: item.title,
41
- url: flattenToAppURL(item['@id']),
42
- })),
43
- root: flattenToAppURL(action.result.root),
44
- loaded: true,
45
- loading: false,
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 { GET_BREADCRUMBS } from '@plone/volto/constants/ActionTypes';
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 { flattenToAppURL } from '@plone/volto/helpers';
7
+ import {
8
+ flattenToAppURL,
9
+ getBaseUrl,
10
+ hasApiExpander,
11
+ } from '@plone/volto/helpers';
8
12
 
9
- import { GET_NAVIGATION } from '@plone/volto/constants/ActionTypes';
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
- return {
52
- ...state,
53
- error: null,
54
- items: getRecursiveItems(action.result.items),
55
- loaded: true,
56
- loading: false,
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 { GET_NAVIGATION } from '@plone/volto/constants/ActionTypes';
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
- return {
33
- ...state,
34
- error: null,
35
- loading: false,
36
- loaded: true,
37
- types: action.result,
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,