@plone/volto 19.0.0-alpha.9 → 19.1.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 (535) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +26 -15
  3. package/AGENTS.md +47 -0
  4. package/CHANGELOG.md +543 -12
  5. package/README.md +22 -19
  6. package/babel.js +1 -9
  7. package/cypress/support/guillotina.js +1 -0
  8. package/cypress.config.js +1 -0
  9. package/global-test-setup.js +1 -2
  10. package/locales/af/LC_MESSAGES/volto.po +5516 -0
  11. package/locales/af.json +1 -1
  12. package/locales/ar/LC_MESSAGES/volto.po +5516 -0
  13. package/locales/ar.json +1 -1
  14. package/locales/bg/LC_MESSAGES/volto.po +5516 -0
  15. package/locales/bg.json +1 -1
  16. package/locales/bn/LC_MESSAGES/volto.po +5516 -0
  17. package/locales/bn.json +1 -1
  18. package/locales/ca/LC_MESSAGES/volto.po +551 -359
  19. package/locales/ca.json +1 -1
  20. package/locales/cs/LC_MESSAGES/volto.po +5516 -0
  21. package/locales/cs.json +1 -1
  22. package/locales/cy/LC_MESSAGES/volto.po +5516 -0
  23. package/locales/cy.json +1 -1
  24. package/locales/da/LC_MESSAGES/volto.po +5516 -0
  25. package/locales/da.json +1 -1
  26. package/locales/de/LC_MESSAGES/volto.po +222 -29
  27. package/locales/de.json +1 -1
  28. package/locales/el/LC_MESSAGES/volto.po +5516 -0
  29. package/locales/el.json +1 -1
  30. package/locales/en/LC_MESSAGES/volto.po +211 -15
  31. package/locales/en.json +1 -1
  32. package/locales/en_AU/LC_MESSAGES/volto.po +5516 -0
  33. package/locales/en_AU.json +1 -1
  34. package/locales/en_GB/LC_MESSAGES/volto.po +5516 -0
  35. package/locales/en_GB.json +1 -1
  36. package/locales/eo/LC_MESSAGES/volto.po +5516 -0
  37. package/locales/eo.json +1 -1
  38. package/locales/es/LC_MESSAGES/volto.po +336 -145
  39. package/locales/es.json +1 -1
  40. package/locales/et/LC_MESSAGES/volto.po +5516 -0
  41. package/locales/et.json +1 -1
  42. package/locales/eu/LC_MESSAGES/volto.po +413 -222
  43. package/locales/eu.json +1 -1
  44. package/locales/fa/LC_MESSAGES/volto.po +5516 -0
  45. package/locales/fa.json +1 -1
  46. package/locales/fi/LC_MESSAGES/volto.po +212 -20
  47. package/locales/fi.json +1 -1
  48. package/locales/fr/LC_MESSAGES/volto.po +369 -178
  49. package/locales/fr.json +1 -1
  50. package/locales/fu/LC_MESSAGES/volto.po +5516 -0
  51. package/locales/fu.json +1 -1
  52. package/locales/gl/LC_MESSAGES/volto.po +5516 -0
  53. package/locales/gl.json +1 -1
  54. package/locales/he/LC_MESSAGES/volto.po +5516 -0
  55. package/locales/he.json +1 -1
  56. package/locales/hi/LC_MESSAGES/volto.po +220 -23
  57. package/locales/hi.json +1 -1
  58. package/locales/hr/LC_MESSAGES/volto.po +5516 -0
  59. package/locales/hr.json +1 -1
  60. package/locales/hu/LC_MESSAGES/volto.po +5516 -0
  61. package/locales/hu.json +1 -1
  62. package/locales/hy/LC_MESSAGES/volto.po +5516 -0
  63. package/locales/hy.json +1 -1
  64. package/locales/id/LC_MESSAGES/volto.po +5516 -0
  65. package/locales/id.json +1 -1
  66. package/locales/it/LC_MESSAGES/volto.po +236 -39
  67. package/locales/it.json +1 -1
  68. package/locales/ja/LC_MESSAGES/volto.po +254 -61
  69. package/locales/ja.json +1 -1
  70. package/locales/ka/LC_MESSAGES/volto.po +5516 -0
  71. package/locales/ka.json +1 -1
  72. package/locales/kn/LC_MESSAGES/volto.po +5516 -0
  73. package/locales/kn.json +1 -1
  74. package/locales/ko/LC_MESSAGES/volto.po +5516 -0
  75. package/locales/ko.json +1 -1
  76. package/locales/lt/LC_MESSAGES/volto.po +5516 -0
  77. package/locales/lt.json +1 -1
  78. package/locales/lv/LC_MESSAGES/volto.po +5516 -0
  79. package/locales/lv.json +1 -1
  80. package/locales/mi/LC_MESSAGES/volto.po +5516 -0
  81. package/locales/mi.json +1 -1
  82. package/locales/mk/LC_MESSAGES/volto.po +5516 -0
  83. package/locales/mk.json +1 -1
  84. package/locales/my/LC_MESSAGES/volto.po +5516 -0
  85. package/locales/my.json +1 -1
  86. package/locales/nb_NO/LC_MESSAGES/volto.po +5516 -0
  87. package/locales/nb_NO.json +1 -1
  88. package/locales/nl/LC_MESSAGES/volto.po +264 -71
  89. package/locales/nl.json +1 -1
  90. package/locales/nn/LC_MESSAGES/volto.po +5516 -0
  91. package/locales/nn.json +1 -1
  92. package/locales/pl/LC_MESSAGES/volto.po +5516 -0
  93. package/locales/pl.json +1 -1
  94. package/locales/pt/LC_MESSAGES/volto.po +862 -670
  95. package/locales/pt.json +1 -1
  96. package/locales/pt_BR/LC_MESSAGES/volto.po +216 -25
  97. package/locales/pt_BR.json +1 -1
  98. package/locales/rm/LC_MESSAGES/volto.po +5516 -0
  99. package/locales/rm.json +1 -1
  100. package/locales/ro/LC_MESSAGES/volto.po +248 -57
  101. package/locales/ro.json +1 -1
  102. package/locales/ru/LC_MESSAGES/volto.po +211 -19
  103. package/locales/ru.json +1 -1
  104. package/locales/sk/LC_MESSAGES/volto.po +5516 -0
  105. package/locales/sk.json +1 -1
  106. package/locales/sl/LC_MESSAGES/volto.po +5516 -0
  107. package/locales/sl.json +1 -1
  108. package/locales/sm/LC_MESSAGES/volto.po +5516 -0
  109. package/locales/sm.json +1 -1
  110. package/locales/sq/LC_MESSAGES/volto.po +5516 -0
  111. package/locales/sq.json +1 -1
  112. package/locales/sr/LC_MESSAGES/volto.po +5516 -0
  113. package/locales/sr.json +1 -1
  114. package/locales/sr@cyrl/LC_MESSAGES/volto.po +5516 -0
  115. package/locales/sr@cyrl.json +1 -1
  116. package/locales/sr@latn/LC_MESSAGES/volto.po +5516 -0
  117. package/locales/sr@latn.json +1 -1
  118. package/locales/sv/LC_MESSAGES/volto.po +5516 -0
  119. package/locales/sv.json +1 -1
  120. package/locales/ta/LC_MESSAGES/volto.po +5516 -0
  121. package/locales/ta.json +1 -1
  122. package/locales/te/LC_MESSAGES/volto.po +5516 -0
  123. package/locales/te.json +1 -1
  124. package/locales/th/LC_MESSAGES/volto.po +5516 -0
  125. package/locales/th.json +1 -1
  126. package/locales/to/LC_MESSAGES/volto.po +5516 -0
  127. package/locales/to.json +1 -1
  128. package/locales/tr/LC_MESSAGES/volto.po +5516 -0
  129. package/locales/tr.json +1 -1
  130. package/locales/uk/LC_MESSAGES/volto.po +5516 -0
  131. package/locales/uk.json +1 -1
  132. package/locales/vi/LC_MESSAGES/volto.po +5516 -0
  133. package/locales/vi.json +1 -1
  134. package/locales/volto.pot +205 -14
  135. package/locales/zh_CN/LC_MESSAGES/volto.po +211 -19
  136. package/locales/zh_CN.json +1 -1
  137. package/locales/zh_Hant/LC_MESSAGES/volto.po +5516 -0
  138. package/locales/zh_Hant.json +1 -1
  139. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +5516 -0
  140. package/locales/zh_Hant_HK.json +1 -1
  141. package/package.json +71 -149
  142. package/razzle.config.js +16 -25
  143. package/src/actions/blockTypes/blockTypes.ts +24 -0
  144. package/src/actions/controlpanels/controlpanels.js +7 -12
  145. package/src/actions/controlpanels/controlpanels.test.js +2 -3
  146. package/src/components/manage/Actions/Actions.test.jsx +1 -5
  147. package/src/components/manage/Add/Add.jsx +15 -10
  148. package/src/components/manage/Add/Add.test.jsx +10 -3
  149. package/src/components/manage/Aliases/Aliases.test.jsx +5 -2
  150. package/src/components/manage/BlockChooser/BlockChooser.jsx +8 -10
  151. package/src/components/manage/Blocks/Block/BlocksForm.jsx +10 -7
  152. package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +3 -14
  153. package/src/components/manage/Blocks/Block/Edit.jsx +19 -10
  154. package/src/components/manage/Blocks/Block/Order/Item.jsx +33 -14
  155. package/src/components/manage/Blocks/Block/Order/Item.test.jsx +90 -0
  156. package/src/components/manage/Blocks/Block/Order/Order.jsx +116 -67
  157. package/src/components/manage/Blocks/Block/Order/utilities.js +28 -11
  158. package/src/components/manage/Blocks/Block/Settings.test.jsx +1 -5
  159. package/src/components/manage/Blocks/Grid/View.jsx +14 -11
  160. package/src/components/manage/Blocks/Grid/context.js +3 -0
  161. package/src/components/manage/Blocks/HTML/Edit.test.jsx +1 -5
  162. package/src/components/manage/Blocks/Image/ImageSidebar.test.jsx +2 -5
  163. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +2 -5
  164. package/src/components/manage/Blocks/Listing/Edit.jsx +1 -0
  165. package/src/components/manage/Blocks/Listing/ListingBody.jsx +4 -0
  166. package/src/components/manage/Blocks/Maps/Body.jsx +37 -0
  167. package/src/components/manage/Blocks/Maps/Edit.jsx +6 -21
  168. package/src/components/manage/Blocks/Maps/MapsSidebar.test.jsx +1 -5
  169. package/src/components/manage/Blocks/Maps/View.jsx +16 -25
  170. package/src/components/manage/Blocks/Search/components/DateRangeFacet.test.jsx +1 -6
  171. package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +22 -1
  172. package/src/components/manage/Blocks/Search/components/SelectFacet.test.jsx +1 -6
  173. package/src/components/manage/Blocks/Search/components/SortOn.jsx +8 -2
  174. package/src/components/manage/Blocks/Search/components/ToggleFacet.jsx +14 -0
  175. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +10 -2
  176. package/src/components/manage/Blocks/Teaser/View.jsx +0 -1
  177. package/src/components/manage/Blocks/Teaser/utils.js +13 -0
  178. package/src/components/manage/Blocks/Teaser/utils.test.js +34 -0
  179. package/src/components/manage/Blocks/Title/Edit.jsx +5 -0
  180. package/src/components/manage/Blocks/Video/Body.jsx +69 -43
  181. package/src/components/manage/Blocks/Video/Body.test.jsx +122 -5
  182. package/src/components/manage/Blocks/Video/Edit.jsx +20 -2
  183. package/src/components/manage/Blocks/Video/Edit.test.jsx +6 -0
  184. package/src/components/manage/Blocks/Video/VideoSidebar.test.jsx +1 -5
  185. package/src/components/manage/Blocks/Video/View.jsx +1 -0
  186. package/src/components/manage/Blocks/Video/View.test.jsx +29 -15
  187. package/src/components/manage/Blocks/Video/schema.js +14 -1
  188. package/src/components/manage/Contents/Contents.jsx +854 -747
  189. package/src/components/manage/Contents/Contents.test.jsx +9 -10
  190. package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +4 -3
  191. package/src/components/manage/Contents/ContentsIndexHeader.jsx +47 -81
  192. package/src/components/manage/Contents/ContentsIndexHeader.test.jsx +10 -3
  193. package/src/components/manage/Contents/ContentsItem.jsx +226 -278
  194. package/src/components/manage/Contents/ContentsItem.test.jsx +10 -6
  195. package/src/components/manage/Contents/ContentsPropertiesModal.test.jsx +1 -5
  196. package/src/components/manage/Contents/ContentsRenameModal.test.jsx +1 -5
  197. package/src/components/manage/Contents/ContentsTagsModal.test.jsx +1 -5
  198. package/src/components/manage/Contents/ContentsWorkflowModal.test.jsx +1 -5
  199. package/src/components/manage/Contents/DropZoneContent.jsx +339 -0
  200. package/src/components/manage/Contents/__mocks__/index.tsx +2 -18
  201. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +7 -0
  202. package/src/components/manage/Controlpanels/AddonsControlpanel.test.jsx +7 -4
  203. package/src/components/manage/Controlpanels/Aliases.test.jsx +8 -9
  204. package/src/components/manage/Controlpanels/BlockType.tsx +165 -0
  205. package/src/components/manage/Controlpanels/BlockTypes.tsx +145 -0
  206. package/src/components/manage/Controlpanels/ContentType.jsx +131 -222
  207. package/src/components/manage/Controlpanels/ContentType.test.jsx +13 -14
  208. package/src/components/manage/Controlpanels/ContentTypeLayout.test.jsx +12 -9
  209. package/src/components/manage/Controlpanels/ContentTypeSchema.jsx +1 -1
  210. package/src/components/manage/Controlpanels/ContentTypes.jsx +9 -2
  211. package/src/components/manage/Controlpanels/ContentTypes.test.jsx +7 -4
  212. package/src/components/manage/Controlpanels/Controlpanel.jsx +122 -218
  213. package/src/components/manage/Controlpanels/Controlpanel.test.jsx +8 -33
  214. package/src/components/manage/Controlpanels/Controlpanels.jsx +28 -5
  215. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +23 -8
  216. package/src/components/manage/Controlpanels/DatabaseInformation.jsx +9 -0
  217. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx +7 -4
  218. package/src/components/manage/Controlpanels/ModerateComments.jsx +8 -0
  219. package/src/components/manage/Controlpanels/ModerateComments.test.jsx +7 -4
  220. package/src/components/manage/Controlpanels/Relations/Relations.jsx +1 -1
  221. package/src/components/manage/Controlpanels/Rules/AddRule.test.jsx +8 -9
  222. package/src/components/manage/Controlpanels/Rules/ConfigureRule.test.jsx +9 -6
  223. package/src/components/manage/Controlpanels/Rules/EditRule.test.jsx +8 -9
  224. package/src/components/manage/Controlpanels/Rules/Rules.test.jsx +7 -4
  225. package/src/components/manage/Controlpanels/UndoControlpanel.test.jsx +8 -9
  226. package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx +10 -4
  227. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +628 -631
  228. package/src/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.jsx +624 -0
  229. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +70 -10
  230. package/src/components/manage/Controlpanels/VersionOverview.jsx +19 -6
  231. package/src/components/manage/Delete/Delete.test.jsx +13 -8
  232. package/src/components/manage/Diff/Diff.jsx +201 -298
  233. package/src/components/manage/Diff/Diff.test.jsx +8 -10
  234. package/src/components/manage/Diff/DiffField.test.jsx +1 -6
  235. package/src/components/manage/Display/Display.test.jsx +2 -11
  236. package/src/components/manage/Edit/Edit.test.jsx +12 -11
  237. package/src/components/manage/Form/BlockDataForm.test.jsx +1 -5
  238. package/src/components/manage/Form/Field.jsx +1 -69
  239. package/src/components/manage/Form/Form.jsx +17 -4
  240. package/src/components/manage/Form/Form.test.jsx +130 -5
  241. package/src/components/manage/Form/InlineForm.test.jsx +1 -5
  242. package/src/components/manage/Form/ModalForm.jsx +175 -97
  243. package/src/components/manage/Form/ModalForm.test.jsx +27 -5
  244. package/src/components/manage/Form/__mocks__/index.tsx +9 -27
  245. package/src/components/manage/History/History.jsx +209 -300
  246. package/src/components/manage/History/History.test.jsx +20 -9
  247. package/src/components/manage/LinksToItem/LinksToItem.test.jsx +7 -4
  248. package/src/components/manage/Multilingual/CompareLanguages.jsx +6 -6
  249. package/src/components/manage/Multilingual/CreateTranslation.jsx +16 -13
  250. package/src/components/manage/Multilingual/ManageTranslations.jsx +5 -5
  251. package/src/components/manage/Multilingual/ManageTranslations.test.jsx +15 -12
  252. package/src/components/manage/Multilingual/TranslationObject.jsx +11 -8
  253. package/src/components/manage/Preferences/ChangePassword.test.jsx +8 -9
  254. package/src/components/manage/Preferences/PersonalPreferences.jsx +8 -5
  255. package/src/components/manage/Preferences/PersonalPreferences.test.jsx +10 -23
  256. package/src/components/manage/Rules/Rules.test.jsx +5 -2
  257. package/src/components/manage/Sharing/Sharing.jsx +21 -15
  258. package/src/components/manage/Sharing/Sharing.test.jsx +9 -6
  259. package/src/components/manage/Sidebar/ObjectBrowser.jsx +10 -0
  260. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +25 -5
  261. package/src/components/manage/Sidebar/ObjectBrowserBody.test.jsx +52 -0
  262. package/src/components/manage/Sidebar/Sidebar.jsx +2 -0
  263. package/src/components/manage/Sidebar/Sidebar.test.jsx +4 -1
  264. package/src/components/manage/Sidebar/SidebarPortal.test.tsx +42 -0
  265. package/src/components/manage/Sidebar/SidebarPortal.tsx +48 -0
  266. package/src/components/manage/TemplateChooser/TemplateChooser.test.jsx +1 -0
  267. package/src/components/manage/Toast/Toast.jsx +32 -0
  268. package/src/components/manage/Toast/Toast.test.jsx +9 -5
  269. package/src/components/manage/Toolbar/PersonalTools.test.jsx +15 -0
  270. package/src/components/manage/Toolbar/Toolbar.jsx +109 -12
  271. package/src/components/manage/Toolbar/Toolbar.test.jsx +15 -10
  272. package/src/components/manage/Toolbar/Types.crash.test.jsx +48 -0
  273. package/src/components/manage/Toolbar/Types.jsx +6 -4
  274. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +16 -0
  275. package/src/components/manage/UniversalLink/UniversalLink.tsx +1 -0
  276. package/src/components/manage/Widgets/AlignWidget.stories.jsx +9 -0
  277. package/src/components/manage/Widgets/AlignWidget.test.tsx +95 -0
  278. package/src/components/manage/Widgets/{AlignWidget.jsx → AlignWidget.tsx} +23 -7
  279. package/src/components/manage/Widgets/ArrayWidget.jsx +111 -88
  280. package/src/components/manage/Widgets/ArrayWidget.test.jsx +1 -12
  281. package/src/components/manage/Widgets/BlockAlignment.stories.tsx +104 -0
  282. package/src/components/manage/Widgets/BlockAlignment.test.tsx +104 -0
  283. package/src/components/manage/Widgets/BlockAlignment.tsx +88 -0
  284. package/src/components/manage/Widgets/BlockWidth.stories.tsx +69 -0
  285. package/src/components/manage/Widgets/BlockWidth.test.tsx +62 -0
  286. package/src/components/manage/Widgets/BlockWidth.tsx +101 -0
  287. package/src/components/manage/Widgets/ButtonsWidget.stories.jsx +61 -0
  288. package/src/components/manage/Widgets/ButtonsWidget.test.tsx +138 -0
  289. package/src/components/manage/Widgets/ButtonsWidget.tsx +195 -0
  290. package/src/components/manage/Widgets/CheckboxGroupWidget.test.jsx +1 -6
  291. package/src/components/manage/Widgets/DatetimeWidget.jsx +98 -54
  292. package/src/components/manage/Widgets/DatetimeWidget.test.jsx +56 -6
  293. package/src/components/manage/Widgets/FileWidget.jsx +7 -0
  294. package/src/components/manage/Widgets/FormFieldWrapper.jsx +143 -163
  295. package/src/components/manage/Widgets/ImageWidget.jsx +18 -5
  296. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +6 -0
  297. package/src/components/manage/Widgets/ObjectListWidget.test.jsx +2 -11
  298. package/src/components/manage/Widgets/ObjectWidget.test.jsx +1 -5
  299. package/src/components/manage/Widgets/QueryWidget.jsx +137 -9
  300. package/src/components/manage/Widgets/QuerystringWidget.test.jsx +3 -1
  301. package/src/components/manage/Widgets/RadioGroupWidget.test.jsx +1 -6
  302. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.test.jsx +1 -6
  303. package/src/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField.jsx +56 -50
  304. package/src/components/manage/Widgets/RegistryImageWidget.jsx +1 -1
  305. package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +1 -0
  306. package/src/components/manage/Widgets/SchemaWidget.test.jsx +1 -6
  307. package/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx +1 -6
  308. package/src/components/manage/Widgets/SelectAutoComplete.test.jsx +1 -6
  309. package/src/components/manage/Widgets/SelectStyling.jsx +52 -20
  310. package/src/components/manage/Widgets/SelectWidget.test.jsx +1 -6
  311. package/src/components/manage/Widgets/Size.stories.tsx +69 -0
  312. package/src/components/manage/Widgets/Size.test.tsx +59 -0
  313. package/src/components/manage/Widgets/Size.tsx +78 -0
  314. package/src/components/manage/Widgets/TextWidget.jsx +4 -0
  315. package/src/components/manage/Widgets/TimeWidget.test.jsx +1 -6
  316. package/src/components/manage/Widgets/TokenWidget.jsx +142 -186
  317. package/src/components/manage/Widgets/TokenWidget.test.jsx +1 -6
  318. package/src/components/manage/Widgets/UrlWidget.jsx +47 -18
  319. package/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx +2 -11
  320. package/src/components/manage/Widgets/__mocks__/index.tsx +33 -51
  321. package/src/components/manage/Widgets/index.tsx +21 -0
  322. package/src/components/manage/Workflow/Workflow.test.jsx +2 -11
  323. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +1 -0
  324. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +30 -0
  325. package/src/components/theme/App/App.jsx +5 -1
  326. package/src/components/theme/App/App.test.jsx +13 -10
  327. package/src/components/theme/Comments/CommentEditModal.test.jsx +1 -5
  328. package/src/components/theme/Comments/Comments.test.jsx +2 -11
  329. package/src/components/theme/ConnectionRefused/ConnectionRefused.jsx +3 -2
  330. package/src/components/theme/ContactForm/ContactForm.test.jsx +14 -13
  331. package/src/components/theme/Image/Image.jsx +14 -5
  332. package/src/components/theme/Image/Image.test.jsx +247 -146
  333. package/src/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.tsx +7 -0
  334. package/src/components/theme/LanguageSelector/LanguageSelector.tsx +3 -3
  335. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +42 -12
  336. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.test.jsx +6 -3
  337. package/src/components/theme/PasswordReset/PasswordReset.jsx +108 -191
  338. package/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx +1 -5
  339. package/src/components/theme/Register/Register.test.jsx +1 -5
  340. package/src/components/theme/Search/Search.jsx +230 -327
  341. package/src/components/theme/Search/Search.test.jsx +14 -14
  342. package/src/components/theme/Sitemap/Sitemap.jsx +22 -30
  343. package/src/components/theme/Sitemap/Sitemap.stories.jsx +82 -0
  344. package/src/components/theme/Sitemap/Sitemap.test.jsx +18 -0
  345. package/src/components/theme/SlotRenderer/SlotRenderer.tsx +12 -6
  346. package/src/components/theme/Unauthorized/Unauthorized.jsx +27 -24
  347. package/src/components/theme/Unauthorized/Unauthorized.test.jsx +31 -2
  348. package/src/components/theme/VideoEmbed/VideoEmbed.jsx +100 -0
  349. package/src/components/theme/View/EventDatesInfo.test.jsx +1 -6
  350. package/src/components/theme/View/EventView.stories.jsx +89 -0
  351. package/src/components/theme/View/EventView.test.jsx +1 -6
  352. package/src/components/theme/View/FileView.stories.jsx +50 -0
  353. package/src/components/theme/View/ImageView.jsx +2 -1
  354. package/src/components/theme/View/ImageView.test.jsx +3 -0
  355. package/src/components/theme/View/LinkView.stories.jsx +57 -0
  356. package/src/components/theme/View/ListingView.stories.jsx +70 -0
  357. package/src/components/theme/View/NewsItemView.stories.jsx +58 -0
  358. package/src/components/theme/View/RenderBlocks.jsx +8 -10
  359. package/src/components/theme/View/RenderBlocks.stories.jsx +112 -0
  360. package/src/components/theme/View/RenderBlocks.test.jsx +14 -4
  361. package/src/components/theme/View/SummaryView.stories.jsx +71 -0
  362. package/src/components/theme/View/TabularView.stories.jsx +66 -0
  363. package/src/components/theme/View/View.jsx +8 -1
  364. package/src/components/theme/View/View.test.jsx +37 -24
  365. package/src/components/theme/Widgets/DateWidget.jsx +4 -5
  366. package/src/components/theme/Widgets/DatetimeWidget.jsx +4 -5
  367. package/src/components/theme/Widgets/ImageWidget.test.jsx +31 -11
  368. package/src/components/theme/Widgets/RichTextWidget.jsx +1 -1
  369. package/src/config/Blocks.jsx +3 -0
  370. package/src/config/ControlPanels.js +2 -0
  371. package/src/config/Loadables.jsx +1 -5
  372. package/src/config/Widgets.jsx +7 -0
  373. package/src/config/index.js +10 -1
  374. package/src/config/slots.js +19 -0
  375. package/src/config/validation.ts +8 -0
  376. package/src/constants/ActionTypes.js +1 -0
  377. package/src/express-middleware/devproxy.js +3 -1
  378. package/src/express-middleware/files.js +1 -0
  379. package/src/express-middleware/files.test.js +59 -0
  380. package/src/express-middleware/images.js +1 -0
  381. package/src/express-middleware/images.test.js +50 -0
  382. package/src/helpers/Api/Api.js +2 -2
  383. package/src/helpers/AuthToken/AuthToken.js +1 -6
  384. package/src/helpers/Blocks/Blocks.js +113 -28
  385. package/src/helpers/Blocks/Blocks.test.js +100 -0
  386. package/src/helpers/Content/Content.js +23 -0
  387. package/src/helpers/Content/Content.test.js +39 -0
  388. package/src/helpers/Extensions/withBlockSchemaEnhancer.jsx +4 -1
  389. package/src/helpers/FormValidation/FormValidation.test.js +31 -0
  390. package/src/helpers/FormValidation/validators.ts +52 -6
  391. package/src/helpers/I18n/I18n.test.ts +44 -0
  392. package/src/helpers/I18n/I18n.ts +31 -0
  393. package/src/helpers/Loadable/__mocks__/Loadable.jsx +7 -22
  394. package/src/helpers/MessageLabels/MessageLabels.js +5 -0
  395. package/src/helpers/Robots/Robots.js +1 -1
  396. package/src/helpers/Robots/Robots.test.js +34 -0
  397. package/src/helpers/Url/Url.js +1 -0
  398. package/src/helpers/Utils/Date.js +26 -1
  399. package/src/helpers/Utils/Date.test.js +237 -0
  400. package/src/helpers/Utils/Utils.jsx +17 -0
  401. package/src/helpers/Utils/Utils.test.jsx +39 -0
  402. package/src/helpers/Utils/withSaveAsDraft.jsx +33 -9
  403. package/src/helpers/index.js +1 -0
  404. package/src/hooks/user/useUser.js +1 -1
  405. package/src/internalChecks.test.ts +11 -0
  406. package/src/middleware/api.js +14 -5
  407. package/src/reducers/blockTypes/blockTypes.js +38 -0
  408. package/src/reducers/content/content.js +3 -18
  409. package/src/reducers/diff/diff.js +5 -1
  410. package/src/reducers/diff/diff.test.js +60 -4
  411. package/src/reducers/index.js +2 -0
  412. package/src/reducers/querystring/querystring.js +8 -1
  413. package/src/reducers/users/users.js +1 -1
  414. package/src/routes.js +10 -0
  415. package/src/server.jsx +14 -6
  416. package/src/start-client.jsx +21 -3
  417. package/test-addons-loader.js +3 -0
  418. package/test-setup-globals.js +56 -2
  419. package/theme/themes/default/elements/segment.variables +9 -16
  420. package/theme/themes/default/globals/site.variables +3 -3
  421. package/theme/themes/pastanaga/collections/form.overrides +22 -1
  422. package/theme/themes/pastanaga/elements/button.overrides +30 -3
  423. package/theme/themes/pastanaga/elements/segment.variables +1 -4
  424. package/theme/themes/pastanaga/extras/block-types.less +17 -0
  425. package/theme/themes/pastanaga/extras/contents.less +63 -4
  426. package/theme/themes/pastanaga/extras/main.less +16 -4
  427. package/theme/themes/pastanaga/extras/toolbar.less +10 -5
  428. package/theme/themes/pastanaga/extras/videoembed.less +22 -0
  429. package/theme/themes/pastanaga/extras/widgets.less +79 -0
  430. package/theme/themes/pastanaga/globals/site.variables +0 -3
  431. package/tsconfig.declarations.json +1 -1
  432. package/tsconfig.json +1 -1
  433. package/types/actions/blockTypes/blockTypes.d.ts +7 -0
  434. package/types/components/index.d.ts +1 -1
  435. package/types/components/manage/Blocks/Block/Order/Item.test.d.ts +1 -0
  436. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +2 -1
  437. package/types/components/manage/Blocks/Grid/context.d.ts +1 -0
  438. package/types/components/manage/Blocks/Maps/Body.d.ts +9 -0
  439. package/types/components/manage/Blocks/Maps/View.d.ts +1 -1
  440. package/types/components/manage/Blocks/Teaser/utils.d.ts +5 -0
  441. package/types/components/manage/Blocks/Video/Body.d.ts +4 -2
  442. package/types/components/manage/Blocks/Video/schema.d.ts +4 -0
  443. package/types/components/manage/Contents/Contents.d.ts +1 -1
  444. package/types/components/manage/Contents/ContentsIndexHeader.d.ts +6 -11
  445. package/types/components/manage/Contents/ContentsItem.d.ts +3 -10
  446. package/types/components/manage/Contents/DropZoneContent.d.ts +2 -0
  447. package/types/components/manage/Contents/__mocks__/index.d.ts +2 -2
  448. package/types/components/manage/Controlpanels/BlockType.d.ts +7 -0
  449. package/types/components/manage/Controlpanels/BlockTypes.d.ts +7 -0
  450. package/types/components/manage/Controlpanels/ContentType.d.ts +2 -2
  451. package/types/components/manage/Controlpanels/Controlpanel.d.ts +2 -5
  452. package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
  453. package/types/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.d.ts +1 -0
  454. package/types/components/manage/Controlpanels/VersionOverview.d.ts +2 -1
  455. package/types/components/manage/Controlpanels/index.d.ts +3 -2
  456. package/types/components/manage/Diff/Diff.d.ts +7 -2
  457. package/types/components/manage/Form/__mocks__/index.d.ts +8 -8
  458. package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
  459. package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
  460. package/types/components/manage/Sidebar/ObjectBrowserBody.test.d.ts +1 -0
  461. package/types/components/manage/Sidebar/SidebarPortal.d.ts +7 -15
  462. package/types/components/manage/Toolbar/Types.crash.test.d.ts +1 -0
  463. package/types/components/manage/Widgets/AlignWidget.d.ts +8 -10
  464. package/types/components/manage/Widgets/AlignWidget.stories.d.ts +1 -0
  465. package/types/components/manage/Widgets/BlockAlignment.d.ts +7 -0
  466. package/types/components/manage/Widgets/BlockAlignment.stories.d.ts +8 -0
  467. package/types/components/manage/Widgets/BlockWidth.d.ts +7 -0
  468. package/types/components/manage/Widgets/BlockWidth.stories.d.ts +6 -0
  469. package/types/components/manage/Widgets/ButtonsWidget.d.ts +48 -1
  470. package/types/components/manage/Widgets/ButtonsWidget.stories.d.ts +3 -0
  471. package/types/components/manage/Widgets/FormFieldWrapper.d.ts +28 -5
  472. package/types/components/manage/Widgets/ImageWidget.d.ts +1 -1
  473. package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
  474. package/types/components/manage/Widgets/ObjectBrowserWidget.d.ts +2 -0
  475. package/types/components/manage/Widgets/QueryWidget.d.ts +5 -2
  476. package/types/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField.d.ts +22 -5
  477. package/types/components/manage/Widgets/SelectStyling.d.ts +1 -0
  478. package/types/components/manage/Widgets/Size.d.ts +7 -0
  479. package/types/components/manage/Widgets/Size.stories.d.ts +6 -0
  480. package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
  481. package/types/components/manage/Widgets/__mocks__/index.d.ts +33 -33
  482. package/types/components/manage/Widgets/index.d.ts +11 -6
  483. package/types/components/theme/ConnectionRefused/ConnectionRefused.d.ts +2 -2
  484. package/types/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.d.ts +3 -0
  485. package/types/components/theme/PasswordReset/PasswordReset.d.ts +6 -2
  486. package/types/components/theme/Search/Search.d.ts +1 -1
  487. package/types/components/theme/Sitemap/Sitemap.stories.d.ts +13 -0
  488. package/types/components/theme/SlotRenderer/SlotRenderer.d.ts +4 -5
  489. package/types/components/theme/Unauthorized/Unauthorized.d.ts +2 -2
  490. package/types/components/theme/VideoEmbed/VideoEmbed.d.ts +2 -0
  491. package/types/components/theme/View/EventView.stories.d.ts +19 -0
  492. package/types/components/theme/View/FileView.stories.d.ts +18 -0
  493. package/types/components/theme/View/LinkView.stories.d.ts +18 -0
  494. package/types/components/theme/View/ListingView.stories.d.ts +24 -0
  495. package/types/components/theme/View/NewsItemView.stories.d.ts +23 -0
  496. package/types/components/theme/View/RenderBlocks.stories.d.ts +23 -0
  497. package/types/components/theme/View/SummaryView.stories.d.ts +23 -0
  498. package/types/components/theme/View/TabularView.stories.d.ts +23 -0
  499. package/types/config/ControlPanels.d.ts +1 -0
  500. package/types/config/Views.d.ts +1 -1
  501. package/types/config/Widgets.d.ts +6 -0
  502. package/types/config/slots.d.ts +7 -0
  503. package/types/constants/ActionTypes.d.ts +1 -0
  504. package/types/helpers/Blocks/Blocks.d.ts +4 -0
  505. package/types/helpers/Content/Content.d.ts +7 -0
  506. package/types/helpers/Extensions/withBlockSchemaEnhancer.d.ts +4 -5
  507. package/types/helpers/FormValidation/validators.d.ts +18 -1
  508. package/types/helpers/I18n/I18n.d.ts +20 -0
  509. package/types/helpers/Loadable/__mocks__/Loadable.d.ts +2 -2
  510. package/types/helpers/MessageLabels/MessageLabels.d.ts +100 -94
  511. package/types/helpers/Utils/Utils.d.ts +1 -0
  512. package/types/helpers/index.d.ts +1 -0
  513. package/types/reducers/blockTypes/blockTypes.d.ts +16 -0
  514. package/types/reducers/index.d.ts +2 -0
  515. package/types/routes.d.ts +7 -5
  516. package/vitest.config.mjs +84 -42
  517. package/webpack-plugins/webpack-less-plugin.js +1 -1
  518. package/webpack-plugins/webpack-scss-plugin.js +172 -0
  519. package/jest-addons-loader.js +0 -3
  520. package/jest-extender-plugin.js +0 -39
  521. package/jest-setup-afterenv.js +0 -2
  522. package/jest-svgsystem-transform.js +0 -10
  523. package/patches/patchit.sh +0 -2
  524. package/patches/razzle-jest.patch +0 -10
  525. package/src/components/manage/Contents/__mocks__/index.vitest.tsx +0 -5
  526. package/src/components/manage/Form/__mocks__/index.vitest.tsx +0 -73
  527. package/src/components/manage/Sidebar/SidebarPortal.jsx +0 -47
  528. package/src/components/manage/Sidebar/SidebarPortal.test.jsx +0 -26
  529. package/src/components/manage/Widgets/AlignWidget.test.jsx +0 -59
  530. package/src/components/manage/Widgets/ButtonsWidget.jsx +0 -41
  531. package/src/components/manage/Widgets/ButtonsWidget.test.jsx +0 -70
  532. package/src/components/manage/Widgets/__mocks__/index.vitest.tsx +0 -41
  533. package/src/helpers/Loadable/__mocks__/Loadable.vitest.jsx +0 -39
  534. package/test-setup-globals-vitest.js +0 -46
  535. package/theme/themes/pastanaga/extras/utils.less +0 -63
