@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.
- package/.eslintrc.js +4 -1
- package/admin/.eslintrc.js +16 -0
- package/admin/custom.d.ts +8 -0
- package/admin/src/components/AuthenticatedApp/index.js +3 -7
- package/admin/src/components/AuthenticatedApp/utils/api.js +1 -39
- package/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.ts +13 -0
- package/admin/src/{hooks/useReleaseNotification/utils/api.js → components/AuthenticatedApp/utils/fetchStrapiLatestRelease.ts} +2 -3
- package/admin/src/components/{DragLayer/DragLayer.js → DragLayer.tsx} +18 -10
- package/admin/src/components/PrivateRoute.tsx +42 -0
- package/admin/src/components/Providers/index.js +2 -2
- package/admin/src/components/Theme.tsx +39 -0
- package/admin/src/components/ThemeToggleProvider.tsx +50 -0
- package/admin/src/components/{UnauthenticatedLogo/index.js → UnauthenticatedLogo.tsx} +2 -4
- package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +22 -3
- package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +263 -134
- package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +362 -95
- package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +15 -0
- package/admin/src/content-manager/components/BlocksEditor/index.js +99 -9
- package/admin/src/content-manager/components/BlocksEditor/plugins/index.js +4 -0
- package/admin/src/content-manager/components/BlocksEditor/plugins/withLinks.js +61 -0
- package/admin/src/content-manager/components/BlocksEditor/plugins/withStrapiSchema.js +33 -0
- package/admin/src/content-manager/components/BlocksEditor/utils/links.js +90 -0
- package/admin/src/content-manager/components/InputUID/index.js +1 -1
- package/admin/src/content-manager/hooks/useAllowedAttributes.js +9 -1
- package/admin/src/content-manager/hooks/useRelation/useRelation.js +1 -0
- package/admin/src/content-manager/pages/EditSettingsView/index.js +1 -0
- package/admin/src/content-manager/pages/EditSettingsView/utils/createPossibleMainFieldsForModelsAndComponents.js +1 -0
- package/admin/src/content-manager/pages/ListSettingsView/constants.js +1 -0
- package/admin/src/content-manager/pages/ListView/index.js +2 -1
- package/admin/src/content-manager/utils/checkIfAttributeIsDisplayable.js +1 -1
- package/admin/src/content-manager/utils/schema.js +2 -2
- package/admin/src/contexts/configuration.ts +15 -0
- package/admin/src/contexts/index.js +1 -2
- package/admin/src/contexts/themeToggle.ts +16 -0
- package/admin/src/hooks/{useConfigurations/__mocks__/index.js → __mocks__/useConfigurations.ts} +4 -2
- package/admin/src/hooks/index.js +1 -5
- package/admin/src/hooks/useConfigurations.ts +5 -0
- package/admin/src/hooks/useDebounce.ts +17 -0
- package/admin/src/hooks/useLicenseLimitNotification.ts +3 -0
- package/admin/src/hooks/useThemeToggle.ts +9 -0
- package/admin/src/pages/App/index.js +1 -1
- package/admin/src/pages/AuthPage/components/ForgotPassword/index.js +1 -1
- package/admin/src/pages/AuthPage/components/ForgotPasswordSuccess/index.js +1 -1
- package/admin/src/pages/AuthPage/components/Login/BaseLogin.js +1 -1
- package/admin/src/pages/AuthPage/components/Oops/index.js +1 -1
- package/admin/src/pages/AuthPage/components/Register/index.js +1 -1
- package/admin/src/pages/AuthPage/components/ResetPassword/index.js +1 -1
- package/admin/src/pages/MarketplacePage/components/NpmPackageCard/index.js +0 -2
- package/admin/src/pages/MarketplacePage/hooks/__mocks__/useNavigatorOnline.ts +1 -0
- package/admin/src/{hooks/useNavigatorOnLine/index.js → pages/MarketplacePage/hooks/useNavigatorOnline.ts} +4 -6
- package/admin/src/pages/MarketplacePage/index.js +3 -3
- package/admin/src/pages/ProfilePage/index.js +1 -1
- package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +1 -1
- package/admin/src/{hooks/useRegenerate/index.js → pages/SettingsPage/hooks/useRegenerate.ts} +13 -7
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +1 -1
- package/admin/src/pages/UseCasePage/index.js +1 -1
- package/admin/src/translations/en.json +8 -0
- package/admin/tsconfig.json +5 -0
- package/build/1049.f7aed23d.chunk.js +1 -0
- package/build/{1227.969e24e6.chunk.js → 1227.f9c74718.chunk.js} +1 -1
- package/build/{1386.db9a2795.chunk.js → 1386.6b8819c6.chunk.js} +2 -2
- package/build/2224.8af54440.chunk.js +138 -0
- package/build/2225.d1bcf7e3.chunk.js +79 -0
- package/build/2379.f0baf826.chunk.js +1 -0
- package/build/{2395.f6ac2863.chunk.js → 2395.aca6ce66.chunk.js} +1 -1
- package/build/2421.a478ba24.chunk.js +105 -0
- package/build/2801.c49f88a1.chunk.js +1 -0
- package/build/{3483.f6b2439f.chunk.js → 3483.5df8e010.chunk.js} +1 -1
- package/build/3911.d4fada48.chunk.js +95 -0
- package/build/412.72afdf0c.chunk.js +689 -0
- package/build/{4174.3e13fb26.chunk.js → 4174.df9aa09a.chunk.js} +1 -1
- package/build/502.8666bbef.chunk.js +25 -0
- package/build/570.2f3b4c56.chunk.js +1 -0
- package/build/5702.5b433d50.chunk.js +1 -0
- package/build/6186.c33ce082.chunk.js +116 -0
- package/build/7464.43a4527c.chunk.js +1 -0
- package/build/7818.d2196a53.chunk.js +29 -0
- package/build/7897.5c03247b.chunk.js +25 -0
- package/build/{8276.951e198e.chunk.js → 8276.d4426fd8.chunk.js} +3 -3
- package/build/8690.33243bba.chunk.js +38 -0
- package/build/{9832.65ed5a44.chunk.js → 8743.31c921b1.chunk.js} +139 -123
- package/build/9218.8bc01ab9.chunk.js +1 -0
- package/build/Admin-authenticatedApp.27545a1b.chunk.js +112 -0
- package/build/{Admin_InternalErrorPage.b3163562.chunk.js → Admin_InternalErrorPage.b66ee9c1.chunk.js} +1 -1
- package/build/Admin_homePage.a6281dd6.chunk.js +124 -0
- package/build/Admin_marketplace.31b962b8.chunk.js +44 -0
- package/build/{Admin_pluginsPage.b9fa2947.chunk.js → Admin_pluginsPage.9217101d.chunk.js} +1 -1
- package/build/{Admin_profilePage.a4d41380.chunk.js → Admin_profilePage.680123d9.chunk.js} +2 -2
- package/build/{Admin_settingsPage.6dc2af9f.chunk.js → Admin_settingsPage.33378310.chunk.js} +1 -1
- package/build/{Upload_ConfigureTheView.cc7ca628.chunk.js → Upload_ConfigureTheView.b40eea4d.chunk.js} +1 -1
- package/build/admin-app.e8c52c37.chunk.js +36 -0
- package/build/admin-edit-roles-page.fcf056bf.chunk.js +275 -0
- package/build/{admin-edit-users.9b42cc9e.chunk.js → admin-edit-users.89efe3c4.chunk.js} +2 -2
- package/build/{admin-roles-list.cf964578.chunk.js → admin-roles-list.8b77704a.chunk.js} +3 -3
- package/build/admin-users.e3f1be14.chunk.js +19 -0
- package/build/{api-tokens-create-page.2f25ddf6.chunk.js → api-tokens-create-page.0dd63e91.chunk.js} +1 -1
- package/build/{api-tokens-edit-page.45faac16.chunk.js → api-tokens-edit-page.78d877f8.chunk.js} +1 -1
- package/build/{api-tokens-list-page.5baabf1a.chunk.js → api-tokens-list-page.ae13346c.chunk.js} +2 -2
- package/build/audit-logs-settings-page.e9c92a75.chunk.js +9 -0
- package/build/content-manager.5849dbe3.chunk.js +1226 -0
- package/build/{content-type-builder-list-view.aa8a5d1a.chunk.js → content-type-builder-list-view.3fffae65.chunk.js} +1 -1
- package/build/{content-type-builder-translation-en-json.b9e5cacd.chunk.js → content-type-builder-translation-en-json.43f9d7bc.chunk.js} +1 -1
- package/build/{content-type-builder.885f2cad.chunk.js → content-type-builder.98c71164.chunk.js} +14 -14
- package/build/{email-settings-page.6bd7b280.chunk.js → email-settings-page.ecfec9b3.chunk.js} +1 -1
- package/build/{en-json.a3973ff5.chunk.js → en-json.bd611a8e.chunk.js} +1 -1
- package/build/{i18n-settings-page.6c0157e7.chunk.js → i18n-settings-page.a9708926.chunk.js} +1 -1
- package/build/index.html +1 -1
- package/build/main.3abb6f34.js +3278 -0
- package/build/{review-workflows-settings-create-view.ae369a88.chunk.js → review-workflows-settings-create-view.b7b0c6c5.chunk.js} +1 -1
- package/build/{review-workflows-settings-edit-view.9a61c69f.chunk.js → review-workflows-settings-edit-view.c331b3fe.chunk.js} +1 -1
- package/build/review-workflows-settings-list-view.70218dc1.chunk.js +75 -0
- package/build/{runtime~main.cec66cd9.js → runtime~main.450561b1.js} +1 -1
- package/build/{sso-settings-page.a29e6c38.chunk.js → sso-settings-page.1a9e7f8f.chunk.js} +1 -1
- package/build/{transfer-tokens-create-page.6e1b8cee.chunk.js → transfer-tokens-create-page.e7f541d3.chunk.js} +1 -1
- package/build/{transfer-tokens-edit-page.10bb22e2.chunk.js → transfer-tokens-edit-page.bd1276c2.chunk.js} +1 -1
- package/build/{transfer-tokens-list-page.0306652c.chunk.js → transfer-tokens-list-page.5de6bb9f.chunk.js} +2 -2
- package/build/upload-settings.97ef4c92.chunk.js +14 -0
- package/build/{upload.19e14c8e.chunk.js → upload.f08715a1.chunk.js} +1 -1
- package/build/{users-advanced-settings-page.ed69812f.chunk.js → users-advanced-settings-page.36a3c363.chunk.js} +1 -1
- package/build/users-email-settings-page.47b47962.chunk.js +149 -0
- package/build/users-providers-settings-page.1e0c8376.chunk.js +154 -0
- package/build/{users-roles-settings-page.afab5a0d.chunk.js → users-roles-settings-page.d5a8e8a1.chunk.js} +4 -4
- package/build/{webhook-edit-page.4c037da4.chunk.js → webhook-edit-page.87456194.chunk.js} +3 -3
- package/build/{webhook-list-page.56c82f4a.chunk.js → webhook-list-page.c88a382b.chunk.js} +3 -3
- package/ee/admin/hooks/{useLicenseLimitNotification.js → useLicenseLimitNotification.ts} +4 -4
- package/ee/admin/pages/AuthPage/components/Providers/index.js +1 -1
- package/ee/admin/pages/SettingsPage/pages/Users/ListPage/index.js +1 -3
- package/package.json +13 -12
- package/scripts/build.js +6 -2
- package/webpack.config.js +1 -0
- package/admin/src/components/AuthenticatedApp/utils/checkLatestStrapiVersion.js +0 -11
- package/admin/src/components/DragLayer/index.js +0 -1
- package/admin/src/components/GlobalStyle/index.js +0 -9
- package/admin/src/components/PrivateRoute/index.js +0 -46
- package/admin/src/components/Theme/index.js +0 -26
- package/admin/src/components/ThemeToggleProvider/index.js +0 -79
- package/admin/src/contexts/Configurations/index.js +0 -5
- package/admin/src/contexts/ThemeToggle/index.js +0 -5
- package/admin/src/hooks/useConfigurations/index.js +0 -11
- package/admin/src/hooks/useDebounce/index.js +0 -19
- package/admin/src/hooks/useLicenseLimitNotification/index.js +0 -5
- package/admin/src/hooks/useReleaseNotification/index.js +0 -31
- package/admin/src/hooks/useReleaseNotification/utils/checkLatestStrapiVersion.js +0 -11
- package/admin/src/hooks/useThemeToggle/index.js +0 -11
- package/admin/src/tsconfig.json +0 -10
- package/build/1049.acb0e730.chunk.js +0 -1
- package/build/2225.78fb9b89.chunk.js +0 -79
- package/build/2379.906334f0.chunk.js +0 -1
- package/build/2614.3e088d3e.chunk.js +0 -35
- package/build/2659.cb94f1e7.chunk.js +0 -105
- package/build/2801.2afb4757.chunk.js +0 -1
- package/build/2950.216f2e89.chunk.js +0 -1
- package/build/3021.33ad47fb.chunk.js +0 -103
- package/build/3911.488fbde3.chunk.js +0 -95
- package/build/4546.1203ac95.chunk.js +0 -1
- package/build/502.9918bff7.chunk.js +0 -1
- package/build/5158.c85f841a.chunk.js +0 -1
- package/build/6266.e8990811.chunk.js +0 -146
- package/build/7464.0280cf59.chunk.js +0 -1
- package/build/7897.4a39de37.chunk.js +0 -6
- package/build/Admin-authenticatedApp.08f32723.chunk.js +0 -112
- package/build/Admin_homePage.6cb51f18.chunk.js +0 -81
- package/build/Admin_marketplace.3eb5e132.chunk.js +0 -55
- package/build/admin-app.98cdf43a.chunk.js +0 -36
- package/build/admin-edit-roles-page.418bb1c5.chunk.js +0 -267
- package/build/admin-users.8385dd73.chunk.js +0 -11
- package/build/audit-logs-settings-page.91489670.chunk.js +0 -1
- package/build/content-manager.0d2b4a60.chunk.js +0 -1199
- package/build/main.105dcf23.js +0 -2665
- package/build/review-workflows-settings-list-view.067e0c35.chunk.js +0 -56
- package/build/upload-settings.0af6edc5.chunk.js +0 -14
- package/build/users-email-settings-page.131a00fb.chunk.js +0 -9
- package/build/users-providers-settings-page.b3dca41d.chunk.js +0 -14
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Box,
|
|
5
|
+
Typography,
|
|
6
|
+
BaseLink,
|
|
7
|
+
Popover,
|
|
8
|
+
IconButton,
|
|
9
|
+
Field,
|
|
10
|
+
FieldLabel,
|
|
11
|
+
FieldInput,
|
|
12
|
+
Flex,
|
|
13
|
+
Button,
|
|
14
|
+
} from '@strapi/design-system';
|
|
4
15
|
import {
|
|
5
16
|
Code,
|
|
6
17
|
Quote,
|
|
@@ -12,11 +23,20 @@ import {
|
|
|
12
23
|
HeadingFour,
|
|
13
24
|
HeadingFive,
|
|
14
25
|
HeadingSix,
|
|
26
|
+
Trash,
|
|
27
|
+
Pencil,
|
|
28
|
+
BulletList,
|
|
29
|
+
NumberList,
|
|
15
30
|
} from '@strapi/icons';
|
|
16
31
|
import PropTypes from 'prop-types';
|
|
17
|
-
import {
|
|
32
|
+
import { useIntl } from 'react-intl';
|
|
33
|
+
import { Editor, Path, Transforms, Range } from 'slate';
|
|
34
|
+
import { useSlate, ReactEditor } from 'slate-react';
|
|
18
35
|
import styled, { css } from 'styled-components';
|
|
19
36
|
|
|
37
|
+
import { composeRefs } from '../../../utils';
|
|
38
|
+
import { editLink, removeLink } from '../utils/links';
|
|
39
|
+
|
|
20
40
|
const H1 = styled(Typography).attrs({ as: 'h1' })`
|
|
21
41
|
font-size: ${42 / 16}rem;
|
|
22
42
|
line-height: ${({ theme }) => theme.lineHeights[1]};
|
|
@@ -76,23 +96,24 @@ Heading.propTypes = {
|
|
|
76
96
|
|
|
77
97
|
const CodeBlock = styled.pre.attrs({ role: 'code' })`
|
|
78
98
|
border-radius: ${({ theme }) => theme.borderRadius};
|
|
79
|
-
background-color:
|
|
99
|
+
background-color: ${({ theme }) => theme.colors.neutral100};
|
|
80
100
|
max-width: 100%;
|
|
81
101
|
overflow: auto;
|
|
82
|
-
padding: ${({ theme }) => theme.spaces[
|
|
102
|
+
padding: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[4]}`};
|
|
103
|
+
flex-shrink: 0;
|
|
83
104
|
& > code {
|
|
84
|
-
|
|
105
|
+
font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas,
|
|
106
|
+
monospace;
|
|
107
|
+
color: ${({ theme }) => theme.colors.neutral800};
|
|
85
108
|
overflow: auto;
|
|
86
109
|
max-width: 100%;
|
|
87
|
-
padding: ${({ theme }) => theme.spaces[2]};
|
|
88
110
|
}
|
|
89
111
|
`;
|
|
90
112
|
|
|
91
113
|
const Blockquote = styled.blockquote.attrs({ role: 'blockquote' })`
|
|
92
|
-
margin: ${({ theme }) => `${theme.spaces[
|
|
114
|
+
margin: ${({ theme }) => `${theme.spaces[4]} 0`};
|
|
93
115
|
font-weight: ${({ theme }) => theme.fontWeights.regular};
|
|
94
116
|
border-left: ${({ theme }) => `${theme.spaces[1]} solid ${theme.colors.neutral150}`};
|
|
95
|
-
font-style: italic;
|
|
96
117
|
padding: ${({ theme }) => theme.spaces[2]} ${({ theme }) => theme.spaces[5]};
|
|
97
118
|
`;
|
|
98
119
|
|
|
@@ -134,35 +155,42 @@ List.propTypes = {
|
|
|
134
155
|
}).isRequired,
|
|
135
156
|
};
|
|
136
157
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
/**
|
|
159
|
+
* @param {import('slate').Editor} editor
|
|
160
|
+
* @param {Path} currentListPath
|
|
161
|
+
*/
|
|
162
|
+
const replaceListWithEmptyBlock = (editor, currentListPath) => {
|
|
163
|
+
// Delete the empty list
|
|
164
|
+
Transforms.removeNodes(editor, { at: currentListPath });
|
|
144
165
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
if (currentListPath[0] === 0) {
|
|
167
|
+
// If the list was the only (or first) block element then insert empty paragraph as editor needs default value
|
|
168
|
+
Transforms.insertNodes(
|
|
169
|
+
editor,
|
|
170
|
+
{
|
|
171
|
+
type: 'paragraph',
|
|
172
|
+
children: [{ type: 'text', text: '' }],
|
|
173
|
+
},
|
|
174
|
+
{ at: currentListPath }
|
|
175
|
+
);
|
|
176
|
+
Transforms.select(editor, currentListPath);
|
|
177
|
+
}
|
|
153
178
|
};
|
|
154
179
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Common handler for the backspace event on ordered and unordered lists
|
|
182
|
+
* @param {import('slate').Editor} editor
|
|
183
|
+
* @param {Event} event
|
|
184
|
+
*/
|
|
185
|
+
const handleBackspaceKeyOnList = (editor, event) => {
|
|
186
|
+
const [currentListItem, currentListItemPath] = Editor.parent(editor, editor.selection.anchor);
|
|
187
|
+
const [currentList, currentListPath] = Editor.parent(editor, currentListItemPath);
|
|
188
|
+
const isListEmpty = currentList.children.length === 1 && currentListItem.children[0].text === '';
|
|
189
|
+
|
|
190
|
+
if (isListEmpty) {
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
replaceListWithEmptyBlock(editor, currentListPath);
|
|
193
|
+
}
|
|
166
194
|
};
|
|
167
195
|
|
|
168
196
|
/**
|
|
@@ -170,18 +198,21 @@ Image.propTypes = {
|
|
|
170
198
|
* @param {import('slate').Editor} editor
|
|
171
199
|
*/
|
|
172
200
|
const handleEnterKeyOnList = (editor) => {
|
|
173
|
-
// Check if the selected list item is empty
|
|
174
201
|
const [currentListItem, currentListItemPath] = Editor.above(editor, {
|
|
175
202
|
matchNode: (node) => node.type === 'list-item',
|
|
176
203
|
});
|
|
177
|
-
const
|
|
204
|
+
const [currentList, currentListPath] = Editor.parent(editor, currentListItemPath);
|
|
205
|
+
const isListEmpty = currentList.children.length === 1 && currentListItem.children[0].text === '';
|
|
206
|
+
const isListItemEmpty =
|
|
178
207
|
currentListItem.children.length === 1 && currentListItem.children[0].text === '';
|
|
179
208
|
|
|
180
|
-
if (
|
|
209
|
+
if (isListEmpty) {
|
|
210
|
+
replaceListWithEmptyBlock(editor, currentListPath);
|
|
211
|
+
} else if (isListItemEmpty) {
|
|
181
212
|
// Delete the empty list item
|
|
182
213
|
Transforms.removeNodes(editor, { at: currentListItemPath });
|
|
183
214
|
|
|
184
|
-
//
|
|
215
|
+
// Create a new paragraph below the parent list
|
|
185
216
|
const listNodeEntry = Editor.above(editor, { match: (n) => n.type === 'list' });
|
|
186
217
|
const createdParagraphPath = Path.next(listNodeEntry[1]);
|
|
187
218
|
Transforms.insertNodes(
|
|
@@ -193,7 +224,7 @@ const handleEnterKeyOnList = (editor) => {
|
|
|
193
224
|
{ at: createdParagraphPath }
|
|
194
225
|
);
|
|
195
226
|
|
|
196
|
-
// Move selection to the newly created paragraph
|
|
227
|
+
// Move the selection to the newly created paragraph
|
|
197
228
|
Transforms.select(editor, createdParagraphPath);
|
|
198
229
|
} else {
|
|
199
230
|
// Otherwise just create a new list item by splitting the current one
|
|
@@ -201,6 +232,193 @@ const handleEnterKeyOnList = (editor) => {
|
|
|
201
232
|
}
|
|
202
233
|
};
|
|
203
234
|
|
|
235
|
+
// The max-height is decided with the design team, the 56px is the height of the toolbar
|
|
236
|
+
const Img = styled.img`
|
|
237
|
+
max-height: calc(512px - 56px);
|
|
238
|
+
max-width: 100%;
|
|
239
|
+
object-fit: contain;
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
// Added a background color to the image wrapper to make it easier to recognize the image block
|
|
243
|
+
const Image = ({ attributes, children, element }) => {
|
|
244
|
+
if (!element.image) return null;
|
|
245
|
+
const { url, alternativeText, width, height } = element.image;
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<Box {...attributes}>
|
|
249
|
+
{children}
|
|
250
|
+
<Flex background="neutral100" contentEditable={false} justifyContent="center">
|
|
251
|
+
<Img src={url} alt={alternativeText} width={width} height={height} />
|
|
252
|
+
</Flex>
|
|
253
|
+
</Box>
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
Image.propTypes = {
|
|
258
|
+
attributes: PropTypes.object.isRequired,
|
|
259
|
+
children: PropTypes.node.isRequired,
|
|
260
|
+
element: PropTypes.shape({
|
|
261
|
+
image: PropTypes.shape({
|
|
262
|
+
url: PropTypes.string.isRequired,
|
|
263
|
+
alternativeText: PropTypes.string,
|
|
264
|
+
width: PropTypes.number,
|
|
265
|
+
height: PropTypes.number,
|
|
266
|
+
}),
|
|
267
|
+
}).isRequired,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const Link = React.forwardRef(({ element, children, ...attributes }, forwardedRef) => {
|
|
271
|
+
const { formatMessage } = useIntl();
|
|
272
|
+
const editor = useSlate();
|
|
273
|
+
const path = ReactEditor.findPath(editor, element);
|
|
274
|
+
const [popoverOpen, setPopoverOpen] = React.useState(
|
|
275
|
+
editor.lastInsertedLinkPath ? Path.equals(path, editor.lastInsertedLinkPath) : false
|
|
276
|
+
);
|
|
277
|
+
const [isEditing, setIsEditing] = React.useState(element.url === '');
|
|
278
|
+
const linkRef = React.useRef(null);
|
|
279
|
+
const elementText = element.children.map((child) => child.text).join('');
|
|
280
|
+
const [linkText, setLinkText] = React.useState(elementText);
|
|
281
|
+
const [linkUrl, setLinkUrl] = React.useState(element.url);
|
|
282
|
+
|
|
283
|
+
const handleOpenEditPopover = (e) => {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
setPopoverOpen(true);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const handleSave = (e) => {
|
|
289
|
+
e.stopPropagation();
|
|
290
|
+
|
|
291
|
+
// If the selection is collapsed, we select the parent node because we want all the link to be replaced
|
|
292
|
+
if (Range.isCollapsed(editor.selection)) {
|
|
293
|
+
const [, parentPath] = Editor.parent(editor, editor.selection.focus?.path);
|
|
294
|
+
Transforms.select(editor, parentPath);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
editLink(editor, { url: linkUrl, text: linkText });
|
|
298
|
+
setIsEditing(false);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const handleCancel = () => {
|
|
302
|
+
setIsEditing(false);
|
|
303
|
+
|
|
304
|
+
if (element.url === '') {
|
|
305
|
+
removeLink(editor);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const handleDismiss = () => {
|
|
310
|
+
setPopoverOpen(false);
|
|
311
|
+
|
|
312
|
+
if (element.url === '') {
|
|
313
|
+
removeLink(editor);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
ReactEditor.focus(editor);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const composedRefs = composeRefs(linkRef, forwardedRef);
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<>
|
|
323
|
+
<BaseLink
|
|
324
|
+
{...attributes}
|
|
325
|
+
ref={composedRefs}
|
|
326
|
+
href={element.url}
|
|
327
|
+
onClick={handleOpenEditPopover}
|
|
328
|
+
color="primary600"
|
|
329
|
+
>
|
|
330
|
+
{children}
|
|
331
|
+
</BaseLink>
|
|
332
|
+
{popoverOpen && (
|
|
333
|
+
<Popover source={linkRef} onDismiss={handleDismiss} padding={4} contentEditable={false}>
|
|
334
|
+
{isEditing ? (
|
|
335
|
+
<Flex as="form" onSubmit={handleSave} direction="column" gap={4}>
|
|
336
|
+
<Field width="300px">
|
|
337
|
+
<FieldLabel>
|
|
338
|
+
{formatMessage({
|
|
339
|
+
id: 'components.Blocks.popover.text',
|
|
340
|
+
defaultMessage: 'Text',
|
|
341
|
+
})}
|
|
342
|
+
</FieldLabel>
|
|
343
|
+
<FieldInput
|
|
344
|
+
name="text"
|
|
345
|
+
placeholder={formatMessage({
|
|
346
|
+
id: 'components.Blocks.popover.text.placeholder',
|
|
347
|
+
defaultMessage: 'Enter link text',
|
|
348
|
+
})}
|
|
349
|
+
value={linkText}
|
|
350
|
+
onChange={(e) => setLinkText(e.target.value)}
|
|
351
|
+
/>
|
|
352
|
+
</Field>
|
|
353
|
+
<Field width="300px">
|
|
354
|
+
<FieldLabel>
|
|
355
|
+
{formatMessage({
|
|
356
|
+
id: 'components.Blocks.popover.link',
|
|
357
|
+
defaultMessage: 'Link',
|
|
358
|
+
})}
|
|
359
|
+
</FieldLabel>
|
|
360
|
+
<FieldInput
|
|
361
|
+
name="url"
|
|
362
|
+
placeholder="https://strapi.io"
|
|
363
|
+
value={linkUrl}
|
|
364
|
+
onChange={(e) => setLinkUrl(e.target.value)}
|
|
365
|
+
/>
|
|
366
|
+
</Field>
|
|
367
|
+
<Flex justifyContent="end" width="100%" gap={2}>
|
|
368
|
+
<Button variant="tertiary" onClick={handleCancel}>
|
|
369
|
+
{formatMessage({
|
|
370
|
+
id: 'components.Blocks.popover.cancel',
|
|
371
|
+
defaultMessage: 'Cancel',
|
|
372
|
+
})}
|
|
373
|
+
</Button>
|
|
374
|
+
<Button type="submit" disabled={!linkText || !linkUrl}>
|
|
375
|
+
{formatMessage({
|
|
376
|
+
id: 'components.Blocks.popover.save',
|
|
377
|
+
defaultMessage: 'Save',
|
|
378
|
+
})}
|
|
379
|
+
</Button>
|
|
380
|
+
</Flex>
|
|
381
|
+
</Flex>
|
|
382
|
+
) : (
|
|
383
|
+
<Flex direction="column" gap={4} alignItems="start" width="400px">
|
|
384
|
+
<Typography>{elementText}</Typography>
|
|
385
|
+
<BaseLink href={element.url} target="_blank" color="primary600">
|
|
386
|
+
{element.url}
|
|
387
|
+
</BaseLink>
|
|
388
|
+
<Flex justifyContent="end" width="100%" gap={2}>
|
|
389
|
+
<IconButton
|
|
390
|
+
icon={<Trash />}
|
|
391
|
+
size="L"
|
|
392
|
+
variant="danger"
|
|
393
|
+
onClick={() => removeLink(editor)}
|
|
394
|
+
label={formatMessage({
|
|
395
|
+
id: 'components.Blocks.popover.delete',
|
|
396
|
+
defaultMessage: 'Delete',
|
|
397
|
+
})}
|
|
398
|
+
/>
|
|
399
|
+
<IconButton
|
|
400
|
+
icon={<Pencil />}
|
|
401
|
+
size="L"
|
|
402
|
+
onClick={() => setIsEditing(true)}
|
|
403
|
+
label={formatMessage({
|
|
404
|
+
id: 'components.Blocks.popover.edit',
|
|
405
|
+
defaultMessage: 'Edit',
|
|
406
|
+
})}
|
|
407
|
+
/>
|
|
408
|
+
</Flex>
|
|
409
|
+
</Flex>
|
|
410
|
+
)}
|
|
411
|
+
</Popover>
|
|
412
|
+
)}
|
|
413
|
+
</>
|
|
414
|
+
);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
Link.propTypes = {
|
|
418
|
+
element: PropTypes.object.isRequired,
|
|
419
|
+
children: PropTypes.node.isRequired,
|
|
420
|
+
};
|
|
421
|
+
|
|
204
422
|
/**
|
|
205
423
|
* Manages a store of all the available blocks.
|
|
206
424
|
*
|
|
@@ -213,6 +431,7 @@ const handleEnterKeyOnList = (editor) => {
|
|
|
213
431
|
* matchNode: (node: Object) => boolean,
|
|
214
432
|
* isInBlocksSelector: true,
|
|
215
433
|
* handleEnterKey: (editor: import('slate').Editor) => void,
|
|
434
|
+
* handleBackspaceKey?:(editor: import('slate').Editor, event: Event) => void,
|
|
216
435
|
* }
|
|
217
436
|
* }} an object containing rendering functions and metadata for different blocks, indexed by name.
|
|
218
437
|
*/
|
|
@@ -235,6 +454,8 @@ export function useBlocksStore() {
|
|
|
235
454
|
matchNode: (node) => node.type === 'paragraph',
|
|
236
455
|
isInBlocksSelector: true,
|
|
237
456
|
handleEnterKey(editor) {
|
|
457
|
+
// We need to keep track of the initial position of the cursor
|
|
458
|
+
const anchorPathInitialPosition = editor.selection.anchor.path;
|
|
238
459
|
/**
|
|
239
460
|
* Split the nodes where the cursor is. This will create a new paragraph with the content
|
|
240
461
|
* after the cursor, while retaining all the children, modifiers etc.
|
|
@@ -254,12 +475,23 @@ export function useBlocksStore() {
|
|
|
254
475
|
* Select the parent of the selection because we want the full block, not the leaf.
|
|
255
476
|
* And copy its children to make sure we keep the modifiers.
|
|
256
477
|
*/
|
|
257
|
-
const [
|
|
478
|
+
const [fragmentedNode] = Editor.parent(editor, editor.selection.anchor.path);
|
|
258
479
|
Transforms.removeNodes(editor, editor.selection);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
480
|
+
|
|
481
|
+
// Check if after the current position there is another node
|
|
482
|
+
const hasNextNode = editor.children.length - anchorPathInitialPosition[0] > 1;
|
|
483
|
+
|
|
484
|
+
// Insert the new node at the right position. The next line after the editor selection if present or otherwise at the end of the editor.
|
|
485
|
+
Transforms.insertNodes(
|
|
486
|
+
editor,
|
|
487
|
+
{
|
|
488
|
+
type: 'paragraph',
|
|
489
|
+
children: fragmentedNode.children,
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
at: hasNextNode ? [anchorPathInitialPosition[0] + 1] : [editor.children.length],
|
|
493
|
+
}
|
|
494
|
+
);
|
|
263
495
|
|
|
264
496
|
/**
|
|
265
497
|
* The new selection will by default be at the end of the created node.
|
|
@@ -267,7 +499,7 @@ export function useBlocksStore() {
|
|
|
267
499
|
* Use slice(0, -1) to go 1 level higher in the tree,
|
|
268
500
|
* so we go to the start of the node and not the start of the leaf.
|
|
269
501
|
*/
|
|
270
|
-
Transforms.select(editor, editor.start(
|
|
502
|
+
Transforms.select(editor, editor.start([anchorPathInitialPosition[0] + 1]));
|
|
271
503
|
},
|
|
272
504
|
},
|
|
273
505
|
'heading-one': {
|
|
@@ -354,73 +586,37 @@ export function useBlocksStore() {
|
|
|
354
586
|
matchNode: (node) => node.type === 'heading' && node.level === 6,
|
|
355
587
|
isInBlocksSelector: true,
|
|
356
588
|
},
|
|
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
589
|
'list-ordered': {
|
|
404
590
|
renderElement: (props) => <List {...props} />,
|
|
591
|
+
label: {
|
|
592
|
+
id: 'components.Blocks.blocks.orderedList',
|
|
593
|
+
defaultMessage: 'Numbered list',
|
|
594
|
+
},
|
|
405
595
|
value: {
|
|
406
596
|
type: 'list',
|
|
407
597
|
format: 'ordered',
|
|
408
598
|
},
|
|
599
|
+
icon: NumberList,
|
|
409
600
|
matchNode: (node) => node.type === 'list' && node.format === 'ordered',
|
|
410
|
-
|
|
411
|
-
isInBlocksSelector: false,
|
|
601
|
+
isInBlocksSelector: true,
|
|
412
602
|
handleEnterKey: handleEnterKeyOnList,
|
|
603
|
+
handleBackspaceKey: handleBackspaceKeyOnList,
|
|
413
604
|
},
|
|
414
605
|
'list-unordered': {
|
|
415
606
|
renderElement: (props) => <List {...props} />,
|
|
607
|
+
label: {
|
|
608
|
+
id: 'components.Blocks.blocks.unorderedList',
|
|
609
|
+
defaultMessage: 'Bulleted list',
|
|
610
|
+
},
|
|
416
611
|
value: {
|
|
417
612
|
type: 'list',
|
|
418
613
|
format: 'unordered',
|
|
419
614
|
},
|
|
615
|
+
icon: BulletList,
|
|
420
616
|
matchNode: (node) => node.type === 'list' && node.format === 'unordered',
|
|
421
|
-
|
|
422
|
-
isInBlocksSelector: false,
|
|
617
|
+
isInBlocksSelector: true,
|
|
423
618
|
handleEnterKey: handleEnterKeyOnList,
|
|
619
|
+
handleBackspaceKey: handleBackspaceKeyOnList,
|
|
424
620
|
},
|
|
425
621
|
'list-item': {
|
|
426
622
|
renderElement: (props) => (
|
|
@@ -434,6 +630,18 @@ export function useBlocksStore() {
|
|
|
434
630
|
matchNode: (node) => node.type === 'list-item',
|
|
435
631
|
isInBlocksSelector: false,
|
|
436
632
|
},
|
|
633
|
+
link: {
|
|
634
|
+
renderElement: (props) => (
|
|
635
|
+
<Link element={props.element} {...props.attributes}>
|
|
636
|
+
{props.children}
|
|
637
|
+
</Link>
|
|
638
|
+
),
|
|
639
|
+
value: {
|
|
640
|
+
type: 'link',
|
|
641
|
+
},
|
|
642
|
+
matchNode: (node) => node.type === 'link',
|
|
643
|
+
isInBlocksSelector: false,
|
|
644
|
+
},
|
|
437
645
|
image: {
|
|
438
646
|
renderElement: (props) => <Image {...props} />,
|
|
439
647
|
icon: Picture,
|
|
@@ -447,5 +655,64 @@ export function useBlocksStore() {
|
|
|
447
655
|
matchNode: (node) => node.type === 'image',
|
|
448
656
|
isInBlocksSelector: true,
|
|
449
657
|
},
|
|
658
|
+
quote: {
|
|
659
|
+
renderElement: (props) => <Blockquote {...props.attributes}>{props.children}</Blockquote>,
|
|
660
|
+
icon: Quote,
|
|
661
|
+
label: {
|
|
662
|
+
id: 'components.Blocks.blocks.quote',
|
|
663
|
+
defaultMessage: 'Quote',
|
|
664
|
+
},
|
|
665
|
+
value: {
|
|
666
|
+
type: 'quote',
|
|
667
|
+
},
|
|
668
|
+
matchNode: (node) => node.type === 'quote',
|
|
669
|
+
isInBlocksSelector: true,
|
|
670
|
+
handleEnterKey(editor) {
|
|
671
|
+
/**
|
|
672
|
+
* To determine if we should break out of the quote node, check 2 things:
|
|
673
|
+
* 1. If the cursor is at the end of the quote node
|
|
674
|
+
* 2. If the last line of the quote node is empty
|
|
675
|
+
*/
|
|
676
|
+
const [quoteNode, quoteNodePath] = Editor.above(editor, {
|
|
677
|
+
match: (n) => n.type === 'quote',
|
|
678
|
+
});
|
|
679
|
+
const isNodeEnd = Editor.isEnd(editor, editor.selection.anchor, quoteNodePath);
|
|
680
|
+
const isEmptyLine = quoteNode.children.at(-1).text.endsWith('\n');
|
|
681
|
+
|
|
682
|
+
if (isNodeEnd && isEmptyLine) {
|
|
683
|
+
// Remove the last line break
|
|
684
|
+
Transforms.delete(editor, { distance: 1, unit: 'character', reverse: true });
|
|
685
|
+
// Break out of the quote node new paragraph
|
|
686
|
+
Transforms.insertNodes(editor, {
|
|
687
|
+
type: 'paragraph',
|
|
688
|
+
children: [{ type: 'text', text: '' }],
|
|
689
|
+
});
|
|
690
|
+
} else {
|
|
691
|
+
// Otherwise insert a new line within the quote node
|
|
692
|
+
Transforms.insertText(editor, '\n');
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
code: {
|
|
697
|
+
renderElement: (props) => (
|
|
698
|
+
<CodeBlock {...props.attributes}>
|
|
699
|
+
<code>{props.children}</code>
|
|
700
|
+
</CodeBlock>
|
|
701
|
+
),
|
|
702
|
+
icon: Code,
|
|
703
|
+
label: {
|
|
704
|
+
id: 'components.Blocks.blocks.code',
|
|
705
|
+
defaultMessage: 'Code',
|
|
706
|
+
},
|
|
707
|
+
value: {
|
|
708
|
+
type: 'code',
|
|
709
|
+
},
|
|
710
|
+
matchNode: (node) => node.type === 'code',
|
|
711
|
+
isInBlocksSelector: true,
|
|
712
|
+
handleEnterKey(editor) {
|
|
713
|
+
// Insert a new line within the block
|
|
714
|
+
Transforms.insertText(editor, '\n');
|
|
715
|
+
},
|
|
716
|
+
},
|
|
450
717
|
};
|
|
451
718
|
}
|
|
@@ -8,19 +8,23 @@ import styled from 'styled-components';
|
|
|
8
8
|
|
|
9
9
|
const BoldText = styled(Typography).attrs({ fontWeight: 'bold' })`
|
|
10
10
|
font-size: inherit;
|
|
11
|
+
color: inherit;
|
|
11
12
|
`;
|
|
12
13
|
|
|
13
14
|
const ItalicText = styled(Typography)`
|
|
14
15
|
font-style: italic;
|
|
15
16
|
font-size: inherit;
|
|
17
|
+
color: inherit;
|
|
16
18
|
`;
|
|
17
19
|
|
|
18
20
|
const UnderlineText = styled(Typography).attrs({ textDecoration: 'underline' })`
|
|
19
21
|
font-size: inherit;
|
|
22
|
+
color: inherit;
|
|
20
23
|
`;
|
|
21
24
|
|
|
22
25
|
const StrikeThroughText = styled(Typography).attrs({ textDecoration: 'line-through' })`
|
|
23
26
|
font-size: inherit;
|
|
27
|
+
color: inherit;
|
|
24
28
|
`;
|
|
25
29
|
|
|
26
30
|
const InlineCode = styled.code`
|
|
@@ -29,6 +33,7 @@ const InlineCode = styled.code`
|
|
|
29
33
|
padding: ${({ theme }) => `0 ${theme.spaces[2]}`};
|
|
30
34
|
font-family: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas,
|
|
31
35
|
monospace;
|
|
36
|
+
color: inherit;
|
|
32
37
|
`;
|
|
33
38
|
|
|
34
39
|
/**
|
|
@@ -48,12 +53,22 @@ export function useModifiersStore() {
|
|
|
48
53
|
const editor = useSlate();
|
|
49
54
|
const modifiers = Editor.marks(editor);
|
|
50
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The default handler for checking if a modifier is active
|
|
58
|
+
*
|
|
59
|
+
* @param {string} name - The name of the modifier to check
|
|
60
|
+
*/
|
|
51
61
|
const baseCheckIsActive = (name) => {
|
|
52
62
|
if (!modifiers) return false;
|
|
53
63
|
|
|
54
64
|
return Boolean(modifiers[name]);
|
|
55
65
|
};
|
|
56
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The default handler for toggling a modifier
|
|
69
|
+
*
|
|
70
|
+
* @param {string} name - The name of the modifier to toggle
|
|
71
|
+
*/
|
|
57
72
|
const baseHandleToggle = (name) => {
|
|
58
73
|
if (modifiers[name]) {
|
|
59
74
|
Editor.removeMark(editor, name);
|