@plone/volto-slate 18.2.0 → 18.2.2

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
@@ -8,6 +8,22 @@
8
8
 
9
9
  <!-- towncrier release notes start -->
10
10
 
11
+ ## 18.2.2 (2025-02-08)
12
+
13
+ ### Bugfix
14
+
15
+ - In `RichTextWidget` and `HtmlSlateWidget`, fix breaking a list by typing Enter. @nileshgulia1 [#6570](https://github.com/plone/volto/issues/6570)
16
+
17
+ ### Internal
18
+
19
+ - Remove hard dependencies on `react`, move to `peerDependencies` @sneridagh [#6728](https://github.com/plone/volto/issues/6728)
20
+
21
+ ## 18.2.1 (2025-01-31)
22
+
23
+ ### Bugfix
24
+
25
+ - Pass `intl` object to `initialValue` function. @wesleybl [#6529](https://github.com/plone/volto/issues/6529)
26
+
11
27
  ## 18.2.0 (2025-01-24)
12
28
 
13
29
  ### Feature
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plone/volto-slate",
3
- "version": "18.2.0",
3
+ "version": "18.2.2",
4
4
  "description": "Slate.js integration with Volto",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -25,8 +25,6 @@
25
25
  "is-url": "1.2.4",
26
26
  "jsdom": "^16.6.0",
27
27
  "lodash": "4.17.21",
28
- "react": "18.2.0",
29
- "react-dom": "18.2.0",
30
28
  "react-intersection-observer": "9.1.0",
31
29
  "react-intl": "3.12.1",
32
30
  "react-redux": "8.1.2",
@@ -46,6 +44,10 @@
46
44
  "babel-plugin-transform-class-properties": "^6.24.1",
47
45
  "release-it": "^17.0.0"
48
46
  },
47
+ "peerDependencies": {
48
+ "react": "^18.2.0",
49
+ "react-dom": "^18.2.0"
50
+ },
49
51
  "scripts": {
50
52
  "dry-release": "release-it --dry-run",
51
53
  "release": "release-it",
@@ -142,10 +142,10 @@ export const DefaultTextBlockEditor = (props) => {
142
142
  const url = flattenToAppURL(imageId);
143
143
  setNewImageId(imageId);
144
144
 
145
- createImageBlock(url, index, props);
145
+ createImageBlock(url, index, props, intl);
146
146
  }
147
147
  prevReq.current = loaded;
148
- }, [props, loaded, loading, prevLoaded, imageId, newImageId, index]);
148
+ }, [props, loaded, loading, prevLoaded, imageId, newImageId, index, intl]);
149
149
 
