@eeacms/volto-clms-theme 1.0.185 → 1.0.187

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/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [1.0.187](https://github.com/eea/volto-clms-theme/compare/1.0.186...1.0.187) - 15 March 2023
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - disable no-direct-mutatuion [ionlizarazu - [`dd11dfd`](https://github.com/eea/volto-clms-theme/commit/dd11dfd1cce62ff29771809f27c01c30aad10e9f)]
12
+ - add an empty slate text editor if there are no children in the slate editor [ionlizarazu - [`fb77de2`](https://github.com/eea/volto-clms-theme/commit/fb77de261fd0d40fae42fa6301ea9b485e144320)]
13
+ - mv files to override and initial original copy of SlateEditor.jsx [ionlizarazu - [`cebd0fc`](https://github.com/eea/volto-clms-theme/commit/cebd0fc3ed517cc0b586a5e3060da77ce94763d1)]
14
+ ### [1.0.186](https://github.com/eea/volto-clms-theme/compare/1.0.185...1.0.186) - 14 March 2023
15
+
16
+ #### :bug: Bug Fixes
17
+
18
+ - fix: formatConversionTable condition to avoid cart first loading error [ionlizarazu - [`054be8e`](https://github.com/eea/volto-clms-theme/commit/054be8e9603d481665d9efeefcb168288aebba40)]
19
+ - fix: remove icon title to avoid duplicated messages [ionlizarazu - [`d4f562a`](https://github.com/eea/volto-clms-theme/commit/d4f562ae4e66847bf02f80d0448e6515da1093e6)]
20
+
21
+ #### :hammer_and_wrench: Others
22
+
23
+ - logged in buttons for editor style [ionlizarazu - [`da560b0`](https://github.com/eea/volto-clms-theme/commit/da560b09e92029ae9cc0c193a35771ea4ec8bf1f)]
7
24
  ### [1.0.185](https://github.com/eea/volto-clms-theme/compare/1.0.184...1.0.185) - 13 March 2023
8
25
 
9
26
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.0.185",
3
+ "version": "1.0.187",
4
4
  "description": "volto-clms-theme: Volto theme for CLMS site",
5
5
  "main": "src/index.js",
6
6
  "author": "CodeSyntax for the European Environment Agency",
@@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
3
3
  import { connect, useDispatch, useSelector } from 'react-redux';
4
4
  import { useLocation } from 'react-router-dom';
5
5
  import { compose } from 'redux';
6
- import { Modal, Segment } from 'semantic-ui-react';
6
+ import { Modal, Segment, Grid } from 'semantic-ui-react';
7
7
 
8
8
  import { getUser } from '@plone/volto/actions';
9
9
  import CclButton from '@eeacms/volto-clms-theme/components/CclButton/CclButton';
@@ -77,191 +77,209 @@ const CLMSDatasetDetailView = ({ content, token }) => {
77
77
  {user?.roles && user.roles.includes('Manager') && (
78
78
  <>
79
79
  <h2>MapLayers importation options:</h2>
80
- <Segment.Group compact horizontal>
81
- <Segment
82
- padded={'very'}
83
- color={'olive'}
84
- key={'wms-layers-import'}
85
- loading={wms_layers_importation?.loading}
86
- circular
87
- style={{ width: '50%' }}
88
- >
89
- <Modal
90
- onClose={() => {
91
- setOpen({ ...open, 'wms-layers-import': false });
92
- }}
93
- onOpen={() => {
94
- setOpen({ ...open, 'wms-layers-import': true });
95
- }}
96
- open={open['wms-layers-import']}
97
- trigger={
98
- <CclButton>
99
- <FormattedMessage
100
- id="Import WMS Layers"
101
- defaultMessage="Import WMS Layers"
102
- />
103
- </CclButton>
104
- }
105
- className={'modal-clms'}
106
- >
107
- <div className={'modal-clms-background'}>
108
- <div className={'modal-clms-container'}>
109
- <div className={'modal-close modal-clms-close'}>
110
- <span
111
- className="ccl-icon-close"
112
- aria-label="Close"
113
- onClick={() => {
114
- setOpen({ ...open, 'wms-layers-import': false });
115
- }}
116
- onKeyDown={() => {
117
- setOpen({ ...open, 'wms-layers-import': false });
118
- }}
119
- tabIndex="0"
120
- role="button"
121
- ></span>
122
- </div>
123
- <div className="modal-login-text">
124
- <h1>
80
+ <Segment basic>
81
+ <Grid columns={2} compact horizontal>
82
+ <Grid.Column>
83
+ <Segment
84
+ padded={'very'}
85
+ color={'olive'}
86
+ key={'wms-layers-import'}
87
+ loading={wms_layers_importation?.loading}
88
+ style={{
89
+ width: '100%',
90
+ display: 'flex',
91
+ justifyContent: 'center',
92
+ }}
93
+ >
94
+ <Modal
95
+ onClose={() => {
96
+ setOpen({ ...open, 'wms-layers-import': false });
97
+ }}
98
+ onOpen={() => {
99
+ setOpen({ ...open, 'wms-layers-import': true });
100
+ }}
101
+ open={open['wms-layers-import']}
102
+ trigger={
103
+ <CclButton>
125
104
  <FormattedMessage
126
105
  id="Import WMS Layers"
127
106
  defaultMessage="Import WMS Layers"
128
107
  />
129
- </h1>
130
- This action will import the WMS Layers from the view
131
- service defined in the dataset or from GeoNetwork if the
132
- view service is not defined and a linked geonetwork record
133
- has a valid WMS service link
134
- <br />
135
- <br />
108
+ </CclButton>
109
+ }
110
+ className={'modal-clms'}
111
+ >
112
+ <div className={'modal-clms-background'}>
113
+ <div className={'modal-clms-container'}>
114
+ <div className={'modal-close modal-clms-close'}>
115
+ <span
116
+ className="ccl-icon-close"
117
+ aria-label="Close"
118
+ onClick={() => {
119
+ setOpen({ ...open, 'wms-layers-import': false });
120
+ }}
121
+ onKeyDown={() => {
122
+ setOpen({ ...open, 'wms-layers-import': false });
123
+ }}
124
+ tabIndex="0"
125
+ role="button"
126
+ ></span>
127
+ </div>
128
+ <div className="modal-login-text">
129
+ <h1>
130
+ <FormattedMessage
131
+ id="Import WMS Layers"
132
+ defaultMessage="Import WMS Layers"
133
+ />
134
+ </h1>
135
+ This action will import the WMS Layers from the view
136
+ service defined in the dataset or from GeoNetwork if
137
+ the view service is not defined and a linked
138
+ geonetwork record has a valid WMS service link
139
+ <br />
140
+ <br />
141
+ </div>
142
+ <CclButton
143
+ onClick={() => {
144
+ handleWMSImport();
145
+ setOpen({ ...open, 'wms-layers-import': false });
146
+ }}
147
+ mode="filled"
148
+ >
149
+ <FormattedMessage
150
+ id="Import data"
151
+ defaultMessage="Import data"
152
+ />
153
+ </CclButton>
154
+ </div>
136
155
  </div>
137
- <CclButton
138
- onClick={() => {
139
- handleWMSImport();
140
- setOpen({ ...open, 'wms-layers-import': false });
141
- }}
142
- mode="filled"
143
- >
144
- <FormattedMessage
145
- id="Import data"
146
- defaultMessage="Import data"
147
- />
148
- </CclButton>
149
- </div>
150
- </div>
151
- </Modal>
152
- {wms_layers_importation?.imported_wms_layers?.status && (
153
- <p>
154
- {wms_layers_importation?.loaded &&
155
- wms_layers_importation?.error === null && (
156
+ </Modal>
157
+ {wms_layers_importation?.imported_wms_layers?.status && (
158
+ <p>
159
+ {wms_layers_importation?.loaded &&
160
+ wms_layers_importation?.error === null && (
161
+ <strong>
162
+ {' '}
163
+ {
164
+ wms_layers_importation?.imported_wms_layers
165
+ ?.message
166
+ }
167
+ </strong>
168
+ )}
169
+ </p>
170
+ )}
171
+ {wms_layers_importation?.imported_wms_layers?.status ===
172
+ 'error' && (
173
+ <p>
156
174
  <strong>
157
175
  {' '}
158
176
  {wms_layers_importation?.imported_wms_layers?.message}
159
177
  </strong>
160
- )}
161
- </p>
162
- )}
163
- {wms_layers_importation?.imported_wms_layers?.status ===
164
- 'error' && (
165
- <p>
166
- <strong>
167
- {' '}
168
- {wms_layers_importation?.imported_wms_layers?.message}
169
- </strong>
170
- </p>
171
- )}
172
- </Segment>
173
- <Segment
174
- padded={'very'}
175
- color={'olive'}
176
- key={'wms-fields-import'}
177
- loading={wms_fields_importation?.loading}
178
- circular
179
- style={{ width: '50%' }}
180
- >
181
- <Modal
182
- onClose={() => {
183
- setOpen({ ...open, 'wms-fields-import': false });
184
- }}
185
- onOpen={() => {
186
- setOpen({ ...open, 'wms-fields-import': true });
187
- }}
188
- open={open['wms-fields-import']}
189
- trigger={
190
- <CclButton>
191
- <FormattedMessage
192
- id="Import WMS Fields"
193
- defaultMessage="Import WMS Fields"
194
- />
195
- </CclButton>
196
- }
197
- className={'modal-clms'}
198
- >
199
- <div className={'modal-clms-background'}>
200
- <div className={'modal-clms-container'}>
201
- <div className={'modal-close modal-clms-close'}>
202
- <span
203
- className="ccl-icon-close"
204
- aria-label="Close"
205
- onClick={() => {
206
- setOpen({ ...open, 'wms-fields-import': false });
207
- }}
208
- onKeyDown={() => {
209
- setOpen({ ...open, 'wms-fields-import': false });
210
- }}
211
- tabIndex="0"
212
- role="button"
213
- ></span>
214
- </div>
215
- <div className="modal-login-text">
216
- <h1>
178
+ </p>
179
+ )}
180
+ </Segment>
181
+ </Grid.Column>
182
+ <Grid.Column>
183
+ <Segment
184
+ padded={'very'}
185
+ color={'olive'}
186
+ key={'wms-fields-import'}
187
+ loading={wms_fields_importation?.loading}
188
+ style={{
189
+ width: '100%',
190
+ display: 'flex',
191
+ justifyContent: 'center',
192
+ }}
193
+ >
194
+ <Modal
195
+ onClose={() => {
196
+ setOpen({ ...open, 'wms-fields-import': false });
197
+ }}
198
+ onOpen={() => {
199
+ setOpen({ ...open, 'wms-fields-import': true });
200
+ }}
201
+ open={open['wms-fields-import']}
202
+ trigger={
203
+ <CclButton>
217
204
  <FormattedMessage
218
205
  id="Import WMS Fields"
219
206
  defaultMessage="Import WMS Fields"
220
207
  />
221
- </h1>
222
- This action will import the WMS Fields from the view
223
- service defined in the dataset if this WMS service is
224
- Arcgis based
225
- <br />
226
- <br />
208
+ </CclButton>
209
+ }
210
+ className={'modal-clms'}
211
+ >
212
+ <div className={'modal-clms-background'}>
213
+ <div className={'modal-clms-container'}>
214
+ <div className={'modal-close modal-clms-close'}>
215
+ <span
216
+ className="ccl-icon-close"
217
+ aria-label="Close"
218
+ onClick={() => {
219
+ setOpen({ ...open, 'wms-fields-import': false });
220
+ }}
221
+ onKeyDown={() => {
222
+ setOpen({ ...open, 'wms-fields-import': false });
223
+ }}
224
+ tabIndex="0"
225
+ role="button"
226
+ ></span>
227
+ </div>
228
+ <div className="modal-login-text">
229
+ <h1>
230
+ <FormattedMessage
231
+ id="Import WMS Fields"
232
+ defaultMessage="Import WMS Fields"
233
+ />
234
+ </h1>
235
+ This action will import the WMS Fields from the view
236
+ service defined in the dataset if this WMS service is
237
+ Arcgis based
238
+ <br />
239
+ <br />
240
+ </div>
241
+ <CclButton
242
+ onClick={() => {
243
+ handleWMSFieldimport();
244
+ setOpen({ ...open, 'wms-fields-import': false });
245
+ }}
246
+ mode="filled"
247
+ >
248
+ <FormattedMessage
249
+ id="Import data"
250
+ defaultMessage="Import data"
251
+ />
252
+ </CclButton>
253
+ </div>
227
254
  </div>
228
- <CclButton
229
- onClick={() => {
230
- handleWMSFieldimport();
231
- setOpen({ ...open, 'wms-fields-import': false });
232
- }}
233
- mode="filled"
234
- >
235
- <FormattedMessage
236
- id="Import data"
237
- defaultMessage="Import data"
238
- />
239
- </CclButton>
240
- </div>
241
- </div>
242
- </Modal>
243
- {wms_fields_importation?.imported_wms_fields?.status && (
244
- <p>
245
- {wms_fields_importation?.loaded &&
246
- wms_fields_importation?.error === null && (
255
+ </Modal>
256
+ {wms_fields_importation?.imported_wms_fields?.status && (
257
+ <p>
258
+ {wms_fields_importation?.loaded &&
259
+ wms_fields_importation?.error === null && (
260
+ <strong>
261
+ {' '}
262
+ {
263
+ wms_fields_importation?.imported_wms_fields
264
+ ?.message
265
+ }
266
+ </strong>
267
+ )}
268
+ </p>
269
+ )}
270
+ {wms_fields_importation?.imported_wms_fields?.status ===
271
+ 'error' && (
272
+ <p>
247
273
  <strong>
248
274
  {' '}
249
275
  {wms_fields_importation?.imported_wms_fields?.message}
250
276
  </strong>
251
- )}
252
- </p>
253
- )}
254
- {wms_fields_importation?.imported_wms_fields?.status ===
255
- 'error' && (
256
- <p>
257
- <strong>
258
- {' '}
259
- {wms_fields_importation?.imported_wms_fields?.message}
260
- </strong>
261
- </p>
262
- )}
263
- </Segment>
264
- </Segment.Group>
277
+ </p>
278
+ )}
279
+ </Segment>
280
+ </Grid.Column>
281
+ </Grid>
282
+ </Segment>
265
283
  </>
266
284
  )}
267
285
 
@@ -287,7 +287,7 @@ const CLMSCartContent = (props) => {
287
287
  getCollectionByItem(item).collection ?? '-'
288
288
  );
289
289
  };
290
- const FormatNaming = ({ item }) => {
290
+ const FormatNaming = ({ item, formatConversionTable }) => {
291
291
  const format_options = getAvailableConversion(
292
292
  formatConversionTable,
293
293
  originalFormatNaming(item),
@@ -405,7 +405,12 @@ const CLMSCartContent = (props) => {
405
405
  <CollectionNaming item={item} />
406
406
  </td>
407
407
  <td className="table-td-format">
408
- <FormatNaming item={item} />
408
+ {formatConversionTable && item && (
409
+ <FormatNaming
410
+ item={item}
411
+ formatConversionTable={formatConversionTable}
412
+ />
413
+ )}
409
414
  </td>
410
415
  <td className="table-td-projections">
411
416
  {!item.file_id ? (
@@ -455,11 +460,7 @@ const CLMSCartContent = (props) => {
455
460
  outline: 'inherit',
456
461
  }}
457
462
  >
458
- <Icon
459
- name={addDocumentSVG}
460
- size={25}
461
- title={'Add a duplicated row below'}
462
- />
463
+ <Icon name={addDocumentSVG} size={25} />
463
464
  </button>
464
465
  </span>
465
466
  ) : (
@@ -493,7 +494,6 @@ const CLMSCartContent = (props) => {
493
494
  name={removeSVG}
494
495
  size={25}
495
496
  color="#e40166"
496
- title={'Remove this row from the cart'}
497
497
  />
498
498
  </button>
499
499
  </span>
@@ -314,13 +314,19 @@ export const CLMSMeetingView = (props) => {
314
314
  </strong>
315
315
  <br />
316
316
  <br />
317
- <CclButton url={location.pathname + '/subscribers'}>
317
+ <CclButton
318
+ url={location.pathname + '/subscribers'}
319
+ style={{ marginLeft: '10px', marginRight: '10px' }}
320
+ >
318
321
  <FormattedMessage
319
322
  id="Participants"
320
323
  defaultMessage="Participants"
321
324
  />
322
325
  </CclButton>
323
- <CclButton url={location.pathname + '/emails'}>
326
+ <CclButton
327
+ url={location.pathname + '/emails'}
328
+ style={{ marginLeft: '10px', marginRight: '10px' }}
329
+ >
324
330
  <FormattedMessage
325
331
  id="Mail archive"
326
332
  defaultMessage="Mail archive"
@@ -0,0 +1,378 @@
1
+ import ReactDOM from 'react-dom';
2
+ import cx from 'classnames';
3
+ import { isEqual } from 'lodash';
4
+ import { Transforms, Editor } from 'slate'; // , Transforms
5
+ import { Slate, Editable, ReactEditor } from 'slate-react';
6
+ import React, { Component } from 'react'; // , useState
7
+ import { v4 as uuid } from 'uuid';
8
+
9
+ import config from '@plone/volto/registry';
10
+
11
+ import { Element, Leaf } from '@plone/volto-slate/editor/render';
12
+
13
+ import withTestingFeatures from '@plone/volto-slate/editor/extensions/withTestingFeatures';
14
+ import {
15
+ makeEditor,
16
+ toggleInlineFormat,
17
+ toggleMark,
18
+ parseDefaultSelection,
19
+ } from '@plone/volto-slate/utils';
20
+ import { InlineToolbar } from '@plone/volto-slate/editor/ui';
21
+ import EditorContext from '@plone/volto-slate/editor/EditorContext';
22
+
23
+ import isHotkey from 'is-hotkey';
24
+
25
+ import '@plone/volto-slate/editor/less/editor.less';
26
+
27
+ import Toolbar from './ui/Toolbar';
28
+
29
+ const handleHotKeys = (editor, event, config) => {
30
+ let wasHotkey = false;
31
+
32
+ for (const hk of Object.entries(config.hotkeys)) {
33
+ const [shortcut, { format, type }] = hk;
34
+ if (isHotkey(shortcut, event)) {
35
+ event.preventDefault();
36
+
37
+ if (type === 'inline') {
38
+ toggleInlineFormat(editor, format);
39
+ } else {
40
+ // type === 'mark'
41
+ toggleMark(editor, format);
42
+ }
43
+
44
+ wasHotkey = true;
45
+ }
46
+ }
47
+
48
+ return wasHotkey;
49
+ };
50
+
51
+ // TODO: implement onFocus
52
+ class SlateEditor extends Component {
53
+ constructor(props) {
54
+ super(props);
55
+
56
+ this.createEditor = this.createEditor.bind(this);
57
+ this.multiDecorator = this.multiDecorator.bind(this);
58
+ this.handleChange = this.handleChange.bind(this);
59
+ this.getSavedSelection = this.getSavedSelection.bind(this);
60
+ this.setSavedSelection = this.setSavedSelection.bind(this);
61
+
62
+ this.savedSelection = null;
63
+
64
+ const uid = uuid(); // used to namespace the editor's plugins
65
+
66
+ this.slateSettings = props.slateSettings || config.settings.slate;
67
+
68
+ this.state = {
69
+ editor: this.createEditor(uid),
70
+ showExpandedToolbar: config.settings.slate.showExpandedToolbar,
71
+ internalValue: this.props.value || this.slateSettings.defaultValue(),
72
+ uid,
73
+ };
74
+
75
+ this.editor = null;
76
+ this.selectionTimeout = null;
77
+ }
78
+
79
+ getSavedSelection() {
80
+ return this.savedSelection;
81
+ }
82
+ setSavedSelection(selection) {
83
+ this.savedSelection = selection;
84
+ }
85
+
86
+ createEditor(uid) {
87
+ // extensions are "editor plugins" or "editor wrappers". It's a similar
88
+ // similar to OOP inheritance, where a callable creates a new copy of the
89
+ // editor, while replacing or adding new capabilities to that editor.
90
+ // Extensions are purely JS, no React components.
91
+ const editor = makeEditor({ extensions: this.props.extensions });
92
+
93
+ // When the editor loses focus it no longer has a valid selections. This
94
+ // makes it impossible to have complex types of interactions (like filling
95
+ // in another text box, operating a select menu, etc). For this reason we
96
+ // save the active selection
97
+
98
+ editor.getSavedSelection = this.getSavedSelection;
99
+ editor.setSavedSelection = this.setSavedSelection;
100
+ editor.uid = uid || this.state.uid;
101
+
102
+ return editor;
103
+ }
104
+
105
+ handleChange(value) {
106
+ ReactDOM.unstable_batchedUpdates(() => {
107
+ this.setState({ internalValue: value });
108
+ if (this.props.onChange && !isEqual(value, this.props.value)) {
109
+ this.props.onChange(value, this.editor);
110
+ }
111
+ });
112
+ }
113
+
114
+ multiDecorator([node, path]) {
115
+ // Decorations (such as higlighting node types, selection, etc).
116
+ const { runtimeDecorators = [] } = this.slateSettings;
117
+ return runtimeDecorators.reduce(
118
+ (acc, deco) => deco(this.state.editor, [node, path], acc),
119
+ [],
120
+ );
121
+ }
122
+
123
+ componentDidMount() {
124
+ // watch the dom change
125
+
126
+ if (this.props.selected) {
127
+ let focused = true;
128
+ try {
129
+ focused = ReactEditor.isFocused(this.state.editor);
130
+ } catch {}
131
+ if (!focused) {
132
+ setTimeout(() => {
133
+ try {
134
+ ReactEditor.focus(this.state.editor);
135
+ } catch {}
136
+ }, 100); // flush
137
+ }
138
+ }
139
+ }
140
+
141
+ componentWillUnmount() {
142
+ this.isUnmounted = true;
143
+ }
144
+
145
+ componentDidUpdate(prevProps) {
146
+ if (!isEqual(prevProps.extensions, this.props.extensions)) {
147
+ this.setState({ editor: this.createEditor() });
148
+ return;
149
+ }
150
+
151
+ if (
152
+ this.props.value &&
153
+ !isEqual(this.props.value, this.state.internalValue)
154
+ ) {
155
+ const { editor } = this.state;
156
+ editor.children = this.props.value;
157
+
158
+ if (this.props.defaultSelection) {
159
+ const selection = parseDefaultSelection(
160
+ editor,
161
+ this.props.defaultSelection,
162
+ );
163
+
164
+ ReactEditor.focus(editor);
165
+ Transforms.select(editor, selection);
166
+ } else {
167
+ Transforms.select(editor, Editor.end(editor, []));
168
+ }
169
+
170
+ this.setState({
171
+ internalValue: this.props.value,
172
+ });
173
+ return;
174
+ }
175
+
176
+ const { editor } = this.state;
177
+
178
+ if (!prevProps.selected && this.props.selected) {
179
+ // if the SlateEditor becomes selected from unselected
180
+ if (window.getSelection().type === 'None') {
181
+ // TODO: why is this condition checked?
182
+ if (this.state.editor.children.length === 0) {
183
+ // eslint-disable-next-line react/no-direct-mutation-state
184
+ this.state.editor.children = [
185
+ { children: [{ text: '' }], type: 'p' },
186
+ ];
187
+ }
188
+ Transforms.select(
189
+ this.state.editor,
190
+ Editor.range(this.state.editor, Editor.start(this.state.editor, [])),
191
+ );
192
+ }
193
+
194
+ ReactEditor.focus(this.state.editor);
195
+ }
196
+
197
+ if (this.props.selected && this.props.onUpdate) {
198
+ this.props.onUpdate(editor);
199
+ }
200
+ }
201
+
202
+ shouldComponentUpdate(nextProps, nextState) {
203
+ const { selected = true, value, readOnly } = nextProps;
204
+ const res =
205
+ selected ||
206
+ this.props.selected !== selected ||
207
+ this.props.readOnly !== readOnly ||
208
+ !isEqual(value, this.props.value);
209
+ return res;
210
+ }
211
+
212
+ render() {
213
+ const {
214
+ selected,
215
+ placeholder,
216
+ onKeyDown,
217
+ testingEditorRef,
218
+ readOnly,
219
+ className,
220
+ renderExtensions = [],
221
+ editableProps = {},
222
+ } = this.props;
223
+ const slateSettings = this.slateSettings;
224
+
225
+ // renderExtensions is needed because the editor is memoized, so if these
226
+ // extensions need an updated state (for example to insert updated
227
+ // blockProps) then we need to always wrap the editor with them
228
+ const editor = renderExtensions.reduce(
229
+ (acc, apply) => apply(acc),
230
+ this.state.editor,
231
+ );
232
+
233
+ // Reset selection if field is reset
234
+ if (
235
+ editor.selection &&
236
+ this.props.value?.length === 1 &&
237
+ this.props.value[0].children.length === 1 &&
238
+ this.props.value[0].children[0].text === ''
239
+ ) {
240
+ Transforms.select(editor, {
241
+ anchor: { path: [0, 0], offset: 0 },
242
+ focus: { path: [0, 0], offset: 0 },
243
+ });
244
+ }
245
+ this.editor = editor;
246
+
247
+ if (testingEditorRef) {
248
+ testingEditorRef.current = editor;
249
+ }
250
+
251
+ // debug-values are `data-` HTML attributes in withTestingFeatures HOC
252
+
253
+ return (
254
+ <div
255
+ {...this.props['debug-values']}
256
+ className={cx('slate-editor', {
257
+ 'show-toolbar': this.state.showExpandedToolbar,
258
+ selected,
259
+ })}
260
+ tabIndex={-1}
261
+ >
262
+ <EditorContext.Provider value={editor}>
263
+ <Slate
264
+ editor={editor}
265
+ value={this.props.value || slateSettings.defaultValue()}
266
+ onChange={this.handleChange}
267
+ >
268
+ {selected ? (
269
+ <>
270
+ <InlineToolbar
271
+ editor={editor}
272
+ className={className}
273
+ slateSettings={this.props.slateSettings}
274
+ />
275
+ {Object.keys(slateSettings.elementToolbarButtons).map(
276
+ (t, i) => {
277
+ return (
278
+ <Toolbar elementType={t} key={i}>
279
+ {slateSettings.elementToolbarButtons[t].map(
280
+ (Btn, b) => {
281
+ return <Btn editor={editor} key={b} />;
282
+ },
283
+ )}
284
+ </Toolbar>
285
+ );
286
+ },
287
+ )}
288
+ </>
289
+ ) : (
290
+ ''
291
+ )}
292
+ <Editable
293
+ tabIndex={this.props.tabIndex || 0}
294
+ readOnly={readOnly}
295
+ placeholder={placeholder}
296
+ renderElement={(props) => <Element {...props} />}
297
+ renderLeaf={(props) => <Leaf {...props} />}
298
+ decorate={this.multiDecorator}
299
+ spellCheck={false}
300
+ scrollSelectionIntoView={
301
+ slateSettings.scrollIntoView ? undefined : () => null
302
+ }
303
+ onBlur={() => {
304
+ this.props.onBlur && this.props.onBlur();
305
+ return null;
306
+ }}
307
+ onClick={this.props.onClick}
308
+ onSelect={(e) => {
309
+ if (!selected && this.props.onFocus) {
310
+ // we can't overwrite the onFocus of Editable, as the onFocus
311
+ // in Slate has too much builtin behaviour that's not
312
+ // accessible otherwise. Instead we try to detect such an
313
+ // event based on observing selected state
314
+ if (!editor.selection) {
315
+ setTimeout(() => {
316
+ this.props.onFocus();
317
+ }, 100); // TODO: why 100 is chosen here?
318
+ }
319
+ }
320
+
321
+ if (this.selectionTimeout) clearTimeout(this.selectionTimeout);
322
+ this.selectionTimeout = setTimeout(() => {
323
+ if (
324
+ editor.selection &&
325
+ !isEqual(editor.selection, this.savedSelection) &&
326
+ !this.isUnmounted
327
+ ) {
328
+ this.setState((state) => ({ update: !this.state.update }));
329
+ this.setSavedSelection(
330
+ JSON.parse(JSON.stringify(editor.selection)),
331
+ );
332
+ }
333
+ }, 200);
334
+ }}
335
+ onKeyDown={(event) => {
336
+ const handled = handleHotKeys(editor, event, slateSettings);
337
+ if (handled) return;
338
+ onKeyDown && onKeyDown({ editor, event });
339
+ }}
340
+ {...editableProps}
341
+ />
342
+ {selected &&
343
+ slateSettings.persistentHelpers.map((Helper, i) => {
344
+ return <Helper key={i} editor={editor} />;
345
+ })}
346
+ {this.props.debug ? (
347
+ <ul>
348
+ <li>{selected ? 'selected' : 'no-selected'}</li>
349
+ <li>
350
+ savedSelection: {JSON.stringify(editor.getSavedSelection())}
351
+ </li>
352
+ <li>live selection: {JSON.stringify(editor.selection)}</li>
353
+ <li>children: {JSON.stringify(editor.children)}</li>
354
+ <li> {selected ? 'selected' : 'notselected'}</li>
355
+ <li>
356
+ {ReactEditor.isFocused(editor) ? 'focused' : 'unfocused'}
357
+ </li>
358
+ </ul>
359
+ ) : (
360
+ ''
361
+ )}
362
+ {this.props.children}
363
+ </Slate>
364
+ </EditorContext.Provider>
365
+ </div>
366
+ );
367
+ }
368
+ }
369
+
370
+ SlateEditor.defaultProps = {
371
+ extensions: [],
372
+ className: '',
373
+ };
374
+
375
+ // May be needed to wrap in React.memo(), it used to be wrapped in connect()
376
+ export default __CLIENT__ && window?.Cypress
377
+ ? withTestingFeatures(SlateEditor)
378
+ : SlateEditor;