@plone/volto 18.34.0 → 18.35.1

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/.release-it.json +3 -0
  2. package/CHANGELOG.md +60 -0
  3. package/locales/af/LC_MESSAGES/volto.po +35 -5
  4. package/locales/af.json +1 -1
  5. package/locales/ar/LC_MESSAGES/volto.po +35 -5
  6. package/locales/ar.json +1 -1
  7. package/locales/bg/LC_MESSAGES/volto.po +35 -5
  8. package/locales/bg.json +1 -1
  9. package/locales/bn/LC_MESSAGES/volto.po +35 -5
  10. package/locales/bn.json +1 -1
  11. package/locales/ca/LC_MESSAGES/volto.po +37 -7
  12. package/locales/ca.json +1 -1
  13. package/locales/cs/LC_MESSAGES/volto.po +35 -5
  14. package/locales/cs.json +1 -1
  15. package/locales/cy/LC_MESSAGES/volto.po +35 -5
  16. package/locales/cy.json +1 -1
  17. package/locales/da/LC_MESSAGES/volto.po +35 -5
  18. package/locales/da.json +1 -1
  19. package/locales/de/LC_MESSAGES/volto.po +39 -9
  20. package/locales/de.json +1 -1
  21. package/locales/el/LC_MESSAGES/volto.po +35 -5
  22. package/locales/el.json +1 -1
  23. package/locales/en/LC_MESSAGES/volto.po +35 -5
  24. package/locales/en.json +1 -1
  25. package/locales/en_AU/LC_MESSAGES/volto.po +35 -5
  26. package/locales/en_AU.json +1 -1
  27. package/locales/en_GB/LC_MESSAGES/volto.po +35 -5
  28. package/locales/en_GB.json +1 -1
  29. package/locales/eo/LC_MESSAGES/volto.po +35 -5
  30. package/locales/eo.json +1 -1
  31. package/locales/es/LC_MESSAGES/volto.po +36 -6
  32. package/locales/es.json +1 -1
  33. package/locales/et/LC_MESSAGES/volto.po +35 -5
  34. package/locales/et.json +1 -1
  35. package/locales/eu/LC_MESSAGES/volto.po +38 -8
  36. package/locales/eu.json +1 -1
  37. package/locales/fa/LC_MESSAGES/volto.po +35 -5
  38. package/locales/fa.json +1 -1
  39. package/locales/fi/LC_MESSAGES/volto.po +35 -5
  40. package/locales/fi.json +1 -1
  41. package/locales/fr/LC_MESSAGES/volto.po +36 -6
  42. package/locales/fr.json +1 -1
  43. package/locales/fu/LC_MESSAGES/volto.po +35 -5
  44. package/locales/fu.json +1 -1
  45. package/locales/gl/LC_MESSAGES/volto.po +35 -5
  46. package/locales/gl.json +1 -1
  47. package/locales/he/LC_MESSAGES/volto.po +35 -5
  48. package/locales/he.json +1 -1
  49. package/locales/hi/LC_MESSAGES/volto.po +38 -8
  50. package/locales/hi.json +1 -1
  51. package/locales/hr/LC_MESSAGES/volto.po +35 -5
  52. package/locales/hr.json +1 -1
  53. package/locales/hu/LC_MESSAGES/volto.po +35 -5
  54. package/locales/hu.json +1 -1
  55. package/locales/hy/LC_MESSAGES/volto.po +35 -5
  56. package/locales/hy.json +1 -1
  57. package/locales/id/LC_MESSAGES/volto.po +35 -5
  58. package/locales/id.json +1 -1
  59. package/locales/it/LC_MESSAGES/volto.po +36 -6
  60. package/locales/it.json +1 -1
  61. package/locales/ja/LC_MESSAGES/volto.po +35 -5
  62. package/locales/ja.json +1 -1
  63. package/locales/ka/LC_MESSAGES/volto.po +35 -5
  64. package/locales/ka.json +1 -1
  65. package/locales/kn/LC_MESSAGES/volto.po +35 -5
  66. package/locales/kn.json +1 -1
  67. package/locales/ko/LC_MESSAGES/volto.po +35 -5
  68. package/locales/ko.json +1 -1
  69. package/locales/lt/LC_MESSAGES/volto.po +35 -5
  70. package/locales/lt.json +1 -1
  71. package/locales/lv/LC_MESSAGES/volto.po +35 -5
  72. package/locales/lv.json +1 -1
  73. package/locales/mi/LC_MESSAGES/volto.po +35 -5
  74. package/locales/mi.json +1 -1
  75. package/locales/mk/LC_MESSAGES/volto.po +35 -5
  76. package/locales/mk.json +1 -1
  77. package/locales/my/LC_MESSAGES/volto.po +35 -5
  78. package/locales/my.json +1 -1
  79. package/locales/nb_NO/LC_MESSAGES/volto.po +35 -5
  80. package/locales/nb_NO.json +1 -1
  81. package/locales/nl/LC_MESSAGES/volto.po +36 -6
  82. package/locales/nl.json +1 -1
  83. package/locales/nn/LC_MESSAGES/volto.po +35 -5
  84. package/locales/nn.json +1 -1
  85. package/locales/pl/LC_MESSAGES/volto.po +35 -5
  86. package/locales/pl.json +1 -1
  87. package/locales/pt/LC_MESSAGES/volto.po +35 -5
  88. package/locales/pt.json +1 -1
  89. package/locales/pt_BR/LC_MESSAGES/volto.po +49 -19
  90. package/locales/pt_BR.json +1 -1
  91. package/locales/rm/LC_MESSAGES/volto.po +35 -5
  92. package/locales/rm.json +1 -1
  93. package/locales/ro/LC_MESSAGES/volto.po +36 -6
  94. package/locales/ro.json +1 -1
  95. package/locales/ru/LC_MESSAGES/volto.po +36 -6
  96. package/locales/ru.json +1 -1
  97. package/locales/sk/LC_MESSAGES/volto.po +35 -5
  98. package/locales/sk.json +1 -1
  99. package/locales/sl/LC_MESSAGES/volto.po +35 -5
  100. package/locales/sl.json +1 -1
  101. package/locales/sm/LC_MESSAGES/volto.po +35 -5
  102. package/locales/sm.json +1 -1
  103. package/locales/sq/LC_MESSAGES/volto.po +35 -5
  104. package/locales/sq.json +1 -1
  105. package/locales/sr/LC_MESSAGES/volto.po +35 -5
  106. package/locales/sr.json +1 -1
  107. package/locales/sr@cyrl/LC_MESSAGES/volto.po +35 -5
  108. package/locales/sr@cyrl.json +1 -1
  109. package/locales/sr@latn/LC_MESSAGES/volto.po +35 -5
  110. package/locales/sr@latn.json +1 -1
  111. package/locales/sv/LC_MESSAGES/volto.po +35 -5
  112. package/locales/sv.json +1 -1
  113. package/locales/ta/LC_MESSAGES/volto.po +36 -6
  114. package/locales/ta.json +1 -1
  115. package/locales/te/LC_MESSAGES/volto.po +35 -5
  116. package/locales/te.json +1 -1
  117. package/locales/th/LC_MESSAGES/volto.po +35 -5
  118. package/locales/th.json +1 -1
  119. package/locales/to/LC_MESSAGES/volto.po +35 -5
  120. package/locales/to.json +1 -1
  121. package/locales/tr/LC_MESSAGES/volto.po +35 -5
  122. package/locales/tr.json +1 -1
  123. package/locales/uk/LC_MESSAGES/volto.po +35 -5
  124. package/locales/uk.json +1 -1
  125. package/locales/vi/LC_MESSAGES/volto.po +35 -5
  126. package/locales/vi.json +1 -1
  127. package/locales/volto.pot +36 -6
  128. package/locales/zh_CN/LC_MESSAGES/volto.po +35 -5
  129. package/locales/zh_CN.json +1 -1
  130. package/locales/zh_Hant/LC_MESSAGES/volto.po +35 -5
  131. package/locales/zh_Hant.json +1 -1
  132. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +35 -5
  133. package/locales/zh_Hant_HK.json +1 -1
  134. package/news/7308.fix +1 -0
  135. package/news/8084.fix +1 -0
  136. package/package.json +28 -32
  137. package/src/actions/querystringsearch/querystringsearch.js +4 -1
  138. package/src/actions/querystringsearch/querystringsearch.test.js +77 -0
  139. package/src/components/manage/BlockChooser/BlockChooser.jsx +7 -10
  140. package/src/components/manage/Blocks/Block/Edit.jsx +9 -10
  141. package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -4
  142. package/src/components/manage/Blocks/Image/View.test.jsx +8 -8
  143. package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +7 -6
  144. package/src/components/manage/Controlpanels/ContentTypes.jsx +9 -2
  145. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +58 -5
  146. package/src/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.jsx +624 -0
  147. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +8 -0
  148. package/src/components/manage/Form/Form.jsx +6 -1
  149. package/src/components/manage/Form/ModalForm.jsx +165 -87
  150. package/src/components/manage/Sidebar/ObjectBrowser.jsx +7 -0
  151. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +7 -3
  152. package/src/components/manage/Sidebar/ObjectBrowserBody.test.jsx +52 -0
  153. package/src/components/manage/Sidebar/Sidebar.jsx +1 -0
  154. package/src/components/manage/Toast/Toast.jsx +35 -1
  155. package/src/components/manage/Toast/Toast.test.jsx +8 -5
  156. package/src/components/manage/Widgets/ArrayWidget.jsx +3 -2
  157. package/src/components/manage/Widgets/FormFieldWrapper.jsx +16 -3
  158. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +1 -0
  159. package/src/components/manage/Widgets/QuerystringWidget.jsx +1 -18
  160. package/src/components/manage/Widgets/QuerystringWidget.test.jsx +45 -2
  161. package/src/components/manage/Widgets/SelectStyling.jsx +33 -1
  162. package/src/components/manage/Widgets/SelectWidget.jsx +3 -2
  163. package/src/components/manage/Widgets/TextWidget.test.jsx +44 -0
  164. package/src/components/theme/Search/Search.jsx +24 -1
  165. package/src/components/theme/Unauthorized/Unauthorized.jsx +1 -2
  166. package/src/components/theme/View/EventView.stories.jsx +89 -0
  167. package/src/components/theme/View/FileView.stories.jsx +50 -0
  168. package/src/components/theme/View/LinkView.stories.jsx +57 -0
  169. package/src/components/theme/View/ListingView.stories.jsx +70 -0
  170. package/src/components/theme/View/NewsItemView.stories.jsx +58 -0
  171. package/src/components/theme/View/RenderBlocks.stories.jsx +112 -0
  172. package/src/components/theme/View/SummaryView.stories.jsx +71 -0
  173. package/src/components/theme/View/TabularView.stories.jsx +66 -0
  174. package/src/helpers/FormValidation/validators.ts +15 -2
  175. package/src/helpers/I18n/I18n.test.ts +44 -0
  176. package/src/helpers/I18n/I18n.ts +31 -0
  177. package/src/helpers/index.js +1 -0
  178. package/src/server.jsx +7 -1
  179. package/theme/themes/pastanaga/collections/form.overrides +21 -0
  180. package/theme/themes/pastanaga/elements/button.overrides +30 -3
  181. package/theme/themes/pastanaga/extras/main.less +1 -0
  182. package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
  183. package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +2 -6
  184. package/types/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.d.ts +1 -0
  185. package/types/components/manage/Controlpanels/index.d.ts +1 -1
  186. package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
  187. package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
  188. package/types/components/manage/Sidebar/ObjectBrowserBody.test.d.ts +1 -0
  189. package/types/components/manage/Widgets/ImageWidget.d.ts +1 -1
  190. package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
  191. package/types/components/manage/Widgets/QuerystringWidget.d.ts +0 -4
  192. package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
  193. package/types/components/manage/Widgets/index.d.ts +2 -2
  194. package/types/components/theme/View/EventView.stories.d.ts +19 -0
  195. package/types/components/theme/View/FileView.stories.d.ts +18 -0
  196. package/types/components/theme/View/LinkView.stories.d.ts +18 -0
  197. package/types/components/theme/View/ListingView.stories.d.ts +24 -0
  198. package/types/components/theme/View/NewsItemView.stories.d.ts +23 -0
  199. package/types/components/theme/View/RenderBlocks.stories.d.ts +23 -0
  200. package/types/components/theme/View/SummaryView.stories.d.ts +23 -0
  201. package/types/components/theme/View/TabularView.stories.d.ts +23 -0
  202. package/types/helpers/I18n/I18n.d.ts +20 -0
  203. package/types/helpers/index.d.ts +1 -0
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "18.34.0",
12
+ "version": "18.35.1",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -142,8 +142,8 @@
142
142
  "@dnd-kit/core": "6.0.8",
