@plone/volto 17.0.0-alpha.9 → 17.0.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 (365) hide show
  1. package/.eslintrc +26 -3
  2. package/CHANGELOG.md +774 -5
  3. package/CONTRIBUTING.md +5 -1
  4. package/README.md +12 -9
  5. package/RELEASING.md +5 -5
  6. package/addon-registry.js +10 -1
  7. package/create-addons-loader.js +1 -1
  8. package/cypress/support/commands.js +70 -14
  9. package/cypress/support/e2e.js +1 -2
  10. package/cypress/support/volto-slate.js +4 -5
  11. package/cypress.config.js +1 -0
  12. package/docker-compose.yml +1 -1
  13. package/locales/ca/LC_MESSAGES/volto.po +281 -53
  14. package/locales/ca.json +1 -1
  15. package/locales/de/LC_MESSAGES/volto.po +289 -61
  16. package/locales/de.json +1 -1
  17. package/locales/en/LC_MESSAGES/volto.po +279 -51
  18. package/locales/en.json +1 -1
  19. package/locales/es/LC_MESSAGES/volto.po +318 -90
  20. package/locales/es.json +1 -1
  21. package/locales/eu/LC_MESSAGES/volto.po +280 -52
  22. package/locales/eu.json +1 -1
  23. package/locales/fi/LC_MESSAGES/volto.po +280 -52
  24. package/locales/fi.json +1 -1
  25. package/locales/fr/LC_MESSAGES/volto.po +281 -53
  26. package/locales/fr.json +1 -1
  27. package/locales/it/LC_MESSAGES/volto.po +474 -246
  28. package/locales/it.json +1 -1
  29. package/locales/ja/LC_MESSAGES/volto.po +280 -52
  30. package/locales/ja.json +1 -1
  31. package/locales/nl/LC_MESSAGES/volto.po +281 -53
  32. package/locales/nl.json +1 -1
  33. package/locales/pt/LC_MESSAGES/volto.po +281 -53
  34. package/locales/pt.json +1 -1
  35. package/locales/pt_BR/LC_MESSAGES/volto.po +314 -86
  36. package/locales/pt_BR.json +1 -1
  37. package/locales/ro/LC_MESSAGES/volto.po +281 -53
  38. package/locales/ro.json +1 -1
  39. package/locales/volto.pot +284 -52
  40. package/locales/zh_CN/LC_MESSAGES/volto.po +281 -53
  41. package/locales/zh_CN.json +1 -1
  42. package/package.json +44 -34
  43. package/packages/volto-slate/package.json +1 -1
  44. package/packages/volto-slate/src/actions/index.js +1 -1
  45. package/packages/volto-slate/src/blocks/Table/TableBlockEdit.jsx +21 -212
  46. package/packages/volto-slate/src/blocks/Table/schema.js +122 -0
  47. package/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +8 -3
  48. package/packages/volto-slate/src/blocks/Text/TextBlockView.jsx +21 -16
  49. package/packages/volto-slate/src/blocks/Text/extensions/withDeserializers.js +3 -1
  50. package/packages/volto-slate/src/blocks/Text/index.js +10 -7
  51. package/packages/volto-slate/src/editor/config.jsx +5 -4
  52. package/packages/volto-slate/src/editor/index.js +4 -4
  53. package/packages/volto-slate/src/editor/less/slate.less +28 -0
  54. package/packages/volto-slate/src/editor/plugins/Link/render.jsx +5 -6
  55. package/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx +14 -4
  56. package/packages/volto-slate/src/editor/plugins/StyleMenu/utils.js +14 -5
  57. package/packages/volto-slate/src/editor/render.jsx +77 -8
  58. package/packages/volto-slate/src/editor/ui/SlateContextToolbar.jsx +2 -2
  59. package/packages/volto-slate/src/editor/ui/index.js +15 -15
  60. package/packages/volto-slate/src/index.js +2 -2
  61. package/packages/volto-slate/src/utils/blocks.js +7 -0
  62. package/packages/volto-slate/src/widgets/RichTextWidget.jsx +15 -8
  63. package/razzle.config.js +4 -6
  64. package/src/actions/index.js +4 -0
  65. package/src/actions/navroot/navroot.js +16 -0
  66. package/src/actions/navroot/navroot.test.js +15 -0
  67. package/src/actions/relations/rebuild.js +7 -7
  68. package/src/actions/relations/relations.js +17 -0
  69. package/src/actions/site/site.js +16 -0
  70. package/src/actions/site/site.test.js +15 -0
  71. package/src/actions/userSession/userSession.js +17 -1
  72. package/src/components/index.js +194 -192
  73. package/src/components/manage/Actions/Actions.jsx +133 -243
  74. package/src/components/manage/Add/Add.jsx +7 -8
  75. package/src/components/manage/AnchorPlugin/index.jsx +2 -2
  76. package/src/components/manage/AnchorPlugin/utils/EditorUtils.js +3 -1
  77. package/src/components/manage/Blocks/Block/BlocksForm.jsx +19 -2
  78. package/src/components/manage/Blocks/Block/Edit.jsx +1 -1
  79. package/src/components/manage/Blocks/Block/Settings.jsx +2 -0
  80. package/src/components/manage/Blocks/Block/Settings.test.jsx +92 -0
  81. package/src/components/manage/Blocks/Block/Style.jsx +2 -2
  82. package/src/components/manage/Blocks/Container/Data.jsx +32 -0
  83. package/src/components/manage/Blocks/Container/Edit.jsx +177 -0
  84. package/src/components/manage/Blocks/Container/EditBlockWrapper.jsx +121 -0
  85. package/src/components/manage/Blocks/Container/NewBlockAddButton.jsx +84 -0
  86. package/src/components/manage/Blocks/Container/SimpleContainerToolbar.jsx +54 -0
  87. package/src/components/manage/Blocks/Grid/Edit.jsx +47 -0
  88. package/src/components/manage/Blocks/Grid/View.jsx +43 -0
  89. package/src/components/manage/Blocks/Grid/adapter.js +14 -0
  90. package/src/components/manage/Blocks/Grid/grid-1.svg +6 -0
  91. package/src/components/manage/Blocks/Grid/grid-2.svg +9 -0
  92. package/src/components/manage/Blocks/Grid/grid-3.svg +10 -0
  93. package/src/components/manage/Blocks/Grid/grid-4.svg +11 -0
  94. package/src/components/manage/Blocks/Grid/schema.js +35 -0
  95. package/src/components/manage/Blocks/Grid/templates.js +47 -0
  96. package/src/components/manage/Blocks/HTML/Edit.jsx +8 -8
  97. package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +36 -26
  98. package/src/components/manage/Blocks/Image/Edit.jsx +51 -12
  99. package/src/components/manage/Blocks/Image/Edit.test.jsx +2 -0
  100. package/src/components/manage/Blocks/Image/ImageSidebar.jsx +66 -16
  101. package/src/components/manage/Blocks/Image/View.jsx +26 -5
  102. package/src/components/manage/Blocks/Image/View.test.jsx +20 -0
  103. package/src/components/manage/Blocks/Image/schema.js +17 -10
  104. package/src/components/manage/Blocks/Image/utils.js +14 -0
  105. package/src/components/manage/Blocks/LeadImage/Edit.jsx +32 -10
  106. package/src/components/manage/Blocks/LeadImage/Edit.test.jsx +11 -1
  107. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +28 -9
  108. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +8 -2
  109. package/src/components/manage/Blocks/LeadImage/View.jsx +50 -38
  110. package/src/components/manage/Blocks/LeadImage/View.test.jsx +11 -1
  111. package/src/components/manage/Blocks/Listing/DefaultTemplate.jsx +18 -3
  112. package/src/components/manage/Blocks/Listing/ListingBody.jsx +32 -8
  113. package/src/components/manage/Blocks/Listing/ListingBody.test.jsx +20 -0
  114. package/src/components/manage/Blocks/Listing/SummaryTemplate.jsx +1 -1
  115. package/src/components/manage/Blocks/Listing/getAsyncData.js +3 -5
  116. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +27 -17
  117. package/src/components/manage/Blocks/Maps/Edit.jsx +135 -209
  118. package/src/components/manage/Blocks/Maps/Edit.test.jsx +1 -2
  119. package/src/components/manage/Blocks/Maps/View.test.jsx +1 -2
  120. package/src/components/manage/Blocks/Search/SearchBlockView.jsx +3 -2
  121. package/src/components/manage/Blocks/Search/components/Facets.jsx +66 -7
  122. package/src/components/manage/Blocks/Search/components/FilterList.jsx +4 -6
  123. package/src/components/manage/Blocks/Search/components/SearchInput.jsx +9 -2
  124. package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +2 -9
  125. package/src/components/manage/Blocks/Search/components/index.js +13 -13
  126. package/src/components/manage/Blocks/Search/hocs/index.js +2 -2
  127. package/src/components/manage/Blocks/Search/hocs/withQueryString.jsx +5 -2
  128. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +70 -36
  129. package/src/components/manage/Blocks/Search/layout/LeftColumnFacets.jsx +17 -5
  130. package/src/components/manage/Blocks/Search/layout/RightColumnFacets.jsx +17 -5
  131. package/src/components/manage/Blocks/Search/layout/TopSideFacets.jsx +21 -5
  132. package/src/components/manage/Blocks/Search/schema.js +29 -14
  133. package/src/components/manage/Blocks/Table/Cell.jsx +2 -3
  134. package/src/components/manage/Blocks/Teaser/Body.jsx +0 -1
  135. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +5 -10
  136. package/src/components/manage/Blocks/Teaser/schema.js +5 -0
  137. package/src/components/manage/Blocks/Text/Edit.jsx +2 -3
  138. package/src/components/manage/Blocks/Title/View.jsx +0 -23
  139. package/src/components/manage/Blocks/Title/View.test.jsx +16 -1
  140. package/src/components/manage/Blocks/ToC/Schema.jsx +40 -7
  141. package/src/components/manage/Blocks/ToC/View.jsx +84 -14
  142. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +8 -3
  143. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.test.jsx +44 -0
  144. package/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx +149 -10
  145. package/src/components/manage/Blocks/ToC/variations/index.js +3 -1
  146. package/src/components/manage/Blocks/Video/View.test.jsx +1 -1
  147. package/src/components/manage/Contents/Contents.jsx +285 -114
  148. package/src/components/manage/Contents/ContentsPropertiesModal.jsx +90 -166
  149. package/src/components/manage/Contents/ContentsRenameModal.jsx +88 -139
  150. package/src/components/manage/Contents/ContentsRenameModal.stories.jsx +61 -0
  151. package/src/components/manage/Contents/ContentsTagsModal.jsx +83 -130
  152. package/src/components/manage/Contents/ContentsTagsModal.stories.jsx +68 -0
  153. package/src/components/manage/Contents/ContentsUploadModal.jsx +11 -7
  154. package/src/components/manage/Contents/ContentsWorkflowModal.jsx +87 -154
  155. package/src/components/manage/Controlpanels/Aliases.jsx +4 -12
  156. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +65 -38
  157. package/src/components/manage/Controlpanels/Groups/RenderGroups.jsx +2 -2
  158. package/src/components/manage/Controlpanels/Relations/BrokenRelations.jsx +38 -13
  159. package/src/components/manage/Controlpanels/Relations/Relations.jsx +5 -5
  160. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +8 -7
  161. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +68 -68
  162. package/src/components/manage/Controlpanels/Rules/AddRule.jsx +3 -10
  163. package/src/components/manage/Controlpanels/Rules/EditRule.jsx +1 -1
  164. package/src/components/manage/Controlpanels/UndoControlpanel.jsx +6 -9
  165. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +97 -7
  166. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +127 -99
  167. package/src/components/manage/Delete/Delete.jsx +96 -171
  168. package/src/components/manage/Diff/DiffField.jsx +25 -1
  169. package/src/components/manage/DragDropList/DragDropList.jsx +18 -13
  170. package/src/components/manage/Form/BlockDataForm.jsx +3 -2
  171. package/src/components/manage/Form/BlockDataForm.test.jsx +51 -17
  172. package/src/components/manage/Form/Form.jsx +7 -6
  173. package/src/components/manage/Form/InlineForm.test.jsx +16 -14
  174. package/src/components/manage/History/History.jsx +11 -1
  175. package/src/components/manage/LinksToItem/LinksToItem.jsx +209 -0
  176. package/src/components/manage/LinksToItem/LinksToItem.test.jsx +100 -0
  177. package/src/components/manage/LockingToastsFactory/LockingToastsFactory.jsx +1 -2
  178. package/src/components/manage/Messages/Messages.jsx +32 -99
  179. package/src/components/manage/Messages/Messages.test.jsx +0 -1
  180. package/src/components/manage/Preferences/ChangePassword.jsx +2 -2
  181. package/src/components/manage/Sharing/Sharing.jsx +80 -22
  182. package/src/components/manage/Sidebar/AlignBlock.jsx +1 -1
  183. package/src/components/manage/Sidebar/Sidebar.jsx +139 -220
  184. package/src/components/manage/TemplateChooser/TemplateChooser.jsx +38 -0
  185. package/src/components/manage/TemplateChooser/TemplateChooser.test.jsx +34 -0
  186. package/src/components/manage/TemplateChooser/template.svg +10 -0
  187. package/src/components/manage/Toast/Toast.jsx +1 -1
  188. package/src/components/manage/Toolbar/More.jsx +17 -2
  189. package/src/components/manage/Toolbar/PersonalTools.jsx +97 -155
  190. package/src/components/manage/Toolbar/Toolbar.jsx +2 -2
  191. package/src/components/manage/UniversalLink/UniversalLink.jsx +6 -12
  192. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +37 -0
  193. package/src/components/manage/Widgets/AlignWidget.jsx +2 -4
  194. package/src/components/manage/Widgets/ArrayWidget.jsx +3 -1
  195. package/src/components/manage/Widgets/ArrayWidget.test.jsx +45 -1
  196. package/src/components/manage/Widgets/ColorPickerWidget.jsx +6 -1
  197. package/src/components/manage/Widgets/ColorPickerWidget.test.jsx +9 -7
  198. package/src/components/manage/Widgets/DatetimeWidget.jsx +2 -8
  199. package/src/components/manage/Widgets/FileWidget.jsx +2 -1
  200. package/src/components/manage/Widgets/FormFieldWrapper.jsx +1 -1
  201. package/src/components/manage/Widgets/IdWidget.jsx +1 -2
  202. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +2 -9
  203. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +3 -10
  204. package/src/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField.jsx +4 -4
  205. package/src/components/manage/Widgets/RegistryImageWidget.jsx +210 -0
  206. package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +91 -0
  207. package/src/components/manage/Widgets/SchemaWidget.jsx +6 -9
  208. package/src/components/manage/Widgets/SelectUtils.js +1 -1
  209. package/src/components/manage/Widgets/SelectWidget.jsx +15 -1
  210. package/src/components/manage/Widgets/SelectWidget.test.jsx +45 -1
  211. package/src/components/manage/Widgets/WysiwygWidget.jsx +2 -9
  212. package/src/components/manage/Workflow/Workflow.jsx +75 -184
  213. package/src/components/theme/Anontools/Anontools.jsx +44 -72
  214. package/src/components/theme/Anontools/Anontools.stories.jsx +16 -6
  215. package/src/components/theme/Anontools/Anontools.test.jsx +16 -2
  216. package/src/components/theme/Breadcrumbs/Breadcrumbs.jsx +52 -99
  217. package/src/components/theme/Breadcrumbs/Breadcrumbs.stories.jsx +14 -13
  218. package/src/components/theme/Comments/Comment.stories.jsx +84 -0
  219. package/src/components/theme/Comments/CommentEditModal.jsx +63 -115
  220. package/src/components/theme/Comments/Comments.jsx +268 -380
  221. package/src/components/theme/Component/Component.jsx +1 -1
  222. package/src/components/theme/ContactForm/ContactForm.jsx +108 -192
  223. package/src/components/theme/ContactForm/ContactForm.stories.jsx +1 -1
  224. package/src/components/theme/ContactForm/ContactForm.test.jsx +2 -3
  225. package/src/components/theme/ContentMetadataTags/ContentMetadataTags.jsx +41 -3
  226. package/src/components/theme/Error/ServerError.jsx +29 -0
  227. package/src/components/theme/Header/Header.jsx +37 -63
  228. package/src/components/theme/Header/Header.test.jsx +18 -0
  229. package/src/components/theme/Image/Image.jsx +96 -0
  230. package/src/components/theme/Image/Image.test.jsx +125 -0
  231. package/src/components/theme/Login/Login.jsx +160 -243
  232. package/src/components/theme/Logo/Logo.Multilingual.test.jsx +131 -1
  233. package/src/components/theme/Logo/Logo.jsx +35 -27
  234. package/src/components/theme/Logo/Logo.test.jsx +135 -1
  235. package/src/components/theme/Logout/Logout.jsx +36 -83
  236. package/src/components/theme/Navigation/Navigation.jsx +86 -171
  237. package/src/components/theme/PasswordReset/PasswordReset.jsx +7 -5
  238. package/src/components/theme/PasswordReset/RequestPasswordReset.jsx +95 -170
  239. package/src/components/theme/PreviewImage/PreviewImage.jsx +31 -15
  240. package/src/components/theme/PreviewImage/PreviewImage.test.js +53 -13
  241. package/src/components/theme/Register/Register.jsx +2 -4
  242. package/src/components/theme/Search/SearchTags.jsx +30 -60
  243. package/src/components/theme/SearchWidget/SearchWidget.jsx +49 -97
  244. package/src/components/theme/SearchWidget/SearchWidget.test.jsx +8 -0
  245. package/src/components/theme/Sitemap/Sitemap.jsx +24 -13
  246. package/src/components/theme/Sitemap/Sitemap.test.jsx +23 -2
  247. package/src/components/theme/TsTest/TsTest.test.tsx +11 -0
  248. package/src/components/theme/TsTest/TsTest.tsx +15 -0
  249. package/src/components/theme/View/AlbumView.jsx +21 -16
  250. package/src/components/theme/View/EventView.jsx +36 -25
  251. package/src/components/theme/View/FileView.jsx +23 -18
  252. package/src/components/theme/View/ImageView.jsx +40 -32
  253. package/src/components/theme/View/ImageView.test.jsx +4 -0
  254. package/src/components/theme/View/LinkView.jsx +53 -78
  255. package/src/components/theme/View/ListingView.jsx +36 -28
  256. package/src/components/theme/View/NewsItemView.jsx +16 -17
  257. package/src/components/theme/View/RenderBlocks.jsx +56 -27
  258. package/src/components/theme/View/RenderEmptyBlock.jsx +5 -0
  259. package/src/components/theme/View/SummaryView.jsx +49 -39
  260. package/src/components/theme/View/TabularView.jsx +59 -53
  261. package/src/components/theme/View/View.jsx +2 -0
  262. package/src/components/theme/Widgets/ImageWidget.stories.jsx +1 -2
  263. package/src/config/Blocks.jsx +46 -0
  264. package/src/config/Components.jsx +3 -1
  265. package/src/config/ControlPanels.js +0 -1
  266. package/src/config/Loadables.jsx +1 -1
  267. package/src/config/NonContentRoutes.jsx +1 -0
  268. package/src/config/RichTextEditor/Blocks.jsx +4 -5
  269. package/src/config/RichTextEditor/FromHTML.jsx +2 -2
  270. package/src/config/RichTextEditor/Plugins.jsx +2 -3
  271. package/src/config/RichTextEditor/Styles.jsx +1 -1
  272. package/src/config/RichTextEditor/ToHTML.jsx +12 -10
  273. package/src/config/RichTextEditor/index.js +2 -3
  274. package/src/config/Views.jsx +6 -4
  275. package/src/config/Widgets.jsx +3 -0
  276. package/src/config/index.js +36 -2
  277. package/src/config/server.js +2 -0
  278. package/src/constants/ActionTypes.js +4 -0
  279. package/src/constants/Indexes.js +3 -1
  280. package/src/express-middleware/devproxy.js +1 -1
  281. package/src/express-middleware/files.js +11 -9
  282. package/src/express-middleware/images.js +12 -5
  283. package/src/express-middleware/ok.js +16 -0
  284. package/src/express-middleware/robotstxt.js +1 -1
  285. package/src/express-middleware/sitemap.js +1 -1
  286. package/src/express-middleware/static.js +3 -3
  287. package/src/helpers/Blocks/Blocks.js +52 -6
  288. package/src/helpers/Blocks/Blocks.test.js +92 -13
  289. package/src/helpers/Extensions/index.js +2 -1
  290. package/src/helpers/Extensions/withBlockSchemaEnhancer.js +63 -61
  291. package/src/helpers/Extensions/withBlockSchemaEnhancer.test.js +145 -0
  292. package/src/helpers/FormValidation/FormValidation.js +37 -7
  293. package/src/helpers/FormValidation/FormValidation.test.js +32 -0
  294. package/src/helpers/Html/Html.jsx +2 -8
  295. package/src/helpers/Loadable/__mocks__/Loadable.js +18 -18
  296. package/src/helpers/MessageLabels/MessageLabels.js +39 -4
  297. package/src/helpers/ScrollToTop/ScrollToTop.jsx +5 -3
  298. package/src/helpers/Site/index.js +21 -0
  299. package/src/helpers/Url/Url.js +22 -1
  300. package/src/helpers/Url/Url.test.js +41 -0
  301. package/src/helpers/Utils/UseDetectClickOutside.stories.jsx +190 -0
  302. package/src/helpers/Utils/Utils.js +35 -0
  303. package/src/helpers/Utils/Utils.test.js +13 -0
  304. package/src/helpers/Utils/usePagination.js +67 -14
  305. package/src/helpers/Utils/usePagination.test.js +115 -0
  306. package/src/helpers/index.js +15 -8
  307. package/src/hooks/client/useClient.js +11 -0
  308. package/src/hooks/clipboard/useClipboard.js +26 -0
  309. package/src/hooks/index.js +2 -0
  310. package/src/icons/grid-block.svg +11 -0
  311. package/src/middleware/api.js +203 -173
  312. package/src/middleware/blacklistRoutes.js +25 -22
  313. package/src/middleware/index.js +2 -2
  314. package/src/middleware/storeProtectLoadUtils.js +61 -62
  315. package/src/middleware/storeProtectLoadUtils.test.js +47 -43
  316. package/src/reducers/actions/actions.js +7 -5
  317. package/src/reducers/actions/actions.test.js +70 -0
  318. package/src/reducers/content/content.test.js +4 -4
  319. package/src/reducers/index.js +4 -0
  320. package/src/reducers/navigation/navigation.js +5 -5
  321. package/src/reducers/navigation/navigation.test.js +30 -0
  322. package/src/reducers/navroot/navroot.js +79 -0
  323. package/src/reducers/navroot/navroot.test.js +110 -0
  324. package/src/reducers/relations/relations.js +74 -46
  325. package/src/reducers/site/site.js +51 -0
  326. package/src/reducers/site/site.test.js +67 -0
  327. package/src/reducers/userSession/userSession.js +15 -1
  328. package/src/registry.js +2 -2
  329. package/src/routes.js +9 -0
  330. package/src/server.jsx +9 -0
  331. package/src/start-server.js +6 -2
  332. package/src/storybook.jsx +24 -38
  333. package/test-setup-config.js +11 -1
  334. package/theme/themes/pastanaga/collections/form.overrides +46 -0
  335. package/theme/themes/pastanaga/collections/menu.overrides +3 -2
  336. package/theme/themes/pastanaga/elements/container.overrides +5 -2
  337. package/theme/themes/pastanaga/elements/input.overrides +11 -1
  338. package/theme/themes/pastanaga/elements/label.overrides +10 -0
  339. package/theme/themes/pastanaga/elements/step.overrides +2 -1
  340. package/theme/themes/pastanaga/extras/blocks.less +25 -15
  341. package/theme/themes/pastanaga/extras/color-picker-widget.less +1 -1
  342. package/theme/themes/pastanaga/extras/contents.less +6 -1
  343. package/theme/themes/pastanaga/extras/draftjs.less +4 -4
  344. package/theme/themes/pastanaga/extras/grid.less +427 -0
  345. package/theme/themes/pastanaga/extras/login.less +3 -0
  346. package/theme/themes/pastanaga/extras/main.less +14 -7
  347. package/theme/themes/pastanaga/extras/react-dates-overrides.less +4 -2
  348. package/theme/themes/pastanaga/extras/search.less +7 -1
  349. package/theme/themes/pastanaga/extras/sidebar.less +5 -4
  350. package/theme/themes/pastanaga/extras/time-picker-overrides.less +5 -3
  351. package/theme/themes/pastanaga/extras/toc.less +29 -0
  352. package/theme/themes/pastanaga/extras/toolbar.less +6 -2
  353. package/theme/themes/pastanaga/extras/userscontrolpanel.less +17 -9
  354. package/theme/themes/pastanaga/extras/widgets.less +1 -1
  355. package/theme/themes/pastanaga/modules/rating.overrides +2 -1
  356. package/theme/themes/pastanaga-cms-ui/elements/container.overrides +2 -1
  357. package/theme/themes/pastanaga-cms-ui/extras/cms-ui.elements.container.less +6 -2
  358. package/theme/themes/pastanaga-cms-ui/extras/cms-ui.site.less +2 -2
  359. package/tsconfig.json +33 -0
  360. package/webpack-plugins/webpack-less-plugin.js +19 -0
  361. package/.yarn/install-state.gz +0 -0
  362. package/.yarn/releases/yarn-3.2.3.cjs +0 -783
  363. package/src/components/manage/Blocks/Teaser/utils.js +0 -44
  364. package/src/components/manage/Blocks/Teaser/utils.test.jsx +0 -229
  365. package/src/components/theme/Header/Header.md +0 -27