@@ -80,6 +80,14 @@ export function blockHasValue(data) {
80
80
  return check(data);
81
81
  }
82
82
 
83
+ /**
84
+ * Block id is valid (not undefined/null or the string "undefined" from object[undefined])
85
+ * @param {*} id Block id
86
+ * @return {boolean}
87
+ */
88
+ const isValidBlockId = (id) =>
89
+ id != null && id !== 'undefined' && (typeof id !== 'string' || id.length > 0);
90
+
83
91
  /**
84
92
  * Get block pairs of [id, block] from content properties
85
93
  * @function getBlocks
@@ -89,12 +97,26 @@ export function blockHasValue(data) {
89
97
  export const getBlocks = (properties) => {
90
98
  const blocksFieldName = getBlocksFieldname(properties);
91
99
  const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
92
- return (
93
- properties[blocksLayoutFieldname]?.items?.map((n) => [
94
- n,
95
- properties[blocksFieldName][n],
96
- ]) || []
97
- );
100
+ const blocks = properties?.[blocksFieldName];
101
+ const items = properties?.[blocksLayoutFieldname]?.items;
102
+ if (!items) return [];
103
+ return items
104
+ .filter((n) => isValidBlockId(n))
105
+ .map((n) => [n, blocks?.[n]])
106
+ .filter(([, block]) => block != null);
107
+ };
108
+
109
+ /**
110
+ * Get layout item IDs that are valid but have no block data (orphaned refs).
111
+ * @param {Object} properties Content form properties
112
+ * @return {string[]} IDs that should be removed from layout
113
+ */
114
+ export const getInvalidBlockLayoutIds = (properties) => {
115
+ const blocksFieldName = getBlocksFieldname(properties);
116
+ const blocksLayoutFieldName = getBlocksLayoutFieldname(properties);
117
+ const blocks = properties?.[blocksFieldName] ?? {};
118
+ const layoutItems = properties?.[blocksLayoutFieldName]?.items ?? [];
119
+ return layoutItems.filter((id) => isValidBlockId(id) && blocks[id] == null);
98
120
  };
