@plone/volto 18.0.0-alpha.32 → 18.0.0-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/locales/ca/LC_MESSAGES/volto.po +17 -0
  3. package/locales/ca.json +1 -1
  4. package/locales/de/LC_MESSAGES/volto.po +17 -0
  5. package/locales/de.json +1 -1
  6. package/locales/en/LC_MESSAGES/volto.po +17 -0
  7. package/locales/en.json +1 -1
  8. package/locales/es/LC_MESSAGES/volto.po +17 -0
  9. package/locales/es.json +1 -1
  10. package/locales/eu/LC_MESSAGES/volto.po +17 -0
  11. package/locales/eu.json +1 -1
  12. package/locales/fi/LC_MESSAGES/volto.po +17 -0
  13. package/locales/fi.json +1 -1
  14. package/locales/fr/LC_MESSAGES/volto.po +17 -0
  15. package/locales/fr.json +1 -1
  16. package/locales/hi/LC_MESSAGES/volto.po +17 -0
  17. package/locales/hi.json +1 -1
  18. package/locales/it/LC_MESSAGES/volto.po +17 -0
  19. package/locales/it.json +1 -1
  20. package/locales/ja/LC_MESSAGES/volto.po +17 -0
  21. package/locales/ja.json +1 -1
  22. package/locales/nl/LC_MESSAGES/volto.po +17 -0
  23. package/locales/nl.json +1 -1
  24. package/locales/pt/LC_MESSAGES/volto.po +17 -0
  25. package/locales/pt.json +1 -1
  26. package/locales/pt_BR/LC_MESSAGES/volto.po +17 -0
  27. package/locales/pt_BR.json +1 -1
  28. package/locales/ro/LC_MESSAGES/volto.po +17 -0
  29. package/locales/ro.json +1 -1
  30. package/locales/volto.pot +18 -1
  31. package/locales/zh_CN/LC_MESSAGES/volto.po +17 -0
  32. package/locales/zh_CN.json +1 -1
  33. package/package.json +10 -5
  34. package/razzle.config.js +58 -0
  35. package/src/actions/form/form.js +18 -2
  36. package/src/actions/index.js +1 -1
  37. package/src/components/manage/BlockChooser/BlockChooserSearch.jsx +1 -0
  38. package/src/components/manage/Blocks/Block/BlocksForm.jsx +157 -81
  39. package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +8 -0
  40. package/src/components/manage/Blocks/Block/Edit.jsx +37 -4
  41. package/src/components/manage/Blocks/Block/Order/Item.jsx +122 -0
  42. package/src/components/manage/Blocks/Block/Order/Order.jsx +367 -0
  43. package/src/components/manage/Blocks/Block/Order/SortableItem.jsx +58 -0
  44. package/src/components/manage/Blocks/Block/Order/utilities.js +113 -0
  45. package/src/components/manage/Blocks/Container/Edit.jsx +1 -0
  46. package/src/components/manage/Blocks/Grid/Edit.jsx +6 -4
  47. package/src/components/manage/Blocks/Image/schema.js +2 -0
  48. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +1 -1
  49. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +1 -1
  50. package/src/components/manage/Form/Form.jsx +159 -151
  51. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +158 -65
  52. package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +125 -71
  53. package/src/components/manage/Sidebar/Sidebar.jsx +28 -1
  54. package/src/components/manage/Widgets/IdWidget.jsx +138 -203
  55. package/src/components/manage/Widgets/InternalUrlWidget.jsx +10 -14
  56. package/src/components/manage/Widgets/TextWidget.jsx +92 -124
  57. package/src/components/manage/Widgets/TextWidget.stories.jsx +14 -3
  58. package/src/components/theme/App/App.jsx +5 -0
  59. package/src/components/theme/Footer/Footer.jsx +1 -0
  60. package/src/components/theme/FormattedDate/FormattedDate.jsx +13 -1
  61. package/src/components/theme/Icon/Icon.jsx +4 -4
  62. package/src/components/theme/Navigation/Navigation.jsx +1 -1
  63. package/src/components/theme/SlotRenderer/SlotRenderer.tsx +3 -4
  64. package/src/components/theme/View/View.jsx +1 -1
  65. package/src/config/Loadables.jsx +18 -0
  66. package/src/config/server.js +0 -1
  67. package/src/constants/ActionTypes.js +1 -0
  68. package/src/express-middleware/static.js +37 -19
  69. package/src/helpers/Blocks/Blocks.js +182 -1
  70. package/src/helpers/Blocks/Blocks.test.js +136 -0
  71. package/src/helpers/Html/Html.jsx +6 -8
  72. package/src/helpers/Slots/index.tsx +12 -5
  73. package/src/helpers/index.js +4 -0
  74. package/src/reducers/form/form.js +18 -1
  75. package/src/reducers/form/form.test.js +15 -1
  76. package/src/server.jsx +34 -36
  77. package/theme/themes/pastanaga/extras/blocks.less +7 -6
  78. package/theme/themes/pastanaga/extras/objectbrowser-widget.less +83 -0
  79. package/theme/themes/pastanaga/extras/sidebar.less +145 -0
  80. package/theme/themes/pastanaga/extras/widgets.less +19 -0
  81. package/types/actions/form/form.d.ts +8 -1
  82. package/types/actions/index.d.ts +1 -1
  83. package/types/components/manage/Blocks/Block/Order/Item.d.ts +2 -0
  84. package/types/components/manage/Blocks/Block/Order/Order.d.ts +13 -0
  85. package/types/components/manage/Blocks/Block/Order/SortableItem.d.ts +9 -0
  86. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +9 -0
  87. package/types/components/manage/Blocks/Image/schema.d.ts +2 -0
  88. package/types/components/manage/Sidebar/ObjectBrowserNav.d.ts +2 -1
  89. package/types/components/manage/Widgets/IdWidget.d.ts +54 -2
  90. package/types/components/manage/Widgets/TextWidget.d.ts +54 -5
  91. package/types/components/manage/Widgets/TextWidget.stories.d.ts +13 -1
  92. package/types/components/manage/Widgets/index.d.ts +2 -2
  93. package/types/components/theme/Icon/Icon.d.ts +8 -8
  94. package/types/config/Loadables.d.ts +15 -162
  95. package/types/config/Widgets.d.ts +1 -1
  96. package/types/config/server.d.ts +0 -3
  97. package/types/constants/ActionTypes.d.ts +1 -0
  98. package/types/helpers/Blocks/Blocks.d.ts +13 -0
  99. package/types/helpers/Slots/index.d.ts +5 -3
  100. package/types/helpers/index.d.ts +2 -2
  101. package/types/start-client.d.ts +1 -1
