@strapi/admin 4.14.0-beta.0 → 4.14.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 (104) 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 +84 -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/build/{1049.bf5230e6.chunk.js → 1049.acb0e730.chunk.js} +1 -1
  20. package/build/{1227.4f48119b.chunk.js → 1227.969e24e6.chunk.js} +1 -1
  21. package/build/{1386.8c070d59.chunk.js → 1386.db9a2795.chunk.js} +1 -1
  22. package/build/{2225.73c9a224.chunk.js → 2225.78fb9b89.chunk.js} +2 -2
  23. package/build/{2379.27c6ab54.chunk.js → 2379.906334f0.chunk.js} +1 -1
  24. package/build/{2395.120256d6.chunk.js → 2395.f6ac2863.chunk.js} +1 -1
  25. package/build/2614.3e088d3e.chunk.js +35 -0
  26. package/build/{2801.fdd4d8a2.chunk.js → 2801.2afb4757.chunk.js} +1 -1
  27. package/build/{4174.924ebd4c.chunk.js → 4174.3e13fb26.chunk.js} +1 -1
  28. package/build/4546.1203ac95.chunk.js +1 -0
  29. package/build/{502.d26ea06b.chunk.js → 502.9918bff7.chunk.js} +1 -1
  30. package/build/{6266.c652bdb1.chunk.js → 6266.e8990811.chunk.js} +5 -5
  31. package/build/6812.00ef5b0d.chunk.js +26 -0
  32. package/build/{7464.d3d1414f.chunk.js → 7464.0280cf59.chunk.js} +1 -1
  33. package/build/{7897.cf22d5fe.chunk.js → 7897.4a39de37.chunk.js} +1 -1
  34. package/build/{8276.b55f4600.chunk.js → 8276.951e198e.chunk.js} +2 -2
  35. package/build/9832.65ed5a44.chunk.js +181 -0
  36. package/build/{Admin-authenticatedApp.a687d9c6.chunk.js → Admin-authenticatedApp.c0c1c027.chunk.js} +2 -2
  37. package/build/{Admin_InternalErrorPage.18ba7fd6.chunk.js → Admin_InternalErrorPage.b3163562.chunk.js} +1 -1
  38. package/build/{Admin_homePage.86c8d4c9.chunk.js → Admin_homePage.6cb51f18.chunk.js} +1 -1
  39. package/build/{Admin_marketplace.e75c9cf4.chunk.js → Admin_marketplace.3eb5e132.chunk.js} +4 -4
  40. package/build/{Admin_pluginsPage.d1afbf28.chunk.js → Admin_pluginsPage.b9fa2947.chunk.js} +1 -1
  41. package/build/{Admin_profilePage.079903a3.chunk.js → Admin_profilePage.a4d41380.chunk.js} +2 -2
  42. package/build/{Admin_settingsPage.60a3e46e.chunk.js → Admin_settingsPage.6dc2af9f.chunk.js} +1 -1
  43. package/build/{Upload_ConfigureTheView.949535b8.chunk.js → Upload_ConfigureTheView.cc7ca628.chunk.js} +1 -1
  44. package/build/{admin-app.4654dc77.chunk.js → admin-app.98cdf43a.chunk.js} +5 -5
  45. package/build/{admin-edit-roles-page.6597d934.chunk.js → admin-edit-roles-page.418bb1c5.chunk.js} +3 -3
  46. package/build/{admin-edit-users.3014605e.chunk.js → admin-edit-users.9b42cc9e.chunk.js} +2 -2
  47. package/build/{admin-roles-list.ab6fcfb7.chunk.js → admin-roles-list.cf964578.chunk.js} +1 -1
  48. package/build/{admin-users.81bf5f4d.chunk.js → admin-users.8385dd73.chunk.js} +2 -2
  49. package/build/{api-tokens-create-page.f5a85725.chunk.js → api-tokens-create-page.2f25ddf6.chunk.js} +1 -1
  50. package/build/{api-tokens-edit-page.06a32cef.chunk.js → api-tokens-edit-page.45faac16.chunk.js} +1 -1
  51. package/build/{api-tokens-list-page.bb27d544.chunk.js → api-tokens-list-page.5baabf1a.chunk.js} +2 -2
  52. package/build/{audit-logs-settings-page.4eb6cdf8.chunk.js → audit-logs-settings-page.91489670.chunk.js} +1 -1
  53. package/build/content-manager.0d2b4a60.chunk.js +1199 -0
  54. package/build/{content-type-builder-list-view.b75fb938.chunk.js → content-type-builder-list-view.aa8a5d1a.chunk.js} +2 -2
  55. package/build/content-type-builder-translation-en-json.b9e5cacd.chunk.js +1 -0
  56. package/build/content-type-builder.885f2cad.chunk.js +146 -0
  57. package/build/{email-settings-page.07d417d5.chunk.js → email-settings-page.6bd7b280.chunk.js} +1 -1
  58. package/build/{en-json.e12fd5fc.chunk.js → en-json.a3973ff5.chunk.js} +1 -1
  59. package/build/{i18n-settings-page.7107e28a.chunk.js → i18n-settings-page.6c0157e7.chunk.js} +1 -1
  60. package/build/index.html +1 -1
  61. package/build/main.105dcf23.js +2665 -0
  62. package/build/{review-workflows-settings-create-view.5d8806b2.chunk.js → review-workflows-settings-create-view.ae369a88.chunk.js} +1 -1
  63. package/build/{review-workflows-settings-edit-view.634903ed.chunk.js → review-workflows-settings-edit-view.9a61c69f.chunk.js} +1 -1
  64. package/build/{review-workflows-settings-list-view.d138c3b5.chunk.js → review-workflows-settings-list-view.067e0c35.chunk.js} +2 -2
  65. package/build/{runtime~main.9589b498.js → runtime~main.6c489074.js} +2 -2
  66. package/build/{sso-settings-page.caa35f7b.chunk.js → sso-settings-page.a29e6c38.chunk.js} +1 -1
  67. package/build/{transfer-tokens-create-page.6e7049ff.chunk.js → transfer-tokens-create-page.6e1b8cee.chunk.js} +1 -1
  68. package/build/{transfer-tokens-edit-page.449b4502.chunk.js → transfer-tokens-edit-page.10bb22e2.chunk.js} +1 -1
  69. package/build/{transfer-tokens-list-page.34caf827.chunk.js → transfer-tokens-list-page.0306652c.chunk.js} +2 -2
  70. package/build/{upload-settings.fede24b9.chunk.js → upload-settings.0af6edc5.chunk.js} +1 -1
  71. package/build/{upload.a96e2452.chunk.js → upload.19e14c8e.chunk.js} +1 -1
  72. package/build/{users-advanced-settings-page.ac7968e7.chunk.js → users-advanced-settings-page.ed69812f.chunk.js} +1 -1
  73. package/build/{users-email-settings-page.125a89e2.chunk.js → users-email-settings-page.131a00fb.chunk.js} +1 -1
  74. package/build/{users-providers-settings-page.ce34951c.chunk.js → users-providers-settings-page.b3dca41d.chunk.js} +1 -1
  75. package/build/{users-roles-settings-page.d415835a.chunk.js → users-roles-settings-page.afab5a0d.chunk.js} +4 -4
  76. package/build/{webhook-edit-page.7498417e.chunk.js → webhook-edit-page.4c037da4.chunk.js} +2 -2
  77. package/build/{webhook-list-page.1b085c7f.chunk.js → webhook-list-page.56c82f4a.chunk.js} +1 -1
  78. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +1 -1
  79. package/ee/server/bootstrap.js +1 -1
  80. package/ee/server/content-types/workflow-stage/index.js +1 -1
  81. package/ee/server/controllers/admin.js +1 -1
  82. package/ee/server/controllers/user.js +1 -1
  83. package/ee/server/destroy.js +1 -1
  84. package/ee/server/register.js +1 -1
  85. package/ee/server/routes/utils.js +1 -1
  86. package/ee/server/services/audit-logs.js +1 -1
  87. package/ee/server/services/passport/sso.js +1 -1
  88. package/ee/server/services/passport.js +1 -1
  89. package/ee/server/services/seat-enforcement.js +1 -1
  90. package/ee/server/utils/sso-lock.js +1 -1
  91. package/ee/server/validation/role.js +1 -1
  92. package/ee/server/validation/user.js +1 -1
  93. package/package.json +20 -12
  94. package/server/controllers/admin.js +1 -1
  95. package/server/domain/permission/index.js +8 -1
  96. package/server/validation/permission.js +1 -1
  97. package/utils/plugins.js +7 -1
  98. package/build/4546.9710f321.chunk.js +0 -1
  99. package/build/6272.4017459a.chunk.js +0 -160
  100. package/build/6812.31979984.chunk.js +0 -26
  101. package/build/content-manager.9187db78.chunk.js +0 -1097
  102. package/build/content-type-builder-translation-en-json.ed29ff4d.chunk.js +0 -1
  103. package/build/content-type-builder.5ff93edd.chunk.js +0 -170
  104. package/build/main.da000219.js +0 -2860