99
121
 
100
122
  /**
@@ -129,12 +151,28 @@ export function deleteBlock(formData, blockId, intl) {
129
151
 
130
152
  let newFormData = {
131
153
  ...formData,
132
- [blocksLayoutFieldname]: {
133
- items: without(formData[blocksLayoutFieldname].items, blockId),
134
- },
135
- [blocksFieldname]: omit(formData[blocksFieldname], [blockId]),
136
154
  };
137
155
 
156
+ let container = findParent(newFormData, {
157
+ blockId,
158
+ });
159
+
160
+ if (container) {
161
+ container[blocksLayoutFieldname].items = without(
162
+ container[blocksLayoutFieldname].items,
163
+ blockId,
164
+ );
165
+ container[blocksFieldname] = omit(container[blocksFieldname], [blockId]);
166
+ } else {
167
+ newFormData[blocksLayoutFieldname].items = without(
168
+ newFormData[blocksLayoutFieldname].items,
169
+ blockId,
170
+ );
171
+ newFormData[blocksFieldname] = omit(newFormData[blocksFieldname], [
172
+ blockId,
173
+ ]);
174
+ }
175
+
138
176
  if (newFormData[blocksLayoutFieldname].items.length === 0) {
139
177
  newFormData = addBlock(
140
178
  newFormData,
@@ -812,12 +850,14 @@ export function findBlocks(blocks = {}, types, result = []) {
812
850
  export const getBlocksHierarchy = (properties) => {
813
851
  const blocksFieldName = getBlocksFieldname(properties);
814
852
  const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
815
- return properties[blocksLayoutFieldname]?.items?.map((n) => ({
853
+ return properties?.[blocksLayoutFieldname]?.items?.map((n) => ({
816
854
  id: n,
817
- title: properties[blocksFieldName][n]?.['@type'],
818
- data: properties[blocksFieldName][n],
819
- children: isBlockContainer(properties[blocksFieldName][n])
820
- ? getBlocksHierarchy(properties[blocksFieldName][n])
855
+ title: properties?.[blocksFieldName]?.[n]?.['@type'],
856
+ data: properties?.[blocksFieldName]?.[n],
857
+ children: isBlockContainer(properties?.[blocksFieldName]?.[n])
858
+ ? properties?.[blocksFieldName]?.[n]?.data
859
+ ? getBlocksHierarchy(properties?.[blocksFieldName]?.[n]?.data)
860
+ : getBlocksHierarchy(properties?.[blocksFieldName]?.[n])
821
861
  : [],
822
862
  }));
823
863
  };
@@ -911,8 +951,13 @@ export function moveBlockEnhanced(formData, { source, destination }) {
911
951
  const destinationContainer = findContainer(clonedFormData, {
912
952
  containerId: destination.parent,
913
953
  });
954
+ const sourceContainer = findContainer(clonedFormData, {
955
+ containerId: source.parent,
956
+ });
957
+
914
958
  destinationContainer[blocksFieldName][source.id] =
915
- formData[blocksFieldName][source.parent][blocksFieldName][source.id];
959
+ sourceContainer[blocksFieldName]?.[source.id] ||
960
+ sourceContainer.data?.[blocksFieldName][source.id];
916
961
 
917
962
  destinationContainer[blocksLayoutFieldname].items = insertInArray(
918
963
  destinationContainer[blocksLayoutFieldname].items,
@@ -921,9 +966,6 @@ export function moveBlockEnhanced(formData, { source, destination }) {
921
966
  );
922
967
 
923
968
  // Remove the source block from the source parent
924
- const sourceContainer = findContainer(clonedFormData, {
925
- containerId: source.parent,
926
- });
927
969
  delete sourceContainer[blocksFieldName][source.id];
928
970
  sourceContainer[blocksLayoutFieldname].items = removeFromArray(
929
971
  sourceContainer[blocksLayoutFieldname].items,
@@ -957,23 +999,25 @@ export function moveBlockEnhanced(formData, { source, destination }) {
957
999
  * @returns {object|undefined} - The container object if found, otherwise undefined.
958
1000
  */
