@strapi/admin 4.14.2 → 4.14.4

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 (173) hide show
  1. package/.eslintrc.js +4 -1
  2. package/admin/.eslintrc.js +16 -0
  3. package/admin/custom.d.ts +8 -0
  4. package/admin/src/components/AuthenticatedApp/index.js +3 -7
  5. package/admin/src/components/AuthenticatedApp/utils/api.js +1 -39
  6. package/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.ts +13 -0
  7. package/admin/src/{hooks/useReleaseNotification/utils/api.js → components/AuthenticatedApp/utils/fetchStrapiLatestRelease.ts} +2 -3
  8. package/admin/src/components/{DragLayer/DragLayer.js → DragLayer.tsx} +18 -10
  9. package/admin/src/components/PrivateRoute.tsx +42 -0
  10. package/admin/src/components/Providers/index.js +2 -2
  11. package/admin/src/components/Theme.tsx +39 -0
  12. package/admin/src/components/ThemeToggleProvider.tsx +50 -0
  13. package/admin/src/components/{UnauthenticatedLogo/index.js → UnauthenticatedLogo.tsx} +2 -4
  14. package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +22 -3
  15. package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +263 -134
  16. package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +362 -95
  17. package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +15 -0
  18. package/admin/src/content-manager/components/BlocksEditor/index.js +99 -9
  19. package/admin/src/content-manager/components/BlocksEditor/plugins/index.js +4 -0
  20. package/admin/src/content-manager/components/BlocksEditor/plugins/withLinks.js +61 -0
  21. package/admin/src/content-manager/components/BlocksEditor/plugins/withStrapiSchema.js +33 -0
  22. package/admin/src/content-manager/components/BlocksEditor/utils/links.js +90 -0
  23. package/admin/src/content-manager/components/InputUID/index.js +1 -1
  24. package/admin/src/content-manager/hooks/useAllowedAttributes.js +9 -1
  25. package/admin/src/content-manager/hooks/useRelation/useRelation.js +1 -0
  26. package/admin/src/content-manager/pages/EditSettingsView/index.js +1 -0
  27. package/admin/src/content-manager/pages/EditSettingsView/utils/createPossibleMainFieldsForModelsAndComponents.js +1 -0
  28. package/admin/src/content-manager/pages/ListSettingsView/constants.js +1 -0
  29. package/admin/src/content-manager/pages/ListView/index.js +2 -1
  30. package/admin/src/content-manager/utils/checkIfAttributeIsDisplayable.js +1 -1
  31. package/admin/src/content-manager/utils/schema.js +2 -2
  32. package/admin/src/contexts/configuration.ts +15 -0
  33. package/admin/src/contexts/index.js +1 -2
  34. package/admin/src/contexts/themeToggle.ts +16 -0
  35. package/admin/src/hooks/{useConfigurations/__mocks__/index.js → __mocks__/useConfigurations.ts} +4 -2
  36. package/admin/src/hooks/index.js +1 -5
  37. package/admin/src/hooks/useConfigurations.ts +5 -0
  38. package/admin/src/hooks/useDebounce.ts +17 -0
  39. package/admin/src/hooks/useLicenseLimitNotification.ts +3 -0
  40. package/admin/src/hooks/useThemeToggle.ts +9 -0
  41. package/admin/src/pages/App/index.js +1 -1
  42. package/admin/src/pages/AuthPage/components/ForgotPassword/index.js +1 -1
  43. package/admin/src/pages/AuthPage/components/ForgotPasswordSuccess/index.js +1 -1
  44. package/admin/src/pages/AuthPage/components/Login/BaseLogin.js +1 -1
  45. package/admin/src/pages/AuthPage/components/Oops/index.js +1 -1
  46. package/admin/src/pages/AuthPage/components/Register/index.js +1 -1
  47. package/admin/src/pages/AuthPage/components/ResetPassword/index.js +1 -1
  48. package/admin/src/pages/MarketplacePage/components/NpmPackageCard/index.js +0 -2
  49. package/admin/src/pages/MarketplacePage/hooks/__mocks__/useNavigatorOnline.ts +1 -0
  50. package/admin/src/{hooks/useNavigatorOnLine/index.js → pages/MarketplacePage/hooks/useNavigatorOnline.ts} +4 -6
  51. package/admin/src/pages/MarketplacePage/index.js +3 -3
  52. package/admin/src/pages/ProfilePage/index.js +1 -1
  53. package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +1 -1
  54. package/admin/src/{hooks/useRegenerate/index.js → pages/SettingsPage/hooks/useRegenerate.ts} +13 -7
  55. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +1 -1
  56. package/admin/src/pages/UseCasePage/index.js +1 -1
  57. package/admin/src/translations/en.json +8 -0
  58. package/admin/tsconfig.json +5 -0
  59. package/build/1049.f7aed23d.chunk.js +1 -0
  60. package/build/{1227.969e24e6.chunk.js → 1227.f9c74718.chunk.js} +1 -1
  61. package/build/{1386.db9a2795.chunk.js → 1386.6b8819c6.chunk.js} +2 -2
  62. package/build/2224.8af54440.chunk.js +138 -0
  63. package/build/2225.d1bcf7e3.chunk.js +79 -0
  64. package/build/2379.f0baf826.chunk.js +1 -0
  65. package/build/{2395.f6ac2863.chunk.js → 2395.aca6ce66.chunk.js} +1 -1
  66. package/build/2421.a478ba24.chunk.js +105 -0
  67. package/build/2801.c49f88a1.chunk.js +1 -0
  68. package/build/{3483.f6b2439f.chunk.js → 3483.5df8e010.chunk.js} +1 -1
  69. package/build/3911.d4fada48.chunk.js +95 -0
  70. package/build/412.72afdf0c.chunk.js +689 -0
  71. package/build/{4174.3e13fb26.chunk.js → 4174.df9aa09a.chunk.js} +1 -1
  72. package/build/502.8666bbef.chunk.js +25 -0
  73. package/build/570.2f3b4c56.chunk.js +1 -0
  74. package/build/5702.5b433d50.chunk.js +1 -0
  75. package/build/6186.c33ce082.chunk.js +116 -0
  76. package/build/7464.43a4527c.chunk.js +1 -0
  77. package/build/7818.d2196a53.chunk.js +29 -0
  78. package/build/7897.5c03247b.chunk.js +25 -0
  79. package/build/{8276.951e198e.chunk.js → 8276.d4426fd8.chunk.js} +3 -3
  80. package/build/8690.33243bba.chunk.js +38 -0
  81. package/build/{9832.65ed5a44.chunk.js → 8743.31c921b1.chunk.js} +139 -123
  82. package/build/9218.8bc01ab9.chunk.js +1 -0
  83. package/build/Admin-authenticatedApp.27545a1b.chunk.js +112 -0
  84. package/build/{Admin_InternalErrorPage.b3163562.chunk.js → Admin_InternalErrorPage.b66ee9c1.chunk.js} +1 -1
  85. package/build/Admin_homePage.a6281dd6.chunk.js +124 -0
  86. package/build/Admin_marketplace.31b962b8.chunk.js +44 -0
  87. package/build/{Admin_pluginsPage.b9fa2947.chunk.js → Admin_pluginsPage.9217101d.chunk.js} +1 -1
  88. package/build/{Admin_profilePage.a4d41380.chunk.js → Admin_profilePage.680123d9.chunk.js} +2 -2
  89. package/build/{Admin_settingsPage.6dc2af9f.chunk.js → Admin_settingsPage.33378310.chunk.js} +1 -1
  90. package/build/{Upload_ConfigureTheView.cc7ca628.chunk.js → Upload_ConfigureTheView.b40eea4d.chunk.js} +1 -1
  91. package/build/admin-app.e8c52c37.chunk.js +36 -0
  92. package/build/admin-edit-roles-page.fcf056bf.chunk.js +275 -0
  93. package/build/{admin-edit-users.9b42cc9e.chunk.js → admin-edit-users.89efe3c4.chunk.js} +2 -2
  94. package/build/{admin-roles-list.cf964578.chunk.js → admin-roles-list.8b77704a.chunk.js} +3 -3
  95. package/build/admin-users.e3f1be14.chunk.js +19 -0
  96. package/build/{api-tokens-create-page.2f25ddf6.chunk.js → api-tokens-create-page.0dd63e91.chunk.js} +1 -1
  97. package/build/{api-tokens-edit-page.45faac16.chunk.js → api-tokens-edit-page.78d877f8.chunk.js} +1 -1
  98. package/build/{api-tokens-list-page.5baabf1a.chunk.js → api-tokens-list-page.ae13346c.chunk.js} +2 -2
  99. package/build/audit-logs-settings-page.e9c92a75.chunk.js +9 -0
  100. package/build/content-manager.5849dbe3.chunk.js +1226 -0
  101. package/build/{content-type-builder-list-view.aa8a5d1a.chunk.js → content-type-builder-list-view.3fffae65.chunk.js} +1 -1
  102. package/build/{content-type-builder-translation-en-json.b9e5cacd.chunk.js → content-type-builder-translation-en-json.43f9d7bc.chunk.js} +1 -1
  103. package/build/{content-type-builder.885f2cad.chunk.js → content-type-builder.98c71164.chunk.js} +14 -14
  104. package/build/{email-settings-page.6bd7b280.chunk.js → email-settings-page.ecfec9b3.chunk.js} +1 -1
  105. package/build/{en-json.a3973ff5.chunk.js → en-json.bd611a8e.chunk.js} +1 -1
  106. package/build/{i18n-settings-page.6c0157e7.chunk.js → i18n-settings-page.a9708926.chunk.js} +1 -1
  107. package/build/index.html +1 -1
  108. package/build/main.3abb6f34.js +3278 -0
  109. package/build/{review-workflows-settings-create-view.ae369a88.chunk.js → review-workflows-settings-create-view.b7b0c6c5.chunk.js} +1 -1
  110. package/build/{review-workflows-settings-edit-view.9a61c69f.chunk.js → review-workflows-settings-edit-view.c331b3fe.chunk.js} +1 -1
  111. package/build/review-workflows-settings-list-view.70218dc1.chunk.js +75 -0
  112. package/build/{runtime~main.cec66cd9.js → runtime~main.450561b1.js} +1 -1
  113. package/build/{sso-settings-page.a29e6c38.chunk.js → sso-settings-page.1a9e7f8f.chunk.js} +1 -1
  114. package/build/{transfer-tokens-create-page.6e1b8cee.chunk.js → transfer-tokens-create-page.e7f541d3.chunk.js} +1 -1
  115. package/build/{transfer-tokens-edit-page.10bb22e2.chunk.js → transfer-tokens-edit-page.bd1276c2.chunk.js} +1 -1
  116. package/build/{transfer-tokens-list-page.0306652c.chunk.js → transfer-tokens-list-page.5de6bb9f.chunk.js} +2 -2
  117. package/build/upload-settings.97ef4c92.chunk.js +14 -0
  118. package/build/{upload.19e14c8e.chunk.js → upload.f08715a1.chunk.js} +1 -1
  119. package/build/{users-advanced-settings-page.ed69812f.chunk.js → users-advanced-settings-page.36a3c363.chunk.js} +1 -1
  120. package/build/users-email-settings-page.47b47962.chunk.js +149 -0
  121. package/build/users-providers-settings-page.1e0c8376.chunk.js +154 -0
  122. package/build/{users-roles-settings-page.afab5a0d.chunk.js → users-roles-settings-page.d5a8e8a1.chunk.js} +4 -4
  123. package/build/{webhook-edit-page.4c037da4.chunk.js → webhook-edit-page.87456194.chunk.js} +3 -3
  124. package/build/{webhook-list-page.56c82f4a.chunk.js → webhook-list-page.c88a382b.chunk.js} +3 -3
  125. package/ee/admin/hooks/{useLicenseLimitNotification.js → useLicenseLimitNotification.ts} +4 -4
  126. package/ee/admin/pages/AuthPage/components/Providers/index.js +1 -1
  127. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/index.js +1 -3
  128. package/package.json +13 -12
  129. package/scripts/build.js +6 -2
  130. package/webpack.config.js +1 -0
  131. package/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.js +0 -11
  132. package/admin/src/components/DragLayer/index.js +0 -1
  133. package/admin/src/components/GlobalStyle/index.js +0 -9
  134. package/admin/src/components/PrivateRoute/index.js +0 -46
  135. package/admin/src/components/Theme/index.js +0 -26
  136. package/admin/src/components/ThemeToggleProvider/index.js +0 -79
  137. package/admin/src/contexts/Configurations/index.js +0 -5
  138. package/admin/src/contexts/ThemeToggle/index.js +0 -5
  139. package/admin/src/hooks/useConfigurations/index.js +0 -11
  140. package/admin/src/hooks/useDebounce/index.js +0 -19
  141. package/admin/src/hooks/useLicenseLimitNotification/index.js +0 -5
  142. package/admin/src/hooks/useReleaseNotification/index.js +0 -31
  143. package/admin/src/hooks/useReleaseNotification/utils/checkLatestStrapiVersion.js +0 -11
  144. package/admin/src/hooks/useThemeToggle/index.js +0 -11
  145. package/admin/src/tsconfig.json +0 -10
  146. package/build/1049.acb0e730.chunk.js +0 -1
  147. package/build/2225.78fb9b89.chunk.js +0 -79
  148. package/build/2379.906334f0.chunk.js +0 -1
  149. package/build/2614.3e088d3e.chunk.js +0 -35
  150. package/build/2659.cb94f1e7.chunk.js +0 -105
  151. package/build/2801.2afb4757.chunk.js +0 -1
  152. package/build/2950.216f2e89.chunk.js +0 -1
  153. package/build/3021.33ad47fb.chunk.js +0 -103
  154. package/build/3911.488fbde3.chunk.js +0 -95
  155. package/build/4546.1203ac95.chunk.js +0 -1
  156. package/build/502.9918bff7.chunk.js +0 -1
  157. package/build/5158.c85f841a.chunk.js +0 -1
  158. package/build/6266.e8990811.chunk.js +0 -146
  159. package/build/7464.0280cf59.chunk.js +0 -1
  160. package/build/7897.4a39de37.chunk.js +0 -6
  161. package/build/Admin-authenticatedApp.08f32723.chunk.js +0 -112
  162. package/build/Admin_homePage.6cb51f18.chunk.js +0 -81
  163. package/build/Admin_marketplace.3eb5e132.chunk.js +0 -55
  164. package/build/admin-app.98cdf43a.chunk.js +0 -36
  165. package/build/admin-edit-roles-page.418bb1c5.chunk.js +0 -267
  166. package/build/admin-users.8385dd73.chunk.js +0 -11
  167. package/build/audit-logs-settings-page.91489670.chunk.js +0 -1
  168. package/build/content-manager.0d2b4a60.chunk.js +0 -1199
  169. package/build/main.105dcf23.js +0 -2665
  170. package/build/review-workflows-settings-list-view.067e0c35.chunk.js +0 -56
  171. package/build/upload-settings.0af6edc5.chunk.js +0 -14
  172. package/build/users-email-settings-page.131a00fb.chunk.js +0 -9
  173. package/build/users-providers-settings-page.b3dca41d.chunk.js +0 -14
