@strapi/admin 4.5.0-beta.0 → 4.5.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 (264) hide show
  1. package/admin/src/StrapiApp.js +17 -6
  2. package/admin/src/assets/images/hot-air-balloon.png +0 -0
  3. package/admin/src/assets/images/icon_offline-cloud.svg +3 -3
  4. package/admin/src/assets/images/logo-strapi-2022.svg +7 -0
  5. package/admin/src/assets/images/upgrade-details.png +0 -0
  6. package/admin/src/content-manager/components/DynamicTable/CellContent/CellValue.js +1 -1
  7. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +5 -4
  8. package/admin/src/content-manager/components/DynamicTable/CellContent/index.js +10 -0
  9. package/admin/src/content-manager/components/DynamicTable/index.js +21 -4
  10. package/admin/src/content-manager/components/DynamicZone/components/{AddComponentButton/index.js → AddComponentButton.js} +12 -6
  11. package/admin/src/content-manager/components/DynamicZone/components/{ComponentPicker/Category/ComponentCard/index.js → ComponentCard.js} +8 -19
  12. package/admin/src/content-manager/components/DynamicZone/components/{ComponentPicker/Category/index.js → ComponentCategory.js} +19 -18
  13. package/admin/src/content-manager/components/DynamicZone/components/{ComponentPicker/index.js → ComponentPicker.js} +36 -38
  14. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +195 -0
  15. package/admin/src/content-manager/components/DynamicZone/components/{DzLabel/index.js → DynamicZoneLabel.js} +13 -5
  16. package/admin/src/content-manager/components/DynamicZone/index.js +35 -116
  17. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +103 -60
  18. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +169 -162
  19. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +70 -16
  20. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/findLeafByPathAndReplace.js +52 -0
  21. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/index.js +2 -0
  22. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/recursivelyFindPathsBasedOnCondition.js +72 -0
  23. package/admin/src/content-manager/components/FieldComponent/index.js +9 -2
  24. package/admin/src/content-manager/components/PreviewWysiwyg/index.js +1 -1
  25. package/admin/src/content-manager/components/RelationInput/RelationInput.js +80 -76
  26. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +95 -63
  27. package/admin/src/content-manager/components/RelationInputDataManager/utils/diffRelations.js +24 -0
  28. package/admin/src/content-manager/components/RelationInputDataManager/utils/index.js +2 -1
  29. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeRelations.js +8 -29
  30. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeSearchResults.js +8 -4
  31. package/admin/src/content-manager/components/RelationInputDataManager/utils/select.js +1 -0
  32. package/admin/src/content-manager/components/RepeatableComponent/index.js +4 -3
  33. package/admin/src/content-manager/hooks/__test__/usePrev.test.js +26 -0
  34. package/admin/src/content-manager/hooks/index.js +1 -0
  35. package/admin/src/content-manager/hooks/useFetchContentTypeLayout/utils/formatLayouts.js +19 -48
  36. package/admin/src/content-manager/hooks/usePrev.js +14 -0
  37. package/admin/src/content-manager/hooks/useRelation/useRelation.js +100 -7
  38. package/admin/src/content-manager/pages/App/reducer.js +3 -0
  39. package/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js +3 -3
  40. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +2 -2
  41. package/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js +1 -1
  42. package/admin/src/core/apis/CustomFields.js +0 -1
  43. package/admin/src/core/store/configureStore.js +17 -2
  44. package/admin/src/favicon.png +0 -0
  45. package/admin/src/hooks/useFetchMarketplacePlugins/index.js +2 -2
  46. package/admin/src/hooks/useFetchMarketplacePlugins/utils/api.js +4 -2
  47. package/admin/src/hooks/useFetchMarketplaceProviders/index.js +3 -3
  48. package/admin/src/hooks/useFetchMarketplaceProviders/utils/api.js +5 -3
  49. package/admin/src/index.js +1 -0
  50. package/admin/src/pages/App/index.js +1 -1
  51. package/admin/src/pages/HomePage/assets/corner-ornament.svg +48 -0
  52. package/admin/src/pages/HomePage/index.js +4 -3
  53. package/admin/src/pages/MarketplacePage/components/NpmPackageCard/CardButton.js +110 -0
  54. package/admin/src/pages/MarketplacePage/components/NpmPackageCard/InstallPluginButton.js +32 -21
  55. package/admin/src/pages/MarketplacePage/components/NpmPackageCard/PackageStats.js +79 -0
  56. package/admin/src/pages/MarketplacePage/components/NpmPackageCard/index.js +28 -11
  57. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/FilterSelect.js +42 -0
  58. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/FiltersPopover.js +96 -0
  59. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/index.js +107 -0
  60. package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +4 -0
  61. package/admin/src/pages/MarketplacePage/components/SortSelect/index.js +70 -0
  62. package/admin/src/pages/MarketplacePage/index.js +68 -8
  63. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +5 -4
  64. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +4 -3
  65. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +6 -2
  66. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +1 -1
  67. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +5 -4
  68. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +1 -1
  69. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ConditionsModal/ActionRow/index.js +7 -38
  70. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ConditionsModal/ActionRow/utils/options.js +31 -0
  71. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ConditionsModal/index.js +32 -43
  72. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ContentTypeCollapse/Collapse/index.js +1 -1
  73. package/admin/src/pages/SettingsPage/pages/Roles/ListPage/components/RoleRow/index.js +3 -1
  74. package/admin/src/pages/SettingsPage/pages/Roles/ListPage/index.js +2 -1
  75. package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/components/EventInput/index.js +2 -2
  76. package/admin/src/translations/ca.json +1 -1
  77. package/admin/src/translations/de.json +1 -1
  78. package/admin/src/translations/dk.json +1 -1
  79. package/admin/src/translations/en.json +21 -7
  80. package/admin/src/translations/es.json +1 -1
  81. package/admin/src/translations/fr.json +1 -1
  82. package/admin/src/translations/gu.json +1 -1
  83. package/admin/src/translations/he.json +1 -1
  84. package/admin/src/translations/hi.json +1 -1
  85. package/admin/src/translations/hu.json +1 -1
  86. package/admin/src/translations/id.json +1 -1
  87. package/admin/src/translations/it.json +1 -1
  88. package/admin/src/translations/ja.json +1 -1
  89. package/admin/src/translations/ko.json +1 -1
  90. package/admin/src/translations/ml.json +1 -1
  91. package/admin/src/translations/nl.json +1 -1
  92. package/admin/src/translations/no.json +1 -1
  93. package/admin/src/translations/pl.json +1 -1
  94. package/admin/src/translations/pt-BR.json +15 -15
  95. package/admin/src/translations/ru.json +1 -1
  96. package/admin/src/translations/sa.json +1 -1
  97. package/admin/src/translations/sk.json +1 -1
  98. package/admin/src/translations/sv.json +118 -86
  99. package/admin/src/translations/th.json +1 -1
  100. package/admin/src/translations/zh-Hans.json +1 -1
  101. package/admin/src/translations/zh.json +1 -1
  102. package/build/1856.db9f5782.chunk.js +174 -0
  103. package/build/2077.fed8c9c3.chunk.js +206 -0
  104. package/build/2912.fccb2c43.chunk.js +259 -0
  105. package/build/4318.5e670740.chunk.js +30 -0
  106. package/build/{9166.8fcb3019.chunk.js → 4610.7614b003.chunk.js} +22 -21
  107. package/build/4715.8e33d630.chunk.js +387 -0
  108. package/build/{4800.d09f1225.chunk.js → 4800.a6935af6.chunk.js} +1 -1
  109. package/build/4982.9e58ea3f.chunk.js +325 -0
  110. package/build/617f9c948fa79e6d73bd.png +0 -0
  111. package/build/6925.bb6dd64d.chunk.js +762 -0
  112. package/build/6d21938306785f176538.png +0 -0
  113. package/build/70674f63fc3904c20de0.svg +7 -0
  114. package/build/{7379.d246dd38.chunk.js → 7379.e972985f.chunk.js} +1 -1
  115. package/build/7692.31e83caa.chunk.js +470 -0
  116. package/build/7841.4804bd98.chunk.js +259 -0
  117. package/build/7866.6db2248d.chunk.js +505 -0
  118. package/build/7e9af4fb7e723fcebf1f.svg +48 -0
  119. package/build/8380.37126e0d.chunk.js +299 -0
  120. package/build/8549.5e5fb6b6.chunk.js +159 -0
  121. package/build/8738.5a02bffb.chunk.js +463 -0
  122. package/build/{9066.26faf397.chunk.js → 9066.5d980488.chunk.js} +5 -5
  123. package/build/9420.7addc099.chunk.js +505 -0
  124. package/build/9649.b6afc945.chunk.js +199 -0
  125. package/build/9d5d788027e86620c234.svg +5 -0
  126. package/build/Admin-authenticatedApp.c07d2a86.chunk.js +80 -0
  127. package/build/{Admin_homePage.4b2be829.chunk.js → Admin_homePage.26d32e30.chunk.js} +5 -4
  128. package/build/Admin_marketplace.444ff7b8.chunk.js +22 -0
  129. package/build/Admin_settingsPage.bf2234e1.chunk.js +178 -0
  130. package/build/admin-app.b157c10a.chunk.js +112 -0
  131. package/build/{admin-edit-roles-page.4dd6bcb9.chunk.js → admin-edit-roles-page.69d9fcb2.chunk.js} +1 -1
  132. package/build/ca-json.07ae0f2c.chunk.js +1 -0
  133. package/build/content-manager.f38edbb6.chunk.js +1202 -0
  134. package/build/content-type-builder-translation-pt-BR-json.6fe3b8d1.chunk.js +1 -0
  135. package/build/content-type-builder-translation-sv-json.6deff030.chunk.js +1 -0
  136. package/build/{content-type-builder.a6e29716.chunk.js → content-type-builder.16af63a6.chunk.js} +13 -13
  137. package/build/de-json.6b3e1894.chunk.js +1 -0
  138. package/build/dk-json.144c6a8e.chunk.js +1 -0
  139. package/build/{email-settings-page.bfe6227f.chunk.js → email-settings-page.91c925a5.chunk.js} +6 -6
  140. package/build/email-translation-en-json.ebad8943.chunk.js +1 -0
  141. package/build/en-json.4a269f6b.chunk.js +1 -0
  142. package/build/es-json.6d123a82.chunk.js +1 -0
  143. package/build/fr-json.28ab54cb.chunk.js +1 -0
  144. package/build/gu-json.9a50ea64.chunk.js +1 -0
  145. package/build/he-json.72f18790.chunk.js +1 -0
  146. package/build/hi-json.0301b7ba.chunk.js +1 -0
  147. package/build/hu-json.c4b641bb.chunk.js +1 -0
  148. package/build/{i18n-settings-page.18166125.chunk.js → i18n-settings-page.4ef64441.chunk.js} +5 -5
  149. package/build/id-json.86035797.chunk.js +1 -0
  150. package/build/index.html +1 -1
  151. package/build/it-json.bbdc8993.chunk.js +1 -0
  152. package/build/ja-json.1c9eeeec.chunk.js +1 -0
  153. package/build/ko-json.e1f66398.chunk.js +1 -0
  154. package/build/main.ca8b0ee3.js +9465 -0
  155. package/build/ml-json.963c889f.chunk.js +1 -0
  156. package/build/nl-json.2b8cc3a0.chunk.js +1 -0
  157. package/build/no-json.a58c28bd.chunk.js +1 -0
  158. package/build/pl-json.249626b3.chunk.js +1 -0
  159. package/build/pt-BR-json.2b72b1d6.chunk.js +1 -0
  160. package/build/ru-json.d7cfc2ff.chunk.js +1 -0
  161. package/build/runtime~main.ede9da1e.js +2 -0
  162. package/build/sa-json.44e95991.chunk.js +1 -0
  163. package/build/sk-json.7ba4b330.chunk.js +1 -0
  164. package/build/sv-json.fb1081ff.chunk.js +1 -0
  165. package/build/th-json.a67309b1.chunk.js +1 -0
  166. package/build/{upload-settings.3d613216.chunk.js → upload-settings.3f7ad973.chunk.js} +5 -5
  167. package/build/{users-advanced-settings-page.f4051d92.chunk.js → users-advanced-settings-page.6a838320.chunk.js} +5 -5
  168. package/build/users-permissions-translation-sv-json.d5d11648.chunk.js +1 -0
  169. package/build/{webhook-edit-page.9e46fc3f.chunk.js → webhook-edit-page.dc9442ce.chunk.js} +1 -1
  170. package/build/webhook-list-page.a110c462.chunk.js +134 -0
  171. package/build/zh-Hans-json.21617c24.chunk.js +1 -0
  172. package/build/zh-json.608aaf24.chunk.js +1 -0
  173. package/ee/admin/pages/SettingsPage/pages/Roles/ListPage/index.js +3 -2
  174. package/env.js +1 -0
  175. package/package.json +18 -17
  176. package/scripts/build.js +11 -0
  177. package/server/content-types/api-token.js +1 -1
  178. package/utils/create-plugins-exclude-path.js +40 -0
  179. package/webpack.alias.js +0 -13
  180. package/webpack.config.js +4 -1
  181. package/admin/src/assets/images/banner_strapi-rocket.png +0 -0
  182. package/admin/src/assets/images/big-logo-home.png +0 -0
  183. package/admin/src/assets/images/homepage-logo.png +0 -0
  184. package/admin/src/assets/images/icon_made-by-strapi.svg +0 -5
  185. package/admin/src/assets/images/logo_strapi_auth.png +0 -0
  186. package/admin/src/assets/images/logo_strapi_auth_v4.png +0 -0
  187. package/admin/src/assets/images/logo_strapi_menu.png +0 -0
  188. package/admin/src/assets/images/oops.png +0 -0
  189. package/admin/src/content-manager/components/DynamicZone/components/Component/Rectangle.js +0 -19
  190. package/admin/src/content-manager/components/DynamicZone/components/Component/index.js +0 -191
  191. package/admin/src/content-manager/components/State/index.js +0 -37
  192. package/admin/src/content-manager/icons/Bold/index.js +0 -22
  193. package/admin/src/content-manager/icons/Code/index.js +0 -13
  194. package/admin/src/content-manager/icons/Cross/index.js +0 -28
  195. package/admin/src/content-manager/icons/Italic/index.js +0 -23
  196. package/admin/src/content-manager/icons/Link/index.js +0 -21
  197. package/admin/src/content-manager/icons/Media/index.js +0 -14
  198. package/admin/src/content-manager/icons/Na/index.js +0 -39
  199. package/admin/src/content-manager/icons/Ol/index.js +0 -13
  200. package/admin/src/content-manager/icons/Quote/index.js +0 -13
  201. package/admin/src/content-manager/icons/Striked/index.js +0 -24
  202. package/admin/src/content-manager/icons/Ul/index.js +0 -15
  203. package/admin/src/content-manager/icons/Underline/index.js +0 -22
  204. package/admin/src/favicon.ico +0 -0
  205. package/build/15026a3d58aeb2828134.png +0 -0
  206. package/build/1856.d8f13391.chunk.js +0 -173
  207. package/build/1939.e3c87653.chunk.js +0 -325
  208. package/build/2077.31a2d91e.chunk.js +0 -205
  209. package/build/2912.ab68a736.chunk.js +0 -258
  210. package/build/4318.7d167b58.chunk.js +0 -30
  211. package/build/4715.44b1ef9b.chunk.js +0 -386
  212. package/build/4982.c2a311b7.chunk.js +0 -324
  213. package/build/6925.f5c8b6fc.chunk.js +0 -761
  214. package/build/7841.4b67af3f.chunk.js +0 -258
  215. package/build/7866.5fbeb7e5.chunk.js +0 -504
  216. package/build/8380.9b53a31d.chunk.js +0 -284
  217. package/build/8549.cf10b5d1.chunk.js +0 -158
  218. package/build/8738.a30a2160.chunk.js +0 -461
  219. package/build/90f49a385afb000fb1d4.svg +0 -5
  220. package/build/9420.0fe11290.chunk.js +0 -504
  221. package/build/962.8651ba3f.chunk.js +0 -184
  222. package/build/Admin-authenticatedApp.883449a5.chunk.js +0 -80
  223. package/build/Admin_marketplace.82c0570b.chunk.js +0 -11
  224. package/build/Admin_settingsPage.98e2a62b.chunk.js +0 -178
  225. package/build/a6b842e0b6d2b61135d1.svg +0 -5
  226. package/build/admin-app.a61d5c2e.chunk.js +0 -112
  227. package/build/b997a22a2e0b87ef1fa2.ico +0 -0
  228. package/build/bd81ba6c07827282255d.png +0 -0
  229. package/build/c3de6118ef47086ad05c.png +0 -0
  230. package/build/ca-json.82df6eab.chunk.js +0 -1
  231. package/build/content-manager.933dc286.chunk.js +0 -1201
  232. package/build/content-type-builder-translation-pt-BR-json.d6c7fcc1.chunk.js +0 -1
  233. package/build/de-json.0ad554eb.chunk.js +0 -1
  234. package/build/dk-json.e195ea1a.chunk.js +0 -1
  235. package/build/email-translation-en-json.3d74ff95.chunk.js +0 -1
  236. package/build/en-json.1889403c.chunk.js +0 -1
  237. package/build/es-json.09f80f6e.chunk.js +0 -1
  238. package/build/fb376b132d18bf4522ca.png +0 -0
  239. package/build/fde9b1ad0670d29a2516.png +0 -0
  240. package/build/fr-json.606d056b.chunk.js +0 -1
  241. package/build/gu-json.9881264f.chunk.js +0 -1
  242. package/build/he-json.3b825d80.chunk.js +0 -1
  243. package/build/hi-json.83dcf48f.chunk.js +0 -1
  244. package/build/hu-json.6f328bce.chunk.js +0 -1
  245. package/build/id-json.1f3c4303.chunk.js +0 -1
  246. package/build/it-json.494ac432.chunk.js +0 -1
  247. package/build/ja-json.6f262117.chunk.js +0 -1
  248. package/build/ko-json.36dc3b9a.chunk.js +0 -1
  249. package/build/main.63e7ea0a.js +0 -9338
  250. package/build/ml-json.9566bf9a.chunk.js +0 -1
  251. package/build/nl-json.94c3a289.chunk.js +0 -1
  252. package/build/no-json.40386397.chunk.js +0 -1
  253. package/build/pl-json.ccc6ef23.chunk.js +0 -1
  254. package/build/pt-BR-json.744f024d.chunk.js +0 -1
  255. package/build/ru-json.d22ea13c.chunk.js +0 -1
  256. package/build/runtime~main.3a5e1b07.js +0 -2
  257. package/build/sa-json.8fb1c04d.chunk.js +0 -1
  258. package/build/sk-json.6c7335d4.chunk.js +0 -1
  259. package/build/sv-json.2e589a7d.chunk.js +0 -1
  260. package/build/th-json.72e8de3d.chunk.js +0 -1
  261. package/build/users-permissions-translation-sv-json.83c60841.chunk.js +0 -1
  262. package/build/webhook-list-page.a712ae40.chunk.js +0 -134
  263. package/build/zh-Hans-json.a4d7dc69.chunk.js +0 -1
  264. package/build/zh-json.66aa2ae1.chunk.js +0 -1