959
1001
  export const findContainer = (formData, { containerId }) => {
1002
+ const block =
1003
+ formData.blocks[containerId]?.data || formData.blocks[containerId];
960
1004
  if (
961
- formData.blocks[containerId] &&
962
- Object.keys(formData.blocks[containerId]).includes('blocks') &&
963
- Object.keys(formData.blocks[containerId]).includes('blocks_layout')
1005
+ block &&
1006
+ Object.keys(block).includes('blocks') &&
1007
+ Object.keys(block).includes('blocks_layout')
964
1008
  ) {
965
- return formData.blocks[containerId];
1009
+ return block;
966
1010
  }
967
1011
 
968
1012
  let container;
969
1013
  Object.keys(formData.blocks).every((blockId) => {
970
- const block = formData.blocks[blockId];
1014
+ const subBlock = formData.blocks[blockId].data || formData.blocks[blockId];
971
1015
  if (
972
- formData.blocks[blockId] &&
973
- Object.keys(formData.blocks[blockId]).includes('blocks') &&
974
- Object.keys(formData.blocks[blockId]).includes('blocks_layout')
1016
+ subBlock &&
1017
+ Object.keys(subBlock).includes('blocks') &&
1018
+ Object.keys(subBlock).includes('blocks_layout')
975
1019
  ) {
976
- container = findContainer(block, { containerId });
1020
+ container = findContainer(subBlock, { containerId });
977
1021
  }
978
1022
  if (container) {
979
1023
  return false;
@@ -985,6 +1029,47 @@ export const findContainer = (formData, { containerId }) => {
985
1029
  return container;
986
1030
  };
987
1031
 
1032
+ /**
1033
+ * Finds parent container of the specified blockId in the given formData.
1034
+ *
1035
+ * @param {object} formData - The form data object.
1036
+ * @param {object} options - The options object.
1037
+ * @param {string} options.blockId - The ID of the block to find.
1038
+ * @returns {object|undefined} - The container object if found, otherwise undefined.
1039
+ */
1040
+ export const findParent = (formData, { blockId }) => {
1041
+ const block = formData.data || formData;
1042
+
1043
+ if (block && block.blocks && Object.keys(block.blocks).includes(blockId)) {
1044
+ return block;
1045
+ }
1046
+
1047
+ let found = false;
1048
+
1049
+ if (block && block.blocks) {
1050
+ Object.keys(block.blocks).every((subBlockId) => {
1051
+ const subBlock =
1052
+ block.blocks[subBlockId].data || block.blocks[subBlockId];
1053
+ if (subBlock && subBlock.blocks) {
1054
+ if (Object.keys(subBlock.blocks).includes(blockId)) {
1055
+ found = subBlock;
1056
+ }
1057
+ const parent = findParent(subBlock, { blockId });
1058
+ if (parent) {
1059
+ found = parent;
1060
+ }
1061
+ }
1062
+ if (found) {
1063
+ return false;
1064
+ } else {
1065
+ return true;
1066
+ }
1067
+ });
1068
+ }
1069
+
1070
+ return found;
1071
+ };
1072
+
988
1073
  const _dummyIntl = {
989
1074
  formatMessage() {},
990
1075
  };
@@ -7,6 +7,7 @@ import {
7
7
  getBlocks,
8
8
  getBlocksFieldname,
9
9
  getBlocksLayoutFieldname,
10
+ getInvalidBlockLayoutIds,
10
11
  hasBlocksData,
11
12
  insertBlock,
12
13
  moveBlock,
@@ -482,6 +483,105 @@ describe('Blocks', () => {
482
483
  ['a', { value: 1 }],
483
484
  ]);
484
485
  });
486
+
487
+ it('filters out invalid block IDs (null, undefined, string "undefined")', () => {
488
+ const validBlock = { '@type': 'search', value: 'test' };
489
+ const result = getBlocks({
490
+ blocks: {
491
+ 'valid-id-123': validBlock,
492
+ // These shouldn't exist but test edge case
493
+ [null]: { '@type': 'invalid' },
494
+ [undefined]: { '@type': 'invalid' },
495
+ undefined: { '@type': 'invalid' },
496
+ },
497
+ blocks_layout: {
498
+ items: [
499
+ 'valid-id-123',
500
+ null, // Invalid: null ID
501
+ undefined, // Invalid: undefined ID
502
+ 'undefined', // Invalid: string "undefined"
503
+ 'missing-block', // Valid ID but block doesn't exist (filtered by block !== undefined)
504
+ ],
505
+ },
506
+ });
507
+
508
+ // Should only return the valid block, filtering out:
509
+ // - null ID
510
+ // - undefined ID
511
+ // - string "undefined" ID
512
+ // - missing block (block is undefined)
513
+ expect(result).toStrictEqual([['valid-id-123', validBlock]]);
514
+ expect(result.length).toBe(1);
515
+
516
+ // Verify no invalid IDs in the result
517
+ const ids = result.map(([id]) => id);
518
+ expect(ids).not.toContain(null);
519
+ expect(ids).not.toContain(undefined);
520
+ expect(ids).not.toContain('undefined');
521
+ });
522
+
523
+ it('filters out invalid block IDs even when blocks object has invalid keys', () => {
524
+ // Simulate edge case where blocks object has invalid keys
525
+ const blocks = {
526
+ 'valid-id': { '@type': 'search' },
527
+ };
528
+ // JavaScript allows this, creating string keys
529
+ blocks[null] = { '@type': 'invalid' };
530
+ blocks[undefined] = { '@type': 'invalid' };
531
+
532
+ const result = getBlocks({
533
+ blocks,
534
+ blocks_layout: {
535
+ items: ['valid-id', null, undefined, 'undefined'],
536
+ },
537
+ });
538
+
539
+ // Should only return valid block
540
+ expect(result).toStrictEqual([['valid-id', { '@type': 'search' }]]);
541
+ expect(result.length).toBe(1);
542
+ });
543
+ });
544
+
545
+ describe('getInvalidBlockLayoutIds', () => {
546
+ it('returns layout IDs that are valid but have no block data', () => {
547
+ const result = getInvalidBlockLayoutIds({
548
+ blocks: {
549
+ a: { '@type': 'custom', text: 'a' },
550
+ b: { '@type': 'custom', text: 'b' },
551
+ },
552
+ blocks_layout: {
553
+ items: ['a', 'b', 'MISSING-1', 'MISSING-2'],
554
+ },
555
+ });
556
+ expect(result).toStrictEqual(['MISSING-1', 'MISSING-2']);
557
+ });
558
+
559
+ it('returns empty when all layout items have block data', () => {
560
+ const result = getInvalidBlockLayoutIds({
561
+ blocks: { a: { '@type': 'custom' }, b: { '@type': 'custom' } },
562
+ blocks_layout: { items: ['a', 'b'] },
563
+ });
564
+ expect(result).toStrictEqual([]);
565
+ });
566
+
567
+ it('filters out invalid IDs (null, undefined, "undefined")', () => {
568
+ const result = getInvalidBlockLayoutIds({
569
+ blocks: {},
570
+ blocks_layout: {
571
+ items: [null, undefined, 'undefined', 'valid-missing'],
572
+ },
573
+ });
574
+ expect(result).toStrictEqual(['valid-missing']);
575
+ });
576
+
577
+ it('returns empty when items is missing or empty', () => {
578
+ expect(
579
+ getInvalidBlockLayoutIds({ blocks: {}, blocks_layout: {} }),
580
+ ).toStrictEqual([]);
581
+ expect(
582
+ getInvalidBlockLayoutIds({ blocks: {}, blocks_layout: { items: [] } }),
583
+ ).toStrictEqual([]);
584
+ });
485
585
  });
486
586
 
487
587
  describe('addBlock', () => {
@@ -11,6 +11,7 @@ import keys from 'lodash/keys';
11
11
  import endsWith from 'lodash/endsWith';
12
12
  import find from 'lodash/find';
13
13
  import config from '@plone/volto/registry';
14
+ import omit from 'lodash/omit';
14
15
 
15
16
  /**
16
17
  * Nest content.
@@ -85,3 +86,25 @@ export function getLanguageIndependentFields(schema) {
85
86
  properties[field]['multilingual_options']?.['language_independent'],
86
87
  );
87
88
  }
89
+
90
+ /**
91
+ * Flattens static behaviors into the parent object with dot-notation keys.
92
+ * @function flattenStaticBehaviors
93
+ * @param {Object} result The result object containing static behaviors.
94
+ * @returns {Object} Result object with flattened static behaviors.
95
+ */
96
+ export function flattenStaticBehaviors(result) {
97
+ if (!result['@static_behaviors']) {
98
+ return result;
99
+ }
100
+
101
+ let flattened = Object.assign({}, result);
102
+ map(result['@static_behaviors'], (behavior) => {
103
+ flattened = {
104
+ ...omit(flattened, behavior),
105
+ ...mapKeys(flattened[behavior], (value, key) => `${behavior}.${key}`),
106
+ };
107
+ });
108
+
109
+ return flattened;
110
+ }
@@ -2,6 +2,7 @@ import {
2
2
  nestContent,
3
3
  getContentIcon,
4
4
  getLanguageIndependentFields,
5
+ flattenStaticBehaviors,
5
6
  } from './Content';
6
7
  import contentExistingSVG from '@plone/volto/icons/content-existing.svg';
7
8
  import linkSVG from '@plone/volto/icons/link.svg';
@@ -96,4 +97,42 @@ describe('Content', () => {
96
97
  expect(getLanguageIndependentFields(schema)).toStrictEqual(['lif']);
97
98
  });
98
99
  });
100
+
101
+ describe('flattenStaticBehaviors', () => {
102
+ it('returns object unchanged when no @static_behaviors', () => {
103
+ const input = {
104
+ title: 'Example',
105
+ creator: 'admin',
106
+ };
107
+ expect(flattenStaticBehaviors(input)).toEqual(input);
108
+ });
109
+
110
+ it('flattens static behaviors into dot-notation keys', () => {
111
+ const input = {
112
+ title: 'Example',
113
+ '@static_behaviors': [
114
+ 'guillotina_cms.interfaces.blocks.IBlocks',
115
+ 'guillotina_cms.interfaces.dublin_core.IDublinCore',
116
+ ],
117
+ 'guillotina_cms.interfaces.blocks.IBlocks': {
118
+ blocks: 'blocks',
119
+ blocks_layout: 'blocks_layout',
120
+ },
121
+ 'guillotina_cms.interfaces.dublin_core.IDublinCore': {
122
+ creator: 'creator',
123
+ },
124
+ };
125
+ expect(flattenStaticBehaviors(input)).toEqual({
126
+ title: 'Example',
127
+ '@static_behaviors': [
128
+ 'guillotina_cms.interfaces.blocks.IBlocks',
129
+ 'guillotina_cms.interfaces.dublin_core.IDublinCore',
130
+ ],
131
+ 'guillotina_cms.interfaces.blocks.IBlocks.blocks': 'blocks',
132
+ 'guillotina_cms.interfaces.blocks.IBlocks.blocks_layout':
133
+ 'blocks_layout',
134
+ 'guillotina_cms.interfaces.dublin_core.IDublinCore.creator': 'creator',
135
+ });
136
+ });
137
+ });
99
138
  });
