@strapi/admin 4.14.0-beta.0 → 4.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/components/RBACProvider/index.js +1 -1
- package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +87 -0
- package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +463 -0
- package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +451 -0
- package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +102 -0
- package/admin/src/content-manager/components/BlocksEditor/index.js +126 -0
- package/admin/src/content-manager/components/InputUID/index.js +92 -109
- package/admin/src/content-manager/components/Inputs/index.js +3 -1
- package/admin/src/content-manager/components/Inputs/utils/getInputType.js +2 -0
- package/admin/src/content-manager/components/RelationInput/RelationInput.js +59 -43
- package/admin/src/content-manager/hooks/useRelation/useRelation.js +84 -86
- package/admin/src/content-manager/pages/ListView/index.js +1 -0
- package/admin/src/content-manager/utils/schema.js +22 -0
- package/admin/src/index.js +7 -1
- package/admin/src/pages/MarketplacePage/components/EmptyNpmPackageSearch/index.js +1 -1
- package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/index.js +0 -1
- package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +1 -1
- package/admin/src/translations/en.json +17 -0
- package/build/{1049.bf5230e6.chunk.js → 1049.acb0e730.chunk.js} +1 -1
- package/build/{1227.4f48119b.chunk.js → 1227.969e24e6.chunk.js} +1 -1
- package/build/{1386.8c070d59.chunk.js → 1386.db9a2795.chunk.js} +1 -1
- package/build/{2225.73c9a224.chunk.js → 2225.78fb9b89.chunk.js} +2 -2
- package/build/{2379.27c6ab54.chunk.js → 2379.906334f0.chunk.js} +1 -1
- package/build/{2395.120256d6.chunk.js → 2395.f6ac2863.chunk.js} +1 -1
- package/build/2614.3e088d3e.chunk.js +35 -0
- package/build/{2801.fdd4d8a2.chunk.js → 2801.2afb4757.chunk.js} +1 -1
- package/build/{4174.924ebd4c.chunk.js → 4174.3e13fb26.chunk.js} +1 -1
- package/build/4546.1203ac95.chunk.js +1 -0
- package/build/{502.d26ea06b.chunk.js → 502.9918bff7.chunk.js} +1 -1
- package/build/{6266.c652bdb1.chunk.js → 6266.e8990811.chunk.js} +5 -5
- package/build/6812.00ef5b0d.chunk.js +26 -0
- package/build/{7464.d3d1414f.chunk.js → 7464.0280cf59.chunk.js} +1 -1
- package/build/{7897.cf22d5fe.chunk.js → 7897.4a39de37.chunk.js} +1 -1
- package/build/{8276.b55f4600.chunk.js → 8276.951e198e.chunk.js} +2 -2
- package/build/9832.65ed5a44.chunk.js +181 -0
- package/build/{Admin-authenticatedApp.a687d9c6.chunk.js → Admin-authenticatedApp.c0c1c027.chunk.js} +2 -2
- package/build/{Admin_InternalErrorPage.18ba7fd6.chunk.js → Admin_InternalErrorPage.b3163562.chunk.js} +1 -1
- package/build/{Admin_homePage.86c8d4c9.chunk.js → Admin_homePage.6cb51f18.chunk.js} +1 -1
- package/build/{Admin_marketplace.e75c9cf4.chunk.js → Admin_marketplace.3eb5e132.chunk.js} +4 -4
- package/build/{Admin_pluginsPage.d1afbf28.chunk.js → Admin_pluginsPage.b9fa2947.chunk.js} +1 -1
- package/build/{Admin_profilePage.079903a3.chunk.js → Admin_profilePage.a4d41380.chunk.js} +2 -2
- package/build/{Admin_settingsPage.60a3e46e.chunk.js → Admin_settingsPage.6dc2af9f.chunk.js} +1 -1
- package/build/{Upload_ConfigureTheView.949535b8.chunk.js → Upload_ConfigureTheView.cc7ca628.chunk.js} +1 -1
- package/build/{admin-app.4654dc77.chunk.js → admin-app.98cdf43a.chunk.js} +5 -5
- package/build/{admin-edit-roles-page.6597d934.chunk.js → admin-edit-roles-page.418bb1c5.chunk.js} +3 -3
- package/build/{admin-edit-users.3014605e.chunk.js → admin-edit-users.9b42cc9e.chunk.js} +2 -2
- package/build/{admin-roles-list.ab6fcfb7.chunk.js → admin-roles-list.cf964578.chunk.js} +1 -1
- package/build/{admin-users.81bf5f4d.chunk.js → admin-users.8385dd73.chunk.js} +2 -2
- package/build/{api-tokens-create-page.f5a85725.chunk.js → api-tokens-create-page.2f25ddf6.chunk.js} +1 -1
- package/build/{api-tokens-edit-page.06a32cef.chunk.js → api-tokens-edit-page.45faac16.chunk.js} +1 -1
- package/build/{api-tokens-list-page.bb27d544.chunk.js → api-tokens-list-page.5baabf1a.chunk.js} +2 -2
- package/build/{audit-logs-settings-page.4eb6cdf8.chunk.js → audit-logs-settings-page.91489670.chunk.js} +1 -1
- package/build/content-manager.0d2b4a60.chunk.js +1199 -0
- package/build/{content-type-builder-list-view.b75fb938.chunk.js → content-type-builder-list-view.aa8a5d1a.chunk.js} +2 -2
- package/build/content-type-builder-translation-en-json.b9e5cacd.chunk.js +1 -0
- package/build/content-type-builder.885f2cad.chunk.js +146 -0
- package/build/{email-settings-page.07d417d5.chunk.js → email-settings-page.6bd7b280.chunk.js} +1 -1
- package/build/{en-json.e12fd5fc.chunk.js → en-json.a3973ff5.chunk.js} +1 -1
- package/build/{i18n-settings-page.7107e28a.chunk.js → i18n-settings-page.6c0157e7.chunk.js} +1 -1
- package/build/index.html +1 -1
- package/build/main.105dcf23.js +2665 -0
- package/build/{review-workflows-settings-create-view.5d8806b2.chunk.js → review-workflows-settings-create-view.ae369a88.chunk.js} +1 -1
- package/build/{review-workflows-settings-edit-view.634903ed.chunk.js → review-workflows-settings-edit-view.9a61c69f.chunk.js} +1 -1
- package/build/{review-workflows-settings-list-view.d138c3b5.chunk.js → review-workflows-settings-list-view.067e0c35.chunk.js} +2 -2
- package/build/{runtime~main.9589b498.js → runtime~main.6c489074.js} +2 -2
- package/build/{sso-settings-page.caa35f7b.chunk.js → sso-settings-page.a29e6c38.chunk.js} +1 -1
- package/build/{transfer-tokens-create-page.6e7049ff.chunk.js → transfer-tokens-create-page.6e1b8cee.chunk.js} +1 -1
- package/build/{transfer-tokens-edit-page.449b4502.chunk.js → transfer-tokens-edit-page.10bb22e2.chunk.js} +1 -1
- package/build/{transfer-tokens-list-page.34caf827.chunk.js → transfer-tokens-list-page.0306652c.chunk.js} +2 -2
- package/build/{upload-settings.fede24b9.chunk.js → upload-settings.0af6edc5.chunk.js} +1 -1
- package/build/{upload.a96e2452.chunk.js → upload.19e14c8e.chunk.js} +1 -1
- package/build/{users-advanced-settings-page.ac7968e7.chunk.js → users-advanced-settings-page.ed69812f.chunk.js} +1 -1
- package/build/{users-email-settings-page.125a89e2.chunk.js → users-email-settings-page.131a00fb.chunk.js} +1 -1
- package/build/{users-providers-settings-page.ce34951c.chunk.js → users-providers-settings-page.b3dca41d.chunk.js} +1 -1
- package/build/{users-roles-settings-page.d415835a.chunk.js → users-roles-settings-page.afab5a0d.chunk.js} +4 -4
- package/build/{webhook-edit-page.7498417e.chunk.js → webhook-edit-page.4c037da4.chunk.js} +2 -2
- package/build/{webhook-list-page.1b085c7f.chunk.js → webhook-list-page.56c82f4a.chunk.js} +1 -1
- package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +1 -1
- package/ee/server/bootstrap.js +1 -1
- package/ee/server/content-types/workflow-stage/index.js +1 -1
- package/ee/server/controllers/admin.js +1 -1
- package/ee/server/controllers/user.js +1 -1
- package/ee/server/destroy.js +1 -1
- package/ee/server/register.js +1 -1
- package/ee/server/routes/utils.js +1 -1
- package/ee/server/services/audit-logs.js +1 -1
- package/ee/server/services/passport/sso.js +1 -1
- package/ee/server/services/passport.js +1 -1
- package/ee/server/services/seat-enforcement.js +1 -1
- package/ee/server/utils/sso-lock.js +1 -1
- package/ee/server/validation/role.js +1 -1
- package/ee/server/validation/user.js +1 -1
- package/package.json +20 -12
- package/server/controllers/admin.js +1 -1
- package/server/domain/permission/index.js +8 -1
- package/server/validation/permission.js +1 -1
- package/utils/plugins.js +7 -1
- package/build/4546.9710f321.chunk.js +0 -1
- package/build/6272.4017459a.chunk.js +0 -160
- package/build/6812.31979984.chunk.js +0 -26
- package/build/content-manager.9187db78.chunk.js +0 -1097
- package/build/content-type-builder-translation-en-json.ed29ff4d.chunk.js +0 -1
- package/build/content-type-builder.5ff93edd.chunk.js +0 -170
- package/build/main.da000219.js +0 -2860
|
@@ -31,7 +31,7 @@ const RBACProvider = ({ children, permissions, refetchPermissions }) => {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
RBACProvider.propTypes = {
|
|
34
|
-
children: PropTypes.
|
|
34
|
+
children: PropTypes.node.isRequired,
|
|
35
35
|
permissions: PropTypes.array.isRequired,
|
|
36
36
|
refetchPermissions: PropTypes.func.isRequired,
|
|
37
37
|
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { Editable, useSlate } from 'slate-react';
|
|
5
|
+
import { useTheme } from 'styled-components';
|
|
6
|
+
|
|
7
|
+
import { useBlocksStore } from '../hooks/useBlocksStore';
|
|
8
|
+
import { useModifiersStore } from '../hooks/useModifiersStore';
|
|
9
|
+
|
|
10
|
+
const getEditorStyle = (theme) => ({
|
|
11
|
+
// The outline style is set on the wrapper with :focus-within
|
|
12
|
+
outline: 'none',
|
|
13
|
+
display: 'flex',
|
|
14
|
+
flexDirection: 'column',
|
|
15
|
+
gap: theme.spaces[2],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const baseRenderLeaf = (props, modifiers) => {
|
|
19
|
+
// Recursively wrap the children for each active modifier
|
|
20
|
+
const wrappedChildren = Object.entries(modifiers).reduce((currentChildren, modifierEntry) => {
|
|
21
|
+
const [name, modifier] = modifierEntry;
|
|
22
|
+
|
|
23
|
+
if (props.leaf[name]) {
|
|
24
|
+
return modifier.renderLeaf(currentChildren);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return currentChildren;
|
|
28
|
+
}, props.children);
|
|
29
|
+
|
|
30
|
+
return <span {...props.attributes}>{wrappedChildren}</span>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const baseRenderElement = (props, blocks) => {
|
|
34
|
+
const blockMatch = Object.values(blocks).find((block) => block.matchNode(props.element));
|
|
35
|
+
const block = blockMatch || blocks.paragraph;
|
|
36
|
+
|
|
37
|
+
return block.renderElement(props);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const BlocksInput = ({ readOnly }) => {
|
|
41
|
+
const theme = useTheme();
|
|
42
|
+
const editor = useSlate();
|
|
43
|
+
|
|
44
|
+
// Create renderLeaf function based on the modifiers store
|
|
45
|
+
const modifiers = useModifiersStore();
|
|
46
|
+
const renderLeaf = React.useCallback((props) => baseRenderLeaf(props, modifiers), [modifiers]);
|
|
47
|
+
|
|
48
|
+
// Create renderElement function base on the blocks store
|
|
49
|
+
const blocks = useBlocksStore();
|
|
50
|
+
const renderElement = React.useCallback((props) => baseRenderElement(props, blocks), [blocks]);
|
|
51
|
+
|
|
52
|
+
const handleEnter = () => {
|
|
53
|
+
const selectedNode = editor.children[editor.selection.anchor.path[0]];
|
|
54
|
+
const selectedBlock = Object.values(blocks).find((block) => block.matchNode(selectedNode));
|
|
55
|
+
|
|
56
|
+
// Check if there's an enter handler for the selected block
|
|
57
|
+
if (selectedBlock.handleEnterKey) {
|
|
58
|
+
selectedBlock.handleEnterKey(editor);
|
|
59
|
+
} else {
|
|
60
|
+
// If not, insert a new paragraph
|
|
61
|
+
blocks.paragraph.handleEnterKey(editor);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleKeyDown = (event) => {
|
|
66
|
+
if (event.key === 'Enter') {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
handleEnter();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Editable
|
|
74
|
+
readOnly={readOnly}
|
|
75
|
+
style={getEditorStyle(theme)}
|
|
76
|
+
renderElement={renderElement}
|
|
77
|
+
renderLeaf={renderLeaf}
|
|
78
|
+
onKeyDown={handleKeyDown}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
BlocksInput.propTypes = {
|
|
84
|
+
readOnly: PropTypes.bool.isRequired,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default BlocksInput;
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import * as Toolbar from '@radix-ui/react-toolbar';
|
|
4
|
+
import { Flex, Icon, Tooltip, Select, Option, Box, Typography } from '@strapi/design-system';
|
|
5
|
+
import { pxToRem, prefixFileUrlWithBackendUrl, useLibrary } from '@strapi/helper-plugin';
|
|
6
|
+
import { BulletList, NumberList } from '@strapi/icons';
|
|
7
|
+
import PropTypes from 'prop-types';
|
|
8
|
+
import { useIntl } from 'react-intl';
|
|
9
|
+
import { Editor, Transforms, Element as SlateElement } from 'slate';
|
|
10
|
+
import { useSlate } from 'slate-react';
|
|
11
|
+
import styled from 'styled-components';
|
|
12
|
+
|
|
13
|
+
import { useBlocksStore } from '../hooks/useBlocksStore';
|
|
14
|
+
import { useModifiersStore } from '../hooks/useModifiersStore';
|
|
15
|
+
|
|
16
|
+
const Separator = styled(Toolbar.Separator)`
|
|
17
|
+
background: ${({ theme }) => theme.colors.neutral150};
|
|
18
|
+
width: 1px;
|
|
19
|
+
height: ${pxToRem(24)};
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const FlexButton = styled(Flex).attrs({ as: 'button' })`
|
|
23
|
+
&:hover {
|
|
24
|
+
background: ${({ theme }) => theme.colors.primary100};
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const ToolbarButton = ({ icon, name, label, isActive, handleClick }) => {
|
|
29
|
+
const { formatMessage } = useIntl();
|
|
30
|
+
const labelMessage = formatMessage(label);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Tooltip description={labelMessage}>
|
|
34
|
+
<Toolbar.ToggleItem value={name} data-state={isActive ? 'on' : 'off'} asChild>
|
|
35
|
+
<FlexButton
|
|
36
|
+
background={isActive ? 'primary100' : ''}
|
|
37
|
+
alignItems="center"
|
|
38
|
+
justifyContent="center"
|
|
39
|
+
width={7}
|
|
40
|
+
height={7}
|
|
41
|
+
hasRadius
|
|
42
|
+
onMouseDown={(e) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
handleClick();
|
|
45
|
+
}}
|
|
46
|
+
aria-label={labelMessage}
|
|
47
|
+
>
|
|
48
|
+
<Icon width={3} height={3} as={icon} color={isActive ? 'primary600' : 'neutral600'} />
|
|
49
|
+
</FlexButton>
|
|
50
|
+
</Toolbar.ToggleItem>
|
|
51
|
+
</Tooltip>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
ToolbarButton.propTypes = {
|
|
56
|
+
icon: PropTypes.elementType.isRequired,
|
|
57
|
+
name: PropTypes.string.isRequired,
|
|
58
|
+
label: PropTypes.shape({
|
|
59
|
+
id: PropTypes.string.isRequired,
|
|
60
|
+
defaultMessage: PropTypes.string.isRequired,
|
|
61
|
+
}).isRequired,
|
|
62
|
+
isActive: PropTypes.bool.isRequired,
|
|
63
|
+
handleClick: PropTypes.func.isRequired,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const ModifierButton = ({ icon, name, label }) => {
|
|
67
|
+
const editor = useSlate();
|
|
68
|
+
|
|
69
|
+
const isModifierActive = () => {
|
|
70
|
+
const modifiers = Editor.marks(editor);
|
|
71
|
+
|
|
72
|
+
if (!modifiers) return false;
|
|
73
|
+
|
|
74
|
+
return Boolean(modifiers[name]);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const isActive = isModifierActive();
|
|
78
|
+
|
|
79
|
+
const toggleModifier = () => {
|
|
80
|
+
if (isActive) {
|
|
81
|
+
Editor.removeMark(editor, name);
|
|
82
|
+
} else {
|
|
83
|
+
Editor.addMark(editor, name, true);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<ToolbarButton
|
|
89
|
+
icon={icon}
|
|
90
|
+
name={name}
|
|
91
|
+
label={label}
|
|
92
|
+
isActive={isActive}
|
|
93
|
+
handleClick={toggleModifier}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
ModifierButton.propTypes = {
|
|
99
|
+
icon: PropTypes.elementType.isRequired,
|
|
100
|
+
name: PropTypes.string.isRequired,
|
|
101
|
+
label: PropTypes.shape({
|
|
102
|
+
id: PropTypes.string.isRequired,
|
|
103
|
+
defaultMessage: PropTypes.string.isRequired,
|
|
104
|
+
}).isRequired,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const isBlockActive = (editor, matchNode) => {
|
|
108
|
+
const { selection } = editor;
|
|
109
|
+
|
|
110
|
+
if (!selection) return false;
|
|
111
|
+
|
|
112
|
+
const match = Array.from(
|
|
113
|
+
Editor.nodes(editor, {
|
|
114
|
+
at: Editor.unhangRange(editor, selection),
|
|
115
|
+
match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && matchNode(n),
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return match.length > 0;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const toggleBlock = (editor, value) => {
|
|
123
|
+
const { type, level } = value;
|
|
124
|
+
|
|
125
|
+
const newProperties = {
|
|
126
|
+
type,
|
|
127
|
+
level: level || null,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
Transforms.setNodes(editor, newProperties);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const ALLOWED_MEDIA_TYPE = 'images';
|
|
134
|
+
|
|
135
|
+
const IMAGE_SCHEMA_FIELDS = [
|
|
136
|
+
'name',
|
|
137
|
+
'alternativeText',
|
|
138
|
+
'url',
|
|
139
|
+
'caption',
|
|
140
|
+
'width',
|
|
141
|
+
'height',
|
|
142
|
+
'formats',
|
|
143
|
+
'hash',
|
|
144
|
+
'ext',
|
|
145
|
+
'mime',
|
|
146
|
+
'size',
|
|
147
|
+
'previewUrl',
|
|
148
|
+
'provider',
|
|
149
|
+
'provider_metadata',
|
|
150
|
+
'createdAt',
|
|
151
|
+
'updatedAt',
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
const pick = (object, imageSchemaFields) => {
|
|
155
|
+
return Object.keys(object).reduce((acc, key) => {
|
|
156
|
+
if (imageSchemaFields.includes(key)) {
|
|
157
|
+
acc[key] = object[key];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return acc;
|
|
161
|
+
}, {});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const ImageDialog = ({ handleClose }) => {
|
|
165
|
+
const editor = useSlate();
|
|
166
|
+
const { components } = useLibrary();
|
|
167
|
+
const MediaLibraryDialog = components['media-library'];
|
|
168
|
+
|
|
169
|
+
const insertImages = (images) => {
|
|
170
|
+
images.forEach((img) => {
|
|
171
|
+
const image = { type: 'image', image: img, children: [{ type: 'text', text: '' }] };
|
|
172
|
+
Transforms.insertNodes(editor, image);
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleSelectAssets = (images) => {
|
|
177
|
+
const formattedImages = images.map((image) => {
|
|
178
|
+
// create an object with imageSchema defined and exclude unnecessary props coming from media library config
|
|
179
|
+
const expectedImage = pick(image, IMAGE_SCHEMA_FIELDS);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
...expectedImage,
|
|
183
|
+
alternativeText: expectedImage.alternativeText || expectedImage.name,
|
|
184
|
+
url: prefixFileUrlWithBackendUrl(image.url),
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
insertImages(formattedImages);
|
|
189
|
+
handleClose();
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<MediaLibraryDialog
|
|
194
|
+
allowedTypes={[ALLOWED_MEDIA_TYPE]}
|
|
195
|
+
onClose={handleClose}
|
|
196
|
+
onSelectAssets={handleSelectAssets}
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
ImageDialog.propTypes = {
|
|
202
|
+
handleClose: PropTypes.func.isRequired,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const isLastBlockImageOrCode = (editor) => {
|
|
206
|
+
const { selection } = editor;
|
|
207
|
+
|
|
208
|
+
if (!selection) return false;
|
|
209
|
+
|
|
210
|
+
const [currentImageORCodeBlock] = Editor.nodes(editor, {
|
|
211
|
+
at: selection,
|
|
212
|
+
match: (n) => n.type === 'image' || n.type === 'code',
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (currentImageORCodeBlock) {
|
|
216
|
+
const [, currentNodePath] = currentImageORCodeBlock;
|
|
217
|
+
|
|
218
|
+
const isNodeAfter = Boolean(Editor.after(editor, currentNodePath));
|
|
219
|
+
|
|
220
|
+
return !isNodeAfter;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return false;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const BlocksDropdown = () => {
|
|
227
|
+
const editor = useSlate();
|
|
228
|
+
const { formatMessage } = useIntl();
|
|
229
|
+
const [isMediaLibraryVisible, setIsMediaLibraryVisible] = React.useState(false);
|
|
230
|
+
|
|
231
|
+
const blocks = useBlocksStore();
|
|
232
|
+
const blockKeysToInclude = Object.entries(blocks).reduce((currentKeys, entry) => {
|
|
233
|
+
const [key, block] = entry;
|
|
234
|
+
|
|
235
|
+
return block.isInBlocksSelector ? [...currentKeys, key] : currentKeys;
|
|
236
|
+
}, []);
|
|
237
|
+
|
|
238
|
+
const [blockSelected, setBlockSelected] = React.useState(Object.keys(blocks)[0]);
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {string} optionKey - key of the heading selected
|
|
242
|
+
*/
|
|
243
|
+
const selectOption = (optionKey) => {
|
|
244
|
+
toggleBlock(editor, blocks[optionKey].value);
|
|
245
|
+
|
|
246
|
+
setBlockSelected(optionKey);
|
|
247
|
+
|
|
248
|
+
if (isLastBlockImageOrCode(editor)) {
|
|
249
|
+
// insert blank line to add new blocks below code or image blocks
|
|
250
|
+
Transforms.insertNodes(
|
|
251
|
+
editor,
|
|
252
|
+
{
|
|
253
|
+
type: 'paragraph',
|
|
254
|
+
children: [{ type: 'text', text: '' }],
|
|
255
|
+
},
|
|
256
|
+
{ at: [editor.children.length] }
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (optionKey === 'image') {
|
|
261
|
+
setIsMediaLibraryVisible(true);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<>
|
|
267
|
+
<Select
|
|
268
|
+
startIcon={<Icon as={blocks[blockSelected].icon} />}
|
|
269
|
+
onChange={selectOption}
|
|
270
|
+
placeholder={blocks[blockSelected].label}
|
|
271
|
+
value={blockSelected}
|
|
272
|
+
aria-label={formatMessage({
|
|
273
|
+
id: 'components.Blocks.blocks.selectBlock',
|
|
274
|
+
defaultMessage: 'Select a block',
|
|
275
|
+
})}
|
|
276
|
+
>
|
|
277
|
+
{blockKeysToInclude.map((key) => (
|
|
278
|
+
<BlockOption
|
|
279
|
+
key={key}
|
|
280
|
+
value={key}
|
|
281
|
+
label={blocks[key].label}
|
|
282
|
+
icon={blocks[key].icon}
|
|
283
|
+
matchNode={blocks[key].matchNode}
|
|
284
|
+
handleSelection={setBlockSelected}
|
|
285
|
+
blockSelected={blockSelected}
|
|
286
|
+
/>
|
|
287
|
+
))}
|
|
288
|
+
</Select>
|
|
289
|
+
{isMediaLibraryVisible && <ImageDialog handleClose={() => setIsMediaLibraryVisible(false)} />}
|
|
290
|
+
</>
|
|
291
|
+
);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const BlockOption = ({ value, icon, label, handleSelection, blockSelected, matchNode }) => {
|
|
295
|
+
const { formatMessage } = useIntl();
|
|
296
|
+
const editor = useSlate();
|
|
297
|
+
|
|
298
|
+
const isActive = isBlockActive(editor, matchNode);
|
|
299
|
+
const isSelected = value === blockSelected;
|
|
300
|
+
|
|
301
|
+
React.useEffect(() => {
|
|
302
|
+
if (isActive && !isSelected) {
|
|
303
|
+
handleSelection(value);
|
|
304
|
+
}
|
|
305
|
+
}, [handleSelection, isActive, isSelected, value]);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<Option
|
|
309
|
+
startIcon={<Icon as={icon} color={isSelected ? 'primary600' : 'neutral600'} />}
|
|
310
|
+
value={value}
|
|
311
|
+
>
|
|
312
|
+
{formatMessage(label)}
|
|
313
|
+
</Option>
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
BlockOption.propTypes = {
|
|
318
|
+
icon: PropTypes.elementType.isRequired,
|
|
319
|
+
value: PropTypes.string.isRequired,
|
|
320
|
+
label: PropTypes.shape({
|
|
321
|
+
id: PropTypes.string.isRequired,
|
|
322
|
+
defaultMessage: PropTypes.string.isRequired,
|
|
323
|
+
}).isRequired,
|
|
324
|
+
matchNode: PropTypes.func.isRequired,
|
|
325
|
+
handleSelection: PropTypes.func.isRequired,
|
|
326
|
+
blockSelected: PropTypes.string.isRequired,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const ListButton = ({ icon, format, label }) => {
|
|
330
|
+
const editor = useSlate();
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
*
|
|
334
|
+
* @param {import('slate').Node} node
|
|
335
|
+
* @returns boolean
|
|
336
|
+
*/
|
|
337
|
+
const isListNode = (node) => {
|
|
338
|
+
return !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === 'list';
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const isListActive = () => {
|
|
342
|
+
const { selection } = editor;
|
|
343
|
+
|
|
344
|
+
if (!selection) return false;
|
|
345
|
+
|
|
346
|
+
const [match] = Array.from(
|
|
347
|
+
Editor.nodes(editor, {
|
|
348
|
+
at: Editor.unhangRange(editor, selection),
|
|
349
|
+
match: (node) => isListNode(node) && node.format === format,
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
return Boolean(match);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const isActive = isListActive();
|
|
357
|
+
|
|
358
|
+
const toggleList = () => {
|
|
359
|
+
// Delete the parent list so that we're left with only the list items directly
|
|
360
|
+
Transforms.unwrapNodes(editor, {
|
|
361
|
+
match: (node) => isListNode(node) && ['ordered', 'unordered'].includes(node.format),
|
|
362
|
+
split: true,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Change the type of the current selection
|
|
366
|
+
Transforms.setNodes(editor, {
|
|
367
|
+
type: isActive ? 'paragraph' : 'list-item',
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// If the selection is now a list item, wrap it inside a list
|
|
371
|
+
if (!isActive) {
|
|
372
|
+
const block = { type: 'list', format, children: [] };
|
|
373
|
+
Transforms.wrapNodes(editor, block);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<ToolbarButton
|
|
379
|
+
icon={icon}
|
|
380
|
+
name={format}
|
|
381
|
+
label={label}
|
|
382
|
+
isActive={isActive}
|
|
383
|
+
handleClick={toggleList}
|
|
384
|
+
/>
|
|
385
|
+
);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
ListButton.propTypes = {
|
|
389
|
+
icon: PropTypes.elementType.isRequired,
|
|
390
|
+
format: PropTypes.string.isRequired,
|
|
391
|
+
label: PropTypes.shape({
|
|
392
|
+
id: PropTypes.string.isRequired,
|
|
393
|
+
defaultMessage: PropTypes.string.isRequired,
|
|
394
|
+
}).isRequired,
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// TODO: Remove after the RTE Blocks Alpha release
|
|
398
|
+
const AlphaTag = styled(Box)`
|
|
399
|
+
background-color: ${({ theme }) => theme.colors.warning100};
|
|
400
|
+
border: ${({ theme }) => `1px solid ${theme.colors.warning200}`};
|
|
401
|
+
border-radius: ${({ theme }) => theme.borderRadius};
|
|
402
|
+
font-size: ${({ theme }) => theme.fontSizes[0]};
|
|
403
|
+
padding: ${({ theme }) => `${2 / 16}rem ${theme.spaces[1]}`};
|
|
404
|
+
`;
|
|
405
|
+
|
|
406
|
+
const BlocksToolbar = () => {
|
|
407
|
+
const modifiers = useModifiersStore();
|
|
408
|
+
|
|
409
|
+
return (
|
|
410
|
+
<Toolbar.Root asChild>
|
|
411
|
+
{/* Remove after the RTE Blocks Alpha release (paddingRight and width) */}
|
|
412
|
+
<Flex gap={1} padding={2} paddingRight={4} width="100%">
|
|
413
|
+
<BlocksDropdown />
|
|
414
|
+
<Separator />
|
|
415
|
+
<Toolbar.ToggleGroup type="multiple" asChild>
|
|
416
|
+
<Flex gap={1}>
|
|
417
|
+
{Object.entries(modifiers).map(([name, modifier]) => (
|
|
418
|
+
<ToolbarButton
|
|
419
|
+
key={name}
|
|
420
|
+
name={name}
|
|
421
|
+
icon={modifier.icon}
|
|
422
|
+
label={modifier.label}
|
|
423
|
+
isActive={modifier.checkIsActive()}
|
|
424
|
+
handleClick={modifier.handleToggle}
|
|
425
|
+
/>
|
|
426
|
+
))}
|
|
427
|
+
</Flex>
|
|
428
|
+
</Toolbar.ToggleGroup>
|
|
429
|
+
<Separator />
|
|
430
|
+
<Toolbar.ToggleGroup type="single" asChild>
|
|
431
|
+
<Flex gap={1}>
|
|
432
|
+
<ListButton
|
|
433
|
+
label={{
|
|
434
|
+
id: 'components.Blocks.blocks.unorderedList',
|
|
435
|
+
defaultMessage: 'Bulleted list',
|
|
436
|
+
}}
|
|
437
|
+
format="unordered"
|
|
438
|
+
icon={BulletList}
|
|
439
|
+
/>
|
|
440
|
+
<ListButton
|
|
441
|
+
label={{
|
|
442
|
+
id: 'components.Blocks.blocks.orderedList',
|
|
443
|
+
defaultMessage: 'Numbered list',
|
|
444
|
+
}}
|
|
445
|
+
format="ordered"
|
|
446
|
+
icon={NumberList}
|
|
447
|
+
/>
|
|
448
|
+
</Flex>
|
|
449
|
+
</Toolbar.ToggleGroup>
|
|
450
|
+
{/* TODO: Remove after the RTE Blocks Alpha release */}
|
|
451
|
+
<Flex grow={1} justifyContent="flex-end">
|
|
452
|
+
<AlphaTag>
|
|
453
|
+
<Typography textColor="warning600" variant="sigma">
|
|
454
|
+
ALPHA
|
|
455
|
+
</Typography>
|
|
456
|
+
</AlphaTag>
|
|
457
|
+
</Flex>
|
|
458
|
+
</Flex>
|
|
459
|
+
</Toolbar.Root>
|
|
460
|
+
);
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
export { BlocksToolbar };
|