@@ -3,8 +3,15 @@ import unset from 'lodash/unset';
3
3
  import get from 'lodash/get';
4
4
  import set from 'lodash/set';
5
5
  import take from 'lodash/take';
6
- import pull from 'lodash/pull';
7
- import { moveFields } from './utils';
6
+ import cloneDeep from 'lodash/cloneDeep';
7
+ import uniqBy from 'lodash/uniqBy';
8
+ import merge from 'lodash/merge';
9
+
10
+ import {
11
+ findLeafByPathAndReplace,
12
+ moveFields,
13
+ recursivelyFindPathsBasedOnCondition,
14
+ } from './utils';
8
15
  import { getMaxTempKey } from '../../utils';
9
16
 
10
17
  const initialState = {
@@ -26,63 +33,111 @@ const reducer = (state, action) =>
26
33
  produce(state, (draftState) => {
27
34
  switch (action.type) {
28
35
  case 'ADD_NON_REPEATABLE_COMPONENT_TO_FIELD': {
29
- set(
30
- draftState,
31
- ['modifiedData', ...action.keys],
32
- state.componentsDataStructure[action.componentUid]
33
- );
36
+ const { componentLayoutData, allComponents } = action;
34
37
 
35
- break;
36
- }
37
- case 'ADD_REPEATABLE_COMPONENT_TO_FIELD': {
38
- let currentValue = get(state, ['modifiedData', ...action.keys], []).slice();
38
+ const relationPaths = recursivelyFindPathsBasedOnCondition(
39
+ allComponents,
40
+ (value) => value.type === 'relation'
41
+ )(componentLayoutData.attributes);
39
42
 
40
43
  const defaultDataStructure = {
41
- ...state.componentsDataStructure[action.componentUid],
42
- __temp_key__: getMaxTempKey(currentValue) + 1,
44
+ ...state.componentsDataStructure[componentLayoutData.uid],
43
45
  };
44
46
 
45
- if (Array.isArray(currentValue)) {
46
- currentValue.push(defaultDataStructure);
47
- } else {
48
- currentValue = [defaultDataStructure];
49
- }
47
+ const repeatableFields = recursivelyFindPathsBasedOnCondition(
48
+ allComponents,
49
+ (value) => value.type === 'component' && value.repeatable
50
+ )(componentLayoutData.attributes);
50
51
 
51
- set(draftState, ['modifiedData', ...action.keys], currentValue);
52
+ const componentDataStructure = relationPaths.reduce((acc, current) => {
53
+ const [componentName] = current.split('.');
52
54
 
53
- if (action.shouldCheckErrors) {
54
- draftState.shouldCheckErrors = !state.shouldCheckErrors;
55
- }
55
+ /**
56
+ * Why do we do this? Because if a repeatable component
57
+ * has another repeatable component inside of it we
58
+ * don't need to attach the array at this point because that will be
59
+ * done again deeper in the nest.
60
+ */
61
+ if (!repeatableFields.includes(componentName)) {
62
+ set(acc, current, []);
63
+ }
64
+
65
+ return acc;
66
+ }, defaultDataStructure);
67
+
68
+ set(draftState, ['modifiedData', ...action.keys], componentDataStructure);
56
69
 
57
70
  break;
58
71
  }
59
- case 'ADD_COMPONENT_TO_DYNAMIC_ZONE': {
60
- draftState.modifiedDZName = action.keys[0];
72
+ case 'ADD_COMPONENT_TO_DYNAMIC_ZONE':
73
+ case 'ADD_REPEATABLE_COMPONENT_TO_FIELD': {
74
+ const { keys, allComponents, componentLayoutData, shouldCheckErrors } = action;
61
75
 
62
- if (action.shouldCheckErrors) {
76
+ if (shouldCheckErrors) {
63
77
  draftState.shouldCheckErrors = !state.shouldCheckErrors;
64
78
  }
65
79
 
66
- const defaultDataStructure = {
67
- ...state.componentsDataStructure[action.componentUid],
68
- __component: action.componentUid,
69
- };
80
+ if (action.type === 'ADD_COMPONENT_TO_DYNAMIC_ZONE') {
81
+ draftState.modifiedDZName = keys[0];
82
+ }
70
83
 
71
- const currentValue = get(state, ['modifiedData', ...action.keys], null);
72
- const updatedValue = currentValue
73
- ? [...currentValue, defaultDataStructure]
74
- : [defaultDataStructure];
84
+ const currentValue = get(state, ['modifiedData', ...keys], []);
75
85
 
76
- set(draftState, ['modifiedData', ...action.keys], updatedValue);
86
+ const defaultDataStructure =
87
+ action.type === 'ADD_COMPONENT_TO_DYNAMIC_ZONE'
88
+ ? {
89
+ ...state.componentsDataStructure[componentLayoutData.uid],
90
+ __component: componentLayoutData.uid,
91
+ }
92
+ : {
93
+ ...state.componentsDataStructure[componentLayoutData.uid],
94
+ __temp_key__: getMaxTempKey(currentValue) + 1,
95
+ };
96
+
97
+ const relationPaths = recursivelyFindPathsBasedOnCondition(
98
+ allComponents,
99
+ (value) => value.type === 'relation'
100
+ )(componentLayoutData.attributes);
101
+
102
+ const repeatableFields = recursivelyFindPathsBasedOnCondition(
103
+ allComponents,
104
+ (value) => value.type === 'component' && value.repeatable
105
+ )(componentLayoutData.attributes);
106
+
107
+ const componentDataStructure = relationPaths.reduce((acc, current) => {
108
+ const [componentName] = current.split('.');
109
+
110
+ /**
111
+ * Why do we do this? Because if a repeatable component
112
+ * has another repeatable component inside of it we
113
+ * don't need to attach the array at this point because that will be
114
+ * done again deeper in the nest.
115
+ */
116
+ if (!repeatableFields.includes(componentName)) {
117
+ set(acc, current, []);
118
+ }
119
+
120
+ return acc;
121
+ }, defaultDataStructure);
122
+
123
+ const newValue = Array.isArray(currentValue)
124
+ ? [...currentValue, componentDataStructure]
125
+ : [componentDataStructure];
126
+
127
+ set(draftState, ['modifiedData', ...keys], newValue);
77
128
 
78
129
  break;
79
130
  }
131
+
80
132
  case 'LOAD_RELATION': {
81
- const initialDataPath = ['initialData', ...action.keys, 'results'];
82
- const modifiedDataPath = ['modifiedData', ...action.keys, 'results'];
133
+ const initialDataPath = ['initialData', ...action.keys];
134
+ const modifiedDataPath = ['modifiedData', ...action.keys];
83
135
  const { value } = action;
84
136
 
85
- set(draftState, initialDataPath, value);
137
+ const initialDataRelations = get(state, initialDataPath);
138
+ const modifiedDataRelations = get(state, modifiedDataPath);
139
+
140
+ set(draftState, initialDataPath, uniqBy([...value, ...initialDataRelations], 'id'));
86
141
 
87
142
  /**
88
143
  * We need to set the value also on modifiedData, because initialData
@@ -90,156 +145,108 @@ const reducer = (state, action) =>
90
145
  * both states, to render the dirty UI state
91
146
  */
92
147
 
93
- set(draftState, modifiedDataPath, value);
148
+ set(draftState, modifiedDataPath, uniqBy([...value, ...modifiedDataRelations], 'id'));
94
149
 
95
150
  break;
96
151
  }
97
152
  case 'CONNECT_RELATION': {
98
153
  const path = ['modifiedData', ...action.keys];
99
- const { value, replace = false } = action;
100
- const connectedRelations = get(state, [...path, 'connect']);
101
- const disconnectedRelations = get(state, [...path, 'disconnect']) ?? [];
102
- const savedRelations = get(state, [...path, 'results']) ?? [];
103
- const existInSavedRelation =
104
- savedRelations?.findIndex((savedRelations) => savedRelations.id === value.id) !== -1;
105
-
106
- if (!connectedRelations) {
107
- set(draftState, [...path, 'connect'], []);
108
- }
154
+ const { value, toOneRelation } = action;
109
155
 
110
- // We should add a relation in the connect array only if it is not an already saved relation
111
- if (!existInSavedRelation) {
112
- if (replace) {
113
- set(draftState, [...path, 'connect'], [value]);
114
- } else {
115
- const nextValue = get(draftState, [...path, 'connect']);
116
- nextValue.push(value);
117
- }
118
- }
119
-
120
- // Disconnect array handling
121
- if (replace) {
122
- // In xToOne relations we should place the saved relation in disconnected array to not display it
123
- // only needed if there is a saved relation and it is not already stored in disconnected array
124
- if (savedRelations.length && !disconnectedRelations.length) {
125
- set(draftState, [...path, 'disconnect'], savedRelations);
126
- }
127
-
128
- // If the saved relation is stored in disconnected array
129
- // We should remove it when an action requires to reconnect this relation
130
- // We then reset the connect/disconnect state
131
- if (disconnectedRelations.length) {
132
- const existsInDisconnectedRelations =
133
- disconnectedRelations.findIndex(
134
- (disconnectedRelation) => disconnectedRelation?.id === value.id
135
- ) > -1;
136
-
137
- if (existsInDisconnectedRelations) {
138
- set(draftState, [...path, 'disconnect'], []);
139
- set(draftState, [...path, 'connect'], []);
140
- }
141
- }
142
- } else if (disconnectedRelations.length) {
143
- // In xToMany relations, when an action requires to connect a relation
144
- // We should remove it from the disconnected array if it existed in it
145
- const existsInDisconnect = disconnectedRelations.find(
146
- (disconnectValue) => disconnectValue.id === value.id
147
- );
148
-
149
- if (existsInDisconnect) {
150
- const newDisconnectArray = pull([...disconnectedRelations], existsInDisconnect);
151
- set(draftState, [...path, 'disconnect'], newDisconnectArray);
152
- }
156
+ /**
157
+ * If the field is a single relation field we don't want to append
158
+ * we just want to replace the value.
159
+ */
160
+ if (toOneRelation) {
161
+ set(draftState, path, [value]);
162
+ } else {
163
+ const modifiedDataRelations = get(state, path);
164
+ const newRelations = [...modifiedDataRelations, value];
165
+ set(draftState, path, newRelations);
153
166
  }
154
167
 
155
168
  break;
156
169
  }
157
170
  case 'DISCONNECT_RELATION': {
158
171
  const path = ['modifiedData', ...action.keys];
159
- const { value } = action;
160
- const connectedRelations = get(state, [...path, 'connect']);
161
- const disconnectedRelations = get(state, [...path, 'disconnect']);
162
-
163
- if (!disconnectedRelations) {
164
- set(draftState, [...path, 'disconnect'], []);
165
- }
172
+ const { id } = action;
173
+ const modifiedDataRelation = get(state, [...path]);
166
174
 
167
- const nextValue = get(draftState, [...path, 'disconnect']);
168
- nextValue.push(value);
169
-
170
- if (connectedRelations?.length) {
171
- const existsInConnect = connectedRelations.find(
172
- (connectValue) => connectValue.id === value.id
173
- );
175
+ /**
176
+ * TODO: before merge make this performant (e.g. 1000 relations === long time)
177
+ */
178
+ const newRelations = modifiedDataRelation.filter((rel) => rel.id !== id);
174
179
 
175
- if (existsInConnect) {
176
- const newConnectArray = pull([...connectedRelations], existsInConnect);
177
- set(draftState, [...path, 'connect'], newConnectArray);
178
- }
179
- }
180
+ set(draftState, path, newRelations);
180
181
 
181
182
  break;
182
183
  }
184
+ /**
185
+ * This action will be called when you open your entry (first load)
186
+ * but also every time you press publish.
187
+ */
183
188
  case 'INIT_FORM': {
184
- const { initialValues, relationalFields = [] } = action;
189
+ const {
190
+ initialValues,
191
+ relationalFieldPaths = [],
192
+ componentPaths = [],
193
+ repeatableComponentPaths = [],
194
+ dynamicZonePaths = [],
195
+ } = action;
185
196
 
186
- draftState.formErrors = {};
187
-
188
- draftState.initialData = {
189
- ...initialValues,
197
+ /**
198
+ * You can't mutate an actions value.
199
+ * and spreading an object only clones
200
+ * the first level, the deeply nested values
201
+ * are a reference.
202
+ */
203
+ const data = cloneDeep(initialValues);
190
204
 
191
- /**
192
- * The state we keep in the client for relations looks like:
193
- *
194
- * {
195
- * count: <int>
196
- * results: [<Relation>]
197
- * }
198
- *
199
- * The content API only returns { count: <int> }, which is why
200
- * we need to extend the existing state rather than overwriting it.
201
- */
205
+ /**
206
+ * relationalFieldPaths won't be an array which is what we're expecting
207
+ * Therefore we reset these bits of state to the correct data type
208
+ * which is an array. Hence why we replace those fields.
209
+ *
210
+ */
202
211
 
203
- ...relationalFields.reduce((acc, name) => {
204
- acc[name] = {
205
- ...(state.initialData?.[name] ?? {}),
206
- ...(initialValues?.[name] ?? {}),
207
- };
212
+ const mergeDataWithPreparedRelations = relationalFieldPaths
213
+ .map((path) => path.split('.'))
214
+ .reduce((acc, currentPaths) => {
215
+ const [componentName] = currentPaths;
216
+
217
+ if (state.modifiedData && get(state.modifiedData, componentName)) {
218
+ /**
219
+ * this will be null on initial load, however subsequent calls
220
+ * will have data in them correlating to the names of the relational fields.
221
+ *
222
+ * We also merge the fetched data so that things like `id` for components can be copied over
223
+ * which would be `undefined` in the `browserState`.
224
+ */
225
+ const currentState = cloneDeep(get(state.modifiedData, componentName));
226
+ set(acc, componentName, merge(currentState, get(initialValues, componentName)));
227
+ } else if (
228
+ repeatableComponentPaths.includes(componentName) ||
229
+ dynamicZonePaths.includes(componentName) ||
230
+ componentPaths.includes(componentName)
231
+ ) {
232
+ /**
233
+ * if the componentName is a repeatable field or dynamic zone we collect the list of paths e.g.
234
+ * ["repeatable_single_component_relation","categories"] and then reduce this
235
+ * recursively
236
+ */
237
+ const findleaf = findLeafByPathAndReplace(currentPaths.slice(-1)[0], []);
238
+ currentPaths.reduce(findleaf, acc);
239
+ } else {
240
+ set(acc, currentPaths, []);
241
+ }
208
242
 
209
243
  return acc;
210
- }, {}),
211
- };
244
+ }, data);
212
245
 
213
- draftState.modifiedData = {
214
- ...initialValues,
246
+ draftState.initialData = mergeDataWithPreparedRelations;
247
+ draftState.modifiedData = mergeDataWithPreparedRelations;
215
248
 
216
- /**
217
- * The client sends the following to the content API:
218
- *
219
- * {
220
- * connect: [<Relation>],
221
- * disconnect: [<Relation>]
222
- * }
223
- *
224
- * but receives only { count: <int> } in return. After save/ publish
225
- * we have to:
226
- *
227
- * 1) reset the connect/ disconnect arrays
228
- * 2) extend the existing state with the API response, so that `count`
229
- * stays in sync
230
- */
231
-
232
- ...relationalFields.reduce((acc, name) => {
233
- const { connect, disconnect, ...currentState } = state.modifiedData?.[name] ?? {};
234
-
235
- acc[name] = {
236
- ...(currentState ?? {}),
237
- ...(initialValues?.[name] ?? {}),
238
- };
239
-
240
- return acc;
241
- }, {}),
242
- };
249
+ draftState.formErrors = {};
243
250
 
244
251
  draftState.modifiedDZName = null;
245
252
  draftState.shouldCheckErrors = false;
@@ -4,14 +4,31 @@ import isObject from 'lodash/isObject';
4
4
 
5
5
  /* eslint-disable indent */
6
6
 
7
- const cleanData = (retrievedData, currentSchema, componentsSchema) => {
7
+ /**
8
+ *
9
+ * @param {{ browserState: object, serverState: object }} browserState – the modifiedData from REDUX, serverState - the initialData from REDUX
10
+ * @param {object} currentSchema
11
+ * @param {object} componentsSchema
12
+ * @returns
13
+ */
14
+ const cleanData = ({ browserState, serverState }, currentSchema, componentsSchema) => {
8
15
  const getType = (schema, attrName) => get(schema, ['attributes', attrName, 'type'], '');
9
16
  const getOtherInfos = (schema, arr) => get(schema, ['attributes', ...arr], '');
10
17
 
11
- const recursiveCleanData = (data, schema) => {
12
- return Object.keys(data).reduce((acc, current) => {
18
+ /**
19
+ *
20
+ * @param {object} browserState – the modifiedData from REDUX
21
+ * @param {object} serverState – the initialData from REDUX
22
+ * @param {*} schema
23
+ * @returns
24
+ */
25
+ const recursiveCleanData = (browserState, serverState, schema) => {
26
+ return Object.keys(browserState).reduce((acc, current) => {
13
27
  const attrType = getType(schema, current);
14
- const value = get(data, current);
28
+
29
+ // This is the field value
30
+ const value = get(browserState, current);
31
+ const oldValue = get(serverState, current);
15
32
  const component = getOtherInfos(schema, [current, 'component']);
16
33
  const isRepeatable = getOtherInfos(schema, [current, 'repeatable']);
17
34
  let cleanedData;
@@ -40,34 +57,71 @@ const cleanData = (retrievedData, currentSchema, componentsSchema) => {
40
57
  case 'component':
41
58
  if (isRepeatable) {
42
59
  cleanedData = value
43
- ? value.map((data) => {
44
- const subCleanedData = recursiveCleanData(data, componentsSchema[component]);
60
+ ? value.map((data, index) => {
61
+ const subCleanedData = recursiveCleanData(
62
+ data,
63
+ (oldValue ?? [])[index],
64
+ componentsSchema[component]
65
+ );
45
66
 
46
67
  return subCleanedData;
47
68
  })
48
69
  : value;
49
70
  } else {
50
- cleanedData = value ? recursiveCleanData(value, componentsSchema[component]) : value;
71
+ cleanedData = value
72
+ ? recursiveCleanData(value, oldValue, componentsSchema[component])
73
+ : value;
51
74
  }
52
75
 
53
76
  break;
54
77
 
55
- case 'relation':
56
- // Instead of the full relation object, we only want to send its ID
57
- // and need to clean-up the connect|disconnect arrays
58
- cleanedData = Object.entries(value).reduce((acc, [key, value]) => {
59
- if (['connect', 'disconnect'].includes(key)) {
60
- acc[key] = value.map((currentValue) => ({ id: currentValue.id }));
78
+ case 'relation': {
79
+ /**
80
+ * Because of how repeatable components work when you dig into them the server
81
+ * will have no object to compare too therefore no relation array will be setup
82
+ * because the component has not been initialised, therefore we can safely assume
83
+ * it needs to be added and provide a default empty array.
84
+ */
85
+ let actualOldValue = oldValue ?? [];
86
+
87
+ /**
88
+ * Instead of the full relation object, we only want to send its ID
89
+ * connectedRelations are the items that are in the browserState
90
+ * array but not in the serverState
91
+ */
92
+ const connectedRelations = value.reduce((acc, relation) => {
93
+ if (!actualOldValue.find((oldRelation) => oldRelation.id === relation.id)) {
94
+ return [...acc, { id: relation.id }];
61
95
  }
62
96
 
63
97
  return acc;
64
- }, {});
98
+ }, []);
99
+
100
+ /**
101
+ * disconnectedRelations are the items that are in the serverState but
102
+ * are no longer in the browserState
103
+ */
104
+ const disconnectedRelations = actualOldValue.reduce((acc, relation) => {
105
+ if (!value.find((newRelation) => newRelation.id === relation.id)) {
106
+ return [...acc, { id: relation.id }];
107
+ }
108
+
109
+ return acc;
110
+ }, []);
111
+
112
+ cleanedData = {
113
+ disconnect: disconnectedRelations,
114
+ connect: connectedRelations,
115
+ };
116
+
65
117
  break;
118
+ }
66
119
 
67
120
  case 'dynamiczone':
68
- cleanedData = value.map((componentData) => {
121
+ cleanedData = value.map((componentData, index) => {
69
122
  const subCleanedData = recursiveCleanData(
70
123
  componentData,
124
+ (oldValue ?? [])[index],
71
125
  componentsSchema[componentData.__component]
72
126
  );
73
127
 
@@ -84,7 +138,7 @@ const cleanData = (retrievedData, currentSchema, componentsSchema) => {
84
138
  }, {});
85
139
  };
86
140
 
87
- return recursiveCleanData(retrievedData, currentSchema);
141
+ return recursiveCleanData(browserState, serverState, currentSchema);
88
142
  };
89
143
 
90
144
  // TODO: check which parts are still needed: I suspect the
@@ -0,0 +1,52 @@
1
+ import set from 'lodash/set';
2
+
3
+ /**
4
+ * @param {string} endpath – the final path that you're looking to replace
5
+ * @returns {function} findLeafReducer – a function that will be used in the reduce
6
+ */
7
+ export const findLeafByPathAndReplace = (endpath, replaceWith) => {
8
+ /**
9
+ * @param {object} acc – the data tree
10
+ * @param {*} curr – string, this _could_ be used to index the accumulator
11
+ * @param {*} ind - your current index of the array you're reducing
12
+ * @returns {object} – the new object with the replaced values
13
+ */
14
+ const findLeafAndReplace = (acc, curr, ind, currentArr) => {
15
+ /**
16
+ * Because we're returning the `accumulator[current]` at the bottom
17
+ * and some components may not exist at this point, we check if `accumulator`
18
+ * exists before trying to access & replace properties.
19
+ */
20
+ if (!acc) return acc;
21
+
22
+ /**
23
+ * If this is the last item in the array of paths
24
+ * and the current path is not undefined in the accumulator
25
+ * then we assume it's a leaf and we can replace it.
26
+ */
27
+ if (endpath === curr && acc[curr] !== undefined) {
28
+ set(acc, curr, replaceWith);
29
+
30
+ return acc;
31
+ }
32
+
33
+ /**
34
+ * If the value of the accumulator[current] is an array
35
+ * then we need to loop over it and call the reducer again
36
+ * this time with each array item being the accumulator.
37
+ */
38
+ if (Array.isArray(acc[curr])) {
39
+ acc[curr].forEach((item) => {
40
+ currentArr.slice(ind + 1).reduce(findLeafAndReplace, item);
41
+ });
42
+ }
43
+
44
+ /**
45
+ * accumulator[current]return accumulator[current] instead of the main accumulator,
46
+ * this will stop the same keys overwrite the wrong objects
47
+ */
48
+ return acc[curr];
49
+ };
50
+
51
+ return findLeafAndReplace;
52
+ };
@@ -1,3 +1,5 @@
1
1
  export { default as moveFields } from './moveFields';
2
2
  export { default as cleanData } from './cleanData';
3
3
  export { default as createYupSchema } from './schema';
4
+ export { recursivelyFindPathsBasedOnCondition } from './recursivelyFindPathsBasedOnCondition';
5
+ export { findLeafByPathAndReplace } from './findLeafByPathAndReplace';
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @typedef Attribute
3
+ * @type { { type: string }}
4
+ *
5
+ * @typedef Attributes
6
+ * @type {{ [key: string]: Attribute }}
7
+ */
8
+
9
+ /**
10
+ * This function will recursively find all the paths in the `currentContentTypeLayout.attributes`
11
+ * based on the boolean return of the condition e.g. `type === 'relation'`.
12
+ *
13
+ * It's original use was for the preperation of action items for the INIT_FORM action. It requires
14
+ * knowledge of the `components` in the entity, however `components` doesn't change nor does the predicate
15
+ * function so we don't need to pass it everytime hence why it's curried.
16
+ *
17
+ *
18
+ * @param {{[key: string]: { attributes: Attributes }}} components
19
+ * @param {(value: Attribute) => boolean} predicate
20
+ * @returns {(attributes: Attributes) => string[]}
21
+ */
22
+ const recursivelyFindPathsBasedOnConditionSetup = (components, predicate = () => false) => {
23
+ /**
24
+ *
25
+ * @param {Attributes} attributes
26
+ * @returns {string[]}
27
+ */
28
+ const recursivelyFindPathsBasedOnCondition = (attributes) => {
29
+ return Object.entries(attributes).reduce((acc, [key, value]) => {
30
+ if (predicate(value)) {
31
+ acc = [...acc, key];
32
+ }
33
+
34
+ if (value.type === 'component') {
35
+ const componentAttributes = components[value.component].attributes;
36
+
37
+ const attributesInComponent = recursivelyFindPathsBasedOnCondition(componentAttributes);
38
+
39
+ const attributesInComponentPaths = attributesInComponent.map((path) => `${key}.${path}`);
40
+
41
+ acc = [...acc, attributesInComponentPaths];
42
+ } else if (value.type === 'dynamiczone') {
43
+ const dynamicComponents = value.components;
44
+
45
+ const attributesInDynamicComponents = dynamicComponents
46
+ .flatMap((componentName) => {
47
+ return recursivelyFindPathsBasedOnCondition({
48
+ [componentName]: { type: 'component', component: componentName },
49
+ /**
50
+ * DynamicZones are an array of components, therefore the componentName shouldn't
51
+ * be part of the path because it's not a property of the component.
52
+ *
53
+ * e.g. { dynamic_zone: [{ __component: 'basic.simple', id: 36, my_name: null, categories: { count: 1, } }] }
54
+ * where the path to `id` is `dynamic_zone.id` and not `dynamic_zone.basic.simple.id`
55
+ *
56
+ * NOTE: we don't need to know the path to the `array` because it's about data shape not about the actual data
57
+ */
58
+ }).map((path) => path.split(`${componentName}.`)[1]);
59
+ })
60
+ .map((path) => `${key}.${path}`);
61
+
62
+ acc = [...acc, attributesInDynamicComponents];
63
+ }
64
+
65
+ return acc.flat();
66
+ }, []);
67
+ };
68
+
69
+ return recursivelyFindPathsBasedOnCondition;
70
+ };
71
+
72
+ export { recursivelyFindPathsBasedOnConditionSetup as recursivelyFindPathsBasedOnCondition };