150
150
  const handleUpdate = React.useCallback(
151
151
  (editor) => {
@@ -11,6 +11,7 @@ import { createEmptyParagraph } from '@plone/volto-slate/utils/blocks';
11
11
  * Handles `Enter` key on empty and non-empty list items.
12
12
  *
13
13
  * @param {Editor} editor The editor which should be modified by this extension
14
+ * @param {Object} intl intl object.
14
15
  * with a new version of the `insertBreak` method of the Slate editor.
15
16
  *
16
17
  * @description If the selection does not exist or is expanded, handle with the
@@ -20,7 +21,7 @@ import { createEmptyParagraph } from '@plone/volto-slate/utils/blocks';
20
21
  * text cursor and then split the editor in two fragments, and convert them to
21
22
  * separate Slate Text blocks, based on the selection.
22
23
  */
23
- export const breakList = (editor) => {
24
+ export const breakList = (editor, intl) => {
24
25
  const { insertBreak } = editor;
25
26
 
26
27
  editor.insertBreak = () => {
@@ -84,7 +85,7 @@ export const breakList = (editor) => {
84
85
  });
85
86
  Transforms.select(editor, Editor.end(editor, []));
86
87
  } else {
87
- createAndSelectNewBlockAfter(editor, [createEmptyParagraph()]);
88
+ createAndSelectNewBlockAfter(editor, [createEmptyParagraph()], intl);
88
89
  Transforms.removeNodes(editor, { at: ref.current });
89
90
  }
90
91
  return true;
@@ -96,7 +97,7 @@ export const breakList = (editor) => {
96
97
  if (detached) {
97
98
  Editor.insertNode(editor, createEmptyParagraph());
98
99
  } else {
99
- createAndSelectNewBlockAfter(editor, [createEmptyParagraph()]);
100
+ createAndSelectNewBlockAfter(editor, [createEmptyParagraph()], intl);
100
101
  }
101
102
  return true;
102
103
  }
@@ -104,7 +105,7 @@ export const breakList = (editor) => {
104
105
  if (!detached) {
105
106
  const [top, bottom] = splitEditorInTwoFragments(editor, ref.current);
106
107
  setEditorContent(editor, top);
107
- createAndSelectNewBlockAfter(editor, bottom);
108
+ createAndSelectNewBlockAfter(editor, bottom, intl);
108
109
  }
109
110
  return true;
110
111
  };
@@ -0,0 +1,67 @@
1
+ import { Editor, Range, Transforms } from 'slate';
2
+
3
+ import config from '@plone/volto/registry';
4
+ import { isCursorAtBlockEnd } from '@plone/volto-slate/utils/selection';
5
+ import { getCurrentListItem } from '@plone/volto-slate/utils/lists';
6
+ import { createEmptyParagraph } from '@plone/volto-slate/utils/blocks';
7
+
8
+ export const breakListInWidget = (editor) => {
9
+ const { insertBreak } = editor;
10
+
11
+ editor.insertBreak = () => {
12
+ if (!(editor.selection && Range.isCollapsed(editor.selection))) {
13
+ insertBreak();
14
+ return false;
15
+ }
16
+
17
+ const { slate } = config.settings;
18
+ const { anchor } = editor.selection;
19
+
20
+ const ref = Editor.rangeRef(editor, editor.selection, {
21
+ affinity: 'inward',
22
+ });
23
+
24
+ const [listItem, listItemPath] = getCurrentListItem(editor);
25
+ if (listItem) {
26
+ if (Editor.string(editor, listItemPath)) {
27
+ Transforms.splitNodes(editor, {
28
+ at: editor.selection,
29
+ match: (node) => node.type === slate.listItemType,
30
+ always: true,
31
+ });
32
+
33
+ return true;
34
+ }
35
+ }
36
+
37
+ const [parent] = Editor.parent(editor, anchor.path);
38
+
39
+ if (parent.type !== slate.listItemType || anchor.offset > 0) {
40
+ insertBreak();
41
+ return;
42
+ }
43
+
44
+ Editor.deleteBackward(editor, { unit: 'line' });
45
+ // also account for empty nodes [{text: ''}]
46
+ if (Editor.isEmpty(editor, parent)) {
47
+ Transforms.removeNodes(editor, { at: ref.current });
48
+
49
+ Transforms.insertNodes(editor, createEmptyParagraph(), {
50
+ at: [editor.children.length],
51
+ });
52
+ Transforms.select(editor, Editor.end(editor, []));
53
+
54
+ return true;
55
+ }
56
+
57
+ Transforms.removeNodes(editor, { at: ref.current });
58
+
59
+ if (isCursorAtBlockEnd(editor)) {
60
+ Editor.insertNode(editor, createEmptyParagraph());
61
+ return true;
62
+ }
63
+ return true;
64
+ };
65
+
66
+ return editor;
67
+ };
@@ -4,3 +4,4 @@ export * from './breakList';
4
4
  export * from './withLists';
5
5
  export * from './isSelected';
6
6
  export * from './normalizeExternalData';
7
+ export * from './breakListInWidget';
@@ -8,6 +8,7 @@ import { rangeIsInSplittableNode } from '@plone/volto-slate/utils/internals';
8
8
 
9
9
  /**
10
10
  * @param {Editor} editor The Slate editor object to extend.
11
+ * @param {Object} intl intl object.
11
12
  * @description If the selection exists and touches with one of its edges a
12
13
  * closest-to-root `Text` node (`Path` with length `2`)
13
14
  *
@@ -18,7 +19,7 @@ import { rangeIsInSplittableNode } from '@plone/volto-slate/utils/internals';
18
19
  * and if the selection does not exist or does not touch with one of its edges a
19
20
  * closest-to-root `Text` node, call the default behavior.
20
21
  */
21
- export const withSplitBlocksOnBreak = (editor) => {
22
+ export const withSplitBlocksOnBreak = (editor, intl) => {
22
23
  const { insertBreak } = editor;
23
24
 
24
25
  editor.insertBreak = () => {
@@ -40,7 +41,7 @@ export const withSplitBlocksOnBreak = (editor) => {
40
41
  ReactDOM.unstable_batchedUpdates(() => {
41
42
  const [top, bottom] = splitEditorInTwoFragments(editor);
42
43
  // ReactEditor.blur(editor);
43
- createAndSelectNewBlockAfter(editor, bottom);
44
+ createAndSelectNewBlockAfter(editor, bottom, intl);
44
45
  setEditorContent(editor, top);
45
46
  });
46
47
  }
@@ -22,6 +22,7 @@ import {
22
22
  import { withDeleteSelectionOnEnter } from '@plone/volto-slate/editor/extensions';
23
23
  import {
24
24
  breakList,
25
+ breakListInWidget,
25
26
  withDeserializers,
26
27
  withLists,
27
28
  withSplitBlocksOnBreak,
@@ -47,6 +48,7 @@ export default function applyConfig(config) {
47
48
  breakList,
48
49
  normalizeExternalData,
49
50
  ],
51
+ slateWidgetExtensions: [breakListInWidget],
50
52
 
51
53
  // Pluggable handlers for the onKeyDown event of <Editable />
52
54
  // Order matters here. A handler can return `true` to stop executing any
@@ -26,7 +26,7 @@ import {
26
26
  * @param {Editor} editor
27
27
  * @param {KeyboardEvent} event
28
28
  */
29
- export function joinWithPreviousBlock({ editor, event }) {
29
+ export function joinWithPreviousBlock({ editor, event }, intl) {
30
30
  if (!isCursorAtBlockStart(editor)) return;
31
31
 
32
32
  const blockProps = editor.getBlockProps();
@@ -60,7 +60,7 @@ export function joinWithPreviousBlock({ editor, event }) {
60
60
  const text = Editor.string(editor, []);
61
61
  if (!text) {
62
62
  const cursor = getBlockEndAsRange(otherBlock);
63
- const newFormData = deleteBlock(properties, block);
63
+ const newFormData = deleteBlock(properties, block, intl);
64
64
 
65
65
  ReactDOM.unstable_batchedUpdates(() => {
66
66
  saveSlateBlockSelection(otherBlockId, cursor);
@@ -89,7 +89,7 @@ export function joinWithPreviousBlock({ editor, event }) {
89
89
  value: combined,
90
90
  plaintext: serializeNodesToText(combined || []),
91
91
  });
92
- const newFormData = deleteBlock(formData, block);
92
+ const newFormData = deleteBlock(formData, block, intl);
93
93
 
94
94
  ReactDOM.unstable_batchedUpdates(() => {
95
95
  saveSlateBlockSelection(otherBlockId, cursor);
@@ -107,7 +107,7 @@ export function joinWithPreviousBlock({ editor, event }) {
107
107
  * @param {Editor} editor
108
108
  * @param {KeyboardEvent} event
109
109
  */
110
- export function joinWithNextBlock({ editor, event }) {
110
+ export function joinWithNextBlock({ editor, event }, intl) {
111
111
  if (!isCursorAtBlockEnd(editor)) return;
112
112
 
113
113
  const blockProps = editor.getBlockProps();
@@ -146,7 +146,7 @@ export function joinWithNextBlock({ editor, event }) {
146
146
  value: combined,
147
147
  plaintext: serializeNodesToText(combined || []),
148
148
  });
149
- const newFormData = deleteBlock(formData, block);
149
+ const newFormData = deleteBlock(formData, block, intl);
150
150
 
151
151
  ReactDOM.unstable_batchedUpdates(() => {
152
152
  // saveSlateBlockSelection(otherBlockId, cursor);
@@ -118,7 +118,7 @@ export function syncCreateSlateBlock(value) {
118
118
  return [id, block];
119
119
  }
120
120
 
121
- export function createImageBlock(url, index, props) {
121
+ export function createImageBlock(url, index, props, intl) {
122
122
  const { properties, onChangeField, onSelectBlock } = props;
123
123
  const blocksFieldname = getBlocksFieldname(properties);
124
124
  const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
@@ -128,7 +128,7 @@ export function createImageBlock(url, index, props) {
128
128
  let id, newFormData;
129
129
 
130
130
  if (currBlockHasValue) {
131
- [id, newFormData] = addBlock(properties, 'image', index + 1);
131
+ [id, newFormData] = addBlock(properties, 'image', index + 1, {}, intl);
132
132
  newFormData = changeBlock(newFormData, id, { '@type': 'image', url });
133
133
  } else {
134
134
  [id, newFormData] = insertBlock(properties, currBlockId, {
@@ -144,12 +144,18 @@ export function createImageBlock(url, index, props) {
144
144
  });
145
145
  }
146
146
 
147
- export const createAndSelectNewBlockAfter = (editor, blockValue) => {
147
+ export const createAndSelectNewBlockAfter = (editor, blockValue, intl) => {
148
148
  const blockProps = editor.getBlockProps();
149
149
 
150
150
  const { onSelectBlock, properties, index, onChangeField } = blockProps;
151
151
 
152
- const [blockId, formData] = addBlock(properties, 'slate', index + 1);
152
+ const [blockId, formData] = addBlock(
153
+ properties,
154
+ 'slate',
155
+ index + 1,
156
+ {},
157
+ intl,
158
+ );
153
159
 
154
160
  const options = {
155
161
  '@type': 'slate',
@@ -12,6 +12,7 @@ import { defineMessages, injectIntl } from 'react-intl';
12
12
  import { FormFieldWrapper } from '@plone/volto/components/manage/Widgets';
13
13
  import SlateEditor from '@plone/volto-slate/editor/SlateEditor';
14
14
  import { serializeNodes } from '@plone/volto-slate/editor/render';
15
+ import { handleKeyDetached } from '@plone/volto-slate/blocks/Text/keyboard';
15
16
  import { makeEditor } from '@plone/volto-slate/utils/editor';
16
17
  import deserialize from '@plone/volto-slate/editor/deserialize';
17
18
 
@@ -19,6 +20,8 @@ import {
19
20
  createEmptyParagraph,
20
21
  normalizeExternalData,
21
22
  } from '@plone/volto-slate/utils';
23
+ import config from '@plone/volto/registry';
24
+
22
25
  import { ErrorBoundary } from './ErrorBoundary';
23
26
 
24
27
  import './style.css';
@@ -44,6 +47,8 @@ const HtmlSlateWidget = (props) => {
44
47
  intl,
45
48
  } = props;
46
49
 
50
+ const { slateWidgetExtensions } = config.settings.slate;
51
+
47
52
  const [selected, setSelected] = React.useState(focus);
48
53
 
49
54
  const editor = React.useMemo(() => makeEditor(), []);
@@ -127,7 +132,10 @@ const HtmlSlateWidget = (props) => {
127
132
  block={block}
128
133
  selected={selected}
129
134
  properties={properties}
135
+ extensions={slateWidgetExtensions}
136
+ onKeyDown={handleKeyDetached}
130
137
  placeholder={placeholder}
138
+ editableProps={{ 'aria-multiline': 'true' }}
131
139
  />
132
140
  </ErrorBoundary>
133
141
  </div>
@@ -7,7 +7,9 @@ import React from 'react';
7
7
  import isUndefined from 'lodash/isUndefined';
8
8
  import isString from 'lodash/isString';
9
9
  import { FormFieldWrapper } from '@plone/volto/components/manage/Widgets';
10
+ import { handleKeyDetached } from '@plone/volto-slate/blocks/Text/keyboard';
10
11
  import SlateEditor from '@plone/volto-slate/editor/SlateEditor';
12
+ import config from '@plone/volto/registry';
11
13
 
12
14
  import { createEmptyParagraph, createParagraph } from '../utils/blocks';
13
15
 
@@ -37,6 +39,7 @@ const SlateRichTextWidget = (props) => {
37
39
  readOnly = false,
38
40
  } = props;
39
41
  const [selected, setSelected] = React.useState(focus);
42
+ const { slateWidgetExtensions } = config.settings.slate;
40
43
 
41
44
  return (
42
45
  <FormFieldWrapper {...props} draggable={false} className="slate_wysiwyg">
@@ -62,7 +65,10 @@ const SlateRichTextWidget = (props) => {
62
65
  block={block}
63
66
  selected={selected}
64
67
  properties={properties}
68
+ extensions={slateWidgetExtensions}
69
+ onKeyDown={handleKeyDetached}
65
70
  placeholder={placeholder}
71
+ editableProps={{ 'aria-multiline': 'true' }}
66
72
  />
67
73
  </div>
68
74
  </FormFieldWrapper>