@plone/volto 19.0.0-alpha.31 → 19.0.0-alpha.32
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
|
@@ -17,6 +17,12 @@ myst:
|
|
|
17
17
|
|
|
18
18
|
<!-- towncrier release notes start -->
|
|
19
19
|
|
|
20
|
+
## 19.0.0-alpha.32 (2026-04-30)
|
|
21
|
+
|
|
22
|
+
### Breaking
|
|
23
|
+
|
|
24
|
+
- Make form autosave opt-in behind `config.experimental.saveAsDraft.enabled`; enable this flag to preserve the previous save-as-draft behavior. @sneridagh
|
|
25
|
+
|
|
20
26
|
## 19.0.0-alpha.31 (2026-04-28)
|
|
21
27
|
|
|
22
28
|
### Feature
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "19.0.0-alpha.
|
|
12
|
+
"version": "19.0.0-alpha.32",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git@github.com:plone/volto.git"
|
|
@@ -183,9 +183,9 @@
|
|
|
183
183
|
"use-deep-compare-effect": "1.8.1",
|
|
184
184
|
"uuid": "^14.0.0",
|
|
185
185
|
"@plone/components": "4.0.0-alpha.7",
|
|
186
|
-
"@plone/
|
|
186
|
+
"@plone/scripts": "4.0.0-alpha.6",
|
|
187
187
|
"@plone/volto-slate": "19.0.0-alpha.15",
|
|
188
|
-
"@plone/
|
|
188
|
+
"@plone/registry": "3.0.0-alpha.11"
|
|
189
189
|
},
|
|
190
190
|
"devDependencies": {
|
|
191
191
|
"@babel/core": "^7.28.5",
|
|
@@ -292,11 +292,11 @@
|
|
|
292
292
|
"webpack-bundle-analyzer": "4.10.1",
|
|
293
293
|
"webpack-dev-server": "^5.2.3",
|
|
294
294
|
"webpack-node-externals": "3.0.0",
|
|
295
|
-
"@plone/
|
|
295
|
+
"@plone/razzle": "1.0.0-alpha.4",
|
|
296
296
|
"@plone/types": "2.0.0-alpha.17",
|
|
297
|
-
"@plone/
|
|
297
|
+
"@plone/babel-preset-razzle": "^1.0.0-alpha.0",
|
|
298
298
|
"@plone/volto-coresandbox": "1.0.0",
|
|
299
|
-
"@plone/razzle": "1.0.0-alpha.
|
|
299
|
+
"@plone/razzle-dev-utils": "1.0.0-alpha.2"
|
|
300
300
|
},
|
|
301
301
|
"scripts": {
|
|
302
302
|
"analyze": "BUNDLE_ANALYZE=true razzle build",
|
|
@@ -59,6 +59,8 @@ import { compose } from 'redux';
|
|
|
59
59
|
import config from '@plone/volto/registry';
|
|
60
60
|
import SlotRenderer from '@plone/volto/components/theme/SlotRenderer/SlotRenderer';
|
|
61
61
|
|
|
62
|
+
const noop = () => {};
|
|
63
|
+
|
|
62
64
|
/**
|
|
63
65
|
* Form container class.
|
|
64
66
|
* @class Form
|
|
@@ -117,6 +119,9 @@ class Form extends Component {
|
|
|
117
119
|
allowedBlocks: PropTypes.arrayOf(PropTypes.string),
|
|
118
120
|
showRestricted: PropTypes.bool,
|
|
119
121
|
global: PropTypes.bool,
|
|
122
|
+
checkSavedDraft: PropTypes.func,
|
|
123
|
+
onSaveDraft: PropTypes.func,
|
|
124
|
+
onCancelDraft: PropTypes.func,
|
|
120
125
|
};
|
|
121
126
|
|
|
122
127
|
/**
|
|
@@ -153,6 +158,9 @@ class Form extends Component {
|
|
|
153
158
|
requestError: null,
|
|
154
159
|
allowedBlocks: null,
|
|
155
160
|
global: false,
|
|
161
|
+
checkSavedDraft: noop,
|
|
162
|
+
onSaveDraft: noop,
|
|
163
|
+
onCancelDraft: noop,
|
|
156
164
|
};
|
|
157
165
|
|
|
158
166
|
/**
|
|
@@ -4,14 +4,47 @@ import configureStore from 'redux-mock-store';
|
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
6
|
import Form from './Form';
|
|
7
|
+
import config from '@plone/volto/registry';
|
|
7
8
|
|
|
8
9
|
const mockStore = configureStore();
|
|
9
10
|
const errorMessage =
|
|
10
11
|
"[{'message': 'The specified email is not valid.', 'field': 'contact_email', 'error': 'ValidationError'}";
|
|
11
12
|
|
|
12
13
|
vi.mock('@plone/volto/components/manage/Form');
|
|
14
|
+
vi.mock('@plone/volto/helpers/Utils/withSaveAsDraft', async () => {
|
|
15
|
+
const React = await import('react');
|
|
16
|
+
const { default: config } = await import('@plone/volto/registry');
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
default: () => (WrappedComponent) =>
|
|
20
|
+
React.forwardRef((props, ref) =>
|
|
21
|
+
config.experimental?.saveAsDraft?.enabled ? (
|
|
22
|
+
<div data-autosave-wrapper="true">
|
|
23
|
+
<WrappedComponent
|
|
24
|
+
{...props}
|
|
25
|
+
checkSavedDraft={vi.fn()}
|
|
26
|
+
onSaveDraft={vi.fn()}
|
|
27
|
+
onCancelDraft={vi.fn()}
|
|
28
|
+
ref={ref}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
) : (
|
|
32
|
+
<WrappedComponent {...props} ref={ref} />
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
13
37
|
|
|
14
38
|
describe('Form', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
config.experimental = {
|
|
41
|
+
...(config.experimental || {}),
|
|
42
|
+
saveAsDraft: {
|
|
43
|
+
enabled: false,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
15
48
|
it('renders a form component', () => {
|
|
16
49
|
const store = mockStore({
|
|
17
50
|
intl: {
|
|
@@ -49,10 +82,106 @@ describe('Form', () => {
|
|
|
49
82
|
onCancel={() => {}}
|
|
50
83
|
/>
|
|
51
84
|
</MemoryRouter>
|
|
52
|
-
,
|
|
53
85
|
</Provider>,
|
|
54
86
|
);
|
|
55
87
|
const json = component.toJSON();
|
|
56
88
|
expect(json).toMatchSnapshot();
|
|
57
89
|
});
|
|
90
|
+
|
|
91
|
+
it('does not wrap the form with autosave when the experimental flag is disabled', () => {
|
|
92
|
+
const store = mockStore({
|
|
93
|
+
intl: {
|
|
94
|
+
locale: 'en',
|
|
95
|
+
messages: {},
|
|
96
|
+
},
|
|
97
|
+
content: {
|
|
98
|
+
data: {},
|
|
99
|
+
create: {
|
|
100
|
+
loading: false,
|
|
101
|
+
loaded: true,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const component = renderer.create(
|
|
107
|
+
<Provider store={store}>
|
|
108
|
+
<MemoryRouter initialEntries={['/some-route']}>
|
|
109
|
+
<Form
|
|
110
|
+
schema={{
|
|
111
|
+
fieldsets: [
|
|
112
|
+
{
|
|
113
|
+
id: 'default',
|
|
114
|
+
title: 'Default',
|
|
115
|
+
fields: ['title'],
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
properties: {
|
|
119
|
+
title: {},
|
|
120
|
+
},
|
|
121
|
+
required: [],
|
|
122
|
+
}}
|
|
123
|
+
requestError={errorMessage}
|
|
124
|
+
onSubmit={() => {}}
|
|
125
|
+
onCancel={() => {}}
|
|
126
|
+
/>
|
|
127
|
+
</MemoryRouter>
|
|
128
|
+
</Provider>,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
component.root.findAllByProps({ 'data-autosave-wrapper': 'true' }),
|
|
133
|
+
).toHaveLength(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('wraps the form with autosave when the experimental flag is enabled', () => {
|
|
137
|
+
config.experimental = {
|
|
138
|
+
...config.experimental,
|
|
139
|
+
saveAsDraft: {
|
|
140
|
+
enabled: true,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const store = mockStore({
|
|
145
|
+
intl: {
|
|
146
|
+
locale: 'en',
|
|
147
|
+
messages: {},
|
|
148
|
+
},
|
|
149
|
+
content: {
|
|
150
|
+
data: {},
|
|
151
|
+
create: {
|
|
152
|
+
loading: false,
|
|
153
|
+
loaded: true,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const component = renderer.create(
|
|
159
|
+
<Provider store={store}>
|
|
160
|
+
<MemoryRouter initialEntries={['/some-route']}>
|
|
161
|
+
<Form
|
|
162
|
+
schema={{
|
|
163
|
+
fieldsets: [
|
|
164
|
+
{
|
|
165
|
+
id: 'default',
|
|
166
|
+
title: 'Default',
|
|
167
|
+
fields: ['title'],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
properties: {
|
|
171
|
+
title: {},
|
|
172
|
+
},
|
|
173
|
+
required: [],
|
|
174
|
+
}}
|
|
175
|
+
requestError={errorMessage}
|
|
176
|
+
onSubmit={() => {}}
|
|
177
|
+
onCancel={() => {}}
|
|
178
|
+
/>
|
|
179
|
+
</MemoryRouter>
|
|
180
|
+
</Provider>,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(
|
|
184
|
+
component.root.findAllByProps({ 'data-autosave-wrapper': 'true' }),
|
|
185
|
+
).toHaveLength(1);
|
|
186
|
+
});
|
|
58
187
|
});
|
package/src/config/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import checkSVG from '@plone/volto/icons/check.svg';
|
|
|
9
9
|
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
10
10
|
import { useIntl, defineMessages } from 'react-intl';
|
|
11
11
|
import { useLocation } from 'react-router-dom';
|
|
12
|
+
import config from '@plone/volto/registry';
|
|
12
13
|
|
|
13
14
|
const messages = defineMessages({
|
|
14
15
|
autoSaveFound: {
|
|
@@ -227,15 +228,38 @@ export default function withSaveAsDraft(options) {
|
|
|
227
228
|
WrappedComponent,
|
|
228
229
|
)})`;
|
|
229
230
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
231
|
+
const DraftWrappedComponent = forwardRef
|
|
232
|
+
? hoistNonReactStatics(
|
|
233
|
+
React.forwardRef((props, ref) => (
|
|
234
|
+
<WithSaveAsDraft {...props} forwardedRef={ref} />
|
|
235
|
+
)),
|
|
236
|
+
WrappedComponent,
|
|
237
|
+
)
|
|
238
|
+
: hoistNonReactStatics(WithSaveAsDraft, WrappedComponent);
|
|
239
|
+
|
|
240
|
+
const WithExperimentalSaveAsDraft = forwardRef
|
|
241
|
+
? hoistNonReactStatics(
|
|
242
|
+
React.forwardRef((props, ref) => {
|
|
243
|
+
const ComponentToRender = config.experimental?.saveAsDraft?.enabled
|
|
244
|
+
? DraftWrappedComponent
|
|
245
|
+
: WrappedComponent;
|
|
246
|
+
|
|
247
|
+
return <ComponentToRender {...props} ref={ref} />;
|
|
248
|
+
}),
|
|
249
|
+
WrappedComponent,
|
|
250
|
+
)
|
|
251
|
+
: hoistNonReactStatics((props) => {
|
|
252
|
+
const ComponentToRender = config.experimental?.saveAsDraft?.enabled
|
|
253
|
+
? DraftWrappedComponent
|
|
254
|
+
: WrappedComponent;
|
|
255
|
+
|
|
256
|
+
return <ComponentToRender {...props} />;
|
|
257
|
+
}, WrappedComponent);
|
|
258
|
+
|
|
259
|
+
WithExperimentalSaveAsDraft.displayName = `WithExperimentalSaveAsDraft(${getDisplayName(
|
|
260
|
+
WrappedComponent,
|
|
261
|
+
)})`;
|
|
238
262
|
|
|
239
|
-
return
|
|
263
|
+
return WithExperimentalSaveAsDraft;
|
|
240
264
|
};
|
|
241
265
|
}
|