@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
|
@@ -3,15 +3,22 @@ import * as React from 'react';
|
|
|
3
3
|
import * as Toolbar from '@radix-ui/react-toolbar';
|
|
4
4
|
import { Flex, Icon, Tooltip, Select, Option, Box, Typography } from '@strapi/design-system';
|
|
5
5
|
import { pxToRem, prefixFileUrlWithBackendUrl, useLibrary } from '@strapi/helper-plugin';
|
|
6
|
-
import {
|
|
6
|
+
import { Link } from '@strapi/icons';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import { useIntl } from 'react-intl';
|
|
9
9
|
import { Editor, Transforms, Element as SlateElement } from 'slate';
|
|
10
|
-
import { useSlate } from 'slate-react';
|
|
10
|
+
import { ReactEditor, useSlate } from 'slate-react';
|
|
11
11
|
import styled from 'styled-components';
|
|
12
12
|
|
|
13
13
|
import { useBlocksStore } from '../hooks/useBlocksStore';
|
|
14
14
|
import { useModifiersStore } from '../hooks/useModifiersStore';
|
|
15
|
+
import { insertLink } from '../utils/links';
|
|
16
|
+
|
|
17
|
+
const ToolbarWrapper = styled(Flex)`
|
|
18
|
+
&[aria-disabled='true'] {
|
|
19
|
+
cursor: not-allowed;
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
15
22
|
|
|
16
23
|
const Separator = styled(Toolbar.Separator)`
|
|
17
24
|
background: ${({ theme }) => theme.colors.neutral150};
|
|
@@ -20,32 +27,58 @@ const Separator = styled(Toolbar.Separator)`
|
|
|
20
27
|
`;
|
|
21
28
|
|
|
22
29
|
const FlexButton = styled(Flex).attrs({ as: 'button' })`
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
// Inherit the not-allowed cursor from ToolbarWrapper when disabled
|
|
31
|
+
&[aria-disabled] {
|
|
32
|
+
cursor: inherit;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&[aria-disabled='false'] {
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
|
|
38
|
+
// Only apply hover styles if the button is enabled
|
|
39
|
+
&:hover {
|
|
40
|
+
background: ${({ theme }) => theme.colors.primary100};
|
|
41
|
+
}
|
|
25
42
|
}
|
|
26
43
|
`;
|
|
27
44
|
|
|
28
|
-
const ToolbarButton = ({ icon, name, label, isActive, handleClick }) => {
|
|
45
|
+
const ToolbarButton = ({ icon, name, label, isActive, disabled, handleClick }) => {
|
|
46
|
+
const editor = useSlate();
|
|
29
47
|
const { formatMessage } = useIntl();
|
|
30
48
|
const labelMessage = formatMessage(label);
|
|
31
49
|
|
|
50
|
+
const enabledColor = isActive ? 'primary600' : 'neutral600';
|
|
51
|
+
|
|
32
52
|
return (
|
|
33
53
|
<Tooltip description={labelMessage}>
|
|
34
|
-
<Toolbar.ToggleItem
|
|
54
|
+
<Toolbar.ToggleItem
|
|
55
|
+
value={name}
|
|
56
|
+
data-state={isActive ? 'on' : 'off'}
|
|
57
|
+
onMouseDown={(e) => {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
handleClick();
|
|
60
|
+
}}
|
|
61
|
+
aria-disabled={disabled}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
aria-label={labelMessage}
|
|
64
|
+
asChild
|
|
65
|
+
>
|
|
35
66
|
<FlexButton
|
|
67
|
+
disabled={disabled}
|
|
36
68
|
background={isActive ? 'primary100' : ''}
|
|
37
69
|
alignItems="center"
|
|
38
70
|
justifyContent="center"
|
|
39
71
|
width={7}
|
|
40
72
|
height={7}
|
|
41
73
|
hasRadius
|
|
42
|
-
onMouseDown={(
|
|
43
|
-
e.preventDefault();
|
|
74
|
+
onMouseDown={() => {
|
|
44
75
|
handleClick();
|
|
76
|
+
// When a button is clicked it blurs the editor, restore the focus to the editor
|
|
77
|
+
ReactEditor.focus(editor);
|
|
45
78
|
}}
|
|
46
79
|
aria-label={labelMessage}
|
|
47
80
|
>
|
|
48
|
-
<Icon width={3} height={3} as={icon} color={
|
|
81
|
+
<Icon width={3} height={3} as={icon} color={disabled ? 'neutral300' : enabledColor} />
|
|
49
82
|
</FlexButton>
|
|
50
83
|
</Toolbar.ToggleItem>
|
|
51
84
|
</Tooltip>
|
|
@@ -60,10 +93,11 @@ ToolbarButton.propTypes = {
|
|
|
60
93
|
defaultMessage: PropTypes.string.isRequired,
|
|
61
94
|
}).isRequired,
|
|
62
95
|
isActive: PropTypes.bool.isRequired,
|
|
96
|
+
disabled: PropTypes.bool.isRequired,
|
|
63
97
|
handleClick: PropTypes.func.isRequired,
|
|
64
98
|
};
|
|
65
99
|
|
|
66
|
-
const ModifierButton = ({ icon, name, label }) => {
|
|
100
|
+
const ModifierButton = ({ icon, name, label, disabled }) => {
|
|
67
101
|
const editor = useSlate();
|
|
68
102
|
|
|
69
103
|
const isModifierActive = () => {
|
|
@@ -90,6 +124,7 @@ const ModifierButton = ({ icon, name, label }) => {
|
|
|
90
124
|
name={name}
|
|
91
125
|
label={label}
|
|
92
126
|
isActive={isActive}
|
|
127
|
+
disabled={disabled}
|
|
93
128
|
handleClick={toggleModifier}
|
|
94
129
|
/>
|
|
95
130
|
);
|
|
@@ -102,32 +137,29 @@ ModifierButton.propTypes = {
|
|
|
102
137
|
id: PropTypes.string.isRequired,
|
|
103
138
|
defaultMessage: PropTypes.string.isRequired,
|
|
104
139
|
}).isRequired,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const isBlockActive = (editor, matchNode) => {
|
|
108
|
-
const { selection } = editor;
|
|
109
|
-
|
|
110
|
-
if (!selection) return false;
|
|
111
|
-
|
|
112
|
-
const match = Array.from(
|
|
113
|
-
Editor.nodes(editor, {
|
|
114
|
-
at: Editor.unhangRange(editor, selection),
|
|
115
|
-
match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && matchNode(n),
|
|
116
|
-
})
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
return match.length > 0;
|
|
140
|
+
disabled: PropTypes.bool.isRequired,
|
|
120
141
|
};
|
|
121
142
|
|
|
122
143
|
const toggleBlock = (editor, value) => {
|
|
123
|
-
const { type, level } = value;
|
|
144
|
+
const { type, level, format } = value;
|
|
124
145
|
|
|
125
|
-
|
|
146
|
+
// Set the selected block properties received from the useBlockStore
|
|
147
|
+
const blockProperties = {
|
|
126
148
|
type,
|
|
127
149
|
level: level || null,
|
|
150
|
+
format: format || null,
|
|
128
151
|
};
|
|
129
152
|
|
|
130
|
-
|
|
153
|
+
if (editor.selection) {
|
|
154
|
+
// When there is a selection, update the existing block in the tree
|
|
155
|
+
Transforms.setNodes(editor, blockProperties);
|
|
156
|
+
} else {
|
|
157
|
+
// Otherwise, add a new block to the tree
|
|
158
|
+
Transforms.insertNodes(editor, { ...blockProperties, children: [{ type: 'text', text: '' }] });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// When the select is clicked it blurs the editor, restore the focus to the editor
|
|
162
|
+
ReactEditor.focus(editor);
|
|
131
163
|
};
|
|
132
164
|
|
|
133
165
|
const ALLOWED_MEDIA_TYPE = 'images';
|
|
@@ -175,7 +207,7 @@ const ImageDialog = ({ handleClose }) => {
|
|
|
175
207
|
|
|
176
208
|
const handleSelectAssets = (images) => {
|
|
177
209
|
const formattedImages = images.map((image) => {
|
|
178
|
-
//
|
|
210
|
+
// Create an object with imageSchema defined and exclude unnecessary props coming from media library config
|
|
179
211
|
const expectedImage = pick(image, IMAGE_SCHEMA_FIELDS);
|
|
180
212
|
|
|
181
213
|
return {
|
|
@@ -186,6 +218,12 @@ const ImageDialog = ({ handleClose }) => {
|
|
|
186
218
|
});
|
|
187
219
|
|
|
188
220
|
insertImages(formattedImages);
|
|
221
|
+
|
|
222
|
+
if (isLastBlockType(editor, 'image')) {
|
|
223
|
+
// Insert blank line to add new blocks below image block
|
|
224
|
+
insertEmptyBlockAtLast(editor);
|
|
225
|
+
}
|
|
226
|
+
|
|
189
227
|
handleClose();
|
|
190
228
|
};
|
|
191
229
|
|
|
@@ -202,18 +240,18 @@ ImageDialog.propTypes = {
|
|
|
202
240
|
handleClose: PropTypes.func.isRequired,
|
|
203
241
|
};
|
|
204
242
|
|
|
205
|
-
const
|
|
243
|
+
const isLastBlockType = (editor, type) => {
|
|
206
244
|
const { selection } = editor;
|
|
207
245
|
|
|
208
246
|
if (!selection) return false;
|
|
209
247
|
|
|
210
|
-
const [
|
|
248
|
+
const [currentBlock] = Editor.nodes(editor, {
|
|
211
249
|
at: selection,
|
|
212
|
-
match: (n) => n.type ===
|
|
250
|
+
match: (n) => n.type === type,
|
|
213
251
|
});
|
|
214
252
|
|
|
215
|
-
if (
|
|
216
|
-
const [, currentNodePath] =
|
|
253
|
+
if (currentBlock) {
|
|
254
|
+
const [, currentNodePath] = currentBlock;
|
|
217
255
|
|
|
218
256
|
const isNodeAfter = Boolean(Editor.after(editor, currentNodePath));
|
|
219
257
|
|
|
@@ -223,7 +261,18 @@ const isLastBlockImageOrCode = (editor) => {
|
|
|
223
261
|
return false;
|
|
224
262
|
};
|
|
225
263
|
|
|
226
|
-
|
|
264
|
+
const insertEmptyBlockAtLast = (editor) => {
|
|
265
|
+
Transforms.insertNodes(
|
|
266
|
+
editor,
|
|
267
|
+
{
|
|
268
|
+
type: 'paragraph',
|
|
269
|
+
children: [{ type: 'text', text: '' }],
|
|
270
|
+
},
|
|
271
|
+
{ at: [editor.children.length] }
|
|
272
|
+
);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export const BlocksDropdown = ({ disabled }) => {
|
|
227
276
|
const editor = useSlate();
|
|
228
277
|
const { formatMessage } = useIntl();
|
|
229
278
|
const [isMediaLibraryVisible, setIsMediaLibraryVisible] = React.useState(false);
|
|
@@ -241,20 +290,27 @@ export const BlocksDropdown = () => {
|
|
|
241
290
|
* @param {string} optionKey - key of the heading selected
|
|
242
291
|
*/
|
|
243
292
|
const selectOption = (optionKey) => {
|
|
244
|
-
|
|
293
|
+
if (optionKey === 'image') {
|
|
294
|
+
// Image node created using select or existing selection node needs to be deleted before adding new image nodes
|
|
295
|
+
Transforms.removeNodes(editor);
|
|
296
|
+
} else if (['list-ordered', 'list-unordered'].includes(optionKey)) {
|
|
297
|
+
// retrieve the list format
|
|
298
|
+
const listFormat = blocks[optionKey].value.format;
|
|
299
|
+
|
|
300
|
+
// check if the list is already active
|
|
301
|
+
const isActive = isListActive(editor, blocks[optionKey].matchNode);
|
|
302
|
+
|
|
303
|
+
// toggle the list
|
|
304
|
+
toggleList(editor, isActive, listFormat);
|
|
305
|
+
} else {
|
|
306
|
+
toggleBlock(editor, blocks[optionKey].value);
|
|
307
|
+
}
|
|
245
308
|
|
|
246
309
|
setBlockSelected(optionKey);
|
|
247
310
|
|
|
248
|
-
if (
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
editor,
|
|
252
|
-
{
|
|
253
|
-
type: 'paragraph',
|
|
254
|
-
children: [{ type: 'text', text: '' }],
|
|
255
|
-
},
|
|
256
|
-
{ at: [editor.children.length] }
|
|
257
|
-
);
|
|
311
|
+
if (optionKey === 'code' && isLastBlockType(editor, 'code')) {
|
|
312
|
+
// Insert blank line to add new blocks below code block
|
|
313
|
+
insertEmptyBlockAtLast(editor);
|
|
258
314
|
}
|
|
259
315
|
|
|
260
316
|
if (optionKey === 'image') {
|
|
@@ -262,6 +318,37 @@ export const BlocksDropdown = () => {
|
|
|
262
318
|
}
|
|
263
319
|
};
|
|
264
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Prevent the select from focusing itself so ReactEditor.focus(editor) can focus the editor instead.
|
|
323
|
+
*
|
|
324
|
+
* The editor first loses focus to a blur event when clicking the select button. However,
|
|
325
|
+
* refocusing the editor is not enough since the select's default behavior is to refocus itself
|
|
326
|
+
* after an option is selected.
|
|
327
|
+
*
|
|
328
|
+
*/
|
|
329
|
+
const preventSelectFocus = (e) => e.preventDefault();
|
|
330
|
+
|
|
331
|
+
// Listen to the selection change and update the selected block in the dropdown
|
|
332
|
+
React.useEffect(() => {
|
|
333
|
+
if (editor.selection) {
|
|
334
|
+
// Get the parent node of the anchor
|
|
335
|
+
// with a depth of two to retrieve also the list item parents
|
|
336
|
+
const [anchorNode] = Editor.parent(editor, editor.selection.anchor, {
|
|
337
|
+
edge: 'start',
|
|
338
|
+
depth: 2,
|
|
339
|
+
});
|
|
340
|
+
// Find the block key that matches the anchor node
|
|
341
|
+
const anchorBlockKey = Object.keys(blocks).find((blockKey) =>
|
|
342
|
+
blocks[blockKey].matchNode(anchorNode)
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Change the value selected in the dropdown if it doesn't match the anchor block key
|
|
346
|
+
if (anchorBlockKey && anchorBlockKey !== blockSelected) {
|
|
347
|
+
setBlockSelected(anchorBlockKey);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}, [editor.selection, editor, blocks, blockSelected]);
|
|
351
|
+
|
|
265
352
|
return (
|
|
266
353
|
<>
|
|
267
354
|
<Select
|
|
@@ -269,10 +356,12 @@ export const BlocksDropdown = () => {
|
|
|
269
356
|
onChange={selectOption}
|
|
270
357
|
placeholder={blocks[blockSelected].label}
|
|
271
358
|
value={blockSelected}
|
|
359
|
+
onCloseAutoFocus={preventSelectFocus}
|
|
272
360
|
aria-label={formatMessage({
|
|
273
361
|
id: 'components.Blocks.blocks.selectBlock',
|
|
274
362
|
defaultMessage: 'Select a block',
|
|
275
363
|
})}
|
|
364
|
+
disabled={disabled}
|
|
276
365
|
>
|
|
277
366
|
{blockKeysToInclude.map((key) => (
|
|
278
367
|
<BlockOption
|
|
@@ -280,8 +369,6 @@ export const BlocksDropdown = () => {
|
|
|
280
369
|
value={key}
|
|
281
370
|
label={blocks[key].label}
|
|
282
371
|
icon={blocks[key].icon}
|
|
283
|
-
matchNode={blocks[key].matchNode}
|
|
284
|
-
handleSelection={setBlockSelected}
|
|
285
372
|
blockSelected={blockSelected}
|
|
286
373
|
/>
|
|
287
374
|
))}
|
|
@@ -291,19 +378,15 @@ export const BlocksDropdown = () => {
|
|
|
291
378
|
);
|
|
292
379
|
};
|
|
293
380
|
|
|
294
|
-
|
|
381
|
+
BlocksDropdown.propTypes = {
|
|
382
|
+
disabled: PropTypes.bool.isRequired,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const BlockOption = ({ value, icon, label, blockSelected }) => {
|
|
295
386
|
const { formatMessage } = useIntl();
|
|
296
|
-
const editor = useSlate();
|
|
297
387
|
|
|
298
|
-
const isActive = isBlockActive(editor, matchNode);
|
|
299
388
|
const isSelected = value === blockSelected;
|
|
300
389
|
|
|
301
|
-
React.useEffect(() => {
|
|
302
|
-
if (isActive && !isSelected) {
|
|
303
|
-
handleSelection(value);
|
|
304
|
-
}
|
|
305
|
-
}, [handleSelection, isActive, isSelected, value]);
|
|
306
|
-
|
|
307
390
|
return (
|
|
308
391
|
<Option
|
|
309
392
|
startIcon={<Icon as={icon} color={isSelected ? 'primary600' : 'neutral600'} />}
|
|
@@ -321,24 +404,95 @@ BlockOption.propTypes = {
|
|
|
321
404
|
id: PropTypes.string.isRequired,
|
|
322
405
|
defaultMessage: PropTypes.string.isRequired,
|
|
323
406
|
}).isRequired,
|
|
324
|
-
matchNode: PropTypes.func.isRequired,
|
|
325
|
-
handleSelection: PropTypes.func.isRequired,
|
|
326
407
|
blockSelected: PropTypes.string.isRequired,
|
|
327
408
|
};
|
|
328
409
|
|
|
329
|
-
|
|
410
|
+
/**
|
|
411
|
+
*
|
|
412
|
+
* @param {import('slate').Node} node
|
|
413
|
+
* @returns boolean
|
|
414
|
+
*/
|
|
415
|
+
const isListNode = (node) => {
|
|
416
|
+
return !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === 'list';
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const isListActive = (editor, matchNode) => {
|
|
420
|
+
const { selection } = editor;
|
|
421
|
+
|
|
422
|
+
if (!selection) return false;
|
|
423
|
+
|
|
424
|
+
const [match] = Array.from(
|
|
425
|
+
Editor.nodes(editor, {
|
|
426
|
+
at: Editor.unhangRange(editor, selection),
|
|
427
|
+
match: matchNode,
|
|
428
|
+
})
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
return Boolean(match);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const toggleList = (editor, isActive, format) => {
|
|
435
|
+
// Delete the parent list so that we're left with only the list items directly
|
|
436
|
+
Transforms.unwrapNodes(editor, {
|
|
437
|
+
match: (node) => isListNode(node) && ['ordered', 'unordered'].includes(node.format),
|
|
438
|
+
split: true,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Change the type of the current selection
|
|
442
|
+
Transforms.setNodes(editor, {
|
|
443
|
+
type: isActive ? 'paragraph' : 'list-item',
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// If the selection is now a list item, wrap it inside a list
|
|
447
|
+
if (!isActive) {
|
|
448
|
+
const block = { type: 'list', format, children: [] };
|
|
449
|
+
Transforms.wrapNodes(editor, block);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const ListButton = ({ block, disabled }) => {
|
|
330
454
|
const editor = useSlate();
|
|
331
455
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
456
|
+
const {
|
|
457
|
+
icon,
|
|
458
|
+
matchNode,
|
|
459
|
+
value: { format },
|
|
460
|
+
label,
|
|
461
|
+
} = block;
|
|
462
|
+
|
|
463
|
+
const isActive = isListActive(editor, matchNode);
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
<ToolbarButton
|
|
467
|
+
icon={icon}
|
|
468
|
+
name={format}
|
|
469
|
+
label={label}
|
|
470
|
+
isActive={isActive}
|
|
471
|
+
disabled={disabled}
|
|
472
|
+
handleClick={() => toggleList(editor, isActive, format)}
|
|
473
|
+
/>
|
|
474
|
+
);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
ListButton.propTypes = {
|
|
478
|
+
block: PropTypes.shape({
|
|
479
|
+
icon: PropTypes.elementType.isRequired,
|
|
480
|
+
matchNode: PropTypes.func.isRequired,
|
|
481
|
+
value: PropTypes.shape({
|
|
482
|
+
format: PropTypes.string.isRequired,
|
|
483
|
+
}).isRequired,
|
|
484
|
+
label: PropTypes.shape({
|
|
485
|
+
id: PropTypes.string.isRequired,
|
|
486
|
+
defaultMessage: PropTypes.string.isRequired,
|
|
487
|
+
}).isRequired,
|
|
488
|
+
}).isRequired,
|
|
489
|
+
disabled: PropTypes.bool.isRequired,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const LinkButton = ({ disabled }) => {
|
|
493
|
+
const editor = useSlate();
|
|
340
494
|
|
|
341
|
-
const
|
|
495
|
+
const isLinkActive = () => {
|
|
342
496
|
const { selection } = editor;
|
|
343
497
|
|
|
344
498
|
if (!selection) return false;
|
|
@@ -346,74 +500,57 @@ const ListButton = ({ icon, format, label }) => {
|
|
|
346
500
|
const [match] = Array.from(
|
|
347
501
|
Editor.nodes(editor, {
|
|
348
502
|
at: Editor.unhangRange(editor, selection),
|
|
349
|
-
match: (node) =>
|
|
503
|
+
match: (node) => SlateElement.isElement(node) && node.type === 'link',
|
|
350
504
|
})
|
|
351
505
|
);
|
|
352
506
|
|
|
353
507
|
return Boolean(match);
|
|
354
508
|
};
|
|
355
509
|
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
// Delete the parent list so that we're left with only the list items directly
|
|
360
|
-
Transforms.unwrapNodes(editor, {
|
|
361
|
-
match: (node) => isListNode(node) && ['ordered', 'unordered'].includes(node.format),
|
|
362
|
-
split: true,
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
// Change the type of the current selection
|
|
366
|
-
Transforms.setNodes(editor, {
|
|
367
|
-
type: isActive ? 'paragraph' : 'list-item',
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// If the selection is now a list item, wrap it inside a list
|
|
371
|
-
if (!isActive) {
|
|
372
|
-
const block = { type: 'list', format, children: [] };
|
|
373
|
-
Transforms.wrapNodes(editor, block);
|
|
374
|
-
}
|
|
510
|
+
const addLink = () => {
|
|
511
|
+
// We insert an empty anchor, so we split the DOM to have a element we can use as reference for the popover
|
|
512
|
+
insertLink(editor, { url: '' });
|
|
375
513
|
};
|
|
376
514
|
|
|
377
515
|
return (
|
|
378
516
|
<ToolbarButton
|
|
379
|
-
icon={
|
|
380
|
-
name=
|
|
381
|
-
label={
|
|
382
|
-
|
|
383
|
-
|
|
517
|
+
icon={Link}
|
|
518
|
+
name="link"
|
|
519
|
+
label={{
|
|
520
|
+
id: 'components.Blocks.link',
|
|
521
|
+
defaultMessage: 'Link',
|
|
522
|
+
}}
|
|
523
|
+
isActive={isLinkActive()}
|
|
524
|
+
handleClick={addLink}
|
|
525
|
+
disabled={disabled}
|
|
384
526
|
/>
|
|
385
527
|
);
|
|
386
528
|
};
|
|
387
529
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
format: PropTypes.string.isRequired,
|
|
391
|
-
label: PropTypes.shape({
|
|
392
|
-
id: PropTypes.string.isRequired,
|
|
393
|
-
defaultMessage: PropTypes.string.isRequired,
|
|
394
|
-
}).isRequired,
|
|
530
|
+
LinkButton.propTypes = {
|
|
531
|
+
disabled: PropTypes.bool.isRequired,
|
|
395
532
|
};
|
|
396
533
|
|
|
397
|
-
// TODO: Remove after the RTE Blocks
|
|
398
|
-
const
|
|
399
|
-
background-color: ${({ theme }) => theme.colors.
|
|
400
|
-
border: ${({ theme }) => `1px solid ${theme.colors.
|
|
534
|
+
// TODO: Remove after the RTE Blocks Beta release
|
|
535
|
+
const BetaTag = styled(Box)`
|
|
536
|
+
background-color: ${({ theme }) => theme.colors.secondary100};
|
|
537
|
+
border: ${({ theme }) => `1px solid ${theme.colors.secondary200}`};
|
|
401
538
|
border-radius: ${({ theme }) => theme.borderRadius};
|
|
402
539
|
font-size: ${({ theme }) => theme.fontSizes[0]};
|
|
403
540
|
padding: ${({ theme }) => `${2 / 16}rem ${theme.spaces[1]}`};
|
|
404
541
|
`;
|
|
405
542
|
|
|
406
|
-
const BlocksToolbar = () => {
|
|
543
|
+
const BlocksToolbar = ({ disabled }) => {
|
|
407
544
|
const modifiers = useModifiersStore();
|
|
545
|
+
const blocks = useBlocksStore();
|
|
408
546
|
|
|
409
547
|
return (
|
|
410
|
-
<Toolbar.Root asChild>
|
|
411
|
-
{/* Remove after the RTE Blocks
|
|
412
|
-
<
|
|
413
|
-
<BlocksDropdown />
|
|
414
|
-
<Separator />
|
|
548
|
+
<Toolbar.Root aria-disabled={disabled} asChild>
|
|
549
|
+
{/* Remove after the RTE Blocks Beta release (paddingRight and width) */}
|
|
550
|
+
<ToolbarWrapper gap={1} padding={2} paddingRight={4} width="100%">
|
|
551
|
+
<BlocksDropdown disabled={disabled} />
|
|
415
552
|
<Toolbar.ToggleGroup type="multiple" asChild>
|
|
416
|
-
<Flex gap={1}>
|
|
553
|
+
<Flex gap={1} marginLeft={1}>
|
|
417
554
|
{Object.entries(modifiers).map(([name, modifier]) => (
|
|
418
555
|
<ToolbarButton
|
|
419
556
|
key={name}
|
|
@@ -422,42 +559,34 @@ const BlocksToolbar = () => {
|
|
|
422
559
|
label={modifier.label}
|
|
423
560
|
isActive={modifier.checkIsActive()}
|
|
424
561
|
handleClick={modifier.handleToggle}
|
|
562
|
+
disabled={disabled}
|
|
425
563
|
/>
|
|
426
564
|
))}
|
|
565
|
+
<LinkButton disabled={disabled} />
|
|
427
566
|
</Flex>
|
|
428
567
|
</Toolbar.ToggleGroup>
|
|
429
568
|
<Separator />
|
|
430
569
|
<Toolbar.ToggleGroup type="single" asChild>
|
|
431
570
|
<Flex gap={1}>
|
|
432
|
-
<ListButton
|
|
433
|
-
|
|
434
|
-
id: 'components.Blocks.blocks.unorderedList',
|
|
435
|
-
defaultMessage: 'Bulleted list',
|
|
436
|
-
}}
|
|
437
|
-
format="unordered"
|
|
438
|
-
icon={BulletList}
|
|
439
|
-
/>
|
|
440
|
-
<ListButton
|
|
441
|
-
label={{
|
|
442
|
-
id: 'components.Blocks.blocks.orderedList',
|
|
443
|
-
defaultMessage: 'Numbered list',
|
|
444
|
-
}}
|
|
445
|
-
format="ordered"
|
|
446
|
-
icon={NumberList}
|
|
447
|
-
/>
|
|
571
|
+
<ListButton block={blocks['list-unordered']} disabled={disabled} />
|
|
572
|
+
<ListButton block={blocks['list-ordered']} disabled={disabled} />
|
|
448
573
|
</Flex>
|
|
449
574
|
</Toolbar.ToggleGroup>
|
|
450
|
-
{/* TODO: Remove after the RTE Blocks
|
|
575
|
+
{/* TODO: Remove after the RTE Blocks Beta release */}
|
|
451
576
|
<Flex grow={1} justifyContent="flex-end">
|
|
452
|
-
<
|
|
453
|
-
<Typography textColor="
|
|
454
|
-
|
|
577
|
+
<BetaTag>
|
|
578
|
+
<Typography textColor="secondary600" variant="sigma">
|
|
579
|
+
BETA
|
|
455
580
|
</Typography>
|
|
456
|
-
</
|
|
581
|
+
</BetaTag>
|
|
457
582
|
</Flex>
|
|
458
|
-
</
|
|
583
|
+
</ToolbarWrapper>
|
|
459
584
|
</Toolbar.Root>
|
|
460
585
|
);
|
|
461
586
|
};
|
|
462
587
|
|
|
588
|
+
BlocksToolbar.propTypes = {
|
|
589
|
+
disabled: PropTypes.bool.isRequired,
|
|
590
|
+
};
|
|
591
|
+
|
|
463
592
|
export { BlocksToolbar };
|