143
143
  "@dnd-kit/sortable": "7.0.2",
144
144
  "@dnd-kit/utilities": "3.2.2",
145
- "@loadable/component": "5.14.1",
146
- "@loadable/server": "5.14.0",
145
+ "@loadable/component": "5.16.7",
146
+ "@loadable/server": "5.16.7",
147
147
  "@redux-devtools/extension": "^3.3.0",
148
148
  "classnames": "2.5.1",
149
149
  "connected-react-router": "6.8.0",
@@ -151,10 +151,9 @@
151
151
  "decorate-component-with-props": "1.2.1",
152
152
  "dependency-graph": "0.10.0",
153
153
  "detect-browser": "5.1.0",
154
- "diff": "3.5.0",
155
- "express": "4.19.2",
154
+ "diff": "3.5.1",
155
+ "express": "4.22.1",
156
156
  "filesize": "6",
157
- "full-icu": "1.4.0",
158
157
  "github-slugger": "1.4.0",
159
158
  "history": "4.10.1",
160
159
  "hoist-non-react-statics": "3.3.2",
@@ -166,14 +165,14 @@
166
165
  "jwt-decode": "2.2.0",
167
166
  "linkify-it": "3.0.2",
168
167
  "locale": "0.1.0",
169
- "lodash": "4.17.23",
168
+ "lodash": "4.18.1",
170
169
  "lodash-move": "1.1.1",