@@ -1,11 +1,14 @@
1
- import React from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
+ import { cloneDeep, map } from 'lodash';
3
4
  import EditBlock from './Edit';
4
5
  import { DragDropList } from '@plone/volto/components';
5
6
  import {
6
7
  getBlocks,
7
8
  getBlocksFieldname,
9
+ getBlocksLayoutFieldname,
8
10
  applyBlockDefaults,
11
+ getBlocksHierarchy,
9
12
  } from '@plone/volto/helpers';
10
13
  import {
11
14
  addBlock,
@@ -13,15 +16,19 @@ import {
13
16
  changeBlock,
14
17
  deleteBlock,
15
18
  moveBlock,
19
+ moveBlockEnhanced,
16
20
  mutateBlock,
17
21
  nextBlockId,
18
22
  previousBlockId,
19
23
  } from '@plone/volto/helpers';
20
24
  import EditBlockWrapper from './EditBlockWrapper';
21
- import { setSidebarTab } from '@plone/volto/actions';
25
+ import { setSidebarTab, setUIState } from '@plone/volto/actions';
22
26
  import { useDispatch } from 'react-redux';
23
27
  import { useDetectClickOutside, useEvent } from '@plone/volto/helpers';
24
28
  import config from '@plone/volto/registry';
29
+ import { createPortal } from 'react-dom';
30
+
31
+ import Order from './Order/Order';
25
32
 
26
33
  const BlocksForm = (props) => {
27
34
  const {
@@ -53,6 +60,12 @@ const BlocksForm = (props) => {
53
60
  token,
54
61
  } = props;
55
62
 
63
+ const [isClient, setIsClient] = useState(false);
64
+
65
+ useEffect(() => {
66
+ setIsClient(true);
67
+ }, []);
68
+
56
69
  const blockList = getBlocks(properties);
57
70
 
58
71
  const dispatch = useDispatch();
@@ -183,6 +196,52 @@ const BlocksForm = (props) => {
183
196
  onChangeFormData(newFormData);
184
197
  };
185
198
 
199
+ const onMoveBlockEnhanced = ({ source, destination }) => {
200
+ const newFormData = moveBlockEnhanced(cloneDeep(properties), {
201
+ source,
202
+ destination,
203
+ });
204
+ const blocksFieldname = getBlocksFieldname(newFormData);
205
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(newFormData);
206
+ let error = false;
207
+
208
+ const allowedBlocks = Object.keys(blocksConfig);
209
+
210
+ map(newFormData[blocksLayoutFieldname].items, (id) => {
211
+ const block = newFormData[blocksFieldname][id];
212
+ if (!allowedBlocks.includes(block['@type'])) {
213
+ error = true;
214
+ }
215
+ if (Array.isArray(block[blocksLayoutFieldname]?.items)) {
216
+ const size = block[blocksLayoutFieldname].items.length;
217
+ const allowedSubBlocks = [
218
+ ...(blocksConfig[block['@type']].allowedBlocks || allowedBlocks),
219
+ 'empty',
220
+ ] || ['empty'];
221
+ if (size < 1 || size > (blocksConfig[block['@type']].maxLength || 4)) {
222
+ error = true;
223
+ }
224
+ map(block[blocksLayoutFieldname].items, (subId) => {
225
+ const subBlock = block[blocksFieldname][subId];
226
+ if (!allowedSubBlocks.includes(subBlock['@type'])) {
227
+ error = true;
228
+ }
229
+ });
230
+ }
231
+ });
232
+
233
+ if (!error) {
234
+ onChangeFormData(newFormData);
235
+ dispatch(
236
+ setUIState({
237
+ selected: null,
238
+ multiSelected: [],
239
+ gridSelected: null,
240
+ }),
241
+ );
242
+ }
243
+ };
244
+
186
245
  const defaultBlockWrapper = ({ draginfo }, editBlock, blockProps) => (
187
246
  <EditBlockWrapper draginfo={draginfo} blockProps={blockProps}>
188
247
  {editBlock}
@@ -195,6 +254,7 @@ const BlocksForm = (props) => {
195
254
  // Note they are alreaady filtered by DragDropList, but we also want them
196
255
  // to be removed when the user saves the page next. Otherwise the invalid
197
256
  // blocks would linger for ever.
257
+
198
258
  for (const [n, v] of blockList) {
199
259
  if (!v) {
200
260
  const newFormData = deleteBlock(properties, n);
@@ -210,85 +270,101 @@ const BlocksForm = (props) => {
210
270
  });
211
271
 
212
272
  return (
213
- <div
214
- className="blocks-form"
215
- role="presentation"
216
- ref={ref}
217
- onKeyDown={(e) => {
218
- if (stopPropagation) {
219
- e.stopPropagation();
220
- }
221
- }}
222
- >
223
- <fieldset className="invisible" disabled={!editable}>
224
- <DragDropList
225
- childList={blockList}
226
- onMoveItem={(result) => {
227
- const { source, destination } = result;
228
- if (!destination) {
229
- return;
230
- }
231
- const newFormData = moveBlock(
232
- properties,
233
- source.index,
234
- destination.index,
235
- );
236
- onChangeFormData(newFormData);
237
- return true;
238
- }}
239
- direction={direction}
240
- >
241
- {(dragProps) => {
242
- const { child, childId, index } = dragProps;
243
- const blockProps = {
244
- allowedBlocks,
245
- showRestricted,
246
- block: childId,
247
- data: child,
248
- handleKeyDown,
249
- id: childId,
250
- formTitle: title,
251
- formDescription: description,
252
- index,
253
- manage,
254
- onAddBlock,
255
- onInsertBlock,
256
- onChangeBlock,
257
- onChangeField,
258
- onChangeFormData,
259
- onDeleteBlock,
260
- onFocusNextBlock,
261
- onFocusPreviousBlock,
262
- onMoveBlock,
263
- onMutateBlock,
264
- onSelectBlock,
265
- pathname,
266
- metadata,
267
- properties,
268
- contentType: type,
269
- navRoot,
270
- blocksConfig,
271
- selected: selectedBlock === childId,
272
- multiSelected: multiSelected?.includes(childId),
273
- type: child['@type'],
274
- editable,
275
- showBlockChooser: selectedBlock === childId,
276
- detached: isContainer,
277
- // Properties to pass to the BlocksForm to match the View ones
278
- content: properties,
279
- history,
280
- location,
281
- token,
282
- };
283
- return editBlockWrapper(
284
- dragProps,
285
- <EditBlock key={childId} {...blockProps} />,
286
- blockProps,
287
- );
288
- }}
289
- </DragDropList>
290
- </fieldset>
291
- </div>
273
+ <>
274
+ {isMainForm &&
275
+ isClient &&
276
+ createPortal(
277
+ <div>
278
+ <Order
279
+ items={getBlocksHierarchy(properties)}
280
+ onMoveBlock={onMoveBlockEnhanced}
281
+ onDeleteBlock={onDeleteBlock}
282
+ onSelectBlock={onSelectBlock}
283
+ removable
284
+ />
285
+ </div>,
286
+ document.getElementById('sidebar-order'),
287
+ )}
288
+ <div
289
+ className="blocks-form"
290
+ role="presentation"
291
+ ref={ref}
292
+ onKeyDown={(e) => {
293
+ if (stopPropagation) {
294
+ e.stopPropagation();
295
+ }
296
+ }}
297
+ >
298
+ <fieldset className="invisible" disabled={!editable}>
299
+ <DragDropList
300
+ childList={blockList}
301
+ onMoveItem={(result) => {
302
+ const { source, destination } = result;
303
+ if (!destination) {
304
+ return;
305
+ }
306
+ const newFormData = moveBlock(
307
+ properties,
308
+ source.index,
309
+ destination.index,
310
+ );
311
+ onChangeFormData(newFormData);
312
+ return true;
313
+ }}
314
+ direction={direction}
315
+ >
316
+ {(dragProps) => {
317
+ const { child, childId, index } = dragProps;
318
+ const blockProps = {
319
+ allowedBlocks,
320
+ showRestricted,
321
+ block: childId,
322
+ data: child,
323
+ handleKeyDown,
324
+ id: childId,
325
+ formTitle: title,
326
+ formDescription: description,
327
+ index,
328
+ manage,
329
+ onAddBlock,
330
+ onInsertBlock,
331
+ onChangeBlock,
332
+ onChangeField,
333
+ onChangeFormData,
334
+ onDeleteBlock,
335
+ onFocusNextBlock,
336
+ onFocusPreviousBlock,
337
+ onMoveBlock,
338
+ onMutateBlock,
339
+ onSelectBlock,
340
+ pathname,
341
+ metadata,
342
+ properties,
343
+ contentType: type,
344
+ navRoot,
345
+ blocksConfig,
346
+ selected: selectedBlock === childId,
347
+ multiSelected: multiSelected?.includes(childId),
348
+ type: child['@type'],
349
+ editable,
350
+ showBlockChooser: selectedBlock === childId,
351
+ detached: isContainer,
352
+ // Properties to pass to the BlocksForm to match the View ones
353
+ content: properties,
354
+ history,
355
+ location,
356
+ token,
357
+ };
358
+ return editBlockWrapper(
359
+ dragProps,
360
+ <EditBlock key={childId} {...blockProps} />,
361
+ blockProps,
362
+ );
363
+ }}
364
+ </DragDropList>
365
+ </fieldset>
366
+ </div>
367
+ </>
292
368
  );
293
369
  };
294
370
 
@@ -30,6 +30,9 @@ test('Allow override of blocksConfig', () => {
30
30
  locale: 'en',
31
31
  messages: {},
32
32
  },
33
+ form: {
34
+ ui: {},
35
+ },
33
36
  });
34
37
 
35
38
  const data = {
@@ -68,6 +71,7 @@ test('Allow override of blocksConfig', () => {
68
71
  const { container } = render(
69
72
  <Provider store={store}>
70
73
  <BlocksForm {...data} />
74
+ <div id="sidebar-order"></div>
71
75
  </Provider>,
72
76
  );
73
77
  expect(container).toMatchSnapshot();
@@ -79,6 +83,9 @@ test('Removes invalid blocks on saving', () => {
79
83
  locale: 'en',
80
84
  messages: {},
81
85
  },
86
+ form: {
87
+ ui: {},
88
+ },
82
89
  });
83
90
 
84
91
  const onChangeFormData = jest.fn(() => {});
@@ -120,6 +127,7 @@ test('Removes invalid blocks on saving', () => {
120
127
  render(
121
128
  <Provider store={store}>
122
129
  <BlocksForm {...data} />
130
+ <div id="sidebar-order"></div>
123
131
  </Provider>,
124
132
  );
125
133
  expect(onChangeFormData).toBeCalledWith({
@@ -9,7 +9,7 @@ import { compose } from 'redux';
9
9
  import { connect } from 'react-redux';
10
10
  import { defineMessages, injectIntl } from 'react-intl';
11
11
  import cx from 'classnames';
12
- import { setSidebarTab } from '@plone/volto/actions';
12
+ import { setSidebarTab, setUIState } from '@plone/volto/actions';
13
13
  import config from '@plone/volto/registry';
14
14
  import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
15
15
  import { applyBlockDefaults } from '@plone/volto/helpers';
@@ -79,7 +79,11 @@ export class Edit extends Component {
79
79
  this.blockNode.current.focus();
80
80
  }
81
81
  const tab = this.props.manage ? 1 : blocksConfig?.[type]?.sidebarTab || 0;
82
- if (this.props.selected && this.props.editable) {
82
+ if (
83
+ this.props.selected &&
84
+ this.props.editable &&
85
+ this.props.sidebarTab !== 2
86
+ ) {
83
87
  this.props.setSidebarTab(tab);
84
88
  }
85
89
  }
@@ -105,7 +109,9 @@ export class Edit extends Component {
105
109
  const tab = this.props.manage
106
110
  ? 1
107
111
  : blocksConfig?.[nextProps.type]?.sidebarTab || 0;
108
- this.props.setSidebarTab(tab);
112
+ if (this.props.sidebarTab !== 2) {
113
+ this.props.setSidebarTab(tab);
114
+ }
109
115
  }
110
116
  }
111
117
 
@@ -138,6 +144,21 @@ export class Edit extends Component {
138
144
  {Block !== null ? (
139
145
  <div
140
146
  role="presentation"
147
+ onMouseOver={() => {
148
+ if (this.props.hovered !== this.props.id) {
149
+ this.props.setUIState({ hovered: this.props.id });
150
+ }
151
+ }}
152
+ onFocus={() => {
153
+ // TODO: This `onFocus` steals somehow the focus from the slate block
154
+ // we have to investigate why this is happening
155
+ // Apparently, I can't see any difference in the behavior
156
+ // If any, we can fix it in successive iterations
157
+ // if (this.props.hovered !== this.props.id) {
158
+ // this.props.setUIState({ hovered: this.props.id });
159
+ // }
160
+ }}
161
+ onMouseLeave={() => this.props.setUIState({ hovered: null })}
141
162
  onClick={(e) => {
142
163
  const isMultipleSelection = e.shiftKey || e.ctrlKey || e.metaKey;
143
164
  !this.props.selected &&
@@ -161,6 +182,7 @@ export class Edit extends Component {
161
182
  className={cx('block', type, this.props.data.variation, {
162
183
  selected: this.props.selected || this.props.multiSelected,
163
184
  multiSelected: this.props.multiSelected,
185
+ hovered: this.props.hovered === this.props.id,
164
186
  })}
165
187
  style={{ outline: 'none' }}
166
188
  ref={this.blockNode}
@@ -185,6 +207,11 @@ export class Edit extends Component {
185
207
  ) : (
186
208
  <div
187
209
  role="presentation"
210
+ onMouseOver={() =>
211
+ this.props.setUIState({ hovered: this.props.id })
212
+ }
213
+ onFocus={() => this.props.setUIState({ hovered: this.props.id })}
214
+ onMouseLeave={() => this.props.setUIState({ hovered: null })}
188
215
  onClick={() =>
189
216
  !this.props.selected && this.props.onSelectBlock(this.props.id)
190
217
  }
@@ -218,5 +245,11 @@ export class Edit extends Component {
218
245
  export default compose(
219
246
  injectIntl,
220
247
  withObjectBrowser,
221
- connect(null, { setSidebarTab }),
248
+ connect(
249
+ (state, props) => ({
250
+ hovered: state.form?.ui.hovered || null,
251
+ sidebarTab: state.sidebar?.tab,
252
+ }),
253
+ { setSidebarTab, setUIState },
254
+ ),
222
255
  )(Edit);
@@ -0,0 +1,122 @@
1
+ import React, { forwardRef } from 'react';
2
+ import classNames from 'classnames';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { includes } from 'lodash';
5
+
6
+ import { Icon } from '@plone/volto/components';
7
+ import { setUIState } from '@plone/volto/actions';
8
+ import config from '@plone/volto/registry';
9
+
10
+ import deleteSVG from '@plone/volto/icons/delete.svg';
11
+ import dragSVG from '@plone/volto/icons/drag.svg';
12
+
13
+ export const Item = forwardRef(
14
+ (
15
+ {
16
+ clone,
17
+ data,
18
+ depth,
19
+ disableSelection,
20
+ disableInteraction,
21
+ ghost,
22
+ id,
23
+ handleProps,
24
+ indentationWidth,
25
+ onRemove,
26
+ onSelectBlock,
27
+ parentId,
28
+ style,
29
+ value,
30
+ wrapperRef,
31
+ ...props
32
+ },
33
+ ref,
34
+ ) => {
35
+ const selected = useSelector((state) => state.form.ui.selected);
36
+ const hovered = useSelector((state) => state.form.ui.hovered);
37
+ const multiSelected = useSelector((state) => state.form.ui.multiSelected);
38
+ const gridSelected = useSelector((state) => state.form.ui.gridSelected);
39
+ const dispatch = useDispatch();
40
+ return (
41
+ <li
42
+ className={classNames(
43
+ 'tree-item-wrapper',
44
+ clone && 'clone',
45
+ ghost && 'ghost',
46
+ disableSelection && 'disable-selection',
47
+ disableInteraction && 'disable-interaction',
48
+ )}
49
+ role="presentation"
50
+ onMouseOver={() => dispatch(setUIState({ hovered: id }))}
51
+ onFocus={() => dispatch(setUIState({ hovered: id }))}
52
+ onMouseLeave={() => dispatch(setUIState({ hovered: null }))}
53
+ onClick={(e) => {
54
+ if (depth === 0) {
55
+ const isMultipleSelection = e.shiftKey || e.ctrlKey || e.metaKey;
56
+ selected !== id &&
57
+ onSelectBlock(
58
+ id,
59
+ selected === id ? false : isMultipleSelection,
60
+ e,
61
+ );
62
+ } else {
63
+ dispatch(
64
+ setUIState({
65
+ selected: parentId,
66
+ multiSelected: [],
67
+ gridSelected: id,
68
+ }),
69
+ );
70
+ }
71
+ }}
72
+ ref={wrapperRef}
73
+ style={{
74
+ '--spacing': `${indentationWidth * depth}px`,
75
+ }}
76
+ {...props}
77
+ >
78
+ <div
79
+ className={classNames(
80
+ 'tree-item',
81
+ (selected === id || gridSelected === id) && 'selected',
82
+ hovered === id && 'hovered',
83
+ includes(multiSelected, id) && 'multiSelected',
84
+ `depth-${depth}`,
85
+ )}
86
+ ref={ref}
87
+ style={style}
88
+ >
89
+ <button
90
+ ref={ref}
91
+ {...handleProps}
92
+ className={classNames('action', 'drag')}
93
+ tabIndex={0}
94
+ data-cypress="draggable-handle"
95
+ >
96
+ <Icon name={dragSVG} size="16px" />
97
+ </button>
98
+ <span className="text">
99
+ {config.blocks.blocksConfig[data?.['@type']]?.icon && (
100
+ <Icon
101
+ name={config.blocks.blocksConfig[data?.['@type']]?.icon}
102
+ size="20px"
103
+ style={{ verticalAlign: 'middle' }}
104
+ />
105
+ )}{' '}
106
+ {data?.plaintext ||
107
+ config.blocks.blocksConfig[data?.['@type']]?.title}
108
+ </span>
109
+ {!clone && onRemove && (
110
+ <button
111
+ onClick={onRemove}
112
+ className={classNames('action', 'delete')}
113
+ tabIndex={0}
114
+ >
115
+ <Icon name={deleteSVG} size="18" />
116
+ </button>
117
+ )}
118
+ </div>
119
+ </li>
120
+ );
121
+ },
122
+ );