@@ -8,20 +8,28 @@ import { withHistory } from 'slate-history';
8
8
  import { Slate, withReact, ReactEditor } from 'slate-react';
9
9
  import styled from 'styled-components';
10
10
 
11
+ import Hint from '../Hint';
12
+
11
13
  import BlocksInput from './BlocksInput';
14
+ import { withLinks, withStrapiSchema } from './plugins';
12
15
  import { BlocksToolbar } from './Toolbar';
13
16
 
14
17
  const TypographyAsterisk = styled(Typography)`
15
18
  line-height: 0;
16
19
  `;
17
20
 
21
+ const LabelAction = styled(Box)`
22
+ svg path {
23
+ fill: ${({ theme }) => theme.colors.neutral500};
24
+ }
25
+ `;
26
+
18
27
  const EditorDivider = styled(Divider)`
19
28
  background: ${({ theme }) => theme.colors.neutral200};
20
29
  `;
21
30
 
22
31
  const Wrapper = styled(Box)`
23
32
  width: 100%;
24
- max-height: 512px;
25
33
  overflow: auto;
26
34
  padding: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[4]}`};
27
35
  font-size: ${({ theme }) => theme.fontSizes[2]};
@@ -31,10 +39,72 @@ const Wrapper = styled(Box)`
31
39
  border-radius: ${({ theme }) => theme.borderRadius};
32
40
  `;