171
170
  "moment": "2.29.4",
172
171
  "object-assign": "4.1.1",
173
172
  "prepend-http": "2",
174
173
  "pretty-bytes": "5.3.0",
175
174
  "prettier": "3.2.5",
176
- "prismjs": "1.27.0",
175
+ "prismjs": "1.30.0",
177
176
  "process": "^0.11.10",
178
177
  "promise-file-reader": "1.0.2",
179
178
  "prop-types": "15.7.2",
@@ -207,7 +206,6 @@
207
206
  "react-side-effect": "2.1.2",
208
207
  "react-simple-code-editor": "0.7.1",
209
208
  "react-sortable-hoc": "2.0.0",
210
- "react-test-renderer": "18.2.0",
211
209
  "react-toastify": "5.5.0",
212
210
  "react-transition-group": "4.4.5",
213
211
  "react-virtualized": "9.22.3",
@@ -215,7 +213,6 @@
215
213
  "redux-actions": "3.0.0",
216
214
  "redux-connect": "10.0.0",
217
215
  "redux-localstorage-simple": "2.5.1",
218
- "redux-mock-store": "1.5.4",
219
216
  "redux-thunk": "2.4.2",
220
217
  "rrule": "2.7.1",
221
218
  "semantic-ui-less": "2.4.1",
