@plone/volto-slate 18.7.1 → 18.8.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/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
<!-- towncrier release notes start -->
|
|
10
10
|
|
|
11
|
+
## 18.8.1 (2026-03-02)
|
|
12
|
+
|
|
13
|
+
### Bugfix
|
|
14
|
+
|
|
15
|
+
- Use Slate Table block when pasting tables snippets (instead of deprecated DraftJS) @cekk [#7865](https://github.com/plone/volto/issues/7865)
|
|
16
|
+
|
|
17
|
+
## 18.8.0 (2025-12-02)
|
|
18
|
+
|
|
19
|
+
### Feature
|
|
20
|
+
|
|
21
|
+
- cross language support and umlaut fix for slash menu @Tishasoumya-02 [#7657](https://github.com/plone/volto/issues/7657)
|
|
22
|
+
|
|
23
|
+
### Bugfix
|
|
24
|
+
|
|
25
|
+
- Ensure Delete at end of a text block merges the next text block and removes it; if the next block is non-text (e.g., Description), do nothing. @aryan7081 [#7263](https://github.com/plone/volto/issues/7263)
|
|
26
|
+
|
|
11
27
|
## 18.7.1 (2025-10-10)
|
|
12
28
|
|
|
13
29
|
### Bugfix
|
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { syncCreateTableBlock } from './deconstruct';
|
|
2
|
+
|
|
3
|
+
describe('syncCreateTableBlock', () => {
|
|
4
|
+
it('creates a slateTable block with the given rows', () => {
|
|
5
|
+
const rows = [
|
|
6
|
+
{
|
|
7
|
+
key: 'row1',
|
|
8
|
+
cells: [
|
|
9
|
+
{
|
|
10
|
+
key: 'cell1',
|
|
11
|
+
type: 'data',
|
|
12
|
+
value: [{ children: [{ text: '1' }] }],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const [id, block] = syncCreateTableBlock(rows);
|
|
19
|
+
|
|
20
|
+
expect(id).toBeDefined();
|
|
21
|
+
expect(block['@type']).toBe('slateTableaa');
|
|
22
|
+
expect(block.table.rows).toEqual(rows);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -120,7 +120,7 @@ const PersistentSlashMenu = ({ editor }) => {
|
|
|
120
120
|
const slashCommand = data.plaintext
|
|
121
121
|
?.toLowerCase()
|
|
122
122
|
.trim()
|
|
123
|
-
.match(/^\/([
|
|
123
|
+
.match(/^\/([\p{L}]*)$/u);
|
|
124
124
|
|
|
125
125
|
const availableBlocks = React.useMemo(
|
|
126
126
|
() =>
|
|
@@ -141,10 +141,12 @@ const PersistentSlashMenu = ({ editor }) => {
|
|
|
141
141
|
.filter((block) => {
|
|
142
142
|
// typed text is a substring of the title or id
|
|
143
143
|
const title = translateBlockTitle(block, intl).toLowerCase();
|
|
144
|
+
const originalTitle = block.title.toLowerCase();
|
|
144
145
|
return (
|
|
145
146
|
block.id !== 'slate' &&
|
|
146
147
|
slashCommand &&
|
|
147
|
-
title.
|
|
148
|
+
(title.includes(slashCommand[1]) ||
|
|
149
|
+
originalTitle.includes(slashCommand[1]))
|
|
148
150
|
);
|
|
149
151
|
})
|
|
150
152
|
.sort((a, b) => {
|
|
@@ -48,8 +48,7 @@ export function joinWithPreviousBlock({ editor, event }, intl) {
|
|
|
48
48
|
const [otherBlock = {}, otherBlockId] = prev;
|
|
49
49
|
|
|
50
50
|
// Don't join with required blocks
|
|
51
|
-
if (data?.required || otherBlock?.required
|
|
52
|
-
return;
|
|
51
|
+
if (data?.required || otherBlock?.required) return;
|
|
53
52
|
|
|
54
53
|
event.stopPropagation();
|
|
55
54
|
event.preventDefault();
|
|
@@ -123,6 +122,10 @@ export function joinWithNextBlock({ editor, event }, intl) {
|
|
|
123
122
|
const { properties, onChangeField } = editor.getBlockProps();
|
|
124
123
|
const [otherBlock = {}, otherBlockId] = getNextVoltoBlock(index, properties);
|
|
125
124
|
|
|
125
|
+
if (!otherBlockId) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
126
129
|
// Don't join with required blocks
|
|
127
130
|
if (data?.required || otherBlock?.required || otherBlock['@type'] !== 'slate')
|
|
128
131
|
return;
|
|
@@ -130,30 +133,51 @@ export function joinWithNextBlock({ editor, event }, intl) {
|
|
|
130
133
|
event.stopPropagation();
|
|
131
134
|
event.preventDefault();
|
|
132
135
|
|
|
133
|
-
|
|
136
|
+
const blocksFieldname = getBlocksFieldname(properties);
|
|
137
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
|
|
134
138
|
|
|
135
|
-
//
|
|
136
|
-
|
|
139
|
+
// If next block is not a slate text block, do nothing
|
|
140
|
+
if (otherBlock['@type'] !== 'slate') {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
137
143
|
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
const nextValue = otherBlock?.value;
|
|
145
|
+
const nextPlaintext =
|
|
146
|
+
otherBlock?.plaintext ?? serializeNodesToText(nextValue || []);
|
|
147
|
+
// Treat the next block as empty if both its structured value and plaintext representation
|
|
148
|
+
// indicate no content. In that case we can delete it instead of attempting a merge.
|
|
149
|
+
const isEmptySlateBlock =
|
|
150
|
+
!Array.isArray(nextValue) ||
|
|
151
|
+
nextValue.length === 0 ||
|
|
152
|
+
!nextPlaintext ||
|
|
153
|
+
nextPlaintext.trim().length === 0;
|
|
154
|
+
|
|
155
|
+
if (isEmptySlateBlock) {
|
|
156
|
+
const newFormData = deleteBlock(properties, otherBlockId, intl);
|
|
157
|
+
ReactDOM.unstable_batchedUpdates(() => {
|
|
158
|
+
onChangeField(blocksFieldname, newFormData[blocksFieldname]);
|
|
159
|
+
onChangeField(blocksLayoutFieldname, newFormData[blocksLayoutFieldname]);
|
|
160
|
+
onSelectBlock(block);
|
|
161
|
+
});
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
// Merge next text block into current one and delete the next block
|
|
165
|
+
mergeSlateWithBlockForward(editor, otherBlock);
|
|
140
166
|
|
|
141
|
-
const
|
|
142
|
-
const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
|
|
167
|
+
const combined = JSON.parse(JSON.stringify(editor.children));
|
|
143
168
|
|
|
144
|
-
const formData = changeBlock(properties,
|
|
145
|
-
// TODO: use a constant specified in src/constants.js instead of 'slate'
|
|
169
|
+
const formData = changeBlock(properties, block, {
|
|
146
170
|
'@type': 'slate',
|
|
147
171
|
value: combined,
|
|
148
172
|
plaintext: serializeNodesToText(combined || []),
|
|
149
173
|
});
|
|
150
|
-
|
|
174
|
+
|
|
175
|
+
const newFormData = deleteBlock(formData, otherBlockId, intl);
|
|
151
176
|
|
|
152
177
|
ReactDOM.unstable_batchedUpdates(() => {
|
|
153
|
-
// saveSlateBlockSelection(otherBlockId, cursor);
|
|
154
178
|
onChangeField(blocksFieldname, newFormData[blocksFieldname]);
|
|
155
179
|
onChangeField(blocksLayoutFieldname, newFormData[blocksLayoutFieldname]);
|
|
156
|
-
onSelectBlock(
|
|
180
|
+
onSelectBlock(block);
|
|
157
181
|
});
|
|
158
182
|
return true;
|
|
159
183
|
}
|
|
@@ -87,7 +87,11 @@ export function mergeSlateWithBlockForward(editor, nextBlock, event) {
|
|
|
87
87
|
// with current block value, then use this result for next block, delete
|
|
88
88
|
// current block
|
|
89
89
|
|
|
90
|
-
const next = nextBlock
|
|
90
|
+
const next = nextBlock?.value;
|
|
91
|
+
// Safeguard: if next block has no value or an empty value, there is nothing to merge
|
|
92
|
+
if (!Array.isArray(next) || next.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
91
95
|
|
|
92
96
|
// collapse the selection to its start point
|
|
93
97
|
Transforms.collapse(editor, { edge: 'end' });
|