@eeacms/volto-eea-website-theme 4.3.2 → 4.3.4
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 +8 -0
- package/package.json +1 -1
- package/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx +236 -0
- package/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx.diff +33 -0
- package/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx.md +92 -0
- package/src/customizations/volto/components/manage/Form/blocksClipboardUtils.js +68 -0
- package/src/customizations/volto/server.jsx +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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
|
+
### [4.3.4](https://github.com/eea/volto-eea-website-theme/compare/4.3.3...4.3.4) - 28 May 2026
|
|
8
|
+
|
|
9
|
+
#### :bug: Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix: 410 Gone pages should not be reported as errors in Sentry - refs #304013 [Alin Voinea - [`9b081df`](https://github.com/eea/volto-eea-website-theme/commit/9b081df15376384ed79b8af2e9e21eba9b348c2c)]
|
|
12
|
+
|
|
13
|
+
### [4.3.3](https://github.com/eea/volto-eea-website-theme/compare/4.3.2...4.3.3) - 27 May 2026
|
|
14
|
+
|
|
7
15
|
### [4.3.2](https://github.com/eea/volto-eea-website-theme/compare/4.3.1...4.3.2) - 15 May 2026
|
|
8
16
|
|
|
9
17
|
#### :bug: Bug Fixes
|
package/package.json
CHANGED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { compose } from 'redux';
|
|
3
|
+
import { connect } from 'react-redux';
|
|
4
|
+
import { injectIntl } from 'react-intl';
|
|
5
|
+
import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
|
|
6
|
+
import {
|
|
7
|
+
getBlocksFieldname,
|
|
8
|
+
getBlocksLayoutFieldname,
|
|
9
|
+
} from '@plone/volto/helpers/Blocks/Blocks';
|
|
10
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
11
|
+
import { Plug } from '@plone/volto/components/manage/Pluggable';
|
|
12
|
+
import { v4 as uuid } from 'uuid';
|
|
13
|
+
import isEqual from 'lodash/isEqual';
|
|
14
|
+
import omit from 'lodash/omit';
|
|
15
|
+
import without from 'lodash/without';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
setBlocksClipboard,
|
|
19
|
+
resetBlocksClipboard,
|
|
20
|
+
} from '@plone/volto/actions/blocksClipboard/blocksClipboard';
|
|
21
|
+
import config from '@plone/volto/registry';
|
|
22
|
+
|
|
23
|
+
import copySVG from '@plone/volto/icons/copy.svg';
|
|
24
|
+
import cutSVG from '@plone/volto/icons/cut.svg';
|
|
25
|
+
import pasteSVG from '@plone/volto/icons/paste.svg';
|
|
26
|
+
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
27
|
+
import {
|
|
28
|
+
cloneBlocks,
|
|
29
|
+
loadBlocksClipboardFromStorage,
|
|
30
|
+
} from './blocksClipboardUtils';
|
|
31
|
+
|
|
32
|
+
export class BlocksToolbarComponent extends React.Component {
|
|
33
|
+
constructor(props) {
|
|
34
|
+
super(props);
|
|
35
|
+
|
|
36
|
+
this.copyBlocksToClipboard = this.copyBlocksToClipboard.bind(this);
|
|
37
|
+
this.cutBlocksToClipboard = this.cutBlocksToClipboard.bind(this);
|
|
38
|
+
this.deleteBlocks = this.deleteBlocks.bind(this);
|
|
39
|
+
this.loadFromStorage = this.loadFromStorage.bind(this);
|
|
40
|
+
this.pasteBlocks = this.pasteBlocks.bind(this);
|
|
41
|
+
this.setBlocksClipboard = this.setBlocksClipboard.bind(this);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
loadFromStorage(event) {
|
|
45
|
+
if (event?.key && !event.key.includes('blocksClipboard')) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const clipboard = loadBlocksClipboardFromStorage();
|
|
50
|
+
const currentClipboard = this.props.blocksClipboard || {};
|
|
51
|
+
const currentClipboardHasBlocks =
|
|
52
|
+
currentClipboard?.cut || currentClipboard?.copy;
|
|
53
|
+
|
|
54
|
+
if (!event && !clipboard && currentClipboardHasBlocks) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!isEqual(clipboard || {}, currentClipboard)) {
|
|
59
|
+
this.props.setBlocksClipboard(clipboard || {});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
componentDidMount() {
|
|
64
|
+
this.loadFromStorage();
|
|
65
|
+
window.addEventListener('storage', this.loadFromStorage, true);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
componentWillUnmount() {
|
|
69
|
+
window.removeEventListener('storage', this.loadFromStorage);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
deleteBlocks() {
|
|
73
|
+
const blockIds = this.props.selectedBlocks;
|
|
74
|
+
|
|
75
|
+
const { formData } = this.props;
|
|
76
|
+
const blocksFieldname = getBlocksFieldname(formData);
|
|
77
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
|
|
78
|
+
|
|
79
|
+
// Might need ReactDOM.unstable_batchedUpdates()
|
|
80
|
+
this.props.onSelectBlock(null);
|
|
81
|
+
const newBlockData = {
|
|
82
|
+
[blocksFieldname]: omit(formData[blocksFieldname], blockIds),
|
|
83
|
+
[blocksLayoutFieldname]: {
|
|
84
|
+
...formData[blocksLayoutFieldname],
|
|
85
|
+
items: without(formData[blocksLayoutFieldname].items, ...blockIds),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
this.props.onChangeBlocks(newBlockData);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
copyBlocksToClipboard() {
|
|
92
|
+
this.setBlocksClipboard('copy');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
cutBlocksToClipboard() {
|
|
96
|
+
this.setBlocksClipboard('cut');
|
|
97
|
+
this.deleteBlocks();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setBlocksClipboard(actionType) {
|
|
101
|
+
const { formData } = this.props;
|
|
102
|
+
const blocksFieldname = getBlocksFieldname(formData);
|
|
103
|
+
const blocks = formData[blocksFieldname];
|
|
104
|
+
const blocksData = this.props.selectedBlocks
|
|
105
|
+
.map((blockId) => [blockId, blocks[blockId]])
|
|
106
|
+
.filter(([blockId]) => !!blockId); // Removes null blocks
|
|
107
|
+
this.props.setBlocksClipboard({ [actionType]: blocksData });
|
|
108
|
+
this.props.onSetSelectedBlocks([]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pasteBlocks(e) {
|
|
112
|
+
const { formData, blocksClipboard = {}, selectedBlock } = this.props;
|
|
113
|
+
const mode = Object.keys(blocksClipboard).includes('cut') ? 'cut' : 'copy';
|
|
114
|
+
const blocksData = blocksClipboard[mode] || [];
|
|
115
|
+
const cloneWithIds = blocksData
|
|
116
|
+
.filter(([blockId, blockData]) => blockId && !!blockData['@type']) // Removes null blocks
|
|
117
|
+
.map(([blockId, blockData]) => {
|
|
118
|
+
const blockConfig = config.blocks.blocksConfig[blockData['@type']];
|
|
119
|
+
return mode === 'copy'
|
|
120
|
+
? blockConfig.cloneData
|
|
121
|
+
? blockConfig.cloneData(blockData)
|
|
122
|
+
: [uuid(), cloneBlocks(blockData)]
|
|
123
|
+
: [blockId, blockData]; // if cut/pasting blocks, we don't clone
|
|
124
|
+
})
|
|
125
|
+
.filter((info) => !!info); // some blocks may refuse to be copied
|
|
126
|
+
const blocksFieldname = getBlocksFieldname(formData);
|
|
127
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
|
|
128
|
+
const selectedIndex =
|
|
129
|
+
formData[blocksLayoutFieldname].items.indexOf(selectedBlock) + 1;
|
|
130
|
+
|
|
131
|
+
const newBlockData = {
|
|
132
|
+
[blocksFieldname]: {
|
|
133
|
+
...formData[blocksFieldname],
|
|
134
|
+
...Object.assign(
|
|
135
|
+
{},
|
|
136
|
+
...cloneWithIds.map(([id, data]) => ({ [id]: data })),
|
|
137
|
+
),
|
|
138
|
+
},
|
|
139
|
+
[blocksLayoutFieldname]: {
|
|
140
|
+
...formData[blocksLayoutFieldname],
|
|
141
|
+
items: [
|
|
142
|
+
...formData[blocksLayoutFieldname].items.slice(0, selectedIndex),
|
|
143
|
+
...cloneWithIds.map(([id]) => id),
|
|
144
|
+
...formData[blocksLayoutFieldname].items.slice(selectedIndex),
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (!(e.ctrlKey || e.metaKey)) this.props.resetBlocksClipboard();
|
|
150
|
+
this.props.onChangeBlocks(newBlockData);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
render() {
|
|
154
|
+
const {
|
|
155
|
+
blocksClipboard = {},
|
|
156
|
+
selectedBlock,
|
|
157
|
+
selectedBlocks,
|
|
158
|
+
intl,
|
|
159
|
+
} = this.props;
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
{selectedBlocks.length > 0 ? (
|
|
163
|
+
<>
|
|
164
|
+
<Plug pluggable="main.toolbar.bottom" id="blocks-delete-btn">
|
|
165
|
+
<button
|
|
166
|
+
aria-label={intl.formatMessage(messages.deleteBlocks)}
|
|
167
|
+
onClick={this.deleteBlocks}
|
|
168
|
+
tabIndex={0}
|
|
169
|
+
className="deleteBlocks"
|
|
170
|
+
id="toolbar-delete-blocks"
|
|
171
|
+
>
|
|
172
|
+
<Icon name={trashSVG} size="30px" />
|
|
173
|
+
</button>
|
|
174
|
+
</Plug>
|
|
175
|
+
<Plug pluggable="main.toolbar.bottom" id="blocks-cut-btn">
|
|
176
|
+
<button
|
|
177
|
+
aria-label={intl.formatMessage(messages.cutBlocks)}
|
|
178
|
+
onClick={this.cutBlocksToClipboard}
|
|
179
|
+
tabIndex={0}
|
|
180
|
+
className="cutBlocks"
|
|
181
|
+
id="toolbar-cut-blocks"
|
|
182
|
+
>
|
|
183
|
+
<Icon name={cutSVG} size="30px" />
|
|
184
|
+
</button>
|
|
185
|
+
</Plug>
|
|
186
|
+
<Plug pluggable="main.toolbar.bottom" id="blocks-copy-btn">
|
|
187
|
+
<button
|
|
188
|
+
aria-label={intl.formatMessage(messages.copyBlocks)}
|
|
189
|
+
onClick={this.copyBlocksToClipboard}
|
|
190
|
+
tabIndex={0}
|
|
191
|
+
className="copyBlocks"
|
|
192
|
+
id="toolbar-copy-blocks"
|
|
193
|
+
>
|
|
194
|
+
<Icon name={copySVG} size="30px" />
|
|
195
|
+
</button>
|
|
196
|
+
</Plug>
|
|
197
|
+
</>
|
|
198
|
+
) : (
|
|
199
|
+
''
|
|
200
|
+
)}
|
|
201
|
+
{selectedBlock && (blocksClipboard?.cut || blocksClipboard?.copy) && (
|
|
202
|
+
<Plug
|
|
203
|
+
pluggable="main.toolbar.bottom"
|
|
204
|
+
id="block-paste-btn"
|
|
205
|
+
dependencies={[selectedBlock]}
|
|
206
|
+
>
|
|
207
|
+
<button
|
|
208
|
+
aria-label={intl.formatMessage(messages.pasteBlocks)}
|
|
209
|
+
onClick={this.pasteBlocks}
|
|
210
|
+
tabIndex={0}
|
|
211
|
+
className="pasteBlocks"
|
|
212
|
+
id="toolbar-paste-blocks"
|
|
213
|
+
>
|
|
214
|
+
<span className="blockCount">
|
|
215
|
+
{(blocksClipboard.cut || blocksClipboard.copy).length}
|
|
216
|
+
</span>
|
|
217
|
+
<Icon name={pasteSVG} size="30px" />
|
|
218
|
+
</button>
|
|
219
|
+
</Plug>
|
|
220
|
+
)}
|
|
221
|
+
</>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export default compose(
|
|
227
|
+
injectIntl,
|
|
228
|
+
connect(
|
|
229
|
+
(state) => {
|
|
230
|
+
return {
|
|
231
|
+
blocksClipboard: state?.blocksClipboard || {},
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
{ setBlocksClipboard, resetBlocksClipboard },
|
|
235
|
+
),
|
|
236
|
+
)(BlocksToolbarComponent);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
13d12
|
|
2
|
+
< import { load } from 'redux-localstorage-simple';
|
|
3
|
+
28c27,30
|
|
4
|
+
< import { cloneBlocks } from '@plone/volto/helpers/Blocks/cloneBlocks';
|
|
5
|
+
---
|
|
6
|
+
> import {
|
|
7
|
+
> cloneBlocks,
|
|
8
|
+
> loadBlocksClipboardFromStorage,
|
|
9
|
+
> } from './blocksClipboardUtils';
|
|
10
|
+
42,44c44,58
|
|
11
|
+
< loadFromStorage() {
|
|
12
|
+
< const clipboard = load({ states: ['blocksClipboard'] })?.blocksClipboard;
|
|
13
|
+
< if (!isEqual(clipboard, this.props.blocksClipboard))
|
|
14
|
+
---
|
|
15
|
+
> loadFromStorage(event) {
|
|
16
|
+
> if (event?.key && !event.key.includes('blocksClipboard')) {
|
|
17
|
+
> return;
|
|
18
|
+
> }
|
|
19
|
+
>
|
|
20
|
+
> const clipboard = loadBlocksClipboardFromStorage();
|
|
21
|
+
> const currentClipboard = this.props.blocksClipboard || {};
|
|
22
|
+
> const currentClipboardHasBlocks =
|
|
23
|
+
> currentClipboard?.cut || currentClipboard?.copy;
|
|
24
|
+
>
|
|
25
|
+
> if (!event && !clipboard && currentClipboardHasBlocks) {
|
|
26
|
+
> return;
|
|
27
|
+
> }
|
|
28
|
+
>
|
|
29
|
+
> if (!isEqual(clipboard || {}, currentClipboard)) {
|
|
30
|
+
45a60
|
|
31
|
+
> }
|
|
32
|
+
48a64
|
|
33
|
+
> this.loadFromStorage();
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# BlocksToolbar.jsx — Customization Explanation
|
|
2
|
+
|
|
3
|
+
**Upstream:** [`@plone/volto` 18.34.0 — `BlocksToolbar.jsx`](https://github.com/plone/volto/blob/18.x.x/packages/volto/src/components/manage/Form/BlocksToolbar.jsx)
|
|
4
|
+
**Local override:** `src/customizations/volto/components/manage/Form/BlocksToolbar.jsx`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Why this override exists
|
|
9
|
+
|
|
10
|
+
Volto 18 changed how `redux-localstorage-simple` persists the `blocksClipboard` Redux slice. Instead of storing it as a single monolithic `blocksClipboard` key in `localStorage`, Volto 18's `persistentReducers` config now stores it as separate keys: `blocksClipboard.cut` and `blocksClipboard.copy`.
|
|
11
|
+
|
|
12
|
+
The upstream `loadFromStorage()` only queries for `states: ['blocksClipboard']` (the monolithic key), so after the Volto 18 migration, clipboard data stored under the new split keys is never found. On component remount or navigation, the clipboard gets wiped to `{}`, causing the paste button to disappear even though the data is still in localStorage.
|
|
13
|
+
|
|
14
|
+
## What changed
|
|
15
|
+
|
|
16
|
+
### 1. Import changes
|
|
17
|
+
|
|
18
|
+
| Upstream | Override |
|
|
19
|
+
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
20
|
+
| `import { load } from 'redux-localstorage-simple'` | _Removed_ |
|
|
21
|
+
| `import { cloneBlocks } from '@plone/volto/helpers/Blocks/cloneBlocks'` | _Removed_ |
|
|
22
|
+
| — | `import { cloneBlocks, loadBlocksClipboardFromStorage } from './blocksClipboardUtils'` |
|
|
23
|
+
|
|
24
|
+
Both `load` and `cloneBlocks` are now provided by the local `blocksClipboardUtils.js` module instead of being imported directly from Volto.
|
|
25
|
+
|
|
26
|
+
### 2. `loadFromStorage(event)` — the core bug fix
|
|
27
|
+
|
|
28
|
+
**Upstream:**
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
loadFromStorage() {
|
|
32
|
+
const clipboard = load({ states: ['blocksClipboard'] })?.blocksClipboard;
|
|
33
|
+
if (!isEqual(clipboard, this.props.blocksClipboard))
|
|
34
|
+
this.props.setBlocksClipboard(clipboard || {});
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Override:**
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
loadFromStorage(event) {
|
|
42
|
+
if (event?.key && !event.key.includes('blocksClipboard')) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const clipboard = loadBlocksClipboardFromStorage();
|
|
47
|
+
const currentClipboard = this.props.blocksClipboard || {};
|
|
48
|
+
const currentClipboardHasBlocks =
|
|
49
|
+
currentClipboard?.cut || currentClipboard?.copy;
|
|
50
|
+
|
|
51
|
+
if (!event && !clipboard && currentClipboardHasBlocks) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!isEqual(clipboard || {}, currentClipboard)) {
|
|
56
|
+
this.props.setBlocksClipboard(clipboard || {});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Three improvements:
|
|
62
|
+
|
|
63
|
+
1. **Event guard** — If called from a `storage` event, returns early when the changed key is unrelated to `blocksClipboard`. Avoids unnecessary work on unrelated localStorage mutations.
|
|
64
|
+
|
|
65
|
+
2. **Volto 18 key resolution** — Delegates to `loadBlocksClipboardFromStorage()`, which tries the split-key format (`blocksClipboard.cut`/`blocksClipboard.copy`) first, then falls back to the monolithic key.
|
|
66
|
+
|
|
67
|
+
3. **Mount guard** — When called on mount (`!event`), if localStorage is empty but Redux still has clipboard data in memory, the old code would wipe Redux to `{}`. The new guard prevents losing an in-memory clipboard on remount.
|
|
68
|
+
|
|
69
|
+
### 3. `componentDidMount()` — eager rehydration
|
|
70
|
+
|
|
71
|
+
**Upstream:**
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
componentDidMount() {
|
|
75
|
+
window.addEventListener('storage', this.loadFromStorage, true);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Override:**
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
componentDidMount() {
|
|
83
|
+
this.loadFromStorage();
|
|
84
|
+
window.addEventListener('storage', this.loadFromStorage, true);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The upstream code only loads clipboard data when a `storage` event fires (typically from another browser tab). It never loads on initial mount, so the paste button is invisible until some other tab triggers a storage event. The override calls `loadFromStorage()` immediately on mount.
|
|
89
|
+
|
|
90
|
+
## What did NOT change
|
|
91
|
+
|
|
92
|
+
All other methods — `deleteBlocks`, `copyBlocksToClipboard`, `cutBlocksToClipboard`, `setBlocksClipboard`, `pasteBlocks`, `render()`, and the `connect`/`compose` wrapper — are **identical** to the Volto 18 upstream.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid';
|
|
2
|
+
import { load } from 'redux-localstorage-simple';
|
|
3
|
+
import {
|
|
4
|
+
getBlocksFieldname,
|
|
5
|
+
getBlocksLayoutFieldname,
|
|
6
|
+
hasBlocksData,
|
|
7
|
+
} from '@plone/volto/helpers/Blocks/Blocks';
|
|
8
|
+
import config from '@plone/volto/registry';
|
|
9
|
+
|
|
10
|
+
const fallbackBlocksClipboardStates = [
|
|
11
|
+
'blocksClipboard.cut',
|
|
12
|
+
'blocksClipboard.copy',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export function cloneBlocks(blocksData) {
|
|
16
|
+
if (hasBlocksData(blocksData)) {
|
|
17
|
+
const blocksFieldname = getBlocksFieldname(blocksData);
|
|
18
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(blocksData);
|
|
19
|
+
|
|
20
|
+
const cloneWithIds = Object.keys(blocksData.blocks)
|
|
21
|
+
.map((key) => {
|
|
22
|
+
const block = blocksData.blocks[key];
|
|
23
|
+
const blockConfig = config.blocks.blocksConfig[blocksData['@type']];
|
|
24
|
+
return blockConfig?.cloneData
|
|
25
|
+
? blockConfig.cloneData(block)
|
|
26
|
+
: [uuid(), cloneBlocks(block)];
|
|
27
|
+
})
|
|
28
|
+
.filter((info) => !!info); // some blocks may refuse to be copied
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...blocksData,
|
|
32
|
+
[blocksFieldname]: {
|
|
33
|
+
...Object.assign(
|
|
34
|
+
{},
|
|
35
|
+
...cloneWithIds.map(([id, data]) => ({ [id]: data })),
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
[blocksLayoutFieldname]: {
|
|
39
|
+
...blocksData[blocksLayoutFieldname],
|
|
40
|
+
items: [...cloneWithIds.map(([id]) => id)],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return blocksData;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const getBlocksClipboardStates = () => {
|
|
49
|
+
const persistentReducers = config.settings?.persistentReducers || [];
|
|
50
|
+
const blocksClipboardStates = persistentReducers.filter(
|
|
51
|
+
(state) =>
|
|
52
|
+
state === 'blocksClipboard' || state.startsWith('blocksClipboard.'),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return blocksClipboardStates.length
|
|
56
|
+
? blocksClipboardStates
|
|
57
|
+
: fallbackBlocksClipboardStates;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const loadBlocksClipboardFromStorage = () =>
|
|
61
|
+
load({
|
|
62
|
+
states: getBlocksClipboardStates(),
|
|
63
|
+
disableWarnings: true,
|
|
64
|
+
})?.blocksClipboard ||
|
|
65
|
+
load({
|
|
66
|
+
states: ['blocksClipboard'],
|
|
67
|
+
disableWarnings: true,
|
|
68
|
+
})?.blocksClipboard;
|
|
@@ -101,7 +101,7 @@ server.use(function (err, req, res, next) {
|
|
|
101
101
|
* TODO:
|
|
102
102
|
* - get ignored codes from Plone error_log
|
|
103
103
|
*/
|
|
104
|
-
const ignoredErrors = [301, 302, 401, 404];
|
|
104
|
+
const ignoredErrors = [301, 302, 401, 404, 410];
|
|
105
105
|
if (!ignoredErrors.includes(err.status)) console.error(err);
|
|
106
106
|
|
|
107
107
|
res
|
|
@@ -184,7 +184,7 @@ function setupServer(req, res, next) {
|
|
|
184
184
|
* TODO:
|
|
185
185
|
* - get ignored codes from Plone error_log
|
|
186
186
|
*/
|
|
187
|
-
const ignoredErrors = [301, 302, 401, 404];
|
|
187
|
+
const ignoredErrors = [301, 302, 401, 404, 410];
|
|
188
188
|
if (!ignoredErrors.includes(error.status)) console.error(error);
|
|
189
189
|
|
|
190
190
|
res
|