@@ -305,8 +305,11 @@ export const EMPTY_STYLES_SCHEMA = {
305
305
 
306
306
  /**
307
307
  * Adds the `styles` field and 'styling' fieldset in a given schema
308
+ * @param {object} params Helper params
309
+ * @param {import('@plone/types').JSONSchema} params.schema Schema to enhance
310
+ * @param {import('react-intl').IntlShape} params.intl intl helper for translations
308
311
  */
309
- export const addStyling = ({ schema, formData, intl }) => {
312
+ export const addStyling = ({ schema, intl }) => {
310
313
  if (isEmpty(find(schema.fieldsets, { id: 'styling' }))) {
311
314
  schema.fieldsets.push({
312
315
  id: 'styling',
@@ -8,6 +8,12 @@ const schema = {
8
8
  username: { title: 'Username', type: 'string', description: '' },
9
9
  email: { title: 'Email', type: 'string', widget: 'email', description: '' },
10
10
  url: { title: 'url', type: 'string', widget: 'url', description: '' },
11
+ file: {
12
+ title: 'file',
13
+ type: 'object',
14
+ description: '',
15
+ size: 5,
16
+ },
11
17
  },
12
18
  fieldsets: [
13
19
  { id: 'default', title: 'FIXME: User Data', fields: ['username'] },
@@ -181,6 +187,31 @@ describe('FormValidation', () => {
181
187
  ).toEqual({});
182
188
  });
183
189
 
190
+ it('file - validates invalid size', () => {
191
+ const validationErrorMessages = [messages.maxSize.defaultMessage];
192
+ validationErrorMessages.title = 'file';
193
+
194
+ expect(
195
+ FormValidation.validateFieldsPerFieldset({
196
+ schema,
197
+ formData: { ...formData, file: { size: 10 } },
198
+ formatMessage,
199
+ }),
200
+ ).toEqual({
201
+ file: validationErrorMessages,
202
+ });
203
+ });
204
+
205
+ it('file - validates size', () => {
206
+ expect(
207
+ FormValidation.validateFieldsPerFieldset({
208
+ schema,
209
+ formData: { ...formData, file: { size: 1 } },
210
+ formatMessage,
211
+ }),
212
+ ).toEqual({});
213
+ });
214
+
184
215
  it('widget - validator from block - Fails', () => {
185
216
  let newSchema = {
186
217
  properties: {
@@ -1,5 +1,6 @@
1
1
  import { validationMessage } from '@plone/volto/helpers/FormValidation/FormValidation';
2
2
  import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
3
+ import config from '@plone/volto/registry';
3
4
 
4
5
  type MinMaxValidator = {
5
6
  value: string | number;
@@ -15,6 +16,24 @@ type Validator = {
15
16
  formatMessage: Function;
16
17
  };
17
18
 
19
+ type Choice = {
20
+ token: string;
21
+ label: string;
22
+ };
23
+ type ChoiceValidator = {
24
+ value: string | Choice;
25
+ field: Record<string, any>;
26
+ formData: any;
27
+ formatMessage: Function;
28
+ };
29
+
30
+ type FileValidator = {
31
+ value: Record<string, any>;
32
+ field: Record<string, any>;
33
+ formData: any;
34
+ formatMessage: Function;
35
+ };
36
+
18
37
  export const isMaxPropertyValid = ({
19
38
  value,
20
39
  fieldSpec,
@@ -134,6 +153,15 @@ export const hasUniqueItemsValidator = ({
134
153
  return !isValid ? formatMessage(messages.uniqueItems) : null;
135
154
  };
136
155
 
156
+ const formatDateValue = (isoString: string) => {
157
+ const date = new Date(isoString);
158
+ if (isNaN(date.getTime())) return isoString;
159
+ return new Intl.DateTimeFormat(undefined, {
160
+ dateStyle: 'medium',
161
+ timeStyle: 'short',
162
+ }).format(date);
163
+ };
164
+
137
165
  export const startEventDateRangeValidator = ({
138
166
  value,
139
167
  field,
@@ -144,7 +172,9 @@ export const startEventDateRangeValidator = ({
144
172
  value && formData.end && new Date(value) < new Date(formData.end);
145
173
  return !isValid
146
174
  ? formatMessage(messages.startEventRange, {
147
- endDateValueOrEndFieldName: formData.end || 'end',
175
+ endDateValueOrEndFieldName: formData.end
176
+ ? formatDateValue(formData.end)
177
+ : 'end',
148
178
  })
149
179
  : null;
150
180
  };
@@ -159,7 +189,9 @@ export const endEventDateRangeValidator = ({
159
189
  value && formData.start && new Date(value) > new Date(formData.start);
160
190
  return !isValid
161
191
  ? formatMessage(messages.endEventRange, {
162
- startDateValueOrStartFieldName: formData.start || 'start',
192
+ startDateValueOrStartFieldName: formData.start
193
+ ? formatDateValue(formData.start)
194
+ : 'start',
163
195
  })
164
196
  : null;
165
197
  };
@@ -211,12 +243,26 @@ export const defaultLanguageControlPanelValidator = ({
211
243
  value,
212
244
  formData,
213
245
  formatMessage,
214
- }: Validator) => {
246
+ }: ChoiceValidator) => {
247
+ const token = typeof value === 'object' ? value.token : value;
215
248
  const isValid =
216
- value &&
249
+ token &&
217
250
  (formData.available_languages.find(
218
- (lang: { token: string }) => lang.token === value,
251
+ (lang: { token: string }) => lang.token === token,
219
252
  ) ||
220
- formData.available_languages.includes(value));
253
+ formData.available_languages.includes(token));
221
254
  return !isValid ? formatMessage(messages.defaultLanguage) : null;
222
255
  };
256
+
257
+ export const sizeValidator = ({
258
+ value,
259
+ field,
260
+ formatMessage,
261
+ }: FileValidator) => {
262
+ const maxSize = field.size
263
+ ? parseInt(field.size, 10)
264
+ : config.settings.maxFileUploadSize;
265
+ return maxSize && value.size > maxSize
266
+ ? formatMessage(messages.maxSize, { maxSize, size: value.size })
267
+ : null;
268
+ };
@@ -0,0 +1,44 @@
1
+ import { createIntl, createIntlCache } from 'react-intl';
2
+ import type { IntlShape } from 'react-intl';
3
+ import { formatMessageWithFallback } from './I18n';
4
+
5
+ const buildIntl = (
6
+ locale: string,
7
+ messages: Record<string, string>,
8
+ ): IntlShape => createIntl({ locale, messages }, createIntlCache());
9
+
10
+ describe('formatMessageWithFallback', () => {
11
+ it('returns the translated string when the locale catalog has an entry', () => {
12
+ const intl = buildIntl('pt-BR', { Image: 'Imagem' });
13
+ expect(formatMessageWithFallback(intl, 'Image')).toBe('Imagem');
14
+ });
15
+
16
+ it('falls back to the input message when no translation is registered', () => {
17
+ const intl = buildIntl('pt-BR', {});
18
+ expect(formatMessageWithFallback(intl, 'Image')).toBe('Image');
19
+ });
20
+
21
+ it('returns undefined unchanged', () => {
22
+ const intl = buildIntl('en', {});
23
+ expect(formatMessageWithFallback(intl, undefined)).toBeUndefined();
24
+ });
25
+
26
+ it('returns null unchanged', () => {
27
+ const intl = buildIntl('en', {});
28
+ expect(formatMessageWithFallback(intl, null)).toBeNull();
29
+ });
30
+
31
+ it('returns an empty string unchanged', () => {
32
+ const intl = buildIntl('en', {});
33
+ expect(formatMessageWithFallback(intl, '')).toBe('');
34
+ });
35
+
36
+ it('does not call intl.formatMessage when the message is falsy', () => {
37
+ const intl = buildIntl('en', {});
38
+ const spy = vi.spyOn(intl, 'formatMessage');
39
+ formatMessageWithFallback(intl, undefined);
40
+ formatMessageWithFallback(intl, null);
41
+ formatMessageWithFallback(intl, '');
42
+ expect(spy).not.toHaveBeenCalled();
43
+ });
44
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * I18n helpers.
3
+ * @module helpers/I18n/I18n
4
+ */
5
+
6
+ import type { IntlShape } from 'react-intl';
7
+
8
+ /**
9
+ * Format a `react-intl` message using a plain string as both the id and the
10
+ * default fallback. Useful when a UI value (e.g. a block title configured as
11
+ * a plain string) should be looked up in the locale catalog when available,
12
+ * and rendered as-is otherwise.
13
+ *
14
+ * Returns the input untouched when it is falsy, so the result can be used
15
+ * directly in `||` fallback chains.
16
+ *
17
+ * @param intl react-intl `intl` instance, typically from `useIntl()`.
18
+ * @param message The string to translate; used as both `id` and
19
+ * `defaultMessage`.
20
+ * @returns Translated text, or `message` unchanged if falsy / no translation.
21
+ */
22
+ export function formatMessageWithFallback<T extends string | undefined | null>(
23
+ intl: IntlShape,
24
+ message: T,
25
+ ): T extends string ? string : T {
26
+ if (!message) return message as T extends string ? string : T;
27
+ return intl.formatMessage({
28
+ id: message,
29
+ defaultMessage: message,
30
+ }) as T extends string ? string : T;
31
+ }
@@ -1,19 +1,4 @@
1
- /* TODO: When the Volto Team removes Jest configuration support from Volto core, update this file with the Vitest version of the mock.
2
- Then, in the tests, we need to replace:
3
-
4
- vi.mock('@plone/volto/helpers/Loadable/Loadable', async () => {
5
- return await import(
6
- '@plone/volto/helpers/Loadable/__mocks__/Loadable.vitest.jsx'
7
- );
8
- });
9
-
10
- with the following:
11
-
12
- vi.mock('@plone/volto/helpers/Loadable/Loadable');
13
-
14
- Finally, remove this comment.
15
- */
16
-
1
+ import React from 'react';
17
2
  import config from '@plone/volto/registry';
18
3
  const loadables = config.settings.loadables;
19
4
 
@@ -33,21 +18,21 @@ export const __setLoadables = async () => {
33
18
  };
34
19
 
35
20
  // TODO: filter mockAllLoadables
36
- export const injectLazyLibs = jest.fn().mockImplementation(function ([
21
+ export const injectLazyLibs = vi.fn().mockImplementation(function ([
37
22
  libraries,
38
23
  ]) {
39
- return jest.fn((WrappedComponent) =>
40
- jest.fn((props) => {
24
+ return vi.fn((WrappedComponent) =>
25
+ vi.fn((props) => {
41
26
  return <WrappedComponent {...props} {...mockAllLoadables} />;
42
27
  }),
43
28
  );
44
29
  });
45
30
 
46
- export const preloadLazyLibs = jest.fn().mockImplementation(function ([
31
+ export const preloadLazyLibs = vi.fn().mockImplementation(function ([
47
32
  libraries,
48
33
  ]) {
49
- return jest.fn((WrappedComponent) =>
50
- jest.fn((props) => {
34
+ return vi.fn((WrappedComponent) =>
35
+ vi.fn((props) => {
51
36
  return <WrappedComponent {...props} />;
52
37
  }),
53
38
  );