@strapi/admin 4.14.0-beta.0 → 4.15.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/admin/src/components/RBACProvider/index.js +1 -1
  2. package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +87 -0
  3. package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +463 -0
  4. package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +451 -0
  5. package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +102 -0
  6. package/admin/src/content-manager/components/BlocksEditor/index.js +126 -0
  7. package/admin/src/content-manager/components/InputUID/index.js +92 -109
  8. package/admin/src/content-manager/components/Inputs/index.js +3 -1
  9. package/admin/src/content-manager/components/Inputs/utils/getInputType.js +2 -0
  10. package/admin/src/content-manager/components/RelationInput/RelationInput.js +59 -43
  11. package/admin/src/content-manager/hooks/useRelation/useRelation.js +85 -86
  12. package/admin/src/content-manager/pages/ListView/index.js +1 -0
  13. package/admin/src/content-manager/utils/schema.js +22 -0
  14. package/admin/src/index.js +7 -1
  15. package/admin/src/pages/MarketplacePage/components/EmptyNpmPackageSearch/index.js +1 -1
  16. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/index.js +0 -1
  17. package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +1 -1
  18. package/admin/src/translations/en.json +17 -0
  19. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +1 -1
  20. package/ee/server/bootstrap.js +1 -1
  21. package/ee/server/content-types/workflow-stage/index.js +1 -1
  22. package/ee/server/controllers/admin.js +1 -1
  23. package/ee/server/controllers/user.js +1 -1
  24. package/ee/server/destroy.js +1 -1
  25. package/ee/server/register.js +1 -1
  26. package/ee/server/routes/utils.js +1 -1
  27. package/ee/server/services/audit-logs.js +1 -1
  28. package/ee/server/services/passport/sso.js +1 -1
  29. package/ee/server/services/passport.js +1 -1
  30. package/ee/server/services/seat-enforcement.js +1 -1
  31. package/ee/server/utils/sso-lock.js +1 -1
  32. package/ee/server/validation/role.js +1 -1
  33. package/ee/server/validation/user.js +1 -1
  34. package/package.json +21 -13
  35. package/server/controllers/admin.js +1 -1
  36. package/server/domain/permission/index.js +8 -1
  37. package/server/validation/permission.js +1 -1
  38. package/utils/plugins.js +7 -1
  39. package/build/0cd5f8915b265d5b1856.png +0 -0
  40. package/build/1049.bf5230e6.chunk.js +0 -1
  41. package/build/1227.4f48119b.chunk.js +0 -1
  42. package/build/1386.8c070d59.chunk.js +0 -7
  43. package/build/1727.b49f0713.chunk.js +0 -1
  44. package/build/19eb2dfcf2603eb55733.png +0 -0
  45. package/build/2225.73c9a224.chunk.js +0 -79
  46. package/build/2379.27c6ab54.chunk.js +0 -1
  47. package/build/2395.120256d6.chunk.js +0 -26
  48. package/build/2659.cb94f1e7.chunk.js +0 -105
  49. package/build/27d16aefee06412db90a.png +0 -0
  50. package/build/2801.fdd4d8a2.chunk.js +0 -1
  51. package/build/2950.216f2e89.chunk.js +0 -1
  52. package/build/3021.33ad47fb.chunk.js +0 -103
  53. package/build/3100.2ba4df95.chunk.js +0 -1
  54. package/build/311.cb0884bb.chunk.js +0 -1
  55. package/build/3483.f6b2439f.chunk.js +0 -1
  56. package/build/3911.488fbde3.chunk.js +0 -95
  57. package/build/4174.924ebd4c.chunk.js +0 -1
  58. package/build/4546.9710f321.chunk.js +0 -1
  59. package/build/502.d26ea06b.chunk.js +0 -1
  60. package/build/5158.c85f841a.chunk.js +0 -1
  61. package/build/6158.c3c13c20.chunk.js +0 -1
  62. package/build/6266.c652bdb1.chunk.js +0 -146
  63. package/build/6272.4017459a.chunk.js +0 -160
  64. package/build/6715.48e37308.chunk.js +0 -1
  65. package/build/6812.31979984.chunk.js +0 -26
  66. package/build/7030.b98dcedf.chunk.js +0 -1
  67. package/build/70674f63fc3904c20de0.svg +0 -7
  68. package/build/7464.d3d1414f.chunk.js +0 -1
  69. package/build/78.dcc6df5c.chunk.js +0 -1
  70. package/build/7897.cf22d5fe.chunk.js +0 -6
  71. package/build/7e9af4fb7e723fcebf1f.svg +0 -48
  72. package/build/8276.b55f4600.chunk.js +0 -26
  73. package/build/918.54414509.chunk.js +0 -1
  74. package/build/9d5d788027e86620c234.svg +0 -5
  75. package/build/Admin-authenticatedApp.a687d9c6.chunk.js +0 -112
  76. package/build/Admin_InternalErrorPage.18ba7fd6.chunk.js +0 -1
  77. package/build/Admin_homePage.86c8d4c9.chunk.js +0 -81
  78. package/build/Admin_marketplace.e75c9cf4.chunk.js +0 -55
  79. package/build/Admin_pluginsPage.d1afbf28.chunk.js +0 -6
  80. package/build/Admin_profilePage.079903a3.chunk.js +0 -13
  81. package/build/Admin_settingsPage.60a3e46e.chunk.js +0 -111
  82. package/build/Upload_ConfigureTheView.949535b8.chunk.js +0 -1
  83. package/build/admin-app.4654dc77.chunk.js +0 -36
  84. package/build/admin-edit-roles-page.6597d934.chunk.js +0 -267
  85. package/build/admin-edit-users.3014605e.chunk.js +0 -10
  86. package/build/admin-roles-list.ab6fcfb7.chunk.js +0 -22
  87. package/build/admin-users.81bf5f4d.chunk.js +0 -11
  88. package/build/api-tokens-create-page.f5a85725.chunk.js +0 -1
  89. package/build/api-tokens-edit-page.06a32cef.chunk.js +0 -1
  90. package/build/api-tokens-list-page.bb27d544.chunk.js +0 -16
  91. package/build/ar-json.74e40bc7.chunk.js +0 -1
  92. package/build/audit-logs-settings-page.4eb6cdf8.chunk.js +0 -1
  93. package/build/bb3108f7fd1e6179bde1.svg +0 -1
  94. package/build/bb4d0d527bdfb161bc5a.svg +0 -1
  95. package/build/ca-json.fc6001d3.chunk.js +0 -1
  96. package/build/content-manager.9187db78.chunk.js +0 -1097
  97. package/build/content-type-builder-list-view.b75fb938.chunk.js +0 -211
  98. package/build/content-type-builder-translation-ar-json.3e808e2f.chunk.js +0 -1
  99. package/build/content-type-builder-translation-cs-json.1ef9e106.chunk.js +0 -1
  100. package/build/content-type-builder-translation-de-json.63fcff7b.chunk.js +0 -1
  101. package/build/content-type-builder-translation-dk-json.fd626b67.chunk.js +0 -1
  102. package/build/content-type-builder-translation-en-json.ed29ff4d.chunk.js +0 -1
  103. package/build/content-type-builder-translation-es-json.a4a361a9.chunk.js +0 -1
  104. package/build/content-type-builder-translation-fr-json.499c3a46.chunk.js +0 -1
  105. package/build/content-type-builder-translation-id-json.65255f93.chunk.js +0 -1
  106. package/build/content-type-builder-translation-it-json.e268ab74.chunk.js +0 -1
  107. package/build/content-type-builder-translation-ja-json.9be0d5b2.chunk.js +0 -1
  108. package/build/content-type-builder-translation-ko-json.04cb309d.chunk.js +0 -1
  109. package/build/content-type-builder-translation-ms-json.f6b743b9.chunk.js +0 -1
  110. package/build/content-type-builder-translation-nl-json.997fe8cc.chunk.js +0 -1
  111. package/build/content-type-builder-translation-pl-json.634f638b.chunk.js +0 -1
  112. package/build/content-type-builder-translation-pt-BR-json.6a95dc71.chunk.js +0 -1
  113. package/build/content-type-builder-translation-pt-json.ddb44f8c.chunk.js +0 -1
  114. package/build/content-type-builder-translation-ru-json.3af65503.chunk.js +0 -1
  115. package/build/content-type-builder-translation-sk-json.c6078082.chunk.js +0 -1
  116. package/build/content-type-builder-translation-sv-json.a6df2462.chunk.js +0 -1
  117. package/build/content-type-builder-translation-th-json.122277cc.chunk.js +0 -1
  118. package/build/content-type-builder-translation-tr-json.41f44f77.chunk.js +0 -1
  119. package/build/content-type-builder-translation-uk-json.e1315acd.chunk.js +0 -1
  120. package/build/content-type-builder-translation-zh-Hans-json.6ff57db6.chunk.js +0 -1
  121. package/build/content-type-builder-translation-zh-json.3532b962.chunk.js +0 -1
  122. package/build/content-type-builder.5ff93edd.chunk.js +0 -170
  123. package/build/cs-json.4b44411c.chunk.js +0 -1
  124. package/build/de-json.e72545cf.chunk.js +0 -1
  125. package/build/dk-json.e77140ef.chunk.js +0 -1
  126. package/build/email-settings-page.07d417d5.chunk.js +0 -11
  127. package/build/email-translation-ar-json.88304564.chunk.js +0 -1
  128. package/build/email-translation-cs-json.6eaeec6a.chunk.js +0 -1
  129. package/build/email-translation-de-json.1b334230.chunk.js +0 -1
  130. package/build/email-translation-dk-json.85402492.chunk.js +0 -1
  131. package/build/email-translation-en-json.4211d4d0.chunk.js +0 -1
  132. package/build/email-translation-es-json.0b6b1006.chunk.js +0 -1
  133. package/build/email-translation-fr-json.78be2787.chunk.js +0 -1
  134. package/build/email-translation-id-json.c97239fe.chunk.js +0 -1
  135. package/build/email-translation-it-json.a2ed8c78.chunk.js +0 -1
  136. package/build/email-translation-ja-json.63eebd02.chunk.js +0 -1
  137. package/build/email-translation-ko-json.4de49b23.chunk.js +0 -1
  138. package/build/email-translation-ms-json.7390477e.chunk.js +0 -1
  139. package/build/email-translation-nl-json.377bdd9f.chunk.js +0 -1
  140. package/build/email-translation-pl-json.97d0db97.chunk.js +0 -1
  141. package/build/email-translation-pt-BR-json.81cca553.chunk.js +0 -1
  142. package/build/email-translation-pt-json.2a2a0643.chunk.js +0 -1
  143. package/build/email-translation-ru-json.6bce37dd.chunk.js +0 -1
  144. package/build/email-translation-sk-json.53da2fcd.chunk.js +0 -1
  145. package/build/email-translation-th-json.660fa9a8.chunk.js +0 -1
  146. package/build/email-translation-tr-json.e6c0f8fc.chunk.js +0 -1
  147. package/build/email-translation-uk-json.bd1fb6bf.chunk.js +0 -1
  148. package/build/email-translation-vi-json.9fb7e6d7.chunk.js +0 -1
  149. package/build/email-translation-zh-Hans-json.c6841563.chunk.js +0 -1
  150. package/build/email-translation-zh-json.7a2232ea.chunk.js +0 -1
  151. package/build/en-json.e12fd5fc.chunk.js +0 -1
  152. package/build/es-json.b1f2284b.chunk.js +0 -1
  153. package/build/eu-json.63d0a898.chunk.js +0 -1
  154. package/build/fr-json.33c6428b.chunk.js +0 -1
  155. package/build/gu-json.7efe8cc2.chunk.js +0 -1
  156. package/build/he-json.3cf0b48a.chunk.js +0 -1
  157. package/build/hi-json.0d633692.chunk.js +0 -1
  158. package/build/highlight.js.28a1547e.chunk.js +0 -85
  159. package/build/hu-json.c74b6a1e.chunk.js +0 -1
  160. package/build/i18n-settings-page.7107e28a.chunk.js +0 -9
  161. package/build/i18n-translation-de-json.362384a6.chunk.js +0 -1
  162. package/build/i18n-translation-dk-json.89401417.chunk.js +0 -1
  163. package/build/i18n-translation-en-json.1ec7becf.chunk.js +0 -1
  164. package/build/i18n-translation-es-json.87b494d1.chunk.js +0 -1
  165. package/build/i18n-translation-fr-json.57ddc77e.chunk.js +0 -1
  166. package/build/i18n-translation-ko-json.ef4f9471.chunk.js +0 -1
  167. package/build/i18n-translation-pl-json.dfac513d.chunk.js +0 -1
  168. package/build/i18n-translation-ru-json.a3dbc125.chunk.js +0 -1
  169. package/build/i18n-translation-tr-json.3bfc812f.chunk.js +0 -1
  170. package/build/i18n-translation-zh-Hans-json.757ce62d.chunk.js +0 -1
  171. package/build/i18n-translation-zh-json.bef2dc07.chunk.js +0 -1
  172. package/build/id-json.41e07c46.chunk.js +0 -1
  173. package/build/index.html +0 -1
  174. package/build/it-json.bfe27ed8.chunk.js +0 -1
  175. package/build/ja-json.e1959a1c.chunk.js +0 -1
  176. package/build/ko-json.ce5d6d94.chunk.js +0 -1
  177. package/build/main.da000219.js +0 -2860
  178. package/build/ml-json.940d7ace.chunk.js +0 -1
  179. package/build/ms-json.0eddffd9.chunk.js +0 -1
  180. package/build/nl-json.fe38f0fb.chunk.js +0 -1
  181. package/build/no-json.19a2dbfa.chunk.js +0 -1
  182. package/build/pl-json.d55e8e78.chunk.js +0 -1
  183. package/build/pt-BR-json.ae0a0d2e.chunk.js +0 -1
  184. package/build/pt-json.ee554a41.chunk.js +0 -1
  185. package/build/review-workflows-settings-create-view.5d8806b2.chunk.js +0 -1
  186. package/build/review-workflows-settings-edit-view.634903ed.chunk.js +0 -1
  187. package/build/review-workflows-settings-list-view.d138c3b5.chunk.js +0 -56
  188. package/build/ru-json.1c976644.chunk.js +0 -1
  189. package/build/runtime~main.9589b498.js +0 -2
  190. package/build/sa-json.2c03ef4e.chunk.js +0 -1
  191. package/build/sk-json.b41847e8.chunk.js +0 -1
  192. package/build/sso-settings-page.caa35f7b.chunk.js +0 -1
  193. package/build/sv-json.568cb7ae.chunk.js +0 -1
  194. package/build/th-json.5f659396.chunk.js +0 -1
  195. package/build/tr-json.c9f22432.chunk.js +0 -1
  196. package/build/transfer-tokens-create-page.6e7049ff.chunk.js +0 -1
  197. package/build/transfer-tokens-edit-page.449b4502.chunk.js +0 -1
  198. package/build/transfer-tokens-list-page.34caf827.chunk.js +0 -16
  199. package/build/uk-json.b7e38370.chunk.js +0 -1
  200. package/build/upload-settings.fede24b9.chunk.js +0 -14
  201. package/build/upload-translation-ca-json.57954414.chunk.js +0 -1
  202. package/build/upload-translation-de-json.420c943b.chunk.js +0 -1
  203. package/build/upload-translation-dk-json.bbb2fa05.chunk.js +0 -1
  204. package/build/upload-translation-en-json.8b7573ce.chunk.js +0 -1
  205. package/build/upload-translation-es-json.ba2eb03a.chunk.js +0 -1
  206. package/build/upload-translation-fr-json.baab9911.chunk.js +0 -1
  207. package/build/upload-translation-he-json.0a830937.chunk.js +0 -1
  208. package/build/upload-translation-it-json.e87d7966.chunk.js +0 -1
  209. package/build/upload-translation-ja-json.44b88e7a.chunk.js +0 -1
  210. package/build/upload-translation-ko-json.a52eab64.chunk.js +0 -1
  211. package/build/upload-translation-ms-json.74f6d746.chunk.js +0 -1
  212. package/build/upload-translation-pl-json.426f31c9.chunk.js +0 -1
  213. package/build/upload-translation-pt-BR-json.d1704f0b.chunk.js +0 -1
  214. package/build/upload-translation-pt-json.6b937fdf.chunk.js +0 -1
  215. package/build/upload-translation-ru-json.675f6b93.chunk.js +0 -1
  216. package/build/upload-translation-sk-json.483a18f6.chunk.js +0 -1
  217. package/build/upload-translation-th-json.98d35574.chunk.js +0 -1
  218. package/build/upload-translation-tr-json.74117e5c.chunk.js +0 -1
  219. package/build/upload-translation-uk-json.9950466a.chunk.js +0 -1
  220. package/build/upload-translation-zh-Hans-json.db163b6b.chunk.js +0 -1
  221. package/build/upload-translation-zh-json.e1dd6eb2.chunk.js +0 -1
  222. package/build/upload.a96e2452.chunk.js +0 -58
  223. package/build/users-advanced-settings-page.ac7968e7.chunk.js +0 -9
  224. package/build/users-email-settings-page.125a89e2.chunk.js +0 -9
  225. package/build/users-permissions-translation-ar-json.7d87d54d.chunk.js +0 -1
  226. package/build/users-permissions-translation-cs-json.7e23424a.chunk.js +0 -1
  227. package/build/users-permissions-translation-de-json.a6fb670f.chunk.js +0 -1
  228. package/build/users-permissions-translation-dk-json.60e50f48.chunk.js +0 -1
  229. package/build/users-permissions-translation-en-json.4b302272.chunk.js +0 -1
  230. package/build/users-permissions-translation-es-json.35007573.chunk.js +0 -1
  231. package/build/users-permissions-translation-fr-json.7e55bbbb.chunk.js +0 -1
  232. package/build/users-permissions-translation-id-json.a5a0fb59.chunk.js +0 -1
  233. package/build/users-permissions-translation-it-json.0705465d.chunk.js +0 -1
  234. package/build/users-permissions-translation-ja-json.891fe76e.chunk.js +0 -1
  235. package/build/users-permissions-translation-ko-json.357d7a33.chunk.js +0 -1
  236. package/build/users-permissions-translation-ms-json.c83f87c4.chunk.js +0 -1
  237. package/build/users-permissions-translation-nl-json.c9f92a3c.chunk.js +0 -1
  238. package/build/users-permissions-translation-pl-json.0a7287d1.chunk.js +0 -1
  239. package/build/users-permissions-translation-pt-BR-json.1b6d2920.chunk.js +0 -1
  240. package/build/users-permissions-translation-pt-json.a7eda429.chunk.js +0 -1
  241. package/build/users-permissions-translation-ru-json.8e883c67.chunk.js +0 -1
  242. package/build/users-permissions-translation-sk-json.7f37180f.chunk.js +0 -1
  243. package/build/users-permissions-translation-sv-json.17187818.chunk.js +0 -1
  244. package/build/users-permissions-translation-th-json.1e9c0247.chunk.js +0 -1
  245. package/build/users-permissions-translation-tr-json.2bd7ff98.chunk.js +0 -1
  246. package/build/users-permissions-translation-uk-json.6a0a1572.chunk.js +0 -1
  247. package/build/users-permissions-translation-vi-json.6722a8a2.chunk.js +0 -1
  248. package/build/users-permissions-translation-zh-Hans-json.8d82c809.chunk.js +0 -1
  249. package/build/users-permissions-translation-zh-json.7978eaa6.chunk.js +0 -1
  250. package/build/users-providers-settings-page.ce34951c.chunk.js +0 -14
  251. package/build/users-roles-settings-page.d415835a.chunk.js +0 -55
  252. package/build/vi-json.ee4c5537.chunk.js +0 -1
  253. package/build/webhook-edit-page.7498417e.chunk.js +0 -33
  254. package/build/webhook-list-page.1b085c7f.chunk.js +0 -63
  255. package/build/zh-Hans-json.97efd015.chunk.js +0 -1
  256. package/build/zh-json.bfc2e036.chunk.js +0 -1