@@ -290,9 +290,8 @@ export function changeBlock(formData, id, value) {
290
290
  */
291
291
  export function nextBlockId(formData, currentBlock) {
292
292
  const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
293
- const currentIndex = formData[blocksLayoutFieldname].items.indexOf(
294
- currentBlock,
295
- );
293
+ const currentIndex =
294
+ formData[blocksLayoutFieldname].items.indexOf(currentBlock);
296
295
 
297
296
  if (currentIndex === formData[blocksLayoutFieldname].items.length - 1) {
298
297
  // We are already at the bottom block don't do anything
@@ -312,9 +311,8 @@ export function nextBlockId(formData, currentBlock) {
312
311
  */
313
312
  export function previousBlockId(formData, currentBlock) {
314
313
  const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
315
- const currentIndex = formData[blocksLayoutFieldname].items.indexOf(
316
- currentBlock,
317
- );
314
+ const currentIndex =
315
+ formData[blocksLayoutFieldname].items.indexOf(currentBlock);
318
316
 
319
317
  if (currentIndex === 0) {
320
318
  // We are already at the top block don't do anything
@@ -343,6 +341,32 @@ export function emptyBlocksForm() {
343
341
  };
344
342
  }
345
343
 
344
+ /**
345
+ * Generate empty blocks blocks/blocks_layout pair given the type
346
+ * (could be empty, if not type given) and the number of blocks
347
+ * @function blocksFormGenerator
348
+ * @param {number} number How many blocks to generate of the type (could be "empty", if no type provided)
349
+ * @param {number} type The type of the blocks
350
+ * @return {Object} blocks/blocks_layout pair filled with the generated blocks
351
+ */
352
+ export function blocksFormGenerator(number, type) {
353
+ const idMap = [...Array(number).keys()].map(() => uuid());
354
+ const start = {
355
+ blocks: {},
356
+ blocks_layout: { items: idMap },
357
+ };
358
+
359
+ return {
360
+ ...start,
361
+ blocks: Object.fromEntries(
362
+ start.blocks_layout.items.map((item) => [
363
+ item,
364
+ { '@type': type || 'empty' },
365
+ ]),
366
+ ),
367
+ };
368
+ }
369
+
346
370
  /**
347
371
  * Recursively discover blocks in data and call the provided callback
348
372
  * @function visitBlocks
@@ -524,3 +548,25 @@ export const getPreviousNextBlock = ({ content, block }) => {
524
548
 
525
549
  return [previousBlock, nextBlock];
526
550
  };
551
+
552
+ /**
553
+ * Given a `block` object and a list of block types, return a list of block ids matching the types
554
+ *
555
+ * @function findBlocks
556
+ * @param {Object} types A list with the list of types to be matched
557
+ * @return {Array} An array of block ids
558
+ */
559
+ export function findBlocks(blocks, types, result = []) {
560
+ const containerBlockTypes = config.settings.containerBlockTypes;
561
+
562
+ Object.keys(blocks).forEach((blockId) => {
563
+ const block = blocks[blockId];
564
+ if (types.includes(block['@type'])) {
565
+ result.push(blockId);
566
+ } else if (containerBlockTypes.includes(block['@type']) || block.blocks) {
567
+ findBlocks(block.blocks, types, result);
568
+ }
569
+ });
570
+
571
+ return result;
572
+ }
@@ -19,6 +19,8 @@ import {
19
19
  buildStyleClassNamesFromData,
20
20
  buildStyleClassNamesExtenders,
21
21
  getPreviousNextBlock,
22
+ blocksFormGenerator,
23
+ findBlocks,
22
24
  } from './Blocks';
23
25
 
24
26
  import config from '@plone/volto/registry';
@@ -586,26 +588,26 @@ describe('Blocks', () => {
586
588
  const d = {
587
589
  data: {
588
590
  blocks: {
589
- '1': {
591
+ 1: {
590
592
  blocks: {
591
- '2': {},
592
- '3': {
593
+ 2: {},
594
+ 3: {
593
595
  data: {
594
596
  blocks: {
595
- '11': {},
596
- '12': {},
597
- '13': {},
597
+ 11: {},
598
+ 12: {},
599
+ 13: {},
598
600
  },
599
601
  blocks_layout: {
600
602
  items: ['11', '12', '13'],
601
603
  },
602
604
  },
603
605
  },
604
- '7': {
606
+ 7: {
605
607
  blocks: {
606
- '8': {},
607
- '9': {},
608
- '10': {},
608
+ 8: {},
609
+ 9: {},
610
+ 10: {},
609
611
  },
610
612
  blocks_layout: {
611
613
  items: ['8', '9', '10'],
@@ -616,10 +618,10 @@ describe('Blocks', () => {
616
618
  items: ['2', '3', '7'],
617
619
  },
618
620
  },
619
- '4': {
621
+ 4: {
620
622
  blocks: {
621
- '5': {},
622
- '6': {},
623
+ 5: {},
624
+ 6: {},
623
625
  },
624
626
  blocks_layout: {
625
627
  items: ['5', '6'],
@@ -1275,4 +1277,81 @@ describe('Blocks', () => {
1275
1277
  ]);
1276
1278
  });
1277
1279
  });
1280
+
1281
+ describe('blocksFormGenerator', () => {
1282
+ it('Returns an empty blocks/blocks_layout pair', () => {
1283
+ expect(blocksFormGenerator(0, '')).toEqual({
1284
+ blocks: {},
1285
+ blocks_layout: { items: [] },
1286
+ });
1287
+ });
1288
+ it('Returns a filled blocks/blocks_layout pair with type block', () => {
1289
+ const result = blocksFormGenerator(2, 'teaser');
1290
+ expect(Object.keys(result.blocks).length).toEqual(2);
1291
+ expect(result.blocks_layout.items.length).toEqual(2);
1292
+ expect(result.blocks[result.blocks_layout.items[0]]['@type']).toEqual(
1293
+ 'teaser',
1294
+ );
1295
+ expect(result.blocks[result.blocks_layout.items[1]]['@type']).toEqual(
1296
+ 'teaser',
1297
+ );
1298
+ });
1299
+ });
1300
+ });
1301
+
1302
+ describe('findBlocks', () => {
1303
+ it('Get all blocks in the first level (main block container)', () => {
1304
+ const blocks = {
1305
+ 1: { title: 'title', '@type': 'title' },
1306
+ 2: { title: 'an image', '@type': 'image' },
1307
+ 3: { title: 'description', '@type': 'description' },
1308
+ 4: { title: 'a text', '@type': 'slate' },
1309
+ };
1310
+ const types = ['description'];
1311
+ expect(findBlocks(blocks, types)).toStrictEqual(['3']);
1312
+ });
1313
+
1314
+ it('Get all blocks in the first level (main block container) given a list', () => {
1315
+ const blocks = {
1316
+ 1: { title: 'title', '@type': 'title' },
1317
+ 2: { title: 'an image', '@type': 'image' },
1318
+ 3: { title: 'description', '@type': 'description' },
1319
+ 4: { title: 'a text', '@type': 'slate' },
1320
+ };
1321
+ const types = ['description', 'slate'];
1322
+ expect(findBlocks(blocks, types)).toStrictEqual(['3', '4']);
1323
+ });
1324
+
1325
+ it('Get all blocks in the first level (main block container) given a list', () => {
1326
+ const blocks = {
1327
+ 1: { title: 'title', '@type': 'title' },
1328
+ 2: { title: 'an image', '@type': 'image' },
1329
+ 3: { title: 'description', '@type': 'description' },
1330
+ 4: { title: 'a text', '@type': 'slate' },
1331
+ 5: { title: 'a text', '@type': 'slate' },
1332
+ };
1333
+ const types = ['description', 'slate'];
1334
+ expect(findBlocks(blocks, types)).toStrictEqual(['3', '4', '5']);
1335
+ });
1336
+
1337
+ it('Get all blocks, including containers, given a list', () => {
1338
+ const blocks = {
1339
+ 1: { title: 'title', '@type': 'title' },
1340
+ 2: { title: 'an image', '@type': 'image' },
1341
+ 3: { title: 'description', '@type': 'description' },
1342
+ 4: { title: 'a text', '@type': 'slate' },
1343
+ 5: {
1344
+ title: 'a container',
1345
+ '@type': 'gridBlock',
1346
+ blocks: {
1347
+ 6: { title: 'title', '@type': 'title' },
1348
+ 7: { title: 'an image', '@type': 'image' },
1349
+ 8: { title: 'description', '@type': 'description' },
1350
+ 9: { title: 'a text', '@type': 'slate' },
1351
+ },
1352
+ },
1353
+ };
1354
+ const types = ['description', 'slate'];
1355
+ expect(findBlocks(blocks, types)).toStrictEqual(['3', '4', '8', '9']);
1356
+ });
1278
1357
  });
@@ -1,5 +1,6 @@
1
1
  export * from './withBlockSchemaEnhancer';
2
- export withBlockExtensions, {
2
+ export {
3
+ default as withBlockExtensions,
3
4
  resolveExtension,
4
5
  resolveBlockExtensions,
5
6
  } from './withBlockExtensions';
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { defineMessages } from 'react-intl';
3
3
  import { useIntl } from 'react-intl';
4
+ import { find, isEmpty } from 'lodash';
4
5
  import config from '@plone/volto/registry';
5
6
  import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils';
6
7
 
@@ -109,61 +110,59 @@ export const addExtensionFieldToSchema = ({
109
110
  * }
110
111
  * ```
111
112
  */
112
- export const withBlockSchemaEnhancer = (
113
- FormComponent,
114
- extensionName = 'vendor',
115
- insertFieldToOrder = _addField,
116
- ) => ({ ...props }) => {
117
- const { formData, schema: originalSchema } = props;
118
- const intl = useIntl();
113
+ export const withBlockSchemaEnhancer =
114
+ (FormComponent, extensionName = 'vendor', insertFieldToOrder = _addField) =>
115
+ ({ ...props }) => {
116
+ const { formData, schema: originalSchema } = props;
117
+ const intl = useIntl();
119
118
 
120
- const blocksConfig = getBlocksConfig(props);
119
+ const blocksConfig = getBlocksConfig(props);
121
120
 
122
- const blockType = formData['@type'];
123
- const extensionConfig =
124
- blocksConfig?.[blockType]?.extensions?.[extensionName];
125
-
126
- if (!extensionConfig)
127
- return <FormComponent {...props} schema={originalSchema} />;
128
-
129
- const activeItemName = formData?.[extensionName];
130
- let activeItem = extensionConfig.items?.find(
131
- (item) => item.id === activeItemName,
132
- );
133
- if (!activeItem)
134
- activeItem = extensionConfig.items?.find((item) => item.isDefault);
135
-
136
- const schemaEnhancer =
137
- // For the main "variation" of blocks, allow simply passing a
138
- // schemaEnhancer in the block configuration
139
- activeItem?.['schemaEnhancer'] ||
140
- (extensionName === 'variation' &&
141
- blocksConfig?.[blockType]?.schemaEnhancer);
142
-
143
- let schema = schemaEnhancer
144
- ? schemaEnhancer({
145
- schema: cloneDeepSchema(originalSchema),
146
- formData,
147
- intl,
148
- })
149
- : cloneDeepSchema(originalSchema);
121
+ const blockType = formData['@type'];
122
+ const extensionConfig =
123
+ blocksConfig?.[blockType]?.extensions?.[extensionName];
150
124
 
151
- const { title = messages.variation, description } = extensionConfig;
125
+ if (!extensionConfig)
126
+ return <FormComponent {...props} schema={originalSchema} />;
152
127
 
153
- if (extensionConfig.items?.length > 1) {
154
- addExtensionFieldToSchema({
155
- schema,
156
- name: extensionName,
157
- items: extensionConfig.items || [],
158
- intl,
159
- title,
160
- description,
161
- insertFieldToOrder,
162
- });
163
- }
128
+ const activeItemName = formData?.[extensionName];
129
+ let activeItem = extensionConfig.items?.find(
130
+ (item) => item.id === activeItemName,
131
+ );
132
+ if (!activeItem)
133
+ activeItem = extensionConfig.items?.find((item) => item.isDefault);
134
+
135
+ const schemaEnhancer =
136
+ // For the main "variation" of blocks, allow simply passing a
137
+ // schemaEnhancer in the block configuration
138
+ activeItem?.['schemaEnhancer'] ||
139
+ (extensionName === 'variation' &&
140
+ blocksConfig?.[blockType]?.schemaEnhancer);
141
+
142
+ let schema = schemaEnhancer
143
+ ? schemaEnhancer({
144
+ schema: cloneDeepSchema(originalSchema),
145
+ formData,
146
+ intl,
147
+ })
148
+ : cloneDeepSchema(originalSchema);
149
+
150
+ const { title = messages.variation, description } = extensionConfig;
151
+
152
+ if (extensionConfig.items?.length > 1) {
153
+ addExtensionFieldToSchema({
154
+ schema,
155
+ name: extensionName,
156
+ items: extensionConfig.items || [],
157
+ intl,
158
+ title,
159
+ description,
160
+ insertFieldToOrder,
161
+ });
162
+ }
164
163
 
165
- return <FormComponent {...props} schema={schema} />;
166
- };
164
+ return <FormComponent {...props} schema={schema} />;
165
+ };
167
166
 
168
167
  /**
169
168
  * Apply block variation schema enhancers to the provided schema, using block
@@ -291,20 +290,23 @@ export const EMPTY_STYLES_SCHEMA = {
291
290
  };
292
291
 
293
292
  /**
294
- * Creates the `styles` field and fieldset in a schema
293
+ * Adds the `styles` field and 'styling' fieldset in a given schema
295
294
  */
296
295
  export const addStyling = ({ schema, formData, intl }) => {
297
- schema.fieldsets.push({
298
- id: 'styling',
299
- title: intl.formatMessage(messages.styling),
300
- fields: ['styles'],
301
- });
296
+ if (isEmpty(find(schema.fieldsets, { id: 'styling' }))) {
297
+ schema.fieldsets.push({
298
+ id: 'styling',
299
+ title: intl.formatMessage(messages.styling),
300
+ fields: ['styles'],
301
+ });
302
+
303
+ schema.properties.styles = {
304
+ widget: 'object',
305
+ title: intl.formatMessage(messages.styling),
306
+ schema: cloneDeepSchema(EMPTY_STYLES_SCHEMA),
307
+ };
308
+ }
302
309
 
303
- schema.properties.styles = {
304
- widget: 'object',
305
- title: intl.formatMessage(messages.styling),
306
- schema: EMPTY_STYLES_SCHEMA,
307
- };
308
310
  return schema;
309
311
  };
310
312
 
@@ -2,6 +2,7 @@ import {
2
2
  addExtensionFieldToSchema,
3
3
  applySchemaEnhancer,
4
4
  composeSchema,
5
+ addStyling,
5
6
  } from './withBlockSchemaEnhancer';
6
7
 
7
8
  import config from '@plone/volto/registry';
@@ -246,3 +247,147 @@ describe('composeSchema', () => {
246
247
  expect(res).toStrictEqual([6, 9]);
247
248
  });
248
249
  });
250
+
251
+ describe('addStyling', () => {
252
+ it('returns an enhanced schema with the styling wrapper object on it', () => {
253
+ const intl = { formatMessage: () => 'Styling' };
254
+
255
+ const schema = {
256
+ fieldsets: [
257
+ {
258
+ id: 'default',
259
+ title: 'Default',
260
+ fields: [],
261
+ },
262
+ ],
263
+ properties: {},
264
+ required: [],
265
+ };
266
+
267
+ const result = addStyling({ schema, intl });
268
+
269
+ expect(result).toStrictEqual({
270
+ fieldsets: [
271
+ { id: 'default', title: 'Default', fields: [] },
272
+ { id: 'styling', title: 'Styling', fields: ['styles'] },
273
+ ],
274
+ properties: {
275
+ styles: {
276
+ widget: 'object',
277
+ title: 'Styling',
278
+ schema: {
279
+ fieldsets: [
280
+ {
281
+ fields: [],
282
+ id: 'default',
283
+ title: 'Default',
284
+ },
285
+ ],
286
+ properties: {},
287
+ required: [],
288
+ },
289
+ },
290
+ },
291
+ required: [],
292
+ });
293
+ });
294
+
295
+ it('multiple schema enhancers', () => {
296
+ const intl = { formatMessage: () => 'Styling' };
297
+
298
+ const schema1 = {
299
+ fieldsets: [
300
+ {
301
+ id: 'default',
302
+ title: 'Default',
303
+ fields: [],
304
+ },
305
+ ],
306
+ properties: {},
307
+ required: [],
308
+ };
309
+
310
+ const schema2 = {
311
+ fieldsets: [
312
+ {
313
+ id: 'default',
314
+ title: 'Default',
315
+ fields: [],
316
+ },
317
+ ],
318
+ properties: {},
319
+ required: [],
320
+ };
321
+
322
+ const result = addStyling({ schema: schema1, intl });
323
+
324
+ // We add some fields to the styling schema
325
+ result.properties.styles.schema.properties.align = {
326
+ widget: 'align',
327
+ title: 'align',
328
+ actions: ['left', 'right', 'center'],
329
+ default: 'left',
330
+ };
331
+
332
+ result.properties.styles.schema.fieldsets[0].fields = ['align'];
333
+
334
+ const result2 = addStyling({ schema: schema2, intl });
335
+
336
+ expect(result).toStrictEqual({
337
+ fieldsets: [
338
+ { id: 'default', title: 'Default', fields: [] },
339
+ { id: 'styling', title: 'Styling', fields: ['styles'] },
340
+ ],
341
+ properties: {
342
+ styles: {
343
+ widget: 'object',
344
+ title: 'Styling',
345
+ schema: {
346
+ fieldsets: [
347
+ {
348
+ fields: ['align'],
349
+ id: 'default',
350
+ title: 'Default',
351
+ },
352
+ ],
353
+ properties: {
354
+ align: {
355
+ widget: 'align',
356
+ title: 'align',
357
+ actions: ['left', 'right', 'center'],
358
+ default: 'left',
359
+ },
360
+ },
361
+ required: [],
362
+ },
363
+ },
364
+ },
365
+ required: [],
366
+ });
367
+
368
+ expect(result2).toStrictEqual({
369
+ fieldsets: [
370
+ { id: 'default', title: 'Default', fields: [] },
371
+ { id: 'styling', title: 'Styling', fields: ['styles'] },
372
+ ],
373
+ properties: {
374
+ styles: {
375
+ widget: 'object',
376
+ title: 'Styling',
377
+ schema: {
378
+ fieldsets: [
379
+ {
380
+ fields: [],
381
+ id: 'default',
382
+ title: 'Default',
383
+ },
384
+ ],
385
+ properties: {},
386
+ required: [],
387
+ },
388
+ },
389
+ },
390
+ required: [],
391
+ });
392
+ });
393
+ });
@@ -1,5 +1,8 @@
1
1
  import { map, uniq, keys, intersection, isEmpty } from 'lodash';
2
2
  import { messages } from '../MessageLabels/MessageLabels';
3
+ import config from '@plone/volto/registry';
4
+ import { toast } from 'react-toastify';
5
+ import Toast from '@plone/volto/components/manage/Toast/Toast';
3
6
 
4
7
  /**
5
8
  * Will return the intl message if invalid
@@ -44,7 +47,8 @@ const widgetValidation = {
44
47
  isValidEmail: (emailValue, emailObj, intlFunc) => {
45
48
  // Email Regex taken from from WHATWG living standard:
46
49
  // https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type=email)
47
- const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
50
+ const emailRegex =
51
+ /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
48
52
  const isValid = emailRegex.test(emailValue);
49
53
  return !isValid ? intlFunc(messages.isValidEmail) : null;
50
54
  },
@@ -67,11 +71,11 @@ const widgetValidation = {
67
71
  isValidURL: (urlValue, urlObj, intlFunc) => {
68
72
  var urlRegex = new RegExp(
69
73
  '^(https?:\\/\\/)?' + // validate protocol
70
- '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // validate domain name
71
- '((\\d{1,3}\\.){3}\\d{1,3}))|' + // validate OR ip (v4) address
72
- '(localhost)' + // validate OR localhost address
73
- '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // validate port and path
74
- '(\\?[;&a-z\\d%_.~+=-]*)?' + // validate query string
74
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // validate domain name
75
+ '((\\d{1,3}\\.){3}\\d{1,3}))|' + // validate OR ip (v4) address
76
+ '(localhost)' + // validate OR localhost address
77
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // validate port and path
78
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // validate query string
75
79
  '(\\#[-a-z\\d_]*)?$', // validate fragment locator
76
80
  'i',
77
81
  );
@@ -203,7 +207,7 @@ const validateRequiredFields = (
203
207
  const type = schema.properties[requiredField]?.type;
204
208
  const widget = schema.properties[requiredField]?.widget;
205
209
 
206
- let isEmpty = !formData[requiredField];
210
+ let isEmpty = !formData[requiredField] && formData[requiredField] !== 0;
207
211
  if (!isEmpty) {
208
212
  if (type === 'array') {
209
213
  isEmpty = formData[requiredField]
@@ -369,3 +373,29 @@ class FormValidation {
369
373
  }
370
374
 
371
375
  export default FormValidation;
376
+
377
+ /**
378
+ * Check if a file upload is within the maximum size limit.
379
+ * @param {File} file
380
+ * @param {Function} intlFunc
381
+ * @returns {Boolean}
382
+ */
383
+ export const validateFileUploadSize = (file, intlFunc) => {
384
+ const isValid =
385
+ !config.settings.maxFileUploadSize ||
386
+ file.size <= config.settings.maxFileUploadSize;
387
+ if (!isValid) {
388
+ toast.error(
389
+ <Toast
390
+ error
391
+ title={intlFunc(messages.error)}
392
+ content={intlFunc(messages.fileTooLarge, {
393
+ limit: `${Math.floor(
394
+ config.settings.maxFileUploadSize / 1024 / 1024,
395
+ )}MB`,
396
+ })}
397
+ />,
398
+ );
399
+ }
400
+ return isValid;
401
+ };
@@ -66,6 +66,38 @@ describe('FormValidation', () => {
66
66
  });
67
67
  });
68
68
 
69
+ it('do not treat 0 as missing required value', () => {
70
+ let newSchema = {
71
+ ...schema,
72
+ properties: {
73
+ ...schema.properties,
74
+ age: {
75
+ title: 'age',
76
+ type: 'integer',
77
+ widget: 'number',
78
+ description: '',
79
+ },
80
+ },
81
+ required: ['age'],
82
+ };
83
+ expect(
84
+ FormValidation.validateFieldsPerFieldset({
85
+ schema: newSchema,
86
+ formData: { username: 'test username', age: null },
87
+ formatMessage,
88
+ }),
89
+ ).toEqual({
90
+ age: [messages.required.defaultMessage],
91
+ });
92
+ expect(
93
+ FormValidation.validateFieldsPerFieldset({
94
+ schema: newSchema,
95
+ formData: { username: 'test username', age: 0 },
96
+ formatMessage,
97
+ }),
98
+ ).toEqual({});
99
+ });
100
+
69
101
  it('validates incorrect email', () => {
70
102
  expect(
71
103
  FormValidation.validateFieldsPerFieldset({
@@ -87,14 +87,8 @@ class Html extends Component {
87
87
  * @returns {string} Markup for the component.
88
88
  */
89
89
  render() {
90
- const {
91
- extractor,
92
- markup,
93
- store,
94
- criticalCss,
95
- apiPath,
96
- publicURL,
97
- } = this.props;
90
+ const { extractor, markup, store, criticalCss, apiPath, publicURL } =
91
+ this.props;
98
92
  const head = Helmet.rewind();
99
93
  const bodyClass = join(BodyClass.rewind(), ' ');
100
94
  const htmlAttributes = head.htmlAttributes.toComponent();