@@ -231,10 +228,10 @@
231
228
  "universal-cookie-express": "4.0.3",
232
229
  "url": "^0.11.3",
233
230
  "use-deep-compare-effect": "1.8.1",
234
- "uuid": "^8.3.2",
235
- "@plone/registry": "2.7.2",
236
- "@plone/scripts": "3.10.5",
237
- "@plone/volto-slate": "18.9.2"
231
+ "uuid": "^14.0.0",
232
+ "@plone/registry": "2.7.3",
233
+ "@plone/volto-slate": "18.10.1",
234
+ "@plone/scripts": "3.10.7"
238
235
  },
239
236
  "devDependencies": {
240
237
  "@babel/core": "^7.0.0",
@@ -249,7 +246,7 @@
249
246
  "@babel/types": "7.20.5",
250
247
  "@fiverr/afterbuild-webpack-plugin": "^1.0.0",
251
248
  "@jest/globals": "^29.7.0",
252
- "@loadable/babel-plugin": "5.13.2",
249
+ "@loadable/babel-plugin": "5.16.1",
253
250
  "@loadable/webpack-plugin": "5.15.2",
254
251
  "@sinonjs/fake-timers": "^6.0.1",
255
252
  "@storybook/addon-actions": "^8.0.4",
@@ -263,12 +260,12 @@
263
260
  "@storybook/test": "^8.0.4",
264
261
  "@storybook/theming": "^8.0.4",
265
262
  "@testing-library/cypress": "10.1.0",
266
- "@testing-library/jest-dom": "6.4.2",
267
- "@testing-library/react": "14.3.1",
263
+ "@testing-library/jest-dom": "6.9.1",
264
+ "@testing-library/react": "^16.3.2",
268
265
  "@testing-library/react-hooks": "8.0.1",
269
266
  "@types/history": "^4.7.11",
270
267
  "@types/jest": "^29.5.8",
271
- "@types/loadable__component": "^5.13.9",
268
+ "@types/loadable__component": "^5.13.10",
272
269
  "@types/lodash": "^4.14.201",
273
270
  "@types/node": "^22",
274
271
  "@types/react": "^18",
@@ -277,11 +274,10 @@
277
274
  "@types/react-router-dom": "^5.3.3",
278
275
  "@types/react-test-renderer": "18.0.7",
279
276
  "@types/redux-mock-store": "^1.5.0",
280
- "@types/uuid": "^9.0.2",
281
277
  "@typescript-eslint/eslint-plugin": "^7.7.0",
282
278
  "@typescript-eslint/parser": "^7.7.0",
283
279
  "@vitejs/plugin-react": "^4.3.4",
284
- "@vitest/ui": "^2.1.8",
280
+ "@vitest/ui": "^3.2.4",
285
281
  "autoprefixer": "10.4.8",
286
282
  "axe-core": "4.4.2",
287
283
  "babel-loader": "9.1.0",
@@ -308,7 +304,7 @@
308
304
  "eslint-plugin-prettier": "^5.1.3",
309
305
  "eslint-plugin-react": "^7.34.1",
310
306
  "eslint-plugin-react-hooks": "^4.6.0",
311
- "html-webpack-plugin": "5.5.0",
307
+ "html-webpack-plugin": "5.6.7",
312
308
  "identity-obj-proxy": "3.0.0",
313
309
  "jest": "26.6.3",
314
310
  "jest-environment-jsdom": "^26",
@@ -319,22 +315,22 @@
319
315
  "less": "3.13.1",
320
316
  "less-loader": "11.1.0",
321
317
  "lodash-webpack-plugin": "0.11.6",
322
- "mini-css-extract-plugin": "2.7.2",
318
+ "mini-css-extract-plugin": "2.10.2",
323
319
  "moment-locales-webpack-plugin": "1.2.0",
324
- "postcss": "8.4.31",
320
+ "postcss": "^8.5.15",
325
321
  "postcss-flexbugs-fixes": "5.0.2",
326
322
  "postcss-less": "6.0.0",
327
- "postcss-load-config": "3.1.4",
328
- "postcss-loader": "7.0.2",
329
- "postcss-overrides": "3.1.4",
330
- "postcss-scss": "4.0.6",
323
+ "postcss-load-config": "^6.0.1",
324
+ "postcss-loader": "^8.2.1",
325
+ "postcss-scss": "4.0.9",
331
326
  "razzle": "4.2.18",
332
327
  "razzle-dev-utils": "4.2.18",
333
328
  "razzle-plugin-scss": "4.2.18",
334
- "react-docgen-typescript-plugin": "^1.0.5",
335
329
  "react-error-overlay": "6.0.9",
336
330
  "react-is": "^18.2.0",
337
- "release-it": "^19.0.5",
331
+ "react-test-renderer": "18.2.0",
332
+ "redux-mock-store": "1.5.4",
333
+ "release-it": "^20.0.1",
338
334
  "semver": "^7.5.4",
339
335
  "start-server-and-test": "2.1.5",
340
336
  "storybook": "^8.6.15",
@@ -345,7 +341,7 @@
345
341
  "svg-loader": "0.0.2",
346
342
  "svgo": "^3.0.0",
347
343
  "svgo-loader": "3.0.3",
348
- "terser-webpack-plugin": "5.3.6",
344
+ "terser-webpack-plugin": "5.4.0",
349
345
  "ts-jest": "^26.4.2",
350
346
  "ts-loader": "9.4.4",
351
347
  "typescript": "^5.7.3",
@@ -357,7 +353,7 @@
357
353
  "webpack-dev-server": "4.11.1",
358
354
  "webpack-node-externals": "3.0.0",
359
355
  "why": "0.6.2",
360
- "@plone/types": "1.6.2",
356
+ "@plone/types": "1.6.3",
361
357
  "@plone/volto-coresandbox": "1.0.0"
362
358
  },
