@plone/volto 19.0.0-alpha.26 → 19.0.0-alpha.28

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 (203) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc +0 -3
  3. package/CHANGELOG.md +69 -0
  4. package/README.md +0 -1
  5. package/babel.js +0 -6
  6. package/locales/af/LC_MESSAGES/volto.po +5455 -0
  7. package/locales/af.json +1 -1
  8. package/locales/ar/LC_MESSAGES/volto.po +5455 -0
  9. package/locales/ar.json +1 -1
  10. package/locales/bg/LC_MESSAGES/volto.po +5455 -0
  11. package/locales/bg.json +1 -1
  12. package/locales/bn/LC_MESSAGES/volto.po +5455 -0
  13. package/locales/bn.json +1 -1
  14. package/locales/ca/LC_MESSAGES/volto.po +406 -345
  15. package/locales/ca.json +1 -1
  16. package/locales/cs/LC_MESSAGES/volto.po +5455 -0
  17. package/locales/cs.json +1 -1
  18. package/locales/cy/LC_MESSAGES/volto.po +5455 -0
  19. package/locales/cy.json +1 -1
  20. package/locales/da/LC_MESSAGES/volto.po +5455 -0
  21. package/locales/da.json +1 -1
  22. package/locales/de/LC_MESSAGES/volto.po +70 -8
  23. package/locales/de.json +1 -1
  24. package/locales/el/LC_MESSAGES/volto.po +5455 -0
  25. package/locales/el.json +1 -1
  26. package/locales/en/LC_MESSAGES/volto.po +66 -0
  27. package/locales/en.json +1 -1
  28. package/locales/en_AU/LC_MESSAGES/volto.po +5455 -0
  29. package/locales/en_AU.json +1 -1
  30. package/locales/en_GB/LC_MESSAGES/volto.po +5455 -0
  31. package/locales/en_GB.json +1 -1
  32. package/locales/eo/LC_MESSAGES/volto.po +5455 -0
  33. package/locales/eo.json +1 -1
  34. package/locales/es/LC_MESSAGES/volto.po +173 -112
  35. package/locales/es.json +1 -1
  36. package/locales/et/LC_MESSAGES/volto.po +5455 -0
  37. package/locales/et.json +1 -1
  38. package/locales/eu/LC_MESSAGES/volto.po +256 -195
  39. package/locales/eu.json +1 -1
  40. package/locales/fa/LC_MESSAGES/volto.po +5455 -0
  41. package/locales/fa.json +1 -1
  42. package/locales/fi/LC_MESSAGES/volto.po +62 -1
  43. package/locales/fi.json +1 -1
  44. package/locales/fr/LC_MESSAGES/volto.po +61 -0
  45. package/locales/fr.json +1 -1
  46. package/locales/fu/LC_MESSAGES/volto.po +5455 -0
  47. package/locales/fu.json +1 -1
  48. package/locales/gl/LC_MESSAGES/volto.po +5456 -0
  49. package/locales/gl.json +1 -1
  50. package/locales/he/LC_MESSAGES/volto.po +5455 -0
  51. package/locales/he.json +1 -1
  52. package/locales/hi/LC_MESSAGES/volto.po +66 -0
  53. package/locales/hi.json +1 -1
  54. package/locales/hr/LC_MESSAGES/volto.po +5455 -0
  55. package/locales/hr.json +1 -1
  56. package/locales/hu/LC_MESSAGES/volto.po +5455 -0
  57. package/locales/hu.json +1 -1
  58. package/locales/hy/LC_MESSAGES/volto.po +5455 -0
  59. package/locales/hy.json +1 -1
  60. package/locales/id/LC_MESSAGES/volto.po +5455 -0
  61. package/locales/id.json +1 -1
  62. package/locales/it/LC_MESSAGES/volto.po +90 -24
  63. package/locales/it.json +1 -1
  64. package/locales/ja/LC_MESSAGES/volto.po +105 -43
  65. package/locales/ja.json +1 -1
  66. package/locales/ka/LC_MESSAGES/volto.po +5455 -0
  67. package/locales/ka.json +1 -1
  68. package/locales/kn/LC_MESSAGES/volto.po +5455 -0
  69. package/locales/kn.json +1 -1
  70. package/locales/ko/LC_MESSAGES/volto.po +5455 -0
  71. package/locales/ko.json +1 -1
  72. package/locales/lt/LC_MESSAGES/volto.po +5455 -0
  73. package/locales/lt.json +1 -1
  74. package/locales/lv/LC_MESSAGES/volto.po +5455 -0
  75. package/locales/lv.json +1 -1
  76. package/locales/mi/LC_MESSAGES/volto.po +5455 -0
  77. package/locales/mi.json +1 -1
  78. package/locales/mk/LC_MESSAGES/volto.po +5455 -0
  79. package/locales/mk.json +1 -1
  80. package/locales/my/LC_MESSAGES/volto.po +5455 -0
  81. package/locales/my.json +1 -1
  82. package/locales/nb_NO/LC_MESSAGES/volto.po +5455 -0
  83. package/locales/nb_NO.json +1 -1
  84. package/locales/nl/LC_MESSAGES/volto.po +93 -31
  85. package/locales/nl.json +1 -1
  86. package/locales/nn/LC_MESSAGES/volto.po +5455 -0
  87. package/locales/nn.json +1 -1
  88. package/locales/pl/LC_MESSAGES/volto.po +5455 -0
  89. package/locales/pl.json +1 -1
  90. package/locales/pt/LC_MESSAGES/volto.po +723 -662
  91. package/locales/pt.json +1 -1
  92. package/locales/pt_BR/LC_MESSAGES/volto.po +61 -0
  93. package/locales/pt_BR.json +1 -1
  94. package/locales/rm/LC_MESSAGES/volto.po +5455 -0
  95. package/locales/rm.json +1 -1
  96. package/locales/ro/LC_MESSAGES/volto.po +61 -0
  97. package/locales/ro.json +1 -1
  98. package/locales/ru/LC_MESSAGES/volto.po +61 -0
  99. package/locales/ru.json +1 -1
  100. package/locales/sk/LC_MESSAGES/volto.po +5455 -0
  101. package/locales/sk.json +1 -1
  102. package/locales/sl/LC_MESSAGES/volto.po +5455 -0
  103. package/locales/sl.json +1 -1
  104. package/locales/sm/LC_MESSAGES/volto.po +5455 -0
  105. package/locales/sm.json +1 -1
  106. package/locales/sq/LC_MESSAGES/volto.po +5455 -0
  107. package/locales/sq.json +1 -1
  108. package/locales/sr/LC_MESSAGES/volto.po +5455 -0
  109. package/locales/sr.json +1 -1
  110. package/locales/sr@cyrl/LC_MESSAGES/volto.po +5455 -0
  111. package/locales/sr@cyrl.json +1 -1
  112. package/locales/sr@latn/LC_MESSAGES/volto.po +5455 -0
  113. package/locales/sr@latn.json +1 -1
  114. package/locales/sv/LC_MESSAGES/volto.po +5455 -0
  115. package/locales/sv.json +1 -1
  116. package/locales/ta/LC_MESSAGES/volto.po +5456 -0
  117. package/locales/ta.json +1 -1
  118. package/locales/te/LC_MESSAGES/volto.po +5455 -0
  119. package/locales/te.json +1 -1
  120. package/locales/th/LC_MESSAGES/volto.po +5455 -0
  121. package/locales/th.json +1 -1
  122. package/locales/to/LC_MESSAGES/volto.po +5455 -0
  123. package/locales/to.json +1 -1
  124. package/locales/tr/LC_MESSAGES/volto.po +5456 -0
  125. package/locales/tr.json +1 -1
  126. package/locales/uk/LC_MESSAGES/volto.po +5455 -0
  127. package/locales/uk.json +1 -1
  128. package/locales/vi/LC_MESSAGES/volto.po +5455 -0
  129. package/locales/vi.json +1 -1
  130. package/locales/volto.pot +62 -1
  131. package/locales/zh_CN/LC_MESSAGES/volto.po +62 -0
  132. package/locales/zh_CN.json +1 -1
  133. package/locales/zh_Hant/LC_MESSAGES/volto.po +5455 -0
  134. package/locales/zh_Hant.json +1 -1
  135. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +5455 -0
  136. package/locales/zh_Hant_HK.json +1 -1
  137. package/package.json +19 -35
  138. package/razzle.config.js +12 -5
  139. package/src/actions/blockTypes/blockTypes.ts +24 -0
  140. package/src/components/manage/Add/Add.jsx +7 -6
  141. package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -3
  142. package/src/components/manage/Blocks/Block/Order/Order.jsx +116 -67
  143. package/src/components/manage/Blocks/Block/Order/utilities.js +28 -11
  144. package/src/components/manage/Blocks/Listing/Edit.jsx +1 -0
  145. package/src/components/manage/Blocks/Title/Edit.jsx +5 -0
  146. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +7 -0
  147. package/src/components/manage/Controlpanels/BlockType.tsx +166 -0
  148. package/src/components/manage/Controlpanels/BlockTypes.tsx +145 -0
  149. package/src/components/manage/Controlpanels/Controlpanels.jsx +28 -5
  150. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +10 -0
  151. package/src/components/manage/Controlpanels/DatabaseInformation.jsx +9 -0
  152. package/src/components/manage/Controlpanels/ModerateComments.jsx +8 -0
  153. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +4 -5
  154. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +57 -11
  155. package/src/components/manage/Diff/Diff.jsx +201 -298
  156. package/src/components/manage/Multilingual/CreateTranslation.jsx +8 -5
  157. package/src/components/manage/Multilingual/ManageTranslations.jsx +1 -1
  158. package/src/components/manage/Multilingual/TranslationObject.jsx +9 -6
  159. package/src/components/manage/Preferences/PersonalPreferences.jsx +8 -5
  160. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +5 -1
  161. package/src/components/manage/Toolbar/Types.crash.test.jsx +48 -0
  162. package/src/components/manage/Widgets/FileWidget.jsx +7 -0
  163. package/src/components/manage/Widgets/RegistryImageWidget.jsx +1 -1
  164. package/src/components/theme/PasswordReset/PasswordReset.jsx +108 -191
  165. package/src/config/ControlPanels.js +2 -0
  166. package/src/config/index.js +1 -1
  167. package/src/config/validation.ts +8 -0
  168. package/src/constants/ActionTypes.js +1 -0
  169. package/src/express-middleware/devproxy.js +3 -1
  170. package/src/helpers/Blocks/Blocks.js +81 -18
  171. package/src/helpers/FormValidation/FormValidation.test.js +31 -0
  172. package/src/helpers/FormValidation/validators.ts +21 -0
  173. package/src/helpers/MessageLabels/MessageLabels.js +5 -0
  174. package/src/helpers/Utils/Utils.jsx +17 -0
  175. package/src/helpers/Utils/Utils.test.jsx +39 -0
  176. package/src/middleware/api.js +7 -3
  177. package/src/reducers/blockTypes/blockTypes.js +38 -0
  178. package/src/reducers/index.js +2 -0
  179. package/src/reducers/users/users.js +1 -1
  180. package/src/routes.js +10 -0
  181. package/src/server.jsx +7 -5
  182. package/test-setup-globals.js +26 -0
  183. package/theme/themes/pastanaga/extras/block-types.less +17 -0
  184. package/theme/themes/pastanaga/extras/main.less +1 -2
  185. package/types/actions/blockTypes/blockTypes.d.ts +7 -0
  186. package/types/components/index.d.ts +1 -1
  187. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +2 -1
  188. package/types/components/manage/Controlpanels/BlockType.d.ts +7 -0
  189. package/types/components/manage/Controlpanels/BlockTypes.d.ts +7 -0
  190. package/types/components/manage/Diff/Diff.d.ts +7 -2
  191. package/types/components/manage/Toolbar/Types.crash.test.d.ts +1 -0
  192. package/types/components/theme/PasswordReset/PasswordReset.d.ts +6 -2
  193. package/types/config/ControlPanels.d.ts +1 -0
  194. package/types/constants/ActionTypes.d.ts +1 -0
  195. package/types/helpers/Blocks/Blocks.d.ts +3 -0
  196. package/types/helpers/FormValidation/validators.d.ts +7 -0
  197. package/types/helpers/MessageLabels/MessageLabels.d.ts +100 -94
  198. package/types/helpers/Utils/Utils.d.ts +1 -0
  199. package/types/reducers/blockTypes/blockTypes.d.ts +16 -0
  200. package/types/reducers/index.d.ts +2 -0
  201. package/types/routes.d.ts +7 -5
  202. package/vitest.config.mjs +84 -42
  203. package/webpack-plugins/webpack-less-plugin.js +1 -1
