@strapi/admin 4.14.4 → 4.14.6

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 (235) hide show
  1. package/admin/.eslintrc.js +12 -0
  2. package/admin/custom.d.ts +20 -0
  3. package/admin/src/StrapiApp.js +14 -16
  4. package/admin/src/components/AuthenticatedApp/index.js +4 -4
  5. package/admin/src/components/ConfigurationProvider.tsx +67 -0
  6. package/admin/src/components/LanguageProvider.tsx +129 -0
  7. package/admin/src/components/{LeftMenu/index.js → LeftMenu.tsx} +23 -18
  8. package/admin/src/components/{NpsSurvey/index.js → NpsSurvey.tsx} +68 -21
  9. package/admin/src/components/PluginsInitializer.tsx +124 -0
  10. package/admin/src/components/Providers/index.js +6 -6
  11. package/admin/src/components/UnauthenticatedLogo.tsx +2 -2
  12. package/admin/src/components/{LocalesProvider/__mocks__/useLocalesProvider.js → __mocks__/LanguageProvider.js} +1 -1
  13. package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +48 -8
  14. package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +146 -68
  15. package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +50 -47
  16. package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +11 -9
  17. package/admin/src/content-manager/components/BlocksEditor/index.js +1 -14
  18. package/admin/src/contexts/admin.ts +18 -0
  19. package/admin/src/contexts/configuration.ts +14 -4
  20. package/admin/src/hooks/__mocks__/useConfigurations.ts +2 -2
  21. package/admin/src/hooks/index.js +0 -3
  22. package/admin/src/hooks/{useAdminRoles/index.js → useAdminRoles.ts} +26 -10
  23. package/admin/src/hooks/useAdminUsers.ts +64 -0
  24. package/admin/src/hooks/useConfiguration.ts +5 -0
  25. package/admin/src/hooks/{useEnterprise/useEnterprise.js → useEnterprise.ts} +15 -5
  26. package/admin/src/hooks/useMenu.ts +153 -0
  27. package/admin/src/index.js +5 -2
  28. package/admin/src/layouts/UnauthenticatedLayout/LocaleToggle/index.js +2 -2
  29. package/admin/src/pages/Admin/index.js +4 -3
  30. package/admin/src/pages/App/index.js +5 -3
  31. package/admin/src/pages/AuthPage/components/Register/index.js +28 -33
  32. package/admin/src/pages/AuthPage/index.js +2 -2
  33. package/admin/src/pages/ProfilePage/index.js +2 -2
  34. package/admin/src/pages/SettingsPage/components/Tokens/Table/index.js +119 -87
  35. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/CustomizationInfos/index.js +2 -2
  36. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/index.js +2 -2
  37. package/admin/src/pages/SettingsPage/pages/Roles/CreatePage/index.js +1 -1
  38. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ConditionsModal/index.js +1 -1
  39. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ContentTypeCollapse/Collapse/index.js +1 -1
  40. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ContentTypeCollapse/CollapsePropertyMatrix/ActionRow/index.js +1 -1
  41. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/ContentTypeCollapse/CollapsePropertyMatrix/SubActionRow/index.js +1 -1
  42. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/GlobalActions/index.js +1 -1
  43. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/PermissionsDataManagerProvider/index.js +1 -1
  44. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/components/PluginsAndSettings/SubCategory/index.js +1 -1
  45. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/hooks/usePermissionsDataManager.ts +28 -0
  46. package/admin/src/pages/SettingsPage/pages/Roles/EditPage/index.js +1 -1
  47. package/admin/src/{hooks/useAdminRolePermissions/index.js → pages/SettingsPage/pages/Roles/hooks/useAdminRolePermissions.ts} +13 -6
  48. package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/index.js +3 -5
  49. package/admin/src/shared/hooks/index.js +0 -1
  50. package/admin/src/shared/hooks/useInjectionZone/index.js +2 -2
  51. package/admin/src/types/adminAPI.ts +29 -0
  52. package/admin/src/utils/createRoute.js +4 -1
  53. package/admin/tsconfig.build.json +4 -0
  54. package/admin/tsconfig.json +7 -1
  55. package/build/{1049.f7aed23d.chunk.js → 1049.9236e785.chunk.js} +1 -1
  56. package/build/1222.fe92c653.chunk.js +35 -0
  57. package/build/{1227.f9c74718.chunk.js → 1227.e0f7447b.chunk.js} +1 -1
  58. package/build/135.ad267b59.chunk.js +1 -0
  59. package/build/{1386.6b8819c6.chunk.js → 1386.07f2bbb3.chunk.js} +1 -1
  60. package/build/1835.eaa696ba.chunk.js +1 -0
  61. package/build/{2225.d1bcf7e3.chunk.js → 2225.a2147b8f.chunk.js} +2 -2
  62. package/build/2325.d705b39a.chunk.js +1 -0
  63. package/build/2379.b0bc4013.chunk.js +1 -0
  64. package/build/{2395.aca6ce66.chunk.js → 2395.d37b1025.chunk.js} +1 -1
  65. package/build/2421.79e5b3d0.chunk.js +1 -0
  66. package/build/267.073a3bcb.chunk.js +1 -0
  67. package/build/2801.12522720.chunk.js +1 -0
  68. package/build/2878.145ebf7c.chunk.js +1 -0
  69. package/build/2950.216f2e89.chunk.js +1 -0
  70. package/build/2953.284a63c0.chunk.js +1 -0
  71. package/build/{8743.31c921b1.chunk.js → 3019.0d74d080.chunk.js} +123 -139
  72. package/build/3021.33ad47fb.chunk.js +103 -0
  73. package/build/{3483.5df8e010.chunk.js → 3483.8f1b25f8.chunk.js} +1 -1
  74. package/build/3911.488fbde3.chunk.js +95 -0
  75. package/build/{4174.df9aa09a.chunk.js → 4174.2c4f958e.chunk.js} +1 -1
  76. package/build/4429.7f044dc7.chunk.js +1 -0
  77. package/build/4555.c883d697.chunk.js +1 -0
  78. package/build/4663.b906cc10.chunk.js +1 -0
  79. package/build/4916.480053a6.chunk.js +1 -0
  80. package/build/4996.d285c30b.chunk.js +1 -0
  81. package/build/502.b845473a.chunk.js +1 -0
  82. package/build/5858.493b31ec.chunk.js +1 -0
  83. package/build/6345.334e7678.chunk.js +1 -0
  84. package/build/6373.1a21d665.chunk.js +105 -0
  85. package/build/6453.4160b5b7.chunk.js +1 -0
  86. package/build/7448.6fd14dd3.chunk.js +1 -0
  87. package/build/7464.91341b4f.chunk.js +1 -0
  88. package/build/7735.9e7c9fdd.chunk.js +10 -0
  89. package/build/782.7243b183.chunk.js +1 -0
  90. package/build/7849.2a500ed8.chunk.js +1 -0
  91. package/build/7897.dffa5ad5.chunk.js +6 -0
  92. package/build/8162.7d1100a0.chunk.js +1 -0
  93. package/build/{8276.d4426fd8.chunk.js → 8276.e9698944.chunk.js} +2 -2
  94. package/build/8894.5ca4852a.chunk.js +26 -0
  95. package/build/8980.f0045cc1.chunk.js +1 -0
  96. package/build/9153.42c1428a.chunk.js +1 -0
  97. package/build/{9218.8bc01ab9.chunk.js → 9218.306ad178.chunk.js} +1 -1
  98. package/build/9285.5f174057.chunk.js +1 -0
  99. package/build/9302.550cf5b7.chunk.js +146 -0
  100. package/build/9547.62987774.chunk.js +1 -0
  101. package/build/9754.b4e73779.chunk.js +1 -0
  102. package/build/Admin-authenticatedApp.e0bf203f.chunk.js +79 -0
  103. package/build/{Admin_InternalErrorPage.b66ee9c1.chunk.js → Admin_InternalErrorPage.e2431a95.chunk.js} +1 -1
  104. package/build/Admin_homePage.71ef8d06.chunk.js +81 -0
  105. package/build/{Admin_marketplace.31b962b8.chunk.js → Admin_marketplace.0db78604.chunk.js} +1 -1
  106. package/build/{Admin_pluginsPage.9217101d.chunk.js → Admin_pluginsPage.1083f7f0.chunk.js} +1 -1
  107. package/build/{Admin_profilePage.680123d9.chunk.js → Admin_profilePage.61704b7d.chunk.js} +2 -2
  108. package/build/Admin_settingsPage.39cb9fca.chunk.js +111 -0
  109. package/build/{Upload_ConfigureTheView.b40eea4d.chunk.js → Upload_ConfigureTheView.3cfeb108.chunk.js} +1 -1
  110. package/build/admin-app.06f5e70a.chunk.js +69 -0
  111. package/build/admin-edit-roles-page.556fac52.chunk.js +267 -0
  112. package/build/admin-edit-users.64fd1318.chunk.js +10 -0
  113. package/build/admin-roles-list.15918328.chunk.js +22 -0
  114. package/build/admin-users.74fddc87.chunk.js +11 -0
  115. package/build/{api-tokens-create-page.0dd63e91.chunk.js → api-tokens-create-page.c08ae118.chunk.js} +1 -1
  116. package/build/{api-tokens-edit-page.78d877f8.chunk.js → api-tokens-edit-page.ce18efdc.chunk.js} +1 -1
  117. package/build/api-tokens-list-page.783b7569.chunk.js +16 -0
  118. package/build/audit-logs-settings-page.12aeea8c.chunk.js +1 -0
  119. package/build/content-manager.2e3f660b.chunk.js +1220 -0
  120. package/build/{content-type-builder-list-view.3fffae65.chunk.js → content-type-builder-list-view.38ed3935.chunk.js} +7 -7
  121. package/build/{content-type-builder.98c71164.chunk.js → content-type-builder.758a9d23.chunk.js} +4 -4
  122. package/build/email-settings-page.e08a587e.chunk.js +11 -0
  123. package/build/{i18n-settings-page.a9708926.chunk.js → i18n-settings-page.3186e3e9.chunk.js} +1 -1
  124. package/build/index.html +1 -1
  125. package/build/main.00ea6f5a.js +2665 -0
  126. package/build/{review-workflows-settings-create-view.b7b0c6c5.chunk.js → review-workflows-settings-create-view.5cdc4d64.chunk.js} +1 -1
  127. package/build/{review-workflows-settings-edit-view.c331b3fe.chunk.js → review-workflows-settings-edit-view.53bf7865.chunk.js} +1 -1
  128. package/build/review-workflows-settings-list-view.b4a8aefb.chunk.js +56 -0
  129. package/build/runtime~main.be0e0649.js +2 -0
  130. package/build/sso-settings-page.6a35d473.chunk.js +1 -0
  131. package/build/{transfer-tokens-create-page.e7f541d3.chunk.js → transfer-tokens-create-page.2662d519.chunk.js} +1 -1
  132. package/build/{transfer-tokens-edit-page.bd1276c2.chunk.js → transfer-tokens-edit-page.f64d8d8c.chunk.js} +1 -1
  133. package/build/transfer-tokens-list-page.e6fd5f87.chunk.js +16 -0
  134. package/build/{upload-settings.97ef4c92.chunk.js → upload-settings.450a1de0.chunk.js} +1 -1
  135. package/build/{upload.f08715a1.chunk.js → upload.0d53e7a3.chunk.js} +1 -1
  136. package/build/{users-advanced-settings-page.36a3c363.chunk.js → users-advanced-settings-page.4a1f1f6d.chunk.js} +1 -1
  137. package/build/users-email-settings-page.ea81fe82.chunk.js +9 -0
  138. package/build/users-providers-settings-page.10280cdb.chunk.js +14 -0
  139. package/build/{users-roles-settings-page.d5a8e8a1.chunk.js → users-roles-settings-page.4a7158be.chunk.js} +1 -1
  140. package/build/{webhook-edit-page.87456194.chunk.js → webhook-edit-page.65ac30ee.chunk.js} +2 -2
  141. package/build/{webhook-list-page.c88a382b.chunk.js → webhook-list-page.f57285ca.chunk.js} +1 -1
  142. package/jest.config.front.js +4 -0
  143. package/package.json +23 -17
  144. package/scripts/build.js +1 -1
  145. package/server/controllers/admin.js +3 -2
  146. package/shared/entities.ts +33 -0
  147. package/shared/permissions.ts +52 -0
  148. package/admin/src/components/ConfigurationsProvider/index.js +0 -66
  149. package/admin/src/components/ConfigurationsProvider/reducer.js +0 -29
  150. package/admin/src/components/LanguageProvider/index.js +0 -54
  151. package/admin/src/components/LanguageProvider/init.js +0 -13
  152. package/admin/src/components/LanguageProvider/reducer.js +0 -30
  153. package/admin/src/components/LanguageProvider/utils/localStorageKey.js +0 -3
  154. package/admin/src/components/LocalesProvider/context.js +0 -5
  155. package/admin/src/components/LocalesProvider/index.js +0 -21
  156. package/admin/src/components/LocalesProvider/useLocalesProvider.js +0 -11
  157. package/admin/src/components/NpsSurvey/hooks/useNpsSurveySettings.js +0 -17
  158. package/admin/src/components/PluginsInitializer/index.js +0 -68
  159. package/admin/src/components/PluginsInitializer/init.js +0 -11
  160. package/admin/src/components/PluginsInitializer/reducer.js +0 -22
  161. package/admin/src/contexts/Admin/index.js +0 -5
  162. package/admin/src/contexts/MarketPlace/index.js +0 -18
  163. package/admin/src/contexts/PermisssionsDataManagerContext/index.js +0 -5
  164. package/admin/src/contexts/index.js +0 -3
  165. package/admin/src/hooks/useAdminRoles/__mocks__/index.js +0 -5
  166. package/admin/src/hooks/useAdminUsers/index.js +0 -1
  167. package/admin/src/hooks/useAdminUsers/useAdminUsers.js +0 -47
  168. package/admin/src/hooks/useConfigurations.ts +0 -5
  169. package/admin/src/hooks/useEnterprise/index.js +0 -1
  170. package/admin/src/hooks/useMenu/index.js +0 -86
  171. package/admin/src/hooks/useMenu/utils/checkPermissions.js +0 -13
  172. package/admin/src/hooks/useMenu/utils/getGeneralLinks.js +0 -31
  173. package/admin/src/hooks/useMenu/utils/getPluginSectionLinks.js +0 -17
  174. package/admin/src/hooks/usePermissionsDataManager/index.js +0 -7
  175. package/admin/src/shared/hooks/useAdminProvider/index.js +0 -11
  176. package/build/2224.8af54440.chunk.js +0 -138
  177. package/build/2379.f0baf826.chunk.js +0 -1
  178. package/build/2421.a478ba24.chunk.js +0 -105
  179. package/build/2801.c49f88a1.chunk.js +0 -1
  180. package/build/3911.d4fada48.chunk.js +0 -95
  181. package/build/412.72afdf0c.chunk.js +0 -689
  182. package/build/502.8666bbef.chunk.js +0 -25
  183. package/build/5702.5b433d50.chunk.js +0 -1
  184. package/build/6186.c33ce082.chunk.js +0 -116
  185. package/build/6715.48e37308.chunk.js +0 -1
  186. package/build/6812.00ef5b0d.chunk.js +0 -26
  187. package/build/7464.43a4527c.chunk.js +0 -1
  188. package/build/7818.d2196a53.chunk.js +0 -29
  189. package/build/7897.5c03247b.chunk.js +0 -25
  190. package/build/8690.33243bba.chunk.js +0 -38
  191. package/build/Admin-authenticatedApp.27545a1b.chunk.js +0 -112
  192. package/build/Admin_homePage.a6281dd6.chunk.js +0 -124
  193. package/build/Admin_settingsPage.33378310.chunk.js +0 -111
  194. package/build/admin-app.e8c52c37.chunk.js +0 -36
  195. package/build/admin-edit-roles-page.fcf056bf.chunk.js +0 -275
  196. package/build/admin-edit-users.89efe3c4.chunk.js +0 -10
  197. package/build/admin-roles-list.8b77704a.chunk.js +0 -22
  198. package/build/admin-users.e3f1be14.chunk.js +0 -19
  199. package/build/api-tokens-list-page.ae13346c.chunk.js +0 -16
  200. package/build/audit-logs-settings-page.e9c92a75.chunk.js +0 -9
  201. package/build/content-manager.5849dbe3.chunk.js +0 -1226
  202. package/build/email-settings-page.ecfec9b3.chunk.js +0 -11
  203. package/build/email-translation-ar-json.88304564.chunk.js +0 -1
  204. package/build/email-translation-cs-json.6eaeec6a.chunk.js +0 -1
  205. package/build/email-translation-de-json.1b334230.chunk.js +0 -1
  206. package/build/email-translation-dk-json.85402492.chunk.js +0 -1
  207. package/build/email-translation-en-json.4211d4d0.chunk.js +0 -1
  208. package/build/email-translation-es-json.0b6b1006.chunk.js +0 -1
  209. package/build/email-translation-fr-json.78be2787.chunk.js +0 -1
  210. package/build/email-translation-id-json.c97239fe.chunk.js +0 -1
  211. package/build/email-translation-it-json.a2ed8c78.chunk.js +0 -1
  212. package/build/email-translation-ja-json.63eebd02.chunk.js +0 -1
  213. package/build/email-translation-ko-json.4de49b23.chunk.js +0 -1
  214. package/build/email-translation-ms-json.7390477e.chunk.js +0 -1
  215. package/build/email-translation-nl-json.377bdd9f.chunk.js +0 -1
  216. package/build/email-translation-pl-json.97d0db97.chunk.js +0 -1
  217. package/build/email-translation-pt-BR-json.81cca553.chunk.js +0 -1
  218. package/build/email-translation-pt-json.2a2a0643.chunk.js +0 -1
  219. package/build/email-translation-ru-json.6bce37dd.chunk.js +0 -1
  220. package/build/email-translation-sk-json.53da2fcd.chunk.js +0 -1
  221. package/build/email-translation-th-json.660fa9a8.chunk.js +0 -1
  222. package/build/email-translation-tr-json.e6c0f8fc.chunk.js +0 -1
  223. package/build/email-translation-uk-json.bd1fb6bf.chunk.js +0 -1
  224. package/build/email-translation-vi-json.9fb7e6d7.chunk.js +0 -1
  225. package/build/email-translation-zh-Hans-json.c6841563.chunk.js +0 -1
  226. package/build/email-translation-zh-json.7a2232ea.chunk.js +0 -1
  227. package/build/main.3abb6f34.js +0 -3278
  228. package/build/review-workflows-settings-list-view.70218dc1.chunk.js +0 -75
  229. package/build/runtime~main.450561b1.js +0 -2
  230. package/build/sso-settings-page.1a9e7f8f.chunk.js +0 -1
  231. package/build/transfer-tokens-list-page.5de6bb9f.chunk.js +0 -16
  232. package/build/users-email-settings-page.47b47962.chunk.js +0 -149
  233. package/build/users-providers-settings-page.1e0c8376.chunk.js +0 -154
  234. /package/admin/src/hooks/{useAdminUsers/__mocks__/index.js → __mocks__/useAdminUsers.ts} +0 -0
  235. /package/admin/src/{hooks/useAdminRolePermissions/__mocks__/index.js → pages/SettingsPage/pages/Roles/hooks/__mocks__/useAdminRolePermissions.ts} +0 -0