@@ -31,7 +31,7 @@ const RBACProvider = ({ children, permissions, refetchPermissions }) => {
31
31
  };
32
32
 
33
33
  RBACProvider.propTypes = {
34
- children: PropTypes.element.isRequired,
34
+ children: PropTypes.node.isRequired,
35
35
  permissions: PropTypes.array.isRequired,
36
36
  refetchPermissions: PropTypes.func.isRequired,
37
37
  };
@@ -0,0 +1,87 @@
1
+ import * as React from 'react';
2
+
3
+ import PropTypes from 'prop-types';
4
+ import { Editable, useSlate } from 'slate-react';
5
+ import { useTheme } from 'styled-components';
6
+
7
+ import { useBlocksStore } from '../hooks/useBlocksStore';
8
+ import { useModifiersStore } from '../hooks/useModifiersStore';
9
+
10
+ const getEditorStyle = (theme) => ({
11
+ // The outline style is set on the wrapper with :focus-within
12
+ outline: 'none',
13
+ display: 'flex',
14
+ flexDirection: 'column',
15
+ gap: theme.spaces[2],
16
+ });
17
+
18
+ const baseRenderLeaf = (props, modifiers) => {
19
+ // Recursively wrap the children for each active modifier
20
+ const wrappedChildren = Object.entries(modifiers).reduce((currentChildren, modifierEntry) => {
21
+ const [name, modifier] = modifierEntry;
22
+
23
+ if (props.leaf[name]) {
24
+ return modifier.renderLeaf(currentChildren);
25
+ }
26
+
27
+ return currentChildren;
28
+ }, props.children);
29
+
30
+ return <span {...props.attributes}>{wrappedChildren}</span>;
31
+ };
32
+
33
+ const baseRenderElement = (props, blocks) => {
34
+ const blockMatch = Object.values(blocks).find((block) => block.matchNode(props.element));
35
+ const block = blockMatch || blocks.paragraph;
36
+
37
+ return block.renderElement(props);
38
+ };
39
+
40
+ const BlocksInput = ({ readOnly }) => {
41
+ const theme = useTheme();
42
+ const editor = useSlate();
43
+
44
+ // Create renderLeaf function based on the modifiers store
45
+ const modifiers = useModifiersStore();
46
+ const renderLeaf = React.useCallback((props) => baseRenderLeaf(props, modifiers), [modifiers]);
47
+
48
+ // Create renderElement function base on the blocks store
49
+ const blocks = useBlocksStore();
50
+ const renderElement = React.useCallback((props) => baseRenderElement(props, blocks), [blocks]);
51
+
52
+ const handleEnter = () => {
53
+ const selectedNode = editor.children[editor.selection.anchor.path[0]];
54
+ const selectedBlock = Object.values(blocks).find((block) => block.matchNode(selectedNode));
55
+
56
+ // Check if there's an enter handler for the selected block
57
+ if (selectedBlock.handleEnterKey) {
58
+ selectedBlock.handleEnterKey(editor);
59
+ } else {
60
+ // If not, insert a new paragraph
61
+ blocks.paragraph.handleEnterKey(editor);
62
+ }
63
+ };
64
+
65
+ const handleKeyDown = (event) => {
66
+ if (event.key === 'Enter') {
67
+ event.preventDefault();
68
+ handleEnter();
69
+ }
70
+ };
71
+
72
+ return (
73
+ <Editable
74
+ readOnly={readOnly}
75
+ style={getEditorStyle(theme)}
76
+ renderElement={renderElement}
77
+ renderLeaf={renderLeaf}
78
+ onKeyDown={handleKeyDown}
79
+ />
80
+ );
81
+ };
82
+
83
+ BlocksInput.propTypes = {
84
+ readOnly: PropTypes.bool.isRequired,
85
+ };
86
+
87
+ export default BlocksInput;
@@ -0,0 +1,463 @@
1
+ import * as React from 'react';
2
+
3
+ import * as Toolbar from '@radix-ui/react-toolbar';
4
+ import { Flex, Icon, Tooltip, Select, Option, Box, Typography } from '@strapi/design-system';
5
+ import { pxToRem, prefixFileUrlWithBackendUrl, useLibrary } from '@strapi/helper-plugin';
6
+ import { BulletList, NumberList } from '@strapi/icons';
7
+ import PropTypes from 'prop-types';
8
+ import { useIntl } from 'react-intl';
9
+ import { Editor, Transforms, Element as SlateElement } from 'slate';
10
+ import { useSlate } from 'slate-react';
11
+ import styled from 'styled-components';
12
+
13
+ import { useBlocksStore } from '../hooks/useBlocksStore';
14
+ import { useModifiersStore } from '../hooks/useModifiersStore';
15
+
16
+ const Separator = styled(Toolbar.Separator)`
17
+ background: ${({ theme }) => theme.colors.neutral150};
18
+ width: 1px;
19
+ height: ${pxToRem(24)};
20
+ `;
21
+
22
+ const FlexButton = styled(Flex).attrs({ as: 'button' })`
23
+ &:hover {
24
+ background: ${({ theme }) => theme.colors.primary100};
25
+ }
26
+ `;
27
+
28
+ const ToolbarButton = ({ icon, name, label, isActive, handleClick }) => {
29
+ const { formatMessage } = useIntl();
30
+ const labelMessage = formatMessage(label);
31
+
32
+ return (
33
+ <Tooltip description={labelMessage}>
34
+ <Toolbar.ToggleItem value={name} data-state={isActive ? 'on' : 'off'} asChild>
35
+ <FlexButton
36
+ background={isActive ? 'primary100' : ''}
37
+ alignItems="center"
38
+ justifyContent="center"
39
+ width={7}
40
+ height={7}
41
+ hasRadius
42
+ onMouseDown={(e) => {
43
+ e.preventDefault();
44
+ handleClick();
45
+ }}
46
+ aria-label={labelMessage}
47
+ >
48
+ <Icon width={3} height={3} as={icon} color={isActive ? 'primary600' : 'neutral600'} />
49
+ </FlexButton>
50
+ </Toolbar.ToggleItem>
51
+ </Tooltip>
52
+ );
53
+ };
54
+
55
+ ToolbarButton.propTypes = {
56
+ icon: PropTypes.elementType.isRequired,
57
+ name: PropTypes.string.isRequired,
58
+ label: PropTypes.shape({
59
+ id: PropTypes.string.isRequired,
60
+ defaultMessage: PropTypes.string.isRequired,
61
+ }).isRequired,
62
+ isActive: PropTypes.bool.isRequired,
63
+ handleClick: PropTypes.func.isRequired,
64
+ };
65
+
66
+ const ModifierButton = ({ icon, name, label }) => {
67
+ const editor = useSlate();
68
+
69
+ const isModifierActive = () => {
70
+ const modifiers = Editor.marks(editor);
71
+
72
+ if (!modifiers) return false;
73
+
74
+ return Boolean(modifiers[name]);
75
+ };
76
+
77
+ const isActive = isModifierActive();
78
+
79
+ const toggleModifier = () => {
80
+ if (isActive) {
81
+ Editor.removeMark(editor, name);
82
+ } else {
83
+ Editor.addMark(editor, name, true);
84
+ }
85
+ };
86
+
87
+ return (
88
+ <ToolbarButton
89
+ icon={icon}
90
+ name={name}
91
+ label={label}
92
+ isActive={isActive}
93
+ handleClick={toggleModifier}
94
+ />
95
+ );
96
+ };
97
+
98
+ ModifierButton.propTypes = {
99
+ icon: PropTypes.elementType.isRequired,
100
+ name: PropTypes.string.isRequired,
101
+ label: PropTypes.shape({
102
+ id: PropTypes.string.isRequired,
103
+ defaultMessage: PropTypes.string.isRequired,
104
+ }).isRequired,
105
+ };
106
+
107
+ const isBlockActive = (editor, matchNode) => {
108
+ const { selection } = editor;
109
+
110
+ if (!selection) return false;
111
+
112
+ const match = Array.from(
113
+ Editor.nodes(editor, {
114
+ at: Editor.unhangRange(editor, selection),
115
+ match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && matchNode(n),
116
+ })
117
+ );
118
+
119
+ return match.length > 0;
120
+ };
121
+
122
+ const toggleBlock = (editor, value) => {
123
+ const { type, level } = value;
124
+
125
+ const newProperties = {
126
+ type,
127
+ level: level || null,
128
+ };
129
+
130
+ Transforms.setNodes(editor, newProperties);
131
+ };
132
+
133
+ const ALLOWED_MEDIA_TYPE = 'images';
134
+
135
+ const IMAGE_SCHEMA_FIELDS = [
136
+ 'name',
137
+ 'alternativeText',
138
+ 'url',
139
+ 'caption',
140
+ 'width',
141
+ 'height',
142
+ 'formats',
143
+ 'hash',
144
+ 'ext',
145
+ 'mime',
146
+ 'size',
147
+ 'previewUrl',
148
+ 'provider',
149
+ 'provider_metadata',
150
+ 'createdAt',
151
+ 'updatedAt',
152
+ ];
153
+
154
+ const pick = (object, imageSchemaFields) => {
155
+ return Object.keys(object).reduce((acc, key) => {
156
+ if (imageSchemaFields.includes(key)) {
157
+ acc[key] = object[key];
158
+ }
159
+
160
+ return acc;
161
+ }, {});
162
+ };
163
+
164
+ const ImageDialog = ({ handleClose }) => {
165
+ const editor = useSlate();
166
+ const { components } = useLibrary();
167
+ const MediaLibraryDialog = components['media-library'];
168
+
169
+ const insertImages = (images) => {
170
+ images.forEach((img) => {
171
+ const image = { type: 'image', image: img, children: [{ type: 'text', text: '' }] };
172
+ Transforms.insertNodes(editor, image);
173
+ });
174
+ };
175
+
176
+ const handleSelectAssets = (images) => {
177
+ const formattedImages = images.map((image) => {
178
+ // create an object with imageSchema defined and exclude unnecessary props coming from media library config
179
+ const expectedImage = pick(image, IMAGE_SCHEMA_FIELDS);
180
+
181
+ return {
182
+ ...expectedImage,
183
+ alternativeText: expectedImage.alternativeText || expectedImage.name,
184
+ url: prefixFileUrlWithBackendUrl(image.url),
185
+ };
186
+ });
187
+
188
+ insertImages(formattedImages);
189
+ handleClose();
190
+ };
191
+
192
+ return (
193
+ <MediaLibraryDialog
194
+ allowedTypes={[ALLOWED_MEDIA_TYPE]}
195
+ onClose={handleClose}
196
+ onSelectAssets={handleSelectAssets}
197
+ />
198
+ );
199
+ };
200
+
201
+ ImageDialog.propTypes = {
202
+ handleClose: PropTypes.func.isRequired,
203
+ };
204
+
205
+ const isLastBlockImageOrCode = (editor) => {
206
+ const { selection } = editor;
207
+
208
+ if (!selection) return false;
209
+
210
+ const [currentImageORCodeBlock] = Editor.nodes(editor, {
211
+ at: selection,
212
+ match: (n) => n.type === 'image' || n.type === 'code',
213
+ });
214
+
215
+ if (currentImageORCodeBlock) {
216
+ const [, currentNodePath] = currentImageORCodeBlock;
217
+
218
+ const isNodeAfter = Boolean(Editor.after(editor, currentNodePath));
219
+
220
+ return !isNodeAfter;
221
+ }
222
+
223
+ return false;
224
+ };
225
+
226
+ export const BlocksDropdown = () => {
227
+ const editor = useSlate();
228
+ const { formatMessage } = useIntl();
229
+ const [isMediaLibraryVisible, setIsMediaLibraryVisible] = React.useState(false);
230
+
231
+ const blocks = useBlocksStore();
232
+ const blockKeysToInclude = Object.entries(blocks).reduce((currentKeys, entry) => {
233
+ const [key, block] = entry;
234
+
235
+ return block.isInBlocksSelector ? [...currentKeys, key] : currentKeys;
236
+ }, []);
237
+
238
+ const [blockSelected, setBlockSelected] = React.useState(Object.keys(blocks)[0]);
239
+
240
+ /**
241
+ * @param {string} optionKey - key of the heading selected
242
+ */
243
+ const selectOption = (optionKey) => {
244
+ toggleBlock(editor, blocks[optionKey].value);
245
+
246
+ setBlockSelected(optionKey);
247
+
248
+ if (isLastBlockImageOrCode(editor)) {
249
+ // insert blank line to add new blocks below code or image blocks
250
+ Transforms.insertNodes(
251
+ editor,
252
+ {
253
+ type: 'paragraph',
254
+ children: [{ type: 'text', text: '' }],
255
+ },
256
+ { at: [editor.children.length] }
257
+ );
258
+ }
259
+
260
+ if (optionKey === 'image') {
261
+ setIsMediaLibraryVisible(true);
262
+ }
263
+ };
264
+
265
+ return (
266
+ <>
267
+ <Select
268
+ startIcon={<Icon as={blocks[blockSelected].icon} />}
269
+ onChange={selectOption}
270
+ placeholder={blocks[blockSelected].label}
271
+ value={blockSelected}
272
+ aria-label={formatMessage({
273
+ id: 'components.Blocks.blocks.selectBlock',
274
+ defaultMessage: 'Select a block',
275
+ })}
276
+ >
277
+ {blockKeysToInclude.map((key) => (
278
+ <BlockOption
279
+ key={key}
280
+ value={key}
281
+ label={blocks[key].label}
282
+ icon={blocks[key].icon}
283
+ matchNode={blocks[key].matchNode}
284
+ handleSelection={setBlockSelected}
285
+ blockSelected={blockSelected}
286
+ />
287
+ ))}
288
+ </Select>
289
+ {isMediaLibraryVisible && <ImageDialog handleClose={() => setIsMediaLibraryVisible(false)} />}
290
+ </>
291
+ );
292
+ };
293
+
294
+ const BlockOption = ({ value, icon, label, handleSelection, blockSelected, matchNode }) => {
295
+ const { formatMessage } = useIntl();
296
+ const editor = useSlate();
297
+
298
+ const isActive = isBlockActive(editor, matchNode);
299
+ const isSelected = value === blockSelected;
300
+
301
+ React.useEffect(() => {
302
+ if (isActive && !isSelected) {
303
+ handleSelection(value);
304
+ }
305
+ }, [handleSelection, isActive, isSelected, value]);
306
+
307
+ return (
308
+ <Option
309
+ startIcon={<Icon as={icon} color={isSelected ? 'primary600' : 'neutral600'} />}
310
+ value={value}
311
+ >
312
+ {formatMessage(label)}
313
+ </Option>
314
+ );
315
+ };
316
+
317
+ BlockOption.propTypes = {
318
+ icon: PropTypes.elementType.isRequired,
319
+ value: PropTypes.string.isRequired,
320
+ label: PropTypes.shape({
321
+ id: PropTypes.string.isRequired,
322
+ defaultMessage: PropTypes.string.isRequired,
323
+ }).isRequired,
324
+ matchNode: PropTypes.func.isRequired,
325
+ handleSelection: PropTypes.func.isRequired,
326
+ blockSelected: PropTypes.string.isRequired,
327
+ };
328
+
329
+ const ListButton = ({ icon, format, label }) => {
330
+ const editor = useSlate();
331
+
332
+ /**
333
+ *
334
+ * @param {import('slate').Node} node
335
+ * @returns boolean
336
+ */
337
+ const isListNode = (node) => {
338
+ return !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === 'list';
339
+ };
340
+
341
+ const isListActive = () => {
342
+ const { selection } = editor;
343
+
344
+ if (!selection) return false;
345
+
346
+ const [match] = Array.from(
347
+ Editor.nodes(editor, {
348
+ at: Editor.unhangRange(editor, selection),
349
+ match: (node) => isListNode(node) && node.format === format,
350
+ })
351
+ );
352
+
353
+ return Boolean(match);
354
+ };
355
+
356
+ const isActive = isListActive();
357
+
358
+ const toggleList = () => {
359
+ // Delete the parent list so that we're left with only the list items directly
360
+ Transforms.unwrapNodes(editor, {
361
+ match: (node) => isListNode(node) && ['ordered', 'unordered'].includes(node.format),
362
+ split: true,
363
+ });
364
+
365
+ // Change the type of the current selection
366
+ Transforms.setNodes(editor, {
367
+ type: isActive ? 'paragraph' : 'list-item',
368
+ });
369
+
370
+ // If the selection is now a list item, wrap it inside a list
371
+ if (!isActive) {
372
+ const block = { type: 'list', format, children: [] };
373
+ Transforms.wrapNodes(editor, block);
374
+ }
375
+ };
376
+
377
+ return (
378
+ <ToolbarButton
379
+ icon={icon}
380
+ name={format}
381
+ label={label}
382
+ isActive={isActive}
383
+ handleClick={toggleList}
384
+ />
385
+ );
386
+ };
387
+
388
+ ListButton.propTypes = {
389
+ icon: PropTypes.elementType.isRequired,
390
+ format: PropTypes.string.isRequired,
391
+ label: PropTypes.shape({
392
+ id: PropTypes.string.isRequired,
393
+ defaultMessage: PropTypes.string.isRequired,
394
+ }).isRequired,
395
+ };
396
+
397
+ // TODO: Remove after the RTE Blocks Alpha release
398
+ const AlphaTag = styled(Box)`
399
+ background-color: ${({ theme }) => theme.colors.warning100};
400
+ border: ${({ theme }) => `1px solid ${theme.colors.warning200}`};
401
+ border-radius: ${({ theme }) => theme.borderRadius};
402
+ font-size: ${({ theme }) => theme.fontSizes[0]};
403
+ padding: ${({ theme }) => `${2 / 16}rem ${theme.spaces[1]}`};
404
+ `;
405
+
406
+ const BlocksToolbar = () => {
407
+ const modifiers = useModifiersStore();
408
+
409
+ return (
410
+ <Toolbar.Root asChild>
411
+ {/* Remove after the RTE Blocks Alpha release (paddingRight and width) */}
412
+ <Flex gap={1} padding={2} paddingRight={4} width="100%">
413
+ <BlocksDropdown />
414
+ <Separator />
415
+ <Toolbar.ToggleGroup type="multiple" asChild>
416
+ <Flex gap={1}>
417
+ {Object.entries(modifiers).map(([name, modifier]) => (
418
+ <ToolbarButton
419
+ key={name}
420
+ name={name}
421
+ icon={modifier.icon}
422
+ label={modifier.label}
423
+ isActive={modifier.checkIsActive()}
424
+ handleClick={modifier.handleToggle}
425
+ />
426
+ ))}
427
+ </Flex>
428
+ </Toolbar.ToggleGroup>
429
+ <Separator />
430
+ <Toolbar.ToggleGroup type="single" asChild>
431
+ <Flex gap={1}>
432
+ <ListButton
433
+ label={{
434
+ id: 'components.Blocks.blocks.unorderedList',
435
+ defaultMessage: 'Bulleted list',
436
+ }}
437
+ format="unordered"
438
+ icon={BulletList}
439
+ />
440
+ <ListButton
441
+ label={{
442
+ id: 'components.Blocks.blocks.orderedList',
443
+ defaultMessage: 'Numbered list',
444
+ }}
445
+ format="ordered"
446
+ icon={NumberList}
447
+ />
448
+ </Flex>
449
+ </Toolbar.ToggleGroup>
450
+ {/* TODO: Remove after the RTE Blocks Alpha release */}
451
+ <Flex grow={1} justifyContent="flex-end">
452
+ <AlphaTag>
453
+ <Typography textColor="warning600" variant="sigma">
454
+ ALPHA
455
+ </Typography>
456
+ </AlphaTag>
457
+ </Flex>
458
+ </Flex>
459
+ </Toolbar.Root>
460
+ );
461
+ };
462
+
463
+ export { BlocksToolbar };