363
359
  "scripts": {
@@ -369,7 +365,7 @@
369
365
  "vitest": "vitest",
370
366
  "coverage": "vitest run --coverage",
371
367
  "test": "razzle test --maxWorkers=${MAX_WORKERS:-50%}",
372
- "test:ci": "CI=true NODE_ICU_DATA=node_modules/full-icu razzle test",
368
+ "test:ci": "CI=true razzle test",
373
369
  "test:husky": "CI=true yarn test --bail --findRelatedTests",
374
370
  "start:prod": "NODE_ENV=production node build/server.js",
375
371
  "prettier": "prettier --single-quote --check '{src,cypress}/**/*.{js,jsx,ts,tsx}' --check '*.js'",
@@ -17,7 +17,10 @@ export function getQueryStringResults(path, data, subrequest, page) {
17
17
  delete requestData.depth;
18
18
  requestData.query.forEach((q) => {
19
19
  if (q.i === 'path') {
20
- q.v += '::' + data.depth;
20
+ // fixes https://github.com/plone/volto/issues/8349
21
+ // Skip if depth is already embedded in the path value (e.g. set via object browser)
22
+ const hasEmbeddedDepth = /::\d+$/.test(q.v);
23
+ if (!hasEmbeddedDepth) q.v += '::' + data.depth;
21
24
  }
22
25
  });
23
26
  }
@@ -73,4 +73,81 @@ describe('querystringsearch action', () => {
73
73
  expect(action.request.path).toEqual('/@querystring-search');
74
74
  expect(action.request.data.sort_order).toEqual('descending');
75
75
  });
76
+
77
+ describe('depth handling', () => {
78
+ it('appends depth to path value when depth is set and path has no embedded depth', () => {
79
+ const data = {
80
+ query: [
81
+ {
82
+ i: 'path',
83
+ o: 'plone.app.querystring.operation.string.path',
84
+ v: '/folder',
85
+ },
86
+ ],
87
+ depth: 2,
88
+ };
89
+ const action = getQueryStringResults('', data);
90
+
91
+ const pathQuery = action.request.data.query.find((q) => q.i === 'path');
92
+ expect(pathQuery.v).toEqual('/folder::2');
93
+ });
94
+
95
+ it('does not double-append depth when path value already contains an embedded depth', () => {
96
+ const data = {
97
+ query: [
98
+ {
99
+ i: 'path',
100
+ o: 'plone.app.querystring.operation.string.path',
101
+ v: '/folder::1',
102
+ },
103
+ ],
104
+ depth: 3,
105
+ };
106
+ const action = getQueryStringResults('', data);
107
+
108
+ const pathQuery = action.request.data.query.find((q) => q.i === 'path');
109
+ expect(pathQuery.v).toEqual('/folder::1');
110
+ });
111
+
112
+ it('removes depth from the top-level request data', () => {
113
+ const data = {
114
+ query: [
115
+ {
116
+ i: 'path',
117
+ o: 'plone.app.querystring.operation.string.path',
118
+ v: '/folder',
119
+ },
120
+ ],
121
+ depth: 2,
122
+ };
123
+ const action = getQueryStringResults('', data);
124
+
125
+ expect(action.request.data.depth).toBeUndefined();
126
+ });
127
+
128
+ it('handles mixed path entries: appends depth only to those without embedded depth', () => {
129
+ const data = {
130
+ query: [
131
+ {
132
+ i: 'path',
133
+ o: 'plone.app.querystring.operation.string.path',
134
+ v: '/folder-a',
135
+ },
136
+ {
137
+ i: 'path',
138
+ o: 'plone.app.querystring.operation.string.path',
139
+ v: '/folder-b::5',
140
+ },
141
+ ],
142
+ depth: 2,
143
+ };
144
+ const action = getQueryStringResults('', data);
145
+
146
+ const [pathA, pathB] = action.request.data.query.filter(
147
+ (q) => q.i === 'path',
148
+ );
149
+ expect(pathA.v).toEqual('/folder-a::2');
150
+ expect(pathB.v).toEqual('/folder-b::5');
151
+ });
152
+ });
76
153
  });
@@ -10,6 +10,7 @@ import { useIntl, defineMessages } from 'react-intl';
10
10
  import Icon from '@plone/volto/components/theme/Icon/Icon';
11
11
  import AnimateHeight from 'react-animate-height';