@@ -0,0 +1,124 @@
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ LoadingIndicatorPage,
5
+ useStrapiApp,
6
+ type StrapiAppContextValue,
7
+ } from '@strapi/helper-plugin';
8
+ import produce from 'immer';
9
+ import set from 'lodash/set';
10
+
11
+ // @ts-expect-error pages/Admin has not been converted yet.
12
+ import Admin from '../pages/Admin';
13
+
14
+ /**
15
+ * TODO: this isn't great, and we really should focus on fixing this.
16
+ */
17
+ const PluginsInitializer = () => {
18
+ const { plugins: appPlugins } = useStrapiApp();
19
+ const [{ plugins }, dispatch] = React.useReducer<React.Reducer<State, Action>, State>(
20
+ reducer,
21
+ initialState,
22
+ () => init(appPlugins)
23
+ );
24
+ const setPlugin = React.useRef((pluginId: string) => {
25
+ dispatch({ type: 'SET_PLUGIN_READY', pluginId });
26
+ });
27
+
28
+ const hasApluginNotReady = Object.keys(plugins).some(
29
+ (plugin) => plugins[plugin].isReady === false
30
+ );
31
+
32
+ /**
33
+ *
34
+ * I have spent some time trying to understand what is happening here, and wanted to
35
+ * leave that knowledge for my future me:
36
+ *
37
+ * `initializer` is an undocumented property of the `registerPlugin` API. At the time
38
+ * of writing it seems only to be used by the i18n plugin.
39
+ *
40
+ * How does it work?
41
+ *
42
+ * Every plugin that has an `initializer` component defined, receives the
43
+ * `setPlugin` function as a component prop. In the case of i18n the plugin fetches locales
44
+ * first and calls `setPlugin` with `pluginId` once they are loaded, which then triggers the
45
+ * reducer of the admin app defined above.
46
+ *
47
+ * Once all plugins are set to `isReady: true` the app renders.
48
+ *
49
+ * This API is used to block rendering of the admin app. We should remove that in v5 completely
50
+ * and make sure plugins can inject data into the global store before they are initialized, to avoid
51
+ * having a new prop-callback based communication channel between plugins and the core admin app.
52
+ *
53
+ */
54
+
55
+ if (hasApluginNotReady) {
56
+ const initializers = Object.keys(plugins).reduce((acc, current) => {
57
+ const InitializerComponent = plugins[current].initializer;
58
+
59
+ if (InitializerComponent) {
60
+ const key = plugins[current].pluginId;
61
+
62
+ acc.push(<InitializerComponent key={key} setPlugin={setPlugin.current} />);
63
+ }
64
+
65
+ return acc;
66
+ }, [] as React.ReactNode[]);
67
+
68
+ return (
69
+ <>
70
+ {initializers}
71
+ <LoadingIndicatorPage />
72
+ </>
73
+ );
74
+ }
75
+
76
+ return <Admin />;
77
+ };
78
+
79
+ /* -------------------------------------------------------------------------------------------------
80
+ * Reducer
81
+ * -----------------------------------------------------------------------------------------------*/
82
+
83
+ interface State {
84
+ plugins: StrapiAppContextValue['plugins'];
85
+ }
86
+
87
+ const initialState: State = {
88
+ plugins: {},
89
+ };
90
+
91
+ type SetPluginReadyAction = {
92
+ type: 'SET_PLUGIN_READY';
93
+ pluginId: string;
94
+ };
95
+
96
+ type Action = SetPluginReadyAction;
97
+
98
+ const reducer: React.Reducer<State, Action> = (state = initialState, action: Action): State =>
99
+ produce(state, (draftState) => {
100
+ switch (action.type) {
101
+ case 'SET_PLUGIN_READY': {
102
+ set(draftState, ['plugins', action.pluginId, 'isReady'], true);
103
+ break;
104
+ }
105
+ default:
106
+ return draftState;
107
+ }
108
+ });
109
+
110
+ /* -------------------------------------------------------------------------------------------------
111
+ * Init state
112
+ * -----------------------------------------------------------------------------------------------*/
113
+
114
+ const init = (plugins: State['plugins']): State => {
115
+ return {
116
+ plugins: Object.keys(plugins).reduce<State['plugins']>((acc, current) => {
117
+ acc[current] = { ...plugins[current] };
118
+
119
+ return acc;
120
+ }, {}),
121
+ };
122
+ };
123
+
124
+ export { PluginsInitializer };
@@ -12,10 +12,10 @@ import PropTypes from 'prop-types';
12
12
  import { QueryClient, QueryClientProvider } from 'react-query';