33
41
 
42
+ /**
43
+ * Images are void elements. They handle the rendering of their children instead of Slate.
44
+ * See the Slate documentation for more information:
45
+ * - https://docs.slatejs.org/api/nodes/element#void-vs-not-void
46
+ * - https://docs.slatejs.org/api/nodes/element#rendering-void-elements
47
+ *
48
+ * @param {import('slate').Editor} editor
49
+ */
50
+ const withImages = (editor) => {
51
+ const { isVoid } = editor;
52
+
53
+ editor.isVoid = (element) => {
54
+ return element.type === 'image' ? true : isVoid(element);
55
+ };
56
+
57
+ return editor;
58
+ };
59
+
60
+ /**
61
+ * Forces an update of the Slate editor when the value prop changes from outside of Slate.
62
+ * The root cause is that Slate is not a controlled component: https://github.com/ianstormtaylor/slate/issues/4612
63
+ * Why not use JSON.stringify(value) as the key?
64
+ * Because it would force a rerender of the entire editor every time the user types a character.
65
+ * Why not use the entity id as the key, since it's unique for each locale?
66
+ * Because it would not solve the problem when using the "fill in from other locale" feature
67
+ *
68
+ * @param {import('slate').Descendant[]} value
69
+ * @returns {{
70
+ * key: number,
71
+ * incrementSlateUpdatesCount: () => void
72
+ * }}
73
+ */
74
+ function useResetKey(value) {
75
+ // Keep track how how many times Slate detected a change from a user interaction in the editor
76
+ const slateUpdatesCount = React.useRef(0);
77
+ // Keep track of how many times the value prop was updated, whether from within editor or from outside
78
+ const valueUpdatesCount = React.useRef(0);
79
+ // Use a key to force a rerender of the Slate editor when needed
80
+ const [key, setKey] = React.useState(0);
81
+
82
+ React.useEffect(() => {
83
+ valueUpdatesCount.current += 1;
84
+
85
+ // If the 2 refs are not equal, it means the value was updated from outside
86
+ if (valueUpdatesCount.current !== slateUpdatesCount.current) {
87
+ // So we change the key to force a rerender of the Slate editor,
88
+ // which will pick up the new value through its initialValue prop
89
+ setKey((previousKey) => previousKey + 1);
90
+
91
+ // Then bring the 2 refs back in sync
92
+ slateUpdatesCount.current = valueUpdatesCount.current;
93
+ }
94
+ }, [value]);
95
+
96
+ return { key, incrementSlateUpdatesCount: () => (slateUpdatesCount.current += 1) };
97
+ }
98
+
34
99
  const BlocksEditor = React.forwardRef(
35
- ({ intlLabel, name, readOnly, required, error, value, onChange }, ref) => {
100
+ (
101
+ { intlLabel, labelAction, name, disabled, required, error, value, onChange, placeholder, hint },
102
+ ref
103
+ ) => {
36
104
  const { formatMessage } = useIntl();
37
- const [editor] = React.useState(() => withReact(withHistory(createEditor())));
105
+ const [editor] = React.useState(() =>
106
+ withReact(withStrapiSchema(withLinks(withImages(withHistory(createEditor())))))
107
+ );
38
108
 
39
109
  const label = intlLabel.id
40
110
  ? formatMessage(
@@ -43,6 +113,10 @@ const BlocksEditor = React.forwardRef(
43
113
  )
44
114
  : name;
45
115
 
116
+ const formattedPlaceholder = placeholder
117
+ ? formatMessage({ id: placeholder.id, defaultMessage: placeholder.defaultMessage })
118
+ : null;
119
+
46
120
  /** Editable is not able to hold the ref, https://github.com/ianstormtaylor/slate/issues/4082
47
121
  * so with "useImperativeHandle" we can use ReactEditor methods to expose to the parent above
48
122
  * also not passing forwarded ref here, gives console warning.
@@ -57,10 +131,14 @@ const BlocksEditor = React.forwardRef(
57
131
  [editor]
58
132
  );
59
133
 
134
+ const { key, incrementSlateUpdatesCount } = useResetKey(value);
135
+
60
136
  const handleSlateChange = (state) => {
61
137
  const isAstChange = editor.operations.some((op) => op.type !== 'set_selection');
62
138
 
63
139
  if (isAstChange) {
140
+ incrementSlateUpdatesCount();
141
+
64
142
  onChange({
65
143
  target: { name, value: state, type: 'blocks' },
66
144
  });
@@ -75,20 +153,23 @@ const BlocksEditor = React.forwardRef(
75
153
  {label}
76
154
  {required && <TypographyAsterisk textColor="danger600">*</TypographyAsterisk>}
77
155
  </Typography>
156
+ {labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>}
78
157
  </Flex>
79
158
  <Slate
80
159
  editor={editor}
81
160
  initialValue={value || [{ type: 'paragraph', children: [{ type: 'text', text: '' }] }]}
82
161
  onChange={handleSlateChange}
162
+ key={key}
83
163
  >
84
- <InputWrapper direction="column" alignItems="flex-start">
85
- <BlocksToolbar />
164
+ <InputWrapper direction="column" alignItems="flex-start" height="512px">
165
+ <BlocksToolbar disabled={disabled} />
86
166
  <EditorDivider width="100%" />
87
- <Wrapper>
88
- <BlocksInput readOnly={readOnly} />
167
+ <Wrapper grow={1}>
168
+ <BlocksInput disabled={disabled} placeholder={formattedPlaceholder} />
89
169
  </Wrapper>
90
170
  </InputWrapper>
91
171
  </Slate>
172
+ <Hint hint={hint} name={name} error={error} />
92
173
  </Flex>
93
174
  {error && (
94
175
  <Box paddingTop={1}>
@@ -103,10 +184,13 @@ const BlocksEditor = React.forwardRef(
103
184
  );
104
185
 
105
186
  BlocksEditor.defaultProps = {
187
+ labelAction: null,
188
+ disabled: false,
106
189
  required: false,
107
- readOnly: false,
108
190
  error: '',
109
191
  value: null,
192
+ placeholder: null,
193
+ hint: null,
110
194
  };
111
195
 
112
196
  BlocksEditor.propTypes = {
@@ -115,12 +199,18 @@ BlocksEditor.propTypes = {
115
199
  defaultMessage: PropTypes.string.isRequired,
116
200
  values: PropTypes.object,
117
201
  }).isRequired,
202
+ labelAction: PropTypes.element,
118
203
  name: PropTypes.string.isRequired,
119
204
  required: PropTypes.bool,
120
- readOnly: PropTypes.bool,
205
+ disabled: PropTypes.bool,
121
206
  error: PropTypes.string,
122
207
  onChange: PropTypes.func.isRequired,
123
208
  value: PropTypes.array,
209
+ placeholder: PropTypes.shape({
210
+ id: PropTypes.string.isRequired,
211
+ defaultMessage: PropTypes.string.isRequired,
212
+ }),
213
+ hint: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
124
214
  };
125
215
 
126
216
  export default BlocksEditor;
@@ -0,0 +1,4 @@
1
+ import { withLinks } from './withLinks';
2
+ import { withStrapiSchema } from './withStrapiSchema';
3
+
4
+ export { withLinks, withStrapiSchema };
@@ -0,0 +1,61 @@
1
+ import { Path, Transforms, Range, Point, Editor } from 'slate';
2
+
3
+ const withLinks = (editor) => {
4
+ const { isInline, apply, insertText } = editor;
5
+
6
+ // Links are inline elements, so we need to override the isInline method for slate
7
+ editor.isInline = (element) => {
8
+ return element.type === 'link' ? true : isInline(element);
9
+ };
10
+
11
+ // We keep a track of the last inserted link path
12
+ // So we can show the popover on the link component if that link is the last one inserted
13
+ editor.lastInsertedLinkPath = null;
14
+
15
+ // We intercept the apply method, so everytime we insert a new link, we save its path
16
+ editor.apply = (operation) => {
17
+ if (operation.type === 'insert_node') {
18
+ if (operation.node.type === 'link') {
19
+ editor.lastInsertedLinkPath = operation.path;
20
+ }
21
+ } else if (operation.type === 'move_node') {
22
+ // We need to update the last inserted link path when link is moved
23
+ // If link is the first word in the paragraph we dont need to update the path
24
+ if (Path.hasPrevious(operation.path)) {
25
+ editor.lastInsertedLinkPath = Path.transform(editor.lastInsertedLinkPath, operation);
26
+ }
27
+ }
28
+
29
+ apply(operation);
30
+ };
31
+
32
+ editor.insertText = (text) => {
33
+ // When selection is at the end of a link and user types a space, we want to break the link
34
+ if (Range.isCollapsed(editor.selection) && text === ' ') {
35
+ const linksInSelection = Array.from(
36
+ Editor.nodes(editor, { at: editor.selection, match: (node) => node.type === 'link' })
37
+ );
38
+
39
+ const selectionIsInLink = editor.selection && linksInSelection.length > 0;
40
+ const selectionIsAtEndOfLink =
41
+ selectionIsInLink &&
42
+ Point.equals(editor.selection.anchor, Editor.end(editor, linksInSelection[0][1]));
43
+
44
+ if (selectionIsAtEndOfLink) {
45
+ Transforms.insertNodes(
46
+ editor,
47
+ { text: ' ', type: 'text' },
48
+ { at: Path.next(linksInSelection[0][1]), select: true }
49
+ );
50
+
51
+ return;
52
+ }
53
+ }
54
+
55
+ insertText(text);
56
+ };
57
+
58
+ return editor;
59
+ };
60
+
61
+ export { withLinks };
@@ -0,0 +1,33 @@
1
+ import { Element, Transforms } from 'slate';
2
+
3
+ /**
4
+ * This plugin is used to normalize the Slate document to match the Strapi schema.
5
+ *
6
+ * @param {import('slate').Editor} editor
7
+ */
8
+ const withStrapiSchema = (editor) => {
9
+ const { normalizeNode } = editor;
10
+
11
+ /**
12
+ * On the strapi schema, we want text nodes to have type: text
13
+ * By default, Slate add text nodes without type: text
14
+ * So we add this normalization for the cases when Slate add text nodes automatically
15
+ */
16
+ editor.normalizeNode = (entry) => {
17
+ const [node, path] = entry;
18
+
19
+ if (!Element.isElement(node)) {
20
+ if (node.type !== 'text') {
21
+ Transforms.setNodes(editor, { type: 'text' }, { at: path });
22
+
23
+ return;
24
+ }
25
+ }
26
+
27
+ normalizeNode(entry);
28
+ };
29
+
30
+ return editor;
31
+ };
32
+
33
+ export { withStrapiSchema };
@@ -0,0 +1,90 @@
1
+ import { Transforms, Editor, Element as SlateElement, Node, Range } from 'slate';
2
+
3
+ /**
4
+ *
5
+ * @param {string} url
6
+ * @param {string} protocol
7
+ */
8
+ const addProtocol = (url, protocol = 'https://') => {
9
+ const allowedProtocols = ['http://', 'https://', 'mailto:', 'tel:'];
10
+
11
+ if (allowedProtocols.some((allowedProtocol) => url.startsWith(allowedProtocol))) {
12
+ return url;
13
+ }
14
+
15
+ return `${protocol}${url}`;
16
+ };
17
+
18
+ /**
19
+ *
20
+ * @param {import('slate').Editor} editor
21
+ */
22
+ const removeLink = (editor) => {
23
+ Transforms.unwrapNodes(editor, {
24
+ match: (node) => !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === 'link',
25
+ });
26
+ };
27
+
28
+ /**
29
+ *
30
+ * @param {import('slate').Editor} editor
31
+ * @param {object} link
32
+ * @param {string} link.url
33
+ */
34
+ const insertLink = (editor, { url }) => {
35
+ if (editor.selection) {
36
+ // We want to remove all link on the selection
37
+ const linkNodes = Array.from(
38
+ Editor.nodes(editor, {
39
+ at: editor.selection,
40
+ match: (node) => node.type === 'link',
41
+ })
42
+ );
43
+
44
+ linkNodes.forEach(([, path]) => {
45
+ Transforms.unwrapNodes(editor, { at: path });
46
+ });
47
+
48
+ if (Range.isCollapsed(editor.selection)) {
49
+ const link = { type: 'link', url: url ? addProtocol(url) : '', children: [{ text: url }] };
50
+
51
+ Transforms.insertNodes(editor, link);
52
+ } else {
53
+ Transforms.wrapNodes(
54
+ editor,
55
+ { type: 'link', url: url ? addProtocol(url) : '' },
56
+ { split: true }
57
+ );
58
+ }
59
+ }
60
+ };
61
+
62
+ /**
63
+ *
64
+ * @param {import('slate').Editor} editor
65
+ * @param {object} link
66
+ * @param {string} link.url
67
+ * @param {string} link.text
68
+ */
69
+ const editLink = (editor, { url, text }) => {
70
+ if (editor.selection) {
71
+ const [linkNode, linkPath] = Editor.above(editor, { match: (node) => node.type === 'link' });
72
+
73
+ if (linkNode) {
74
+ Transforms.setNodes(editor, { url: addProtocol(url) }, { at: linkPath });
75
+
76
+ // If link text is different, we remove the old text and insert the new one
77
+ if (text !== '' && text !== Editor.string(editor, linkPath)) {
78
+ const linkNodeChildrens = Array.from(Node.children(editor, linkPath, { reverse: true }));
79
+
80
+ linkNodeChildrens.forEach(([, childPath]) => {
81
+ Transforms.removeNodes(editor, { at: childPath });
82
+ });
83
+
84
+ Transforms.insertNodes(editor, [{ text }], { at: linkPath.concat(0) });
85
+ }
86
+ }
87
+ }
88
+ };
89
+
90
+ export { insertLink, editLink, removeLink };
@@ -12,7 +12,7 @@ import PropTypes from 'prop-types';
12
12
  import { useIntl } from 'react-intl';
13
13
  import { useMutation, useQuery } from 'react-query';
14
14
 
15
- import useDebounce from '../../../hooks/useDebounce';
15
+ import { useDebounce } from '../../../hooks/useDebounce';
16
16
 
17
17
  import { FieldActionWrapper, LoadingWrapper, TextValidation } from './endActionStyle';
18
18
  import UID_REGEX from './regex';
@@ -2,7 +2,15 @@ import { useRBACProvider, findMatchingPermissions } from '@strapi/helper-plugin'
2
2
 
3
3
  import { CREATOR_FIELDS } from '../constants/attributes';
4
4
 
5
- const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamiczone', 'password'];
5
+ const NOT_ALLOWED_FILTERS = [
6
+ 'json',
7
+ 'component',
8
+ 'media',
9
+ 'richtext',
10
+ 'dynamiczone',
11
+ 'password',
12
+ 'blocks',
13
+ ];
6
14
  const TIMESTAMPS = ['createdAt', 'updatedAt'];
7
15
 
8
16
  export const useAllowedAttributes = (contentType, slug) => {
@@ -166,6 +166,7 @@ export const useRelation = (cacheKey, { relation, search }) => {
166
166
  setSearchParams({
167
167
  ...options,
168
168
  _q: term,
169
+ _filter: '$startsWithi',
169
170
  });
170
171
  };
171
172
 
@@ -73,6 +73,7 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
73
73
  'password',
74
74
  'richtext',
75
75
  'timestamp',
76
+ 'blocks',
76
77
  ].includes(type) && !!type
77
78
  );
78
79
  });
@@ -12,6 +12,7 @@ const createPossibleMainFieldsForModelsAndComponents = (array) => {
12
12
  'relation',
13
13
  'text',
14
14
  'richtext',
15
+ 'blocks',
15
16
  ].includes(attributes?.[attr]?.type ?? '');
16
17
  });