@@ -0,0 +1,451 @@
1
+ import * as React from 'react';
2
+
3
+ import { Box, Typography, BaseLink } from '@strapi/design-system';
4
+ import {
5
+ Code,
6
+ Quote,
7
+ Picture,
8
+ Paragraph,
9
+ HeadingOne,
10
+ HeadingTwo,
11
+ HeadingThree,
12
+ HeadingFour,
13
+ HeadingFive,
14
+ HeadingSix,
15
+ } from '@strapi/icons';
16
+ import PropTypes from 'prop-types';
17
+ import { Editor, Path, Transforms } from 'slate';
18
+ import styled, { css } from 'styled-components';
19
+
20
+ const H1 = styled(Typography).attrs({ as: 'h1' })`
21
+ font-size: ${42 / 16}rem;
22
+ line-height: ${({ theme }) => theme.lineHeights[1]};
23
+ `;
24
+
25
+ const H2 = styled(Typography).attrs({ as: 'h2' })`
26
+ font-size: ${35 / 16}rem;
27
+ line-height: ${({ theme }) => theme.lineHeights[1]};
28
+ `;
29
+
30
+ const H3 = styled(Typography).attrs({ as: 'h3' })`
31
+ font-size: ${29 / 16}rem;
32
+ line-height: ${({ theme }) => theme.lineHeights[1]};
33
+ `;
34
+
35
+ const H4 = styled(Typography).attrs({ as: 'h4' })`
36
+ font-size: ${24 / 16}rem;
37
+ line-height: ${({ theme }) => theme.lineHeights[1]};
38
+ `;
39
+
40
+ const H5 = styled(Typography).attrs({ as: 'h5' })`
41
+ font-size: ${20 / 16}rem;
42
+ line-height: ${({ theme }) => theme.lineHeights[1]};
43
+ `;
44
+
45
+ const H6 = styled(Typography).attrs({ as: 'h6' })`
46
+ font-size: 1rem;
47
+ line-height: ${({ theme }) => theme.lineHeights[1]};
48
+ `;
49
+
50
+ const Heading = ({ attributes, children, element }) => {
51
+ switch (element.level) {
52
+ case 1:
53
+ return <H1 {...attributes}>{children}</H1>;
54
+ case 2:
55
+ return <H2 {...attributes}>{children}</H2>;
56
+ case 3:
57
+ return <H3 {...attributes}>{children}</H3>;
58
+ case 4:
59
+ return <H4 {...attributes}>{children}</H4>;
60
+ case 5:
61
+ return <H5 {...attributes}>{children}</H5>;
62
+ case 6:
63
+ return <H6 {...attributes}>{children}</H6>;
64
+ default: // do nothing
65
+ return null;
66
+ }
67
+ };
68
+
69
+ Heading.propTypes = {
70
+ attributes: PropTypes.object.isRequired,
71
+ children: PropTypes.node.isRequired,
72
+ element: PropTypes.shape({
73
+ level: PropTypes.oneOf([1, 2, 3, 4, 5, 6]).isRequired,
74
+ }).isRequired,
75
+ };
76
+
77
+ const CodeBlock = styled.pre.attrs({ role: 'code' })`
78
+ border-radius: ${({ theme }) => theme.borderRadius};
79
+ background-color: #32324d; // since the color is same between the themes
80
+ max-width: 100%;
81
+ overflow: auto;
82
+ padding: ${({ theme }) => theme.spaces[2]};
83
+ & > code {
84
+ color: #839496; // TODO: to confirm with design and get theme color
85
+ overflow: auto;
86
+ max-width: 100%;
87
+ padding: ${({ theme }) => theme.spaces[2]};
88
+ }
89
+ `;
90
+
91
+ const Blockquote = styled.blockquote.attrs({ role: 'blockquote' })`
92
+ margin: ${({ theme }) => `${theme.spaces[6]} 0`};
93
+ font-weight: ${({ theme }) => theme.fontWeights.regular};
94
+ border-left: ${({ theme }) => `${theme.spaces[1]} solid ${theme.colors.neutral150}`};
95
+ font-style: italic;
96
+ padding: ${({ theme }) => theme.spaces[2]} ${({ theme }) => theme.spaces[5]};
97
+ `;
98
+
99
+ const listStyle = css`
100
+ margin-block-start: ${({ theme }) => theme.spaces[4]};
101
+ margin-block-end: ${({ theme }) => theme.spaces[4]};
102
+ margin-inline-start: ${({ theme }) => theme.spaces[0]};
103
+ margin-inline-end: ${({ theme }) => theme.spaces[0]};
104
+ padding-inline-start: ${({ theme }) => theme.spaces[4]};
105
+
106
+ ol,
107
+ ul {
108
+ margin-block-start: ${({ theme }) => theme.spaces[0]};
109
+ margin-block-end: ${({ theme }) => theme.spaces[0]};
110
+ }
111
+ `;
112
+
113
+ const Orderedlist = styled.ol`
114
+ list-style-type: decimal;
115
+ ${listStyle}
116
+ `;
117
+
118
+ const Unorderedlist = styled.ul`
119
+ list-style-type: disc;
120
+ ${listStyle}
121
+ `;
122
+
123
+ const List = ({ attributes, children, element }) => {
124
+ if (element.format === 'ordered') return <Orderedlist {...attributes}>{children}</Orderedlist>;
125
+
126
+ return <Unorderedlist {...attributes}>{children}</Unorderedlist>;
127
+ };
128
+
129
+ List.propTypes = {
130
+ attributes: PropTypes.object.isRequired,
131
+ children: PropTypes.node.isRequired,
132
+ element: PropTypes.shape({
133
+ format: PropTypes.string.isRequired,
134
+ }).isRequired,
135
+ };
136
+
137
+ const Img = styled.img`
138
+ max-width: 100%;
139
+ `;
140
+
141
+ const Image = ({ attributes, children, element }) => {
142
+ if (!element.image) return null;
143
+ const { url, alternativeText, width, height } = element.image;
144
+
145
+ return (
146
+ <Box {...attributes}>
147
+ {children}
148
+ <Box contentEditable={false}>
149
+ <Img src={url} alt={alternativeText} width={width} height={height} />
150
+ </Box>
151
+ </Box>
152
+ );
153
+ };
154
+
155
+ Image.propTypes = {
156
+ attributes: PropTypes.object.isRequired,
157
+ children: PropTypes.node.isRequired,
158
+ element: PropTypes.shape({
159
+ image: PropTypes.shape({
160
+ url: PropTypes.string.isRequired,
161
+ alternativeText: PropTypes.string,
162
+ width: PropTypes.number,
163
+ height: PropTypes.number,
164
+ }),
165
+ }).isRequired,
166
+ };
167
+
168
+ /**
169
+ * Common handler for the enter key on ordered and unordered lists
170
+ * @param {import('slate').Editor} editor
171
+ */
172
+ const handleEnterKeyOnList = (editor) => {
173
+ // Check if the selected list item is empty
174
+ const [currentListItem, currentListItemPath] = Editor.above(editor, {
175
+ matchNode: (node) => node.type === 'list-item',
176
+ });
177
+ const isEmptyListItem =
178
+ currentListItem.children.length === 1 && currentListItem.children[0].text === '';
179
+
180
+ if (isEmptyListItem) {
181
+ // Delete the empty list item
182
+ Transforms.removeNodes(editor, { at: currentListItemPath });
183
+
184
+ // And create a new paragraph below the parent list
185
+ const listNodeEntry = Editor.above(editor, { match: (n) => n.type === 'list' });
186
+ const createdParagraphPath = Path.next(listNodeEntry[1]);
187
+ Transforms.insertNodes(
188
+ editor,
189
+ {
190
+ type: 'paragraph',
191
+ children: [{ type: 'text', text: '' }],
192
+ },
193
+ { at: createdParagraphPath }
194
+ );
195
+
196
+ // Move selection to the newly created paragraph
197
+ Transforms.select(editor, createdParagraphPath);
198
+ } else {
199
+ // Otherwise just create a new list item by splitting the current one
200
+ Transforms.splitNodes(editor, { always: true });
201
+ }
202
+ };
203
+
204
+ /**
205
+ * Manages a store of all the available blocks.
206
+ *
207
+ * @returns {{
208
+ * [key: string]: {
209
+ * renderElement: (props: Object) => JSX.Element,
210
+ * icon: React.ComponentType,
211
+ * label: {id: string, defaultMessage: string},
212
+ * value: Object,
213
+ * matchNode: (node: Object) => boolean,
214
+ * isInBlocksSelector: true,
215
+ * handleEnterKey: (editor: import('slate').Editor) => void,
216
+ * }
217
+ * }} an object containing rendering functions and metadata for different blocks, indexed by name.
218
+ */
219
+ export function useBlocksStore() {
220
+ return {
221
+ paragraph: {
222
+ renderElement: (props) => (
223
+ <Typography as="p" variant="omega" {...props.attributes}>
224
+ {props.children}
225
+ </Typography>
226
+ ),
227
+ icon: Paragraph,
228
+ label: {
229
+ id: 'components.Blocks.blocks.text',
230
+ defaultMessage: 'Text',
231
+ },
232
+ value: {
233
+ type: 'paragraph',
234
+ },
235
+ matchNode: (node) => node.type === 'paragraph',
236
+ isInBlocksSelector: true,
237
+ handleEnterKey(editor) {
238
+ /**
239
+ * Split the nodes where the cursor is. This will create a new paragraph with the content
240
+ * after the cursor, while retaining all the children, modifiers etc.
241
+ */
242
+ Transforms.splitNodes(editor, {
243
+ /**
244
+ * Makes sure we always create a new node,
245
+ * even if there's nothing to the right of the cursor in the node.
246
+ */
247
+ always: true,
248
+ });
249
+
250
+ /**
251
+ * Delete and recreate the node that was created at the right of the cursor.
252
+ * This is to avoid node pollution
253
+ * (e.g. keeping the level attribute when converting a heading to a paragraph).
254
+ * Select the parent of the selection because we want the full block, not the leaf.
255
+ * And copy its children to make sure we keep the modifiers.
256
+ */
257
+ const [createdNode] = Editor.parent(editor, editor.selection.anchor.path);
258
+ Transforms.removeNodes(editor, editor.selection);
259
+ Transforms.insertNodes(editor, {
260
+ type: 'paragraph',
261
+ children: createdNode.children,
262
+ });
263
+
264
+ /**
265
+ * The new selection will by default be at the end of the created node.
266
+ * Instead we manually move it to the start of the created node.
267
+ * Use slice(0, -1) to go 1 level higher in the tree,
268
+ * so we go to the start of the node and not the start of the leaf.
269
+ */
270
+ Transforms.select(editor, editor.start(editor.selection.anchor.path.slice(0, -1)));
271
+ },
272
+ },
273
+ 'heading-one': {
274
+ renderElement: (props) => <Heading {...props} />,
275
+ icon: HeadingOne,
276
+ label: {
277
+ id: 'components.Blocks.blocks.heading1',
278
+ defaultMessage: 'Heading 1',
279
+ },
280
+ value: {
281
+ type: 'heading',
282
+ level: 1,
283
+ },
284
+ matchNode: (node) => node.type === 'heading' && node.level === 1,
285
+ isInBlocksSelector: true,
286
+ },
287
+ 'heading-two': {
288
+ renderElement: (props) => <Heading {...props} />,
289
+ icon: HeadingTwo,
290
+ label: {
291
+ id: 'components.Blocks.blocks.heading2',
292
+ defaultMessage: 'Heading 2',
293
+ },
294
+ value: {
295
+ type: 'heading',
296
+ level: 2,
297
+ },
298
+ matchNode: (node) => node.type === 'heading' && node.level === 2,
299
+ isInBlocksSelector: true,
300
+ },
301
+ 'heading-three': {
302
+ renderElement: (props) => <Heading {...props} />,
303
+ icon: HeadingThree,
304
+ label: {
305
+ id: 'components.Blocks.blocks.heading3',
306
+ defaultMessage: 'Heading 3',
307
+ },
308
+ value: {
309
+ type: 'heading',
310
+ level: 3,
311
+ },
312
+ matchNode: (node) => node.type === 'heading' && node.level === 3,
313
+ isInBlocksSelector: true,
314
+ },
315
+ 'heading-four': {
316
+ renderElement: (props) => <Heading {...props} />,
317
+ icon: HeadingFour,
318
+ label: {
319
+ id: 'components.Blocks.blocks.heading4',
320
+ defaultMessage: 'Heading 4',
321
+ },
322
+ value: {
323
+ type: 'heading',
324
+ level: 4,
325
+ },
326
+ matchNode: (node) => node.type === 'heading' && node.level === 4,
327
+ isInBlocksSelector: true,
328
+ },
329
+ 'heading-five': {
330
+ renderElement: (props) => <Heading {...props} />,
331
+ icon: HeadingFive,
332
+ label: {
333
+ id: 'components.Blocks.blocks.heading5',
334
+ defaultMessage: 'Heading 5',
335
+ },
336
+ value: {
337
+ type: 'heading',
338
+ level: 5,
339
+ },
340
+ matchNode: (node) => node.type === 'heading' && node.level === 5,
341
+ isInBlocksSelector: true,
342
+ },
343
+ 'heading-six': {
344
+ renderElement: (props) => <Heading {...props} />,
345
+ icon: HeadingSix,
346
+ label: {
347
+ id: 'components.Blocks.blocks.heading6',
348
+ defaultMessage: 'Heading 6',
349
+ },
350
+ value: {
351
+ type: 'heading',
352
+ level: 6,
353
+ },
354
+ matchNode: (node) => node.type === 'heading' && node.level === 6,
355
+ isInBlocksSelector: true,
356
+ },
357
+ link: {
358
+ renderElement: (props) => (
359
+ <BaseLink href={props.element.url} {...props.attributes}>
360
+ {props.children}
361
+ </BaseLink>
362
+ ),
363
+ value: {
364
+ type: 'link',
365
+ },
366
+ matchNode: (node) => node.type === 'link',
367
+ isInBlocksSelector: false,
368
+ },
369
+ code: {
370
+ renderElement: (props) => (
371
+ <CodeBlock {...props.attributes}>
372
+ <code>{props.children}</code>
373
+ </CodeBlock>
374
+ ),
375
+ icon: Code,
376
+ label: {
377
+ id: 'components.Blocks.blocks.code',
378
+ defaultMessage: 'Code',
379
+ },
380
+ value: {
381
+ type: 'code',
382
+ },
383
+ matchNode: (node) => node.type === 'code',
384
+ isInBlocksSelector: true,
385
+ handleEnterKey(editor) {
386
+ // Insert a new line within the block
387
+ Transforms.insertText(editor, '\n');
388
+ },
389
+ },
390
+ quote: {
391
+ renderElement: (props) => <Blockquote {...props.attributes}>{props.children}</Blockquote>,
392
+ icon: Quote,
393
+ label: {
394
+ id: 'components.Blocks.blocks.quote',
395
+ defaultMessage: 'Quote',
396
+ },
397
+ value: {
398
+ type: 'quote',
399
+ },
400
+ matchNode: (node) => node.type === 'quote',
401
+ isInBlocksSelector: true,
402
+ },
403
+ 'list-ordered': {
404
+ renderElement: (props) => <List {...props} />,
405
+ value: {
406
+ type: 'list',
407
+ format: 'ordered',
408
+ },
409
+ matchNode: (node) => node.type === 'list' && node.format === 'ordered',
410
+ // TODO add icon and label and set isInBlocksEditor to true
411
+ isInBlocksSelector: false,
412
+ handleEnterKey: handleEnterKeyOnList,
413
+ },
414
+ 'list-unordered': {
415
+ renderElement: (props) => <List {...props} />,
416
+ value: {
417
+ type: 'list',
418
+ format: 'unordered',
419
+ },
420
+ matchNode: (node) => node.type === 'list' && node.format === 'unordered',
421
+ // TODO add icon and label and set isInBlocksEditor to true
422
+ isInBlocksSelector: false,
423
+ handleEnterKey: handleEnterKeyOnList,
424
+ },
425
+ 'list-item': {
426
+ renderElement: (props) => (
427
+ <Typography as="li" {...props.attributes}>
428
+ {props.children}
429
+ </Typography>
430
+ ),
431
+ value: {
432
+ type: 'list-item',
433
+ },
434
+ matchNode: (node) => node.type === 'list-item',
435
+ isInBlocksSelector: false,
436
+ },
437
+ image: {
438
+ renderElement: (props) => <Image {...props} />,
439
+ icon: Picture,
440
+ label: {
441
+ id: 'components.Blocks.blocks.image',
442
+ defaultMessage: 'Image',
443
+ },
444
+ value: {
445
+ type: 'image',
446
+ },
447
+ matchNode: (node) => node.type === 'image',
448
+ isInBlocksSelector: true,
449
+ },
450
+ };
451
+ }
@@ -0,0 +1,102 @@
1
+ import * as React from 'react';
2
+
3
+ import { Typography } from '@strapi/design-system';
4
+ import { Bold, Italic, Underline, StrikeThrough, Code } from '@strapi/icons';
5
+ import { Editor } from 'slate';
6
+ import { useSlate } from 'slate-react';
7
+ import styled from 'styled-components';
8
+
9
+ const BoldText = styled(Typography).attrs({ fontWeight: 'bold' })`
10
+ font-size: inherit;
11
+ `;
12
+
13
+ const ItalicText = styled(Typography)`
14
+ font-style: italic;
15
+ font-size: inherit;
16
+ `;
17
+
18
+ const UnderlineText = styled(Typography).attrs({ textDecoration: 'underline' })`
19
+ font-size: inherit;
20
+ `;
21
+
22
+ const StrikeThroughText = styled(Typography).attrs({ textDecoration: 'line-through' })`
23
+ font-size: inherit;
24
+ `;
25
+
26
+ const InlineCode = styled.code`
27
+ background-color: ${({ theme }) => theme.colors.neutral150};
28
+ border-radius: ${({ theme }) => theme.borderRadius};
29
+ padding: ${({ theme }) => `0 ${theme.spaces[2]}`};
30
+ font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas,
31
+ monospace;
32
+ `;
33
+
34
+ /**
35
+ * Manages a store of all the available modifiers.
36
+ *
37
+ * @returns {{
38
+ * [key: string]: {
39
+ * icon: IconComponent,
40
+ * label: {id: string, defaultMessage: string},
41
+ * checkIsActive: () => boolean,
42
+ * handleToggle: () => void,
43
+ * renderLeaf: (children: JSX.Element) => JSX.Element,
44
+ * }
45
+ * }} An object containing rendering functions and metadata for different text modifiers, indexed by name.
46
+ */
47
+ export function useModifiersStore() {
48
+ const editor = useSlate();
49
+ const modifiers = Editor.marks(editor);
50
+
51
+ const baseCheckIsActive = (name) => {
52
+ if (!modifiers) return false;
53
+
54
+ return Boolean(modifiers[name]);
55
+ };
56
+
57
+ const baseHandleToggle = (name) => {
58
+ if (modifiers[name]) {
59
+ Editor.removeMark(editor, name);
60
+ } else {
61
+ Editor.addMark(editor, name, true);
62
+ }
63
+ };
64
+
65
+ return {
66
+ bold: {
67
+ icon: Bold,
68
+ label: { id: 'components.Blocks.modifiers.bold', defaultMessage: 'Bold' },
69
+ checkIsActive: () => baseCheckIsActive('bold'),
70
+ handleToggle: () => baseHandleToggle('bold'),
71
+ renderLeaf: (children) => <BoldText>{children}</BoldText>,
72
+ },
73
+ italic: {
74
+ icon: Italic,
75
+ label: { id: 'components.Blocks.modifiers.italic', defaultMessage: 'Italic' },
76
+ checkIsActive: () => baseCheckIsActive('italic'),
77
+ handleToggle: () => baseHandleToggle('italic'),
78
+ renderLeaf: (children) => <ItalicText>{children}</ItalicText>,
79
+ },
80
+ underline: {
81
+ icon: Underline,
82
+ label: { id: 'components.Blocks.modifiers.underline', defaultMessage: 'Underline' },
83
+ checkIsActive: () => baseCheckIsActive('underline'),
84
+ handleToggle: () => baseHandleToggle('underline'),
85
+ renderLeaf: (children) => <UnderlineText>{children}</UnderlineText>,
86
+ },
87
+ strikethrough: {
88
+ icon: StrikeThrough,
89
+ label: { id: 'components.Blocks.modifiers.strikethrough', defaultMessage: 'Strikethrough' },
90
+ checkIsActive: () => baseCheckIsActive('strikethrough'),
91
+ handleToggle: () => baseHandleToggle('strikethrough'),
92
+ renderLeaf: (children) => <StrikeThroughText>{children}</StrikeThroughText>,
93
+ },
94
+ code: {
95
+ icon: Code,
96
+ label: { id: 'components.Blocks.modifiers.code', defaultMessage: 'Code' },
97
+ checkIsActive: () => baseCheckIsActive('code'),
98
+ handleToggle: () => baseHandleToggle('code'),
99
+ renderLeaf: (children) => <InlineCode>{children}</InlineCode>,
100
+ },
101
+ };
102
+ }
@@ -0,0 +1,126 @@
1
+ import * as React from 'react';
2
+
3
+ import { Box, Flex, Typography, InputWrapper, Divider } from '@strapi/design-system';
4
+ import PropTypes from 'prop-types';
5
+ import { useIntl } from 'react-intl';
6
+ import { createEditor } from 'slate';
7
+ import { withHistory } from 'slate-history';
8
+ import { Slate, withReact, ReactEditor } from 'slate-react';
9
+ import styled from 'styled-components';
10
+
11
+ import BlocksInput from './BlocksInput';
12
+ import { BlocksToolbar } from './Toolbar';
13
+
14
+ const TypographyAsterisk = styled(Typography)`
15
+ line-height: 0;
16
+ `;
17
+
18
+ const EditorDivider = styled(Divider)`
19
+ background: ${({ theme }) => theme.colors.neutral200};
20
+ `;
21
+
22
+ const Wrapper = styled(Box)`
23
+ width: 100%;
24
+ max-height: 512px;
25
+ overflow: auto;
26
+ padding: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[4]}`};
27
+ font-size: ${({ theme }) => theme.fontSizes[2]};
28
+ background-color: ${({ theme }) => theme.colors.neutral0};
29
+ color: ${({ theme }) => theme.colors.neutral800};
30
+ line-height: ${({ theme }) => theme.lineHeights[6]};
31
+ border-radius: ${({ theme }) => theme.borderRadius};
32
+ `;
33
+
34
+ const BlocksEditor = React.forwardRef(
35
+ ({ intlLabel, name, readOnly, required, error, value, onChange }, ref) => {
36
+ const { formatMessage } = useIntl();
37
+ const [editor] = React.useState(() => withReact(withHistory(createEditor())));
38
+
39
+ const label = intlLabel.id
40
+ ? formatMessage(
41
+ { id: intlLabel.id, defaultMessage: intlLabel.defaultMessage },
42
+ { ...intlLabel.values }
43
+ )
44
+ : name;
45
+
46
+ /** Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082
47
+ * so with "useImperativeHandle" we can use ReactEditor methods to expose to the parent above
48
+ * also not passing forwarded ref here, gives console warning.
49
+ */
50
+ React.useImperativeHandle(
51
+ ref,
52
+ () => ({
53
+ focus() {
54
+ ReactEditor.focus(editor);
55
+ },
56
+ }),
57
+ [editor]
58
+ );
59
+
60
+ const handleSlateChange = (state) => {
61
+ const isAstChange = editor.operations.some((op) => op.type !== 'set_selection');
62
+
63
+ if (isAstChange) {
64
+ onChange({
65
+ target: { name, value: state, type: 'blocks' },
66
+ });
67
+ }
68
+ };
69
+
70
+ return (
71
+ <>
72
+ <Flex direction="column" alignItems="stretch" gap={1}>
73
+ <Flex gap={1}>
74
+ <Typography variant="pi" fontWeight="bold" textColor="neutral800">
75
+ {label}
76
+ {required && <TypographyAsterisk textColor="danger600">*</TypographyAsterisk>}
77
+ </Typography>
78
+ </Flex>
79
+ <Slate
80
+ editor={editor}
81
+ initialValue={value || [{ type: 'paragraph', children: [{ type: 'text', text: '' }] }]}
82
+ onChange={handleSlateChange}
83
+ >
84
+ <InputWrapper direction="column" alignItems="flex-start">
85
+ <BlocksToolbar />
86
+ <EditorDivider width="100%" />
87
+ <Wrapper>
88
+ <BlocksInput readOnly={readOnly} />
89
+ </Wrapper>
90
+ </InputWrapper>
91
+ </Slate>
92
+ </Flex>
93
+ {error && (
94
+ <Box paddingTop={1}>
95
+ <Typography variant="pi" textColor="danger600" data-strapi-field-error>
96
+ {error}
97
+ </Typography>
98
+ </Box>
99
+ )}
100
+ </>
101
+ );
102
+ }
103
+ );
104
+
105
+ BlocksEditor.defaultProps = {
106
+ required: false,
107
+ readOnly: false,
108
+ error: '',
109
+ value: null,
110
+ };
111
+
112
+ BlocksEditor.propTypes = {
113
+ intlLabel: PropTypes.shape({
114
+ id: PropTypes.string.isRequired,
115
+ defaultMessage: PropTypes.string.isRequired,
116
+ values: PropTypes.object,
117
+ }).isRequired,
118
+ name: PropTypes.string.isRequired,
119
+ required: PropTypes.bool,
120
+ readOnly: PropTypes.bool,
121
+ error: PropTypes.string,
122
+ onChange: PropTypes.func.isRequired,
123
+ value: PropTypes.array,
124
+ };
125
+
126
+ export default BlocksEditor;