12
12
  import config from '@plone/volto/registry';
13
+ import { formatMessageWithFallback } from '@plone/volto/helpers/I18n/I18n';
13
14
  import upSVG from '@plone/volto/icons/up-key.svg';
14
15
  import downSVG from '@plone/volto/icons/down-key.svg';
15
16
  import BlockChooserSearch from './BlockChooserSearch';
@@ -98,16 +99,10 @@ const BlockChooser = ({
98
99
  }
99
100
  const [filterValue, setFilterValue] = React.useState('');
100
101
 
101
- const getFormatMessage = (message) =>
102
- intl.formatMessage({
103
- id: message,
104
- defaultMessage: message,
105
- });
106
-
107
102
  function blocksAvailableFilter(blocks) {
108
103
  return blocks.filter(
109
104
  (block) =>
110
- getFormatMessage(block.title)
105
+ formatMessageWithFallback(intl, block.title)
111
106
  .toLowerCase()
112
107
  .includes(filterValue.toLowerCase()) ||
113
108
  block.title.toLowerCase().includes(filterValue.toLowerCase()) ||
@@ -117,7 +112,7 @@ const BlockChooser = ({
117
112
  function filterVariations(block) {
118
113
  return block.variations?.filter(
119
114
  (variation) =>
120
- getFormatMessage(variation.title)
115
+ formatMessageWithFallback(intl, variation.title)
121
116
  .toLowerCase()
122
117
  .includes(filterValue.toLowerCase()) &&
123
118
  !variation.title.toLowerCase().includes('default'),
@@ -145,9 +140,11 @@ const BlockChooser = ({
145
140
  }}
146
141
  >
147
142
  <Icon name={block.icon} size="36px" />
148
- {getFormatMessage(block.title)}
143
+ {formatMessageWithFallback(intl, block.title)}
149
144
  {filterValue && variations?.[0]?.title && (
150
- <small>{getFormatMessage(variations[0].title)}</small>
145
+ <small>
146
+ {formatMessageWithFallback(intl, variations[0].title)}
147
+ </small>
151
148
  )}
152
149
  </Button>
153
150
  </Button.Group>
@@ -149,15 +149,6 @@ export class Edit extends Component {
149
149
  this.props.setUIState({ hovered: this.props.id });
150
150
  }
151
151
  }}
152
- onFocus={(e) => {
153
- // TODO: This `onFocus` steals somehow the focus from the slate block
154
- // we have to investigate why this is happening
155
- // Apparently, I can't see any difference in the behavior
156
- // If any, we can fix it in successive iterations
157
- // if (this.props.hovered !== this.props.id) {
158
- // this.props.setUIState({ hovered: this.props.id });
159
- // }
160
- }}
161
152
  onMouseLeave={(e) => {
162
153
  e.preventDefault();
163
154
  e.stopPropagation();
@@ -172,6 +163,15 @@ export class Edit extends Component {
172
163
  e,
173
164
  );
174
165
  }}
166
+ onFocus={(e) => {
167
+ const isMultipleSelection = e.shiftKey || e.ctrlKey || e.metaKey;
168
+ !this.props.selected &&
169
+ this.props.onSelectBlock(
170
+ this.props.id,
171
+ this.props.selected ? false : isMultipleSelection,
172
+ e,
173
+ );
174
+ }}
175
175
  onKeyDown={
176
176
  !(blockHasOwnFocusManagement || disableNewBlocks)
177
177
  ? (e) =>
@@ -245,7 +245,6 @@ export class Edit extends Component {
245
245
  className={cx(`block ${type}`, { selected: this.props.selected })}
246
246
  style={{ outline: 'none' }}
247
247
  ref={this.blockNode}
248
- // The tabIndex is required for the keyboard navigation
249
248
  tabIndex={-1}
250
249
  >
251
250
  {this.props.intl.formatMessage(messages.unknownBlock, {
@@ -5,7 +5,9 @@ import includes from 'lodash/includes';
5
5
  import cx from 'classnames';
6
6
  import Icon from '@plone/volto/components/theme/Icon/Icon';
7
7
  import { setUIState } from '@plone/volto/actions/form/form';
8
+ import { formatMessageWithFallback } from '@plone/volto/helpers/I18n/I18n';
8
9
  import config from '@plone/volto/registry';
10
+ import { useIntl } from 'react-intl';
9
11
 
10
12
  import deleteSVG from '@plone/volto/icons/delete.svg';
11
13
  import dragSVG from '@plone/volto/icons/drag.svg';
@@ -34,6 +36,7 @@ export const Item = forwardRef(
34
36
  },
35
37
  ref,
36
38
  ) => {
39
+ const intl = useIntl();
37
40
  const selected = useSelector((state) => state.form.ui.selected);
38
41
  const hovered = useSelector((state) => state.form.ui.hovered);
39
42
  const multiSelected = useSelector((state) => state.form.ui.multiSelected);
@@ -49,7 +52,11 @@ export const Item = forwardRef(
49
52
  ? data.required
50
53
  : includes(config.blocks.requiredBlocks, data?.['@type']);
51
54
  const fixed = !!data?.fixed;
52
-
55
+ const configTitle = config.blocks.blocksConfig[data?.['@type']]?.title;
56
+ const blockTitle =
57
+ data?.plaintext ||
58
+ formatMessageWithFallback(intl, configTitle) ||
59
+ data?.title;
53
60
  return (
54
61
  <li
55
62
  className={classNames(
@@ -122,9 +129,7 @@ export const Item = forwardRef(
122
129
  style={{ verticalAlign: 'middle' }}
123
130
  />
124
131
  )}{' '}
125
- {data?.plaintext ||
126
- config.blocks.blocksConfig[data?.['@type']]?.title ||
127
- data?.title}
132
+ {blockTitle}
128
133
  </span>
129
134
  {!clone && onRemove && !required && (
130
135
  <button
@@ -36,13 +36,13 @@ config.blocks.blocksConfig = {
36
36
 
37
37
  describe('Image View Component', () => {
38
38
  test('renders a view image component with a local image', () => {
39
- const { getByRole } = render(<View data={{ url: '/image.jpg' }} />);
40
- const img = getByRole('img');
39
+ const { container } = render(<View data={{ url: '/image.jpg' }} />);
40
+ const img = container.querySelector('img');
41
41
  expect(img).toHaveAttribute('src', '/image.jpg/@@images/image');
42
42
  expect(img).toHaveAttribute('loading', 'lazy');
43
43
  });
44
44
  test('renders a view image component with a local image with a link', () => {
45
- const { container, getByRole } = render(
45
+ const { container } = render(
46
46
  <Provider store={store}>
47
47
  <MemoryRouter>
48
48
  <View
@@ -51,22 +51,22 @@ describe('Image View Component', () => {
51
51
  </MemoryRouter>
52
52
  </Provider>,
53
53
  );
54
- const img = getByRole('img');
54
+ const img = container.querySelector('img');
55
55
  const a = container.querySelector('a');
56
56
  expect(img).toHaveAttribute('src', '/image.jpg/@@images/image');
57
57
  expect(a).toHaveAttribute('href', '/front-page');
58
58
  });
59
59
  test('renders a view image component with an external image', () => {
60
- const { getByRole } = render(
60
+ const { container } = render(
61
61
  <Provider store={store}>
62
62
  <View data={{ url: 'https://plone.org/logo.jpg' }} />
63
63
  </Provider>,
64
64
  );
65
- const img = getByRole('img');
65
+ const img = container.querySelector('img');
66
66
  expect(img).toHaveAttribute('src', 'https://plone.org/logo.jpg');
67
67
  });
68
68
  test('renders a view image component with an external image with a link', () => {
69
- const { container, getByRole } = render(
69
+ const { container } = render(
70
70
  <Provider store={store}>
71
71
  <View
72
72
  data={{
@@ -76,7 +76,7 @@ describe('Image View Component', () => {
76
76
  />
77
77
  </Provider>,
78
78
  );
79
- const img = getByRole('img');
79
+ const img = container.querySelector('img');
80
80
  const a = container.querySelector('a');
81
81
  expect(img).toHaveAttribute('src', 'https://plone.org/logo.jpg');
82
82
  expect(a).toHaveAttribute('href', 'http://front-page');
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Breadcrumb } from 'semantic-ui-react';
3
+ import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
3
4
  import { Link, useLocation } from 'react-router-dom';
4
5
  import { defineMessages, useIntl } from 'react-intl';
5
6
  import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
@@ -41,22 +42,22 @@ const ContentsBreadcrumbs = (props) => {
41
42
  </>
42
43
  )}
43
44
  {settings.isMultilingual && pathname?.split('/')?.length > 2 && (
44
- <Link
45
- to={`/${lang}/contents`}
45
+ <UniversalLink
46
+ href={`/${lang}/contents`}
46
47
  className="section"
47
48
  title={intl.formatMessage(messages.home)}
48
49
  >
49
50
  {langmap?.[lang]?.nativeName ?? lang}
50
- </Link>
51
+ </UniversalLink>
51
52
  )}
52
53
  {!settings.isMultilingual && (
53
- <Link
54
- to="/contents"
54
+ <UniversalLink
55
+ href="/contents"
55
56
  className="section"
56
57
  title={intl.formatMessage(messages.home)}
57
58
  >
58
59
  <ContentsBreadcrumbsHomeItem />
59
- </Link>
60
+ </UniversalLink>
60
61
  )}
61
62
  {items.map((breadcrumb, index, breadcrumbs) => [
62
63
  <Breadcrumb.Divider key={`divider-${breadcrumb.url}`} />,
@@ -227,6 +227,7 @@ class ContentTypes extends Component {
227
227
  addTypeError: undefined,
228
228
  addTypeSetFormDataCallback: undefined,
229
229
  });
230
+ this._addTypeTrigger?.focus();
230
231
  toast.success(
231
232
  <Toast
232
233
  success
@@ -369,10 +370,13 @@ class ContentTypes extends Component {
369
370
  />
370
371
  <ModalForm
371
372
  open={this.state.showAddType}
372
- className="modal"
373
+ className="modal add-content-type"
373
374
  onSubmit={this.onAddTypeSubmit}
374
375
  submitError={this.state.addTypeError}
375
- onCancel={() => this.setState({ showAddType: false })}
376
+ onCancel={() => {
377
+ this.setState({ showAddType: false });
378
+ this._addTypeTrigger?.focus();
379
+ }}
376
380
  title={this.props.intl.formatMessage(messages.addTypeFormTitle)}
377
381
  loading={this.props.cpanelRequest.post.loading}
378
382
  schema={{
@@ -474,6 +478,9 @@ class ContentTypes extends Component {
474
478
  aria-label={this.props.intl.formatMessage(messages.add)}
475
479
  tabIndex={0}
476
480
  id="toolbar-add"
481
+ ref={(el) => {
482
+ this._addTypeTrigger = el;
483
+ }}
477
484
  onClick={() => {
478
485
  this.setState({ showAddType: true });
479
486
  }}
@@ -13,6 +13,7 @@ import { listRoles } from '@plone/volto/actions/roles/roles';
13
13
  import { listGroups, updateGroup } from '@plone/volto/actions/groups/groups';
14
14
  import { getControlpanel } from '@plone/volto/actions/controlpanels/controlpanels';
15
15
  import { getUserSchema } from '@plone/volto/actions/userschema/userschema';
16
+ import { asyncConnect } from '@plone/volto/helpers/AsyncConnect';
16
17
  import jwtDecode from 'jwt-decode';
17
18
  import Icon from '@plone/volto/components/theme/Icon/Icon';
18
19
  import Toast from '@plone/volto/components/manage/Toast/Toast';
@@ -57,7 +58,8 @@ import {
57
58
  * UsersControlpanel functional component.
58
59
  * @function UsersControlpanel
59
60
  */
60
- const UsersControlpanel = () => {
61
+ const UsersControlpanel = (props) => {
62
+ const { staticContext } = props;
61
63
  const intl = useIntl();
62
64
  const dispatch = useDispatch();
63
65
 
@@ -143,7 +145,10 @@ const UsersControlpanel = () => {
143
145
  await listUsersAction();
144
146
  setEntries(users);
145
147
  }
146
- await getUserSchemaAction();
148
+ // Only fetch user schema if it hasn't been loaded yet (e.g. by asyncConnect SSR)
149
+ if (!userschema?.loaded) {
150
+ await getUserSchemaAction();
151
+ }
147
152
  await getUserAction(userId);
148
153
  }, [
149
154
  getControlpanelAction,
@@ -152,6 +157,7 @@ const UsersControlpanel = () => {
152
157
  listGroupsAction,
153
158
  listUsersAction,
154
159
  users,
160
+ userschema,
155
161
  getUserSchemaAction,
156
162
  getUserAction,
157
163
  userId,
@@ -451,6 +457,8 @@ const UsersControlpanel = () => {
451
457
 
452
458
  useEffect(() => {
453
459
  setIsClient(true);
460
+ // Skip fetching if the store already has an error.
461
+ if (loadRolesRequest?.error) return;
454
462
  fetchData();
455
463
  checkLoginUsingEmailStatus();
456
464
  }, []); // eslint-disable-line react-hooks/exhaustive-deps
@@ -471,8 +479,14 @@ const UsersControlpanel = () => {
471
479
  }
472
480
  }, [loadRolesRequest?.error, loadRolesRequest?.loading]);
473
481
 
474
- if (error) {
475
- return <Error error={error} />;
482
+ const effectiveError =
483
+ error ||
484
+ (loadRolesRequest?.error && !loadRolesRequest?.loading
485
+ ? loadRolesRequest.error
486
+ : null);
487
+
488
+ if (effectiveError) {
489
+ return <Error error={effectiveError} staticContext={staticContext} />;
476
490
  }
477
491
 
478
492
  const usernameToDelete = userToDelete ? userToDelete.username : '';
@@ -729,4 +743,43 @@ const UsersControlpanel = () => {
729
743
  );
730
744
  };
731
745
 
732
- export default UsersControlpanel;
746
+ export default asyncConnect([
747
+ {
748
+ key: 'controlpanels',
749
+ promise: ({ store: { dispatch, getState } }) => {
750
+ return dispatch(getControlpanel('usergroup')).then(() => {
751
+ const state = getState();
752
+ const many_users = state.controlpanels?.controlpanel?.data?.many_users;
753
+ if (!many_users) {
754
+ dispatch(listUsers());
755
+ dispatch(listGroups());
756
+ }
757
+ });
758
+ },
759
+ },
760
+ {
761
+ key: 'roles',
762
+ promise: ({ store: { dispatch } }) => {
763
+ return dispatch(listRoles());
764
+ },
765
+ },
766
+ {
767
+ key: 'userschema',
768
+ promise: ({ store: { dispatch } }) => {
769
+ return dispatch(getUserSchema());
770
+ },
771
+ },
772
+ {
773
+ key: 'user',
774
+ promise: ({ store: { dispatch, getState } }) => {
775
+ const state = getState();
776
+ const token = state.userSession.token;
777
+ if (token) {
778
+ const userId = jwtDecode(token).sub;
779
+ if (userId) {
780
+ return dispatch(getUser(userId));
781
+ }
782
+ }
783
+ },
784
+ },
785
+ ])(UsersControlpanel);