@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plone/volto-slate",
3
- "version": "18.7.1",
3
+ "version": "18.8.1",
4
4
  "description": "Slate.js integration with Volto",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -19,7 +19,7 @@ import {
19
19
  export function syncCreateTableBlock(rows) {
20
20
  const id = uuid();
21
21
  const block = {
22
- '@type': 'table',
22
+ '@type': 'slateTable',
23
23
  table: {
24
24
  rows,
25
25
  },
@@ -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(/^\/([a-z]*)$/);
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.indexOf(slashCommand[1]) !== -1
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 || otherBlock['@type'] !== 'slate')
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
- mergeSlateWithBlockForward(editor, otherBlock);
136
+ const blocksFieldname = getBlocksFieldname(properties);
137
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
134
138
 
135
- // const cursor = JSON.parse(JSON.stringify(editor.selection));
136
- const combined = JSON.parse(JSON.stringify(editor.children));
139
+ // If next block is not a slate text block, do nothing
140
+ if (otherBlock['@type'] !== 'slate') {
141
+ return;
142
+ }
137
143
 
138
- // TODO: don't remove undo history, etc Should probably save both undo
139
- // histories, so that the blocks are split, the undos can be restored??
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 blocksFieldname = getBlocksFieldname(properties);
142
- const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
167
+ const combined = JSON.parse(JSON.stringify(editor.children));
143
168
 
144
- const formData = changeBlock(properties, otherBlockId, {
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
- const newFormData = deleteBlock(formData, block, intl);
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(otherBlockId);
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.value;
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' });