17
18
 
@@ -5,4 +5,5 @@ export const EXCLUDED_SORT_ATTRIBUTE_TYPES = [
5
5
  'relation',
6
6
  'component',
7
7
  'json',
8
+ 'blocks',
8
9
  ];
@@ -185,6 +185,7 @@ function ListView({
185
185
  };
186
186
 
187
187
  filter.fieldSchema.mainField = {
188
+ ...mainField,
188
189
  name: 'id',
189
190
  };
190
191
  }
@@ -720,7 +721,7 @@ function ListView({
720
721
  {/* Empty content */}
721
722
  <Table.EmptyBody
722
723
  contentType={headerLayoutTitle}
723
- aciton={getCreateAction({ variant: 'secondary' })}
724
+ action={getCreateAction({ variant: 'secondary' })}
724
725
  />
725
726
  {/* Content */}
726
727
  <Body.Root
@@ -5,7 +5,7 @@ const checkIfAttributeIsDisplayable = (attribute) => {
5
5
  return !(attribute?.relationType ?? '').toLowerCase().includes('morph');
6
6
  }
7
7
 
8
- return !['json', 'dynamiczone', 'richtext', 'password'].includes(type) && !!type;
8
+ return !['json', 'dynamiczone', 'richtext', 'password', 'blocks'].includes(type) && !!type;
9
9
  };
10
10
 
11
11
  export default checkIfAttributeIsDisplayable;
@@ -228,8 +228,8 @@ const createYupSchemaAttribute = (type, validations, options) => {
228
228
  return true;
229
229
  }
230
230
 
231
- // The backend validates the actual schema
232
- if (!Array.isArray(value)) {
231
+ // The backend validates the actual schema, check if a value different than null is not an array
232
+ if (value && !Array.isArray(value)) {
233
233
  return false;
234
234
  }
235
235
 
@@ -0,0 +1,15 @@
1
+ import { createContext } from 'react';
2
+
3
+ interface ConfigurationsContextValue {
4
+ logos: {
5
+ auth: { custom?: string; default: string };
6
+ };
7
+ }
8
+
9
+ const ConfigurationsContext = createContext<ConfigurationsContextValue>({
10
+ logos: {
11
+ auth: { default: '' },
12
+ },
13
+ });
14
+
15
+ export { ConfigurationsContext };
@@ -1,4 +1,3 @@
1
1
  export { default as AdminContext } from './Admin';
2
- export { default as ConfigurationsContext } from './Configurations';
2
+ export { ConfigurationsContext } from './configuration';
3
3
  export { default as PermissionsDataManagerContext } from './PermisssionsDataManagerContext';
4
- export { default as ThemeToggleContext } from './ThemeToggle';
@@ -0,0 +1,16 @@
1
+ import { createContext } from 'react';
2
+
3
+ import { DefaultTheme } from 'styled-components';
4
+
5
+ export type ThemeName = 'light' | 'dark';
6
+
7
+ interface ThemeToggleContextContextValue {
8
+ currentTheme?: ThemeName;
9
+ onChangeTheme?: (nextTheme: ThemeName) => void;
10
+ themes?: {
11
+ dark: DefaultTheme;
12
+ light: DefaultTheme;
13
+ };
14
+ }
15
+
16
+ export const ThemeToggleContext = createContext<ThemeToggleContextContextValue>({});
@@ -1,7 +1,9 @@
1
- export default function () {
1
+ const useConfigurations = () => {
2
2
  return {
3
3
  logos: {
4
4
  auth: { custom: 'customAuthLogo.png', default: 'defaultAuthLogo.png' },
5
5
  },
6
6
  };
7
- }
7
+ };
8
+
9
+ export { useConfigurations };
@@ -1,10 +1,6 @@
1
- export { default as useConfigurations } from './useConfigurations';
1
+ export { useConfigurations } from './useConfigurations';
2
2
  export { useContentTypes } from './useContentTypes';
3
- export { default as useLicenseLimitNotification } from './useLicenseLimitNotification';
4
3
  export { default as useMenu } from './useMenu';
5
4
  export { default as usePermissionsDataManager } from './usePermissionsDataManager';
6
- export { default as useRegenerate } from './useRegenerate';
7
- export { default as useReleaseNotification } from './useReleaseNotification';
8
5
  export { default as useSettingsForm } from './useSettingsForm';
9
6
  export { default as useSettingsMenu } from './useSettingsMenu';
10
- export { default as useThemeToggle } from './useThemeToggle';
@@ -0,0 +1,5 @@
1
+ import { useContext } from 'react';
2
+
3
+ import { ConfigurationsContext } from '../contexts/configuration';
4
+
5
+ export const useConfigurations = () => useContext(ConfigurationsContext);
@@ -0,0 +1,17 @@
1
+ import * as React from 'react';
2
+
3
+ export function useDebounce<TValue>(value: TValue, delay: number): TValue {
4
+ const [debouncedValue, setDebouncedValue] = React.useState(value);
5
+
6
+ React.useEffect(() => {
7
+ const handler = setTimeout(() => {
8
+ setDebouncedValue(value);
9
+ }, delay);
10
+
11
+ return () => {
12
+ clearTimeout(handler);
13
+ };
14
+ }, [value, delay]);
15
+
16
+ return debouncedValue;
17
+ }
@@ -0,0 +1,3 @@
1
+ export const useLicenseLimitNotification = () => {
2
+ return null;
3
+ };
@@ -0,0 +1,9 @@
1
+ import { useContext } from 'react';
2
+
3
+ import { ThemeToggleContext } from '../contexts/themeToggle';
4
+
5
+ export const useThemeToggle = () => {
6
+ const context = useContext(ThemeToggleContext);
7
+
8
+ return context;
9
+ };
@@ -21,7 +21,7 @@ import { useIntl } from 'react-intl';
21
21
  import { useDispatch } from 'react-redux';
22
22
  import { Route, Switch } from 'react-router-dom';
23
23
 
24
- import PrivateRoute from '../../components/PrivateRoute';
24
+ import { PrivateRoute } from '../../components/PrivateRoute';
25
25
  import { ADMIN_PERMISSIONS_CE } from '../../constants';
26
26
  import { useConfigurations } from '../../hooks';
27
27
  import { useEnterprise } from '../../hooks/useEnterprise';
@@ -6,7 +6,7 @@ import { Formik } from 'formik';
6
6
  import PropTypes from 'prop-types';
7
7
  import { useIntl } from 'react-intl';
8
8
 
9
- import Logo from '../../../../components/UnauthenticatedLogo';
9
+ import { Logo } from '../../../../components/UnauthenticatedLogo';
10
10
  import UnauthenticatedLayout, {
11
11
  Column,
12
12
  LayoutContent,
@@ -4,7 +4,7 @@ import { Box, Flex, Main, Typography } from '@strapi/design-system';
4
4
  import { Link } from '@strapi/helper-plugin';
5
5
  import { useIntl } from 'react-intl';
6
6
 
7
- import Logo from '../../../../components/UnauthenticatedLogo';
7
+ import { Logo } from '../../../../components/UnauthenticatedLogo';
8
8
  import UnauthenticatedLayout, {
9
9
  Column,
10
10
  LayoutContent,
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
8
8
  import { useIntl } from 'react-intl';
9
9
  import styled from 'styled-components';
10
10
 
11
- import Logo from '../../../../components/UnauthenticatedLogo';
11
+ import { Logo } from '../../../../components/UnauthenticatedLogo';
12
12
  import { Column, LayoutContent } from '../../../../layouts/UnauthenticatedLayout';
13
13
  import FieldActionWrapper from '../FieldActionWrapper';
14
14
 
@@ -4,7 +4,7 @@ import { Box, Flex, Main, Typography } from '@strapi/design-system';
4
4
  import { Link, useQuery } from '@strapi/helper-plugin';
5
5
  import { useIntl } from 'react-intl';
6
6
 
7
- import Logo from '../../../../components/UnauthenticatedLogo';
7
+ import { Logo } from '../../../../components/UnauthenticatedLogo';
8
8
  import UnauthenticatedLayout, {
9
9
  Column,
10
10
  LayoutContent,
@@ -30,7 +30,7 @@ import { useHistory } from 'react-router-dom';
30
30
  import styled from 'styled-components';
31
31
 
32
32
  import { useNpsSurveySettings } from '../../../../components/NpsSurvey/hooks/useNpsSurveySettings';
33
- import Logo from '../../../../components/UnauthenticatedLogo';
33
+ import { Logo } from '../../../../components/UnauthenticatedLogo';
34
34
  import UnauthenticatedLayout, { LayoutContent } from '../../../../layouts/UnauthenticatedLayout';
35
35
  import FieldActionWrapper from '../FieldActionWrapper';
36
36
 
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
8
8
  import { useIntl } from 'react-intl';
9
9
  import styled from 'styled-components';
10
10
 
11
- import Logo from '../../../../components/UnauthenticatedLogo';
11
+ import { Logo } from '../../../../components/UnauthenticatedLogo';
12
12
  import UnauthenticatedLayout, {
13
13
  Column,
14
14
  LayoutContent,