@@ -1,13 +1,23 @@
1
1
  import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { defineMessages, useIntl } from 'react-intl';
2
3
  import { createPortal } from 'react-dom';
4
+ import { toast } from 'react-toastify';
5
+ import Toast from '@plone/volto/components/manage/Toast/Toast';
3
6
  import find from 'lodash/find';
4
- import min from 'lodash/min';
7
+ import { messages as defaultMessages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
5
8
 
6
9
  import { flattenTree, getProjection, removeChildrenOf } from './utilities';
7
10
  import SortableItem from './SortableItem';
8
11
 
9
12
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
10
13
 
14
+ const messages = defineMessages({
15
+ invalidBlockType: {
16
+ id: 'You can not move this type of block to the new location',
17
+ defaultMessage: 'You can not move this type of block to the new location',
18
+ },
19
+ });
20
+
11
21
  export function Order({
12
22
  items = [],
13
23
  onMoveBlock,
@@ -24,6 +34,7 @@ export function Order({
24
34
  const [overId, setOverId] = useState(null);
25
35
  const [offsetLeft, setOffsetLeft] = useState(0);
26
36
  const [currentPosition, setCurrentPosition] = useState(null);
37
+ const intl = useIntl();
27
38
 
28
39
  const {
29
40
  DndContext,
@@ -72,6 +83,7 @@ export function Order({
72
83
  () => removeChildrenOf(flattenTree(items), activeId ? [activeId] : []),
73
84
  [activeId, items],
74
85
  );
86
+
75
87
  const projected =
76
88
  activeId && overId
77
89
  ? getProjection(
@@ -135,16 +147,14 @@ export function Order({
135
147
  onDragCancel={handleDragCancel}
136
148
  >
137
149
  <SortableContext items={sortedIds} strategy={verticalListSortingStrategy}>
138
- {flattenedItems.map(({ id, parentId, depth, data }) => (
150
+ {flattenedItems.map(({ id, parentId, parentType, depth, data }) => (
139
151
  <SortableItem
140
152
  key={id}
141
153
  id={id}
142
154
  parentId={parentId}
155
+ parentType={parentType}
143
156
  data={data}
144
- depth={min([
145
- id === activeId && projected ? projected.depth : depth,
146
- 1,
147
- ])}
157
+ depth={id === activeId && projected ? projected.depth : depth}
148
158
  indentationWidth={indentationWidth}
149
159
  onRemove={removable ? () => handleRemove(id) : undefined}
150
160
  onSelectBlock={onSelectBlock}
@@ -178,6 +188,7 @@ export function Order({
178
188
  if (activeItem) {
179
189
  setCurrentPosition({
180
190
  parentId: activeItem.parentId,
191
+ parentType: activeItem.parentType,
181
192
  overId: activeId,
182
193
  });
183
194
  }
@@ -195,18 +206,24 @@ export function Order({
195
206
 
196
207
  function handleDragEnd({ active, over }) {
197
208
  if (projected && over) {
198
- const { depth, parentId } = projected;
209
+ const { depth, parentId, parentType } = projected;
199
210
  const clonedItems = JSON.parse(JSON.stringify(flattenedItems));
200
211
  const overIndex = clonedItems.findIndex(({ id }) => id === over.id);
201
212
  const activeIndex = clonedItems.findIndex(({ id }) => id === active.id);
202
213
  const activeTreeItem = clonedItems[activeIndex];
203
214
  const oldParentId = activeTreeItem.parentId;
215
+ const oldParentType = activeTreeItem.parentType;
204
216
 
205
- clonedItems[activeIndex] = { ...activeTreeItem, depth, parentId };
217
+ clonedItems[activeIndex] = {
218
+ ...activeTreeItem,
219
+ depth,
220
+ parentId,
221
+ parentType,
222
+ };
206
223
 
207
224
  // Translate position depending on parent
208
225
  if (parentId === oldParentId) {
209
- // Move from and to toplevel or move within the same grid block
226
+ // Move from and to toplevel or move within the same sub block
210
227
 
211
228
  let destIndex = clonedItems[overIndex].index;
212
229
  if (clonedItems[overIndex].depth > clonedItems[activeIndex].depth) {
@@ -226,66 +243,97 @@ export function Order({
226
243
  },
227
244
  });
228
245
  } else if (parentId && oldParentId) {
229
- // Move from one gridblock to another
230
-
231
- onMoveBlock({
232
- source: {
233
- position: clonedItems[activeIndex].index,
234
- parent: oldParentId,
235
- id: active.id,
236
- },
237
- destination: {
238
- position:
239
- overIndex < activeIndex
240
- ? clonedItems[overIndex - 1].parentId
241
- ? clonedItems[overIndex - 1].index + 1
242
- : clonedItems[overIndex].index
243
- : overIndex + 1 < clonedItems.length
244
- ? clonedItems[overIndex + 1].index
245
- : clonedItems[overIndex].index + 1,
246
- parent: parentId,
247
- },
248
- });
246
+ // Move from one subblock to another
247
+
248
+ if (parentType === oldParentType) {
249
+ // Moving within the same type of subblock
250
+ onMoveBlock({
251
+ source: {
252
+ position: clonedItems[activeIndex].index,
253
+ parent: oldParentId,
254
+ id: active.id,
255
+ },
256
+ destination: {
257
+ position:
258
+ overIndex < activeIndex
259
+ ? clonedItems[overIndex - 1].parentId
260
+ ? clonedItems[overIndex - 1].index + 1
261
+ : clonedItems[overIndex].index
262
+ : overIndex + 1 < clonedItems.length
263
+ ? clonedItems[overIndex + 1].index
264
+ : clonedItems[overIndex].index + 1,
265
+ parent: parentId,
266
+ },
267
+ });
268
+ } else {
269
+ toast.error(
270
+ <Toast
271
+ error
272
+ title={intl.formatMessage(defaultMessages.error)}
273
+ content={intl.formatMessage(messages.invalidBlockType)}
274
+ />,
275
+ );
276
+ }
249
277
  } else if (oldParentId) {
250
- // Moving to the main container from a gridblock
251
-
252
- onMoveBlock({
253
- source: {
254
- position: clonedItems[activeIndex].index,
255
- parent: oldParentId,
256
- id: active.id,
257
- },
258
- destination: {
259
- position:
260
- overIndex > activeIndex
261
- ? overIndex + 1 < clonedItems.length
262
- ? clonedItems[overIndex + 1].index
263
- : clonedItems[overIndex].index + 1
264
- : clonedItems[overIndex].index,
265
- parent: parentId,
266
- },
267
- });
278
+ // Moving to the main container from a subblock
279
+
280
+ if (oldParentType === 'gridBlock') {
281
+ onMoveBlock({
282
+ source: {
283
+ position: clonedItems[activeIndex].index,
284
+ parent: oldParentId,
285
+ id: active.id,
286
+ },
287
+ destination: {
288
+ position:
289
+ overIndex > activeIndex
290
+ ? overIndex + 1 < clonedItems.length
291
+ ? clonedItems[overIndex + 1].index
292
+ : clonedItems[overIndex].index + 1
293
+ : clonedItems[overIndex].index,
294
+ parent: parentId,
295
+ },
296
+ });
297
+ } else {
298
+ toast.error(
299
+ <Toast
300
+ error
301
+ title={intl.formatMessage(defaultMessages.error)}
302
+ content={intl.formatMessage(messages.invalidBlockType)}
303
+ />,
304
+ );
305
+ }
268
306
  } else {
269
- // Moving from the main container to a gridblock
270
-
271
- onMoveBlock({
272
- source: {
273
- position: clonedItems[activeIndex].index,
274
- parent: oldParentId,
275
- id: active.id,
276
- },
277
- destination: {
278
- position:
279
- overIndex < activeIndex
280
- ? clonedItems[overIndex - 1].parentId
281
- ? clonedItems[overIndex - 1].index + 1
282
- : clonedItems[overIndex].index
283
- : overIndex + 1 < clonedItems.length
284
- ? clonedItems[overIndex + 1].index
285
- : clonedItems[overIndex].index + 1,
286
- parent: parentId,
287
- },
288
- });
307
+ // Moving from the main container to a subblock
308
+
309
+ if (parentType === 'gridBlock') {
310
+ onMoveBlock({
311
+ source: {
312
+ position: clonedItems[activeIndex].index,
313
+ parent: oldParentId,
314
+ id: active.id,
315
+ },
316
+ destination: {
317
+ position:
318
+ overIndex < activeIndex
319
+ ? clonedItems[overIndex - 1].parentId
320
+ ? clonedItems[overIndex - 1].index + 1
321
+ : clonedItems[overIndex].index
322
+ : overIndex + 1 < clonedItems.length
323
+ ? clonedItems[overIndex + 1].index
324
+ : clonedItems[overIndex].index + 1,
325
+ parent: parentId,
326
+ },
327
+ });
328
+ } else {
329
+ toast.error(
330
+ <Toast
331
+ error
332
+ title={intl.formatMessage(defaultMessages.error)}
333
+ content={intl.formatMessage(messages.invalidBlockType)}
334
+ />,
335
+ );
336
+ }
289
337
  }
290
338
  }
291
339
 
@@ -321,6 +369,7 @@ export function Order({
321
369
  } else {
322
370
  setCurrentPosition({
323
371
  parentId: projected.parentId,
372
+ parentType: projected.parentType,
324
373
  overId,
325
374
  });
326
375
  }
@@ -34,27 +34,44 @@ export function getProjection(
34
34
  depth = minDepth;
35
35
  }
36
36
 
37
- return { depth, maxDepth, minDepth, parentId: getParentId() };
37
+ return { depth, maxDepth, minDepth, ...getParent() };
38
38
 
39
- function getParentId() {
39
+ function getParent() {
40
40
  if (depth === 0 || !previousItem) {
41
- return null;
41
+ return {
42
+ parentId: null,
43
+ parentType: null,
44
+ };
42
45
  }
43
46
 
44
47
  if (depth <= previousItem.depth) {
45
- return previousItem.parentId;
48
+ return {
49
+ parentId: previousItem.parentId,
50
+ parentType: previousItem.parentType,
51
+ };
46
52
  }
47
53
 
48
54
  if (depth > previousItem.depth) {
49
- return previousItem.id;
55
+ return {
56
+ parentId: previousItem.id,
57
+ parentType: previousItem.data?.['@type'] || null,
58
+ };
50
59
  }
51
60
 
52
61
  const newParent = newItems
53
62
  .slice(0, overItemIndex)
54
63
  .reverse()
55
- .find((item) => item.depth === depth)?.parentId;
56
-
57
- return newParent ?? null;
64
+ .find((item) => item.depth === depth);
65
+
66
+ return newParent
67
+ ? {
68
+ parentId: newParent.parentId,
69
+ parentType: newParent.parentType,
70
+ }
71
+ : {
72
+ parentId: null,
73
+ parentType: null,
74
+ };
58
75
  }
59
76
  }
60
77
 
@@ -79,12 +96,12 @@ function getMinDepth({ nextItem }) {
79
96
  return 0;
80
97
  }
81
98
 
82
- function flatten(items = [], parentId = null, depth = 0) {
99
+ function flatten(items = [], parentId = null, parentType = null, depth = 0) {
83
100
  return items.reduce((acc, item, index) => {
84
101
  return [
85
102
  ...acc,
86
- { ...item, parentId, depth, index },
87
- ...flatten(item.children, item.id, depth + 1),
103
+ { ...item, parentId, parentType, depth, index },
104
+ ...flatten(item.children, item.id, item.data?.['@type'], depth + 1),
88
105
  ];
89
106
  }, []);
90
107
  }
@@ -50,6 +50,7 @@ const Edit = React.memo(
50
50
  function areEquals(prevProps, nextProps) {
51
51
  return !(
52
52
  nextProps.selected !== prevProps.selected ||
53
+ nextProps.index !== prevProps.index ||
53
54
  !isEqual(prevProps.data, nextProps.data)
54
55
  );
55
56
  },
@@ -17,6 +17,10 @@ const messages = defineMessages({
17
17
  id: 'Type the title…',
18
18
  defaultMessage: 'Type the title…',
19
19
  },
20
+ editable_title: {
21
+ id: 'Content title',
22
+ defaultMessage: 'Content title',
23
+ },
20
24
  });
21
25
 
22
26
  function usePrevious(value) {
@@ -159,6 +163,7 @@ export const TitleBlockEdit = (props) => {
159
163
  renderElement={renderElement}
160
164
  onFocus={handleFocus}
161
165
  aria-multiline="false"
166
+ aria-label={intl.formatMessage(messages.editable_title)}
162
167
  ></Editable>
163
168
  </Slate>
164
169
  );
@@ -27,6 +27,7 @@ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
27
27
  import Icon from '@plone/volto/components/theme/Icon/Icon';
28
28
  import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
29
29
  import Toast from '@plone/volto/components/manage/Toast/Toast';
30
+ import Error from '@plone/volto/components/theme/Error/Error';
30
31
  import circleBottomSVG from '@plone/volto/icons/circle-bottom.svg';
31
32
  import circleTopSVG from '@plone/volto/icons/circle-top.svg';
32
33
  import backSVG from '@plone/volto/icons/back.svg';
@@ -148,6 +149,7 @@ const AddonsControlpanel = (props) => {
148
149
  shallowEqual,
149
150
  );
150
151
  const loadingAddons = useSelector((state) => state.addons.loading);
152
+ const addonsError = useSelector((state) => state.addons.error);
151
153
 
152
154
  useEffect(() => {
153
155
  dispatch(listAddons());
@@ -245,6 +247,11 @@ const AddonsControlpanel = (props) => {
245
247
  setactiveIndex(newIndex);
246
248
  };
247
249
 
250
+ // Error handling for unauthorized access
251
+ if (addonsError) {
252
+ return <Error error={addonsError} />;
253
+ }
254
+
248
255
  return (
249
256
  <Container id="page-addons" className="controlpanel-addons">
250
257
  <Helmet title={intl.formatMessage(messages.addOns)} />
@@ -0,0 +1,166 @@
1
+ import { getBlockTypes } from '@plone/volto/actions/blockTypes/blockTypes';
2
+ import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
3
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
4
+ import { getParentUrl, flattenToAppURL } from '@plone/volto/helpers/Url/Url';
5
+ import { useClient } from '@plone/volto/hooks';
6
+ import config from '@plone/volto/registry';
7
+ import { useEffect } from 'react';
8
+ import { createPortal } from 'react-dom';
9
+ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
10
+ import { useDispatch, useSelector } from 'react-redux';
11
+ import { Link, useParams } from 'react-router-dom';
12
+ import Error from '@plone/volto/components/theme/Error/Error';
13
+ import { Table } from '@plone/components';
14
+ import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
15
+
16
+ import backSVG from '@plone/volto/icons/back.svg';
17
+ import type { Location } from 'history';
18
+ import type { BlockTypeItem } from '@plone/types';
19
+
20
+ const messages = defineMessages({
21
+ back: {
22
+ id: 'Back',
23
+ defaultMessage: 'Back',
24
+ },
25
+ controlpanelTitle: {
26
+ id: 'controlpanel_block_type',
27
+ defaultMessage: 'Block: {title}',
28
+ },
29
+ item: {
30
+ id: 'Item',
31
+ defaultMessage: 'Item',
32
+ },
33
+ path: {
34
+ id: 'Path',
35
+ defaultMessage: 'Path',
36
+ },
37
+ occurrences: {
38
+ id: 'Occurrences',
39
+ defaultMessage: 'Occurrences',
40
+ },
41
+ loading: {
42
+ id: 'loading',
43
+ defaultMessage: 'Loading',
44
+ },
45
+ });
46
+
47
+ type RouteProps = {
48
+ history: History;
49
+ location: Location;
50
+ };
51
+
52
+ type BlockTypesState = {
53
+ error: {
54
+ status?: number;
55
+ } | null;
56
+ items: BlockTypeItem[];
57
+ loaded: boolean;
58
+ loading: boolean;
59
+ };
60
+
61
+ type SelectorState = {
62
+ blockTypes: BlockTypesState;
63
+ };
64
+
65
+ const BlockTypeControlpanel = (props: RouteProps) => {
66
+ const { location } = props;
67
+ const params = useParams<{ id: string }>();
68
+ const id = params.id;
69
+ const intl = useIntl();
70
+ const blockTypes = useSelector((state: SelectorState) => state.blockTypes);
71
+ const isClient = useClient();
72
+ const dispatch = useDispatch();
73
+ const pathname = location.pathname;
74
+ const block =
75
+ config.blocks.blocksConfig[id as keyof typeof config.blocks.blocksConfig];
76
+
77
+ useEffect(() => {
78
+ dispatch(getBlockTypes(id));
79
+ }, [dispatch, id]);
80
+
81
+ if (blockTypes.loading) {
82
+ return <div>{intl.formatMessage(messages.loading)}&hellip;</div>;
83
+ }
84
+
85
+ if (blockTypes?.error?.status) {
86
+ return <Error error={blockTypes.error} />;
87
+ }
88
+
89
+ const translatedTitle = block?.title
90
+ ? intl.formatMessage({ id: block.title, defaultMessage: block.title })
91
+ : id;
92
+
93
+ return (
94
+ blockTypes.loaded && (
95
+ <div
96
+ id="page-block_type"
97
+ className="ui container controlpanel-block-type"
98
+ >
99
+ <h1>
100
+ {intl.formatMessage(messages.controlpanelTitle, {
101
+ title: translatedTitle,
102
+ })}
103
+ </h1>
104
+ {blockTypes.items?.length > 0 ? (
105
+ <Table
106
+ className="react-aria-Table cmsui-table"
107
+ columns={[
108
+ {
109
+ id: 'title',
110
+ name: intl.formatMessage(messages.item),
111
+ isRowHeader: true,
112
+ },
113
+ {
114
+ id: 'path',
115
+ name: intl.formatMessage(messages.path),
116
+ isRowHeader: true,
117
+ },
118
+ {
119
+ id: 'occurrences',
120
+ name: intl.formatMessage(messages.occurrences),
121
+ isRowHeader: true,
122
+ },
123
+ ]}
124
+ rows={blockTypes.items.map((item) => ({
125
+ id: item['@id'],
126
+ textValue: item.title,
127
+ title: (
128
+ <UniversalLink href={item['@id']}>{item.title}</UniversalLink>
129
+ ),
130
+ path: flattenToAppURL(item['@id']) || '/',
131
+ occurrences: item.count,
132
+ }))}
133
+ />
134
+ ) : (
135
+ <FormattedMessage
136
+ id="no-blocks-found"
137
+ defaultMessage="No items found for type: {type}."
138
+ values={{
139
+ type: translatedTitle,
140
+ }}
141
+ />
142
+ )}
143
+ {isClient &&
144
+ createPortal(
145
+ <Toolbar
146
+ pathname={pathname}
147
+ hideDefaultViewButtons
148
+ inner={
149
+ <Link to={getParentUrl(pathname)} className="item">
150
+ <Icon
151
+ name={backSVG}
152
+ className="contents circled"
153
+ size="30px"
154
+ title={intl.formatMessage(messages.back)}
155
+ />
156
+ </Link>
157
+ }
158
+ />,
159
+ document.getElementById('toolbar') as HTMLElement,
160
+ )}
161
+ </div>
162
+ )
163
+ );
164
+ };
165
+
166
+ export default BlockTypeControlpanel;