@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 +16 -0
- package/package.json +5 -3
- package/src/blocks/Text/DefaultTextBlockEditor.jsx +2 -2
- package/src/blocks/Text/extensions/breakList.js +5 -4
- package/src/blocks/Text/extensions/breakListInWidget.js +67 -0
- package/src/blocks/Text/extensions/index.js +1 -0
- package/src/blocks/Text/extensions/insertBreak.js +3 -2
- package/src/blocks/Text/index.jsx +2 -0
- package/src/blocks/Text/keyboard/joinBlocks.js +5 -5
- package/src/utils/volto-blocks.js +10 -4
- package/src/widgets/HtmlSlateWidget.jsx +8 -0
- package/src/widgets/RichTextWidget.jsx +6 -0
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.
|
|
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
|
+
};
|
|
@@ -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(
|
|
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>
|