13
13
  import { Provider } from 'react-redux';
14
14
 
15
- import { AdminContext } from '../../contexts';
16
- import ConfigurationsProvider from '../ConfigurationsProvider';
15
+ import { AdminContext } from '../../contexts/admin';
16
+ import { ConfigurationProvider } from '../ConfigurationProvider';
17
17
  import GuidedTour from '../GuidedTour';
18
- import LanguageProvider from '../LanguageProvider';
18
+ import { LanguageProvider } from '../LanguageProvider';
19
19
  import { Theme } from '../Theme';
20
20
  import { ThemeToggleProvider } from '../ThemeToggleProvider';
21
21
 
@@ -56,7 +56,7 @@ const Providers = ({
56
56
  <QueryClientProvider client={queryClient}>
57
57
  <Provider store={store}>
58
58
  <AdminContext.Provider value={{ getAdminInjectedComponents }}>
59
- <ConfigurationsProvider
59
+ <ConfigurationProvider
60
60
  authLogo={authLogo}
61
61
  menuLogo={menuLogo}
62
62
  showReleaseNotification={showReleaseNotification}
@@ -83,7 +83,7 @@ const Providers = ({
83
83
  </CustomFieldsProvider>
84
84
  </LibraryProvider>
85
85
  </StrapiAppProvider>
86
- </ConfigurationsProvider>
86
+ </ConfigurationProvider>
87
87
  </AdminContext.Provider>
88
88
  </Provider>
89
89
  </QueryClientProvider>
@@ -95,7 +95,7 @@ const Providers = ({
95
95
 
96
96
  Providers.propTypes = {
97
97
  authLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired,
98
- children: PropTypes.element.isRequired,
98
+ children: PropTypes.node.isRequired,
99
99
  components: PropTypes.object.isRequired,
100
100
  customFields: PropTypes.object.isRequired,
101
101
  fields: PropTypes.object.isRequired,
@@ -1,6 +1,6 @@
1
1
  import styled from 'styled-components';
2
2
 
3
- import { useConfigurations } from '../hooks/useConfigurations';
3
+ import { useConfiguration } from '../hooks/useConfiguration';
4
4
 
5
5
  const Img = styled.img`
6
6
  height: ${72 / 16}rem;
@@ -9,7 +9,7 @@ const Img = styled.img`
9
9
  const Logo = () => {
10
10
  const {
11
11
  logos: { auth },
12
- } = useConfigurations();
12
+ } = useConfiguration();
13
13
 
14
14
  return <Img src={auth?.custom ?? auth.default} aria-hidden alt="" />;
15
15
  };
@@ -1,4 +1,4 @@
1
- export default function useLocalesProvider() {
1
+ export function useLocalesProvider() {
2
2
  return {
3
3
  changeLocale() {},
4
4
  localeNames: { en: 'English' },
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import { Box } from '@strapi/design-system';
3
4
  import PropTypes from 'prop-types';
4
5
  import { Editable, useSlate } from 'slate-react';
5
6
  import { useTheme } from 'styled-components';
@@ -41,6 +42,7 @@ const baseRenderElement = (props, blocks) => {
41
42
  const BlocksInput = ({ disabled, placeholder }) => {
42
43
  const theme = useTheme();
43
44
  const editor = useSlate();
45
+ const blocksRef = React.useRef();
44
46
 
45
47
  // Create renderLeaf function based on the modifiers store
46
48
  const modifiers = useModifiersStore();
@@ -82,15 +84,53 @@ const BlocksInput = ({ disabled, placeholder }) => {
82
84
  }
83
85
  };
84
86
 
87
+ /**
88
+ * scrollSelectionIntoView : Slate's default method to scroll a DOM selection into the view,
89
+ * thats shifting layout for us when there is a overflowY:scroll on the viewport.
90
+ * We are overriding it to check if the selection is not fully within the visible area of the editor,
91
+ * we use scrollBy one line to the bottom
92
+ */
93
+ const handleScrollSelectionIntoView = (_, domRange) => {
94
+ const domRect = domRange.getBoundingClientRect();
95
+ const blocksInput = blocksRef.current;
96
+ const editorRect = blocksInput.getBoundingClientRect();
97
+
98
+ // Check if the selection is not fully within the visible area of the editor
99
+ if (domRect.top < editorRect.top || domRect.bottom > editorRect.bottom) {
100
+ // Scroll by one line to the bottom
101
+ blocksInput.scrollBy({
102
+ top: 28, // 20px is the line-height + 8px line gap
103
+ behavior: 'smooth',
104
+ });
105
+ }
106
+ };
107
+
85
108
  return (
86
- <Editable
87
- readOnly={disabled}
88
- placeholder={placeholder}
89
- style={getEditorStyle(theme)}
90
- renderElement={renderElement}
91
- renderLeaf={renderLeaf}
92
- onKeyDown={handleKeyDown}
93
- />
109
+ <Box
110
+ ref={blocksRef}
111
+ grow={1}
112
+ width="100%"
113
+ overflow="auto"
114
+ fontSize={2}
115
+ background="neutral0"
116
+ color="neutral800"
117
+ lineHeight={6}
118
+ hasRadius
119
+ paddingLeft={4}
120
+ paddingRight={4}
121
+ marginTop={3}
122
+ marginBottom={3}
123
+ >
124
+ <Editable
125
+ readOnly={disabled}
126
+ placeholder={placeholder}
127
+ style={getEditorStyle(theme)}
128
+ renderElement={renderElement}
129
+ renderLeaf={renderLeaf}
130
+ onKeyDown={handleKeyDown}
131
+ scrollSelectionIntoView={handleScrollSelectionIntoView}
132
+ />
133
+ </Box>
94
134
  );
95
135
  };
96
136
 
@@ -97,49 +97,6 @@ ToolbarButton.propTypes = {
97
97
  handleClick: PropTypes.func.isRequired,
98
98
  };
99
99
 
100
- const ModifierButton = ({ icon, name, label, disabled }) => {
101
- const editor = useSlate();
102
-
103
- const isModifierActive = () => {
104
- const modifiers = Editor.marks(editor);
105
-
106
- if (!modifiers) return false;
107
-
108
- return Boolean(modifiers[name]);
109
- };
110
-
111
- const isActive = isModifierActive();
112
-
113
- const toggleModifier = () => {
114
- if (isActive) {
115
- Editor.removeMark(editor, name);
116
- } else {
117
- Editor.addMark(editor, name, true);
118
- }
119
- };
120
-
121
- return (
122
- <ToolbarButton
123
- icon={icon}
124
- name={name}
125
- label={label}
126
- isActive={isActive}
127
- disabled={disabled}
128
- handleClick={toggleModifier}
129
- />
130
- );
131
- };
132
-
133
- ModifierButton.propTypes = {
134
- icon: PropTypes.elementType.isRequired,
135
- name: PropTypes.string.isRequired,
136
- label: PropTypes.shape({
137
- id: PropTypes.string.isRequired,
138
- defaultMessage: PropTypes.string.isRequired,
139
- }).isRequired,
140
- disabled: PropTypes.bool.isRequired,
141
- };
142
-
143
100
  const toggleBlock = (editor, value) => {
144
101
  const { type, level, format } = value;
145
102
 
@@ -151,11 +108,44 @@ const toggleBlock = (editor, value) => {
151
108
  };
152
109
 
153
110
  if (editor.selection) {
111
+ // If the selection is inside a list, split the list so that the modified block is outside of it
112
+ Transforms.unwrapNodes(editor, {
113
+ match: (node) => node.type === 'list',
114
+ split: true,
115
+ });
116
+
154
117
  // When there is a selection, update the existing block in the tree
155
118
  Transforms.setNodes(editor, blockProperties);
156
119
  } else {
157
- // Otherwise, add a new block to the tree
158
- Transforms.insertNodes(editor, { ...blockProperties, children: [{ type: 'text', text: '' }] });
120
+ /**
121
+ * When there is no selection, we want to insert a new block just after
122
+ * the last node inserted and prevent the code to add an empty paragraph
123
+ * between them.
124
+ */
125
+ const [, lastNodePath] = Editor.last(editor, []);
126
+ const [parentNode] = Editor.parent(editor, lastNodePath, {
127
+ // Makes sure we get a block node, not an inline node
128
+ match: (node) => node.type !== 'text',
129
+ });
130
+ Transforms.removeNodes(editor, {
131
+ void: true,
132
+ hanging: true,
133
+ at: {
134
+ anchor: { path: lastNodePath, offset: 0 },
135
+ focus: { path: lastNodePath, offset: 0 },
136
+ },
137
+ });
138
+ Transforms.insertNodes(
139
+ editor,
140
+ {
141
+ ...blockProperties,
142
+ children: parentNode.children,
143
+ },
144
+ {
145
+ at: [lastNodePath[0]],
146
+ select: true,
147
+ }
148
+ );
159
149
  }
160
150
 
161
151
  // When the select is clicked it blurs the editor, restore the focus to the editor
@@ -199,6 +189,8 @@ const ImageDialog = ({ handleClose }) => {
199
189
  const MediaLibraryDialog = components['media-library'];
200
190
 
201
191
  const insertImages = (images) => {
192
+ // Image node created using select or existing selection node needs to be deleted before adding new image nodes
193
+ Transforms.removeNodes(editor);
202
194
  images.forEach((img) => {
203
195
  const image = { type: 'image', image: img, children: [{ type: 'text', text: '' }] };
204
196
  Transforms.insertNodes(editor, image);
@@ -272,7 +264,7 @@ const insertEmptyBlockAtLast = (editor) => {
272
264
  );
273
265
  };
274
266
 
275
- export const BlocksDropdown = ({ disabled }) => {
267
+ const BlocksDropdown = ({ disabled }) => {
276
268
  const editor = useSlate();
277
269
  const { formatMessage } = useIntl();
278
270
  const [isMediaLibraryVisible, setIsMediaLibraryVisible] = React.useState(false);
@@ -290,10 +282,7 @@ export const BlocksDropdown = ({ disabled }) => {
290
282
  * @param {string} optionKey - key of the heading selected
291
283
  */
292
284
  const selectOption = (optionKey) => {
293
- if (optionKey === 'image') {
294
- // Image node created using select or existing selection node needs to be deleted before adding new image nodes
295
- Transforms.removeNodes(editor);
296
- } else if (['list-ordered', 'list-unordered'].includes(optionKey)) {
285
+ if (['list-ordered', 'list-unordered'].includes(optionKey)) {
297
286
  // retrieve the list format
298
287
  const listFormat = blocks[optionKey].value.format;
299
288
 
@@ -302,7 +291,7 @@ export const BlocksDropdown = ({ disabled }) => {
302
291
 
303
292
  // toggle the list
304
293
  toggleList(editor, isActive, listFormat);
305
- } else {
294
+ } else if (optionKey !== 'image') {
306
295
  toggleBlock(editor, blocks[optionKey].value);
307
296
  }
308
297
 
@@ -432,21 +421,59 @@ const isListActive = (editor, matchNode) => {
432
421
  };
433
422
 
434
423
  const toggleList = (editor, isActive, format) => {
435
- // Delete the parent list so that we're left with only the list items directly
436
- Transforms.unwrapNodes(editor, {
437
- match: (node) => isListNode(node) && ['ordered', 'unordered'].includes(node.format),
438
- split: true,
439
- });
424
+ // If we have selected a portion of content in the editor,
425
+ // we want to convert it to a list or if it is already a list,
426
+ // convert it back to a paragraph
427
+ if (editor.selection) {
428
+ Transforms.unwrapNodes(editor, {
429
+ match: (node) => isListNode(node) && ['ordered', 'unordered'].includes(node.format),
430
+ split: true,
431
+ });
440
432
 
441
- // Change the type of the current selection
442
- Transforms.setNodes(editor, {
443
- type: isActive ? 'paragraph' : 'list-item',
444
- });
433
+ Transforms.setNodes(editor, {
434
+ type: isActive ? 'paragraph' : 'list-item',
435
+ });
436
+
437
+ if (!isActive) {
438
+ const block = { type: 'list', format, children: [] };
439
+ Transforms.wrapNodes(editor, block);
440
+ }
441
+ } else {
442
+ // There is no selection, convert the last inserted node to a list
443
+ // If it is already a list, convert it back to a paragraph
444
+ const [, lastNodePath] = Editor.last(editor, []);
445
+
446
+ const [parentNode] = Editor.parent(editor, lastNodePath, {
447
+ // Makes sure we get a block node, not an inline node
448
+ match: (node) => node.type !== 'text',
449
+ });
450
+
451
+ Transforms.removeNodes(editor, {
452
+ void: true,
453
+ hanging: true,
454
+ at: {
455
+ anchor: { path: lastNodePath, offset: 0 },
456
+ focus: { path: lastNodePath, offset: 0 },
457
+ },
458
+ });
445
459
 
446
- // If the selection is now a list item, wrap it inside a list
447
- if (!isActive) {
448
- const block = { type: 'list', format, children: [] };
449
- Transforms.wrapNodes(editor, block);
460
+ Transforms.insertNodes(
461
+ editor,
462
+ {
463
+ type: isActive ? 'paragraph' : 'list-item',
464
+ children: [...parentNode.children],
465
+ },
466
+ {
467
+ at: [lastNodePath[0]],
468
+ select: true,
469
+ }
470
+ );
471
+
472
+ if (!isActive) {
473
+ // If the selection is now a list item, wrap it inside a list
474
+ const block = { type: 'list', format, children: [] };
475
+ Transforms.wrapNodes(editor, block);
476
+ }
450
477
  }
451
478
  };
452
479
 
@@ -507,6 +534,31 @@ const LinkButton = ({ disabled }) => {
507
534
  return Boolean(match);
508
535
  };
509
536
 
537
+ const isLinkDisabled = () => {
538
+ // Always disabled when the whole editor is disabled
539
+ if (disabled) {
540
+ return true;
541
+ }
542
+
543
+ // Always enabled when there's no selection
544
+ if (!editor.selection) {
545
+ return false;
546
+ }
547
+
548
+ // Get the block node closest to the anchor and focus
549
+ const anchorNodeEntry = Editor.above(editor, {
550
+ at: editor.selection.anchor,
551
+ match: (node) => node.type !== 'text',
552
+ });
553
+ const focusNodeEntry = Editor.above(editor, {
554
+ at: editor.selection.focus,
555
+ match: (node) => node.type !== 'text',
556
+ });
557
+
558
+ // Disabled if the anchor and focus are not in the same block
559
+ return anchorNodeEntry[0] !== focusNodeEntry[0];
560
+ };
561
+
510
562
  const addLink = () => {
511
563
  // We insert an empty anchor, so we split the DOM to have a element we can use as reference for the popover
512
564
  insertLink(editor, { url: '' });
@@ -522,7 +574,7 @@ const LinkButton = ({ disabled }) => {
522
574
  }}
523
575
  isActive={isLinkActive()}
524
576
  handleClick={addLink}
525
- disabled={disabled}
577
+ disabled={isLinkDisabled()}
526
578
  />
527
579
  );
528
580
  };
@@ -543,11 +595,37 @@ const BetaTag = styled(Box)`
543
595
  const BlocksToolbar = ({ disabled }) => {
544
596
  const modifiers = useModifiersStore();
545
597
  const blocks = useBlocksStore();
598
+ const editor = useSlate();
599
+
600
+ /**
601
+ * The modifier buttons are disabled when an image is selected.
602
+ */
603
+
604
+ const checkButtonDisabled = () => {
605
+ // Always disabled when the whole editor is disabled
606
+ if (disabled) {
607
+ return true;
608
+ }
609
+
610
+ if (!editor.selection) {
611
+ return false;
612
+ }
613
+
614
+ const selectedNode = editor.children[editor.selection.anchor.path[0]];
615
+
616
+ if (['image', 'code'].includes(selectedNode.type)) {
617
+ return true;
618
+ }
619
+
620
+ return false;
621
+ };
622
+
623
+ const isButtonDisabled = checkButtonDisabled();
546
624
 
547
625
  return (
548
626
  <Toolbar.Root aria-disabled={disabled} asChild>
549
627
  {/* Remove after the RTE Blocks Beta release (paddingRight and width) */}
550
- <ToolbarWrapper gap={1} padding={2} paddingRight={4} width="100%">
628
+ <ToolbarWrapper gap={2} padding={2} paddingRight={4} width="100%">
551
629
  <BlocksDropdown disabled={disabled} />
552
630
  <Toolbar.ToggleGroup type="multiple" asChild>
553
631
  <Flex gap={1} marginLeft={1}>
@@ -559,10 +637,10 @@ const BlocksToolbar = ({ disabled }) => {
559
637
  label={modifier.label}
560
638
  isActive={modifier.checkIsActive()}
561
639
  handleClick={modifier.handleToggle}
562
- disabled={disabled}
640
+ disabled={isButtonDisabled}
563
641
  />
564
642
  ))}
565
- <LinkButton disabled={disabled} />
643
+ <LinkButton disabled={isButtonDisabled} />
566
644
  </Flex>
567
645
  </Toolbar.ToggleGroup>
568
646
  <Separator />