@plone/volto 18.32.1 → 18.32.3
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/.eslintignore +1 -0
- package/CHANGELOG.md +40 -0
- package/README.md +0 -4
- package/locales/af/LC_MESSAGES/volto.po +20 -0
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +20 -0
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +20 -0
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +20 -0
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +20 -0
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +20 -0
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +20 -0
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +20 -0
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +20 -0
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +20 -0
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +20 -0
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +20 -0
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +20 -0
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +20 -0
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +20 -0
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +20 -0
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +20 -0
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +20 -0
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +20 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +20 -0
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +20 -0
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +20 -0
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +20 -0
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +20 -0
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +20 -0
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +20 -0
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +20 -0
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +20 -0
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +31 -11
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +20 -0
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +20 -0
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +20 -0
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +20 -0
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +20 -0
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +20 -0
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +20 -0
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +20 -0
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +20 -0
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +20 -0
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +20 -0
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +20 -0
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +20 -0
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +20 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +20 -0
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +20 -0
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +20 -0
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +20 -0
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +20 -0
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +20 -0
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +20 -0
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +20 -0
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +20 -0
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +20 -0
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +20 -0
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +20 -0
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +20 -0
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +20 -0
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +20 -0
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +20 -0
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +20 -0
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +20 -0
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +20 -0
- package/locales/vi.json +1 -1
- package/locales/volto.pot +21 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +20 -0
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +20 -0
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +20 -0
- package/locales/zh_Hant_HK.json +1 -1
- package/package.json +6 -5
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +10 -7
- package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +2 -9
- package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +10 -1
- package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -3
- package/src/components/manage/Blocks/Block/Order/Order.jsx +116 -67
- package/src/components/manage/Blocks/Block/Order/utilities.js +28 -11
- package/src/components/manage/Blocks/Listing/Edit.jsx +1 -0
- package/src/components/manage/Controlpanels/ContentTypeSchema.jsx +1 -1
- package/src/components/manage/Sharing/Sharing.jsx +10 -12
- package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +5 -1
- package/src/components/manage/UniversalLink/UniversalLink.test.jsx +16 -0
- package/src/components/manage/UniversalLink/UniversalLink.tsx +1 -0
- package/src/components/theme/App/App.jsx +3 -1
- package/src/components/theme/ConnectionRefused/ConnectionRefused.jsx +3 -2
- package/src/components/theme/PasswordReset/PasswordReset.jsx +108 -191
- package/src/components/theme/View/RenderBlocks.jsx +8 -10
- package/src/components/theme/View/RenderBlocks.test.jsx +14 -4
- package/src/config/index.js +1 -1
- package/src/config/validation.ts +8 -0
- package/src/helpers/Blocks/Blocks.js +109 -24
- package/src/helpers/Blocks/Blocks.test.js +100 -0
- package/src/helpers/FormValidation/FormValidation.test.js +47 -0
- package/src/helpers/FormValidation/validators.ts +37 -4
- package/src/helpers/MessageLabels/MessageLabels.js +5 -0
- package/types/components/manage/Blocks/Block/Order/utilities.d.ts +2 -1
- package/types/components/theme/ConnectionRefused/ConnectionRefused.d.ts +2 -2
- package/types/components/theme/PasswordReset/PasswordReset.d.ts +6 -2
- package/types/config/Views.d.ts +1 -1
- package/types/helpers/Blocks/Blocks.d.ts +4 -0
- package/types/helpers/FormValidation/validators.d.ts +18 -1
- package/types/helpers/MessageLabels/MessageLabels.d.ts +100 -94
- package/types/routes.d.ts +7 -5
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
* @module components/theme/PasswordReset/PasswordReset
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import { compose } from 'redux';
|
|
10
|
-
import { Link, withRouter } from 'react-router-dom';
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
import { useSelector, useDispatch } from 'react-redux';
|
|
8
|
+
import { Link, useHistory, useParams } from 'react-router-dom';
|
|
11
9
|
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
12
10
|
import { Container } from 'semantic-ui-react';
|
|
13
|
-
import { FormattedMessage, defineMessages,
|
|
11
|
+
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|
14
12
|
|
|
15
13
|
import { Form } from '@plone/volto/components/manage/Form';
|
|
16
14
|
import { setInitialPassword } from '@plone/volto/actions/users/users';
|
|
@@ -90,220 +88,139 @@ const messages = defineMessages({
|
|
|
90
88
|
});
|
|
91
89
|
|
|
92
90
|
/**
|
|
93
|
-
* PasswordReset
|
|
94
|
-
* @
|
|
95
|
-
* @extends Component
|
|
91
|
+
* @function PasswordReset
|
|
92
|
+
* @returns {JSX.Element}
|
|
96
93
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* @static
|
|
102
|
-
*/
|
|
103
|
-
static propTypes = {
|
|
104
|
-
loading: PropTypes.bool.isRequired,
|
|
105
|
-
loaded: PropTypes.bool.isRequired,
|
|
106
|
-
error: PropTypes.string,
|
|
107
|
-
token: PropTypes.string.isRequired,
|
|
108
|
-
setInitialPassword: PropTypes.func.isRequired,
|
|
109
|
-
};
|
|
94
|
+
function PasswordReset() {
|
|
95
|
+
const dispatch = useDispatch();
|
|
96
|
+
const history = useHistory();
|
|
97
|
+
const { token } = useParams();
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* @static
|
|
115
|
-
*/
|
|
116
|
-
static defaultProps = {
|
|
117
|
-
error: null,
|
|
118
|
-
};
|
|
99
|
+
const loading = useSelector((state) => state.users.initial.loading);
|
|
100
|
+
const loaded = useSelector((state) => state.users.initial.loaded);
|
|
101
|
+
const error = useSelector((state) => state.users.initial.error);
|
|
119
102
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
* @param {Object} props Component properties
|
|
124
|
-
* @constructs Controlpanel
|
|
125
|
-
*/
|
|
126
|
-
constructor(props) {
|
|
127
|
-
super(props);
|
|
128
|
-
this.onCancel = this.onCancel.bind(this);
|
|
129
|
-
this.onSubmit = this.onSubmit.bind(this);
|
|
130
|
-
this.state = {
|
|
131
|
-
error: null,
|
|
132
|
-
isSuccessful: false,
|
|
133
|
-
};
|
|
103
|
+
const [localError, setLocalError] = useState(null);
|
|
104
|
+
const [isSuccessful, setIsSuccessful] = useState(false);
|
|
105
|
+
const intl = useIntl();
|
|
134
106
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
107
|
+
const identifierField = config.settings.useEmailAsLogin
|
|
108
|
+
? 'email'
|
|
109
|
+
: 'username';
|
|
138
110
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
111
|
+
const identifierTitle =
|
|
112
|
+
identifierField === 'email'
|
|
113
|
+
? intl.formatMessage(messages.emailTitle)
|
|
114
|
+
: intl.formatMessage(messages.usernameTitle);
|
|
143
115
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
116
|
+
const identifierDescription =
|
|
117
|
+
identifierField === 'email'
|
|
118
|
+
? intl.formatMessage(messages.emailDescription)
|
|
119
|
+
: intl.formatMessage(messages.usernameDescription);
|
|
149
120
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* @param {Object} nextProps Next properties
|
|
154
|
-
* @returns {undefined}
|
|
155
|
-
*/
|
|
156
|
-
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
157
|
-
if (this.props.loading && nextProps.loaded) {
|
|
158
|
-
this.setState({ isSuccessful: true });
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!loading && loaded) {
|
|
123
|
+
setIsSuccessful(true);
|
|
159
124
|
}
|
|
160
|
-
}
|
|
125
|
+
}, [loading, loaded]);
|
|
161
126
|
|
|
162
127
|
/**
|
|
163
128
|
* Submit handler
|
|
164
129
|
* @method onSubmit
|
|
165
130
|
* @param {object} data Form data.
|
|
166
|
-
* @param {object} event Form data.
|
|
167
131
|
* @returns {undefined}
|
|
168
132
|
*/
|
|
169
|
-
onSubmit(data) {
|
|
133
|
+
const onSubmit = (data) => {
|
|
170
134
|
if (data.password === data.passwordRepeat) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.props.token,
|
|
174
|
-
data.password,
|
|
175
|
-
);
|
|
176
|
-
this.setState({
|
|
177
|
-
error: null,
|
|
178
|
-
});
|
|
135
|
+
dispatch(setInitialPassword(data[identifierField], token, data.password));
|
|
136
|
+
setLocalError(null);
|
|
179
137
|
} else {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
message: this.props.intl.formatMessage(messages.passwordsDoNotMatch),
|
|
183
|
-
},
|
|
138
|
+
setLocalError({
|
|
139
|
+
message: intl.formatMessage(messages.passwordsDoNotMatch),
|
|
184
140
|
});
|
|
185
141
|
}
|
|
186
|
-
}
|
|
142
|
+
};
|
|
187
143
|
|
|
188
144
|
/**
|
|
189
145
|
* Cancel handler
|
|
190
146
|
* @method onCancel
|
|
191
147
|
* @returns {undefined}
|
|
192
148
|
*/
|
|
193
|
-
onCancel() {
|
|
194
|
-
|
|
149
|
+
const onCancel = () => {
|
|
150
|
+
history.goBack();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (isSuccessful) {
|
|
154
|
+
return (
|
|
155
|
+
<Container>
|
|
156
|
+
<h1 className="documentFirstHeading">
|
|
157
|
+
<FormattedMessage {...messages.successRedirectToLoginTitle} />
|
|
158
|
+
</h1>
|
|
159
|
+
<p className="description">
|
|
160
|
+
<FormattedMessage
|
|
161
|
+
{...messages.successRedirectToLoginBody}
|
|
162
|
+
values={{
|
|
163
|
+
link: (
|
|
164
|
+
<Link to="/login">{intl.formatMessage({ id: 'Log In' })}</Link>
|
|
165
|
+
),
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
</p>
|
|
169
|
+
</Container>
|
|
170
|
+
);
|
|
195
171
|
}
|
|
196
172
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
render() {
|
|
203
|
-
if (this.state.isSuccessful) {
|
|
204
|
-
return (
|
|
173
|
+
if (token) {
|
|
174
|
+
const errmsg = error ? error.response?.body?.error || error : null;
|
|
175
|
+
return (
|
|
176
|
+
<div id="page-password-reset">
|
|
177
|
+
<Helmet title={intl.formatMessage(messages.passwordReset)} />
|
|
205
178
|
<Container>
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
<Link to="/login">
|
|
219
|
-
{this.props.intl.formatMessage({ id: 'Log In' })}
|
|
220
|
-
</Link>
|
|
221
|
-
),
|
|
222
|
-
}}
|
|
223
|
-
/>
|
|
224
|
-
</p>
|
|
225
|
-
</Container>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
if (this.props.token) {
|
|
229
|
-
const errmsg = this.props.error
|
|
230
|
-
? this.props.error.response.body.error
|
|
231
|
-
: null;
|
|
232
|
-
return (
|
|
233
|
-
<div id="page-password-reset">
|
|
234
|
-
<Helmet
|
|
235
|
-
title={this.props.intl.formatMessage(messages.passwordReset)}
|
|
236
|
-
/>
|
|
237
|
-
<Container>
|
|
238
|
-
<Form
|
|
239
|
-
title={this.props.intl.formatMessage(messages.title)}
|
|
240
|
-
description={this.props.intl.formatMessage(messages.description)}
|
|
241
|
-
onSubmit={this.onSubmit}
|
|
242
|
-
onCancel={this.onCancel}
|
|
243
|
-
error={this.state.error || errmsg}
|
|
244
|
-
schema={{
|
|
245
|
-
fieldsets: [
|
|
246
|
-
{
|
|
247
|
-
id: 'default',
|
|
248
|
-
title: this.props.intl.formatMessage(messages.default),
|
|
249
|
-
fields: [
|
|
250
|
-
this.identifierField,
|
|
251
|
-
'password',
|
|
252
|
-
'passwordRepeat',
|
|
253
|
-
],
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
properties: {
|
|
257
|
-
[this.identifierField]: {
|
|
258
|
-
type: 'string',
|
|
259
|
-
title: this.identifierTitle,
|
|
260
|
-
description: this.identifierDescription,
|
|
261
|
-
},
|
|
262
|
-
password: {
|
|
263
|
-
description: this.props.intl.formatMessage(
|
|
264
|
-
messages.passwordDescription,
|
|
265
|
-
),
|
|
266
|
-
title: this.props.intl.formatMessage(
|
|
267
|
-
messages.passwordTitle,
|
|
268
|
-
),
|
|
269
|
-
type: 'string',
|
|
270
|
-
widget: 'password',
|
|
271
|
-
},
|
|
272
|
-
passwordRepeat: {
|
|
273
|
-
description: this.props.intl.formatMessage(
|
|
274
|
-
messages.passwordRepeatDescription,
|
|
275
|
-
),
|
|
276
|
-
title: this.props.intl.formatMessage(
|
|
277
|
-
messages.passwordRepeatTitle,
|
|
278
|
-
),
|
|
279
|
-
type: 'string',
|
|
280
|
-
widget: 'password',
|
|
281
|
-
},
|
|
179
|
+
<Form
|
|
180
|
+
title={intl.formatMessage(messages.title)}
|
|
181
|
+
description={intl.formatMessage(messages.description)}
|
|
182
|
+
onSubmit={onSubmit}
|
|
183
|
+
onCancel={onCancel}
|
|
184
|
+
error={localError || errmsg}
|
|
185
|
+
schema={{
|
|
186
|
+
fieldsets: [
|
|
187
|
+
{
|
|
188
|
+
id: 'default',
|
|
189
|
+
title: intl.formatMessage(messages.default),
|
|
190
|
+
fields: [identifierField, 'password', 'passwordRepeat'],
|
|
282
191
|
},
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
192
|
+
],
|
|
193
|
+
properties: {
|
|
194
|
+
[identifierField]: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
title: identifierTitle,
|
|
197
|
+
description: identifierDescription,
|
|
198
|
+
},
|
|
199
|
+
password: {
|
|
200
|
+
description: intl.formatMessage(messages.passwordDescription),
|
|
201
|
+
title: intl.formatMessage(messages.passwordTitle),
|
|
202
|
+
type: 'string',
|
|
203
|
+
widget: 'password',
|
|
204
|
+
},
|
|
205
|
+
passwordRepeat: {
|
|
206
|
+
description: intl.formatMessage(
|
|
207
|
+
messages.passwordRepeatDescription,
|
|
208
|
+
),
|
|
209
|
+
title: intl.formatMessage(messages.passwordRepeatTitle),
|
|
210
|
+
type: 'string',
|
|
211
|
+
widget: 'password',
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
submitLabel: intl.formatMessage(messages.setMyPassword),
|
|
215
|
+
required: [identifierField, 'password', 'passwordRepeat'],
|
|
216
|
+
}}
|
|
217
|
+
/>
|
|
218
|
+
</Container>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
294
221
|
}
|
|
222
|
+
|
|
223
|
+
return <div />;
|
|
295
224
|
}
|
|
296
225
|
|
|
297
|
-
export default
|
|
298
|
-
withRouter,
|
|
299
|
-
injectIntl,
|
|
300
|
-
connect(
|
|
301
|
-
(state, props) => ({
|
|
302
|
-
loading: state.users.initial.loading,
|
|
303
|
-
loaded: state.users.initial.loaded,
|
|
304
|
-
error: state.users.initial.error,
|
|
305
|
-
token: props.match.params.token,
|
|
306
|
-
}),
|
|
307
|
-
{ setInitialPassword },
|
|
308
|
-
),
|
|
309
|
-
)(PasswordReset);
|
|
226
|
+
export default PasswordReset;
|
|
@@ -5,8 +5,7 @@ import map from 'lodash/map';
|
|
|
5
5
|
import MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap';
|
|
6
6
|
import {
|
|
7
7
|
applyBlockDefaults,
|
|
8
|
-
|
|
9
|
-
getBlocksLayoutFieldname,
|
|
8
|
+
getBlocks,
|
|
10
9
|
hasBlocksData,
|
|
11
10
|
} from '@plone/volto/helpers/Blocks/Blocks';
|
|
12
11
|
import StyleWrapper from '@plone/volto/components/manage/Blocks/Block/StyleWrapper';
|
|
@@ -28,26 +27,25 @@ const messages = defineMessages({
|
|
|
28
27
|
const RenderBlocks = (props) => {
|
|
29
28
|
const { blockWrapperTag, content, location, isContainer, metadata } = props;
|
|
30
29
|
const intl = useIntl();
|
|
31
|
-
const blocksFieldname = getBlocksFieldname(content);
|
|
32
|
-
const blocksLayoutFieldname = getBlocksLayoutFieldname(content);
|
|
33
30
|
const blocksConfig = props.blocksConfig || config.blocks.blocksConfig;
|
|
34
31
|
const CustomTag = props.as || React.Fragment;
|
|
35
32
|
|
|
33
|
+
const blockList = getBlocks(content);
|
|
34
|
+
|
|
36
35
|
return hasBlocksData(content) ? (
|
|
37
36
|
<CustomTag>
|
|
38
|
-
{map(
|
|
37
|
+
{map(blockList, ([block, rawBlockData]) => {
|
|
39
38
|
const Block =
|
|
40
|
-
blocksConfig[
|
|
41
|
-
ViewDefaultBlock;
|
|
39
|
+
blocksConfig[rawBlockData?.['@type']]?.view || ViewDefaultBlock;
|
|
42
40
|
|
|
43
41
|
const blockData = applyBlockDefaults({
|
|
44
|
-
data:
|
|
42
|
+
data: rawBlockData,
|
|
45
43
|
intl,
|
|
46
44
|
metadata,
|
|
47
45
|
properties: content,
|
|
48
46
|
});
|
|
49
47
|
|
|
50
|
-
if (
|
|
48
|
+
if (rawBlockData?.['@type'] === 'empty') {
|
|
51
49
|
return (
|
|
52
50
|
<MaybeWrap
|
|
53
51
|
key={block}
|
|
@@ -91,7 +89,7 @@ const RenderBlocks = (props) => {
|
|
|
91
89
|
return (
|
|
92
90
|
<div key={block}>
|
|
93
91
|
{intl.formatMessage(messages.unknownBlock, {
|
|
94
|
-
block:
|
|
92
|
+
block: rawBlockData?.['@type'],
|
|
95
93
|
})}
|
|
96
94
|
</div>
|
|
97
95
|
);
|
|
@@ -90,14 +90,14 @@ test('Provides path to blocks', () => {
|
|
|
90
90
|
expect(container).toMatchSnapshot();
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
test('
|
|
93
|
+
test('Filters out invalid blocks', () => {
|
|
94
94
|
const store = mockStore({
|
|
95
95
|
intl: {
|
|
96
96
|
locale: 'en',
|
|
97
97
|
messages: {},
|
|
98
98
|
},
|
|
99
99
|
});
|
|
100
|
-
const { queryAllByText } = render(
|
|
100
|
+
const { queryAllByText, queryByText } = render(
|
|
101
101
|
<Provider store={store}>
|
|
102
102
|
<RenderBlocks
|
|
103
103
|
blocksConfig={{
|
|
@@ -113,7 +113,14 @@ test('Renders invalid blocks', () => {
|
|
|
113
113
|
}}
|
|
114
114
|
content={{
|
|
115
115
|
blocks_layout: {
|
|
116
|
-
items: [
|
|
116
|
+
items: [
|
|
117
|
+
'MISSING-YOU-1',
|
|
118
|
+
'a',
|
|
119
|
+
'MISSING-YOU-2',
|
|
120
|
+
null,
|
|
121
|
+
undefined,
|
|
122
|
+
'undefined',
|
|
123
|
+
],
|
|
117
124
|
},
|
|
118
125
|
blocks: {
|
|
119
126
|
a: {
|
|
@@ -126,7 +133,10 @@ test('Renders invalid blocks', () => {
|
|
|
126
133
|
/>
|
|
127
134
|
</Provider>,
|
|
128
135
|
);
|
|
136
|
+
// Invalid blocks (missing from blocks object or invalid IDs) are filtered out and not rendered
|
|
129
137
|
expect(
|
|
130
138
|
queryAllByText('Invalid block - Will be removed on saving'),
|
|
131
|
-
).toHaveLength(
|
|
139
|
+
).toHaveLength(0);
|
|
140
|
+
// Only valid blocks are rendered
|
|
141
|
+
expect(queryByText('id: a - text: bar - path: /foo')).not.toBeNull();
|
|
132
142
|
});
|
package/src/config/index.js
CHANGED
|
@@ -119,7 +119,7 @@ let config = {
|
|
|
119
119
|
defaultBlockType: 'slate',
|
|
120
120
|
verticalFormTabs: false,
|
|
121
121
|
useEmailAsLogin: false,
|
|
122
|
-
persistentReducers: ['blocksClipboard'],
|
|
122
|
+
persistentReducers: ['blocksClipboard.cut', 'blocksClipboard.copy'],
|
|
123
123
|
initialReducersBlacklist: [], // reducers in this list won't be hydrated in windows.__data
|
|
124
124
|
asyncPropsExtenders: [getSiteAsyncPropExtender], // per route asyncConnect customizers
|
|
125
125
|
contentIcons: contentIcons,
|
package/src/config/validation.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
endEventDateRangeValidator,
|
|
17
17
|
patternValidator,
|
|
18
18
|
defaultLanguageControlPanelValidator,
|
|
19
|
+
sizeValidator,
|
|
19
20
|
} from '@plone/volto/helpers/FormValidation/validators';
|
|
20
21
|
|
|
21
22
|
const registerValidators = (config: ConfigType) => {
|
|
@@ -33,6 +34,13 @@ const registerValidators = (config: ConfigType) => {
|
|
|
33
34
|
method: maxLengthValidator,
|
|
34
35
|
});
|
|
35
36
|
|
|
37
|
+
config.registerUtility({
|
|
38
|
+
name: 'size',
|
|
39
|
+
type: 'validator',
|
|
40
|
+
dependencies: { fieldType: 'object' },
|
|
41
|
+
method: sizeValidator,
|
|
42
|
+
});
|
|
43
|
+
|
|
36
44
|
config.registerUtility({
|
|
37
45
|
name: 'pattern',
|
|
38
46
|
type: 'validator',
|
|
@@ -80,6 +80,14 @@ export function blockHasValue(data) {
|
|
|
80
80
|
return check(data);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Block id is valid (not undefined/null or the string "undefined" from object[undefined])
|
|
85
|
+
* @param {*} id Block id
|
|
86
|
+
* @return {boolean}
|
|
87
|
+
*/
|
|
88
|
+
const isValidBlockId = (id) =>
|
|
89
|
+
id != null && id !== 'undefined' && (typeof id !== 'string' || id.length > 0);
|
|
90
|
+
|
|
83
91
|
/**
|
|
84
92
|
* Get block pairs of [id, block] from content properties
|
|
85
93
|
* @function getBlocks
|
|
@@ -89,12 +97,26 @@ export function blockHasValue(data) {
|
|
|
89
97
|
export const getBlocks = (properties) => {
|
|
90
98
|
const blocksFieldName = getBlocksFieldname(properties);
|
|
91
99
|
const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
const blocks = properties?.[blocksFieldName];
|
|
101
|
+
const items = properties?.[blocksLayoutFieldname]?.items;
|
|
102
|
+
if (!items) return [];
|
|
103
|
+
return items
|
|
104
|
+
.filter((n) => isValidBlockId(n))
|
|
105
|
+
.map((n) => [n, blocks?.[n]])
|
|
106
|
+
.filter(([, block]) => block != null);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get layout item IDs that are valid but have no block data (orphaned refs).
|
|
111
|
+
* @param {Object} properties Content form properties
|
|
112
|
+
* @return {string[]} IDs that should be removed from layout
|
|
113
|
+
*/
|
|
114
|
+
export const getInvalidBlockLayoutIds = (properties) => {
|
|
115
|
+
const blocksFieldName = getBlocksFieldname(properties);
|
|
116
|
+
const blocksLayoutFieldName = getBlocksLayoutFieldname(properties);
|
|
117
|
+
const blocks = properties?.[blocksFieldName] ?? {};
|
|
118
|
+
const layoutItems = properties?.[blocksLayoutFieldName]?.items ?? [];
|
|
119
|
+
return layoutItems.filter((id) => isValidBlockId(id) && blocks[id] == null);
|
|
98
120
|
};
|
|
99
121
|
|
|
100
122
|
/**
|
|
@@ -129,12 +151,28 @@ export function deleteBlock(formData, blockId, intl) {
|
|
|
129
151
|
|
|
130
152
|
let newFormData = {
|
|
131
153
|
...formData,
|
|
132
|
-
[blocksLayoutFieldname]: {
|
|
133
|
-
items: without(formData[blocksLayoutFieldname].items, blockId),
|
|
134
|
-
},
|
|
135
|
-
[blocksFieldname]: omit(formData[blocksFieldname], [blockId]),
|
|
136
154
|
};
|
|
137
155
|
|
|
156
|
+
let container = findParent(newFormData, {
|
|
157
|
+
blockId,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (container) {
|
|
161
|
+
container[blocksLayoutFieldname].items = without(
|
|
162
|
+
container[blocksLayoutFieldname].items,
|
|
163
|
+
blockId,
|
|
164
|
+
);
|
|
165
|
+
container[blocksFieldname] = omit(container[blocksFieldname], [blockId]);
|
|
166
|
+
} else {
|
|
167
|
+
newFormData[blocksLayoutFieldname].items = without(
|
|
168
|
+
newFormData[blocksLayoutFieldname].items,
|
|
169
|
+
blockId,
|
|
170
|
+
);
|
|
171
|
+
newFormData[blocksFieldname] = omit(newFormData[blocksFieldname], [
|
|
172
|
+
blockId,
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
|
|
138
176
|
if (newFormData[blocksLayoutFieldname].items.length === 0) {
|
|
139
177
|
newFormData = addBlock(
|
|
140
178
|
newFormData,
|
|
@@ -817,7 +855,9 @@ export const getBlocksHierarchy = (properties) => {
|
|
|
817
855
|
title: properties[blocksFieldName][n]?.['@type'],
|
|
818
856
|
data: properties[blocksFieldName][n],
|
|
819
857
|
children: isBlockContainer(properties[blocksFieldName][n])
|
|
820
|
-
?
|
|
858
|
+
? properties[blocksFieldName][n].data
|
|
859
|
+
? getBlocksHierarchy(properties[blocksFieldName][n].data)
|
|
860
|
+
: getBlocksHierarchy(properties[blocksFieldName][n])
|
|
821
861
|
: [],
|
|
822
862
|
}));
|
|
823
863
|
};
|
|
@@ -911,8 +951,13 @@ export function moveBlockEnhanced(formData, { source, destination }) {
|
|
|
911
951
|
const destinationContainer = findContainer(clonedFormData, {
|
|
912
952
|
containerId: destination.parent,
|
|
913
953
|
});
|
|
954
|
+
const sourceContainer = findContainer(clonedFormData, {
|
|
955
|
+
containerId: source.parent,
|
|
956
|
+
});
|
|
957
|
+
|
|
914
958
|
destinationContainer[blocksFieldName][source.id] =
|
|
915
|
-
|
|
959
|
+
sourceContainer[blocksFieldName]?.[source.id] ||
|
|
960
|
+
sourceContainer.data?.[blocksFieldName][source.id];
|
|
916
961
|
|
|
917
962
|
destinationContainer[blocksLayoutFieldname].items = insertInArray(
|
|
918
963
|
destinationContainer[blocksLayoutFieldname].items,
|
|
@@ -921,9 +966,6 @@ export function moveBlockEnhanced(formData, { source, destination }) {
|
|
|
921
966
|
);
|
|
922
967
|
|
|
923
968
|
// Remove the source block from the source parent
|
|
924
|
-
const sourceContainer = findContainer(clonedFormData, {
|
|
925
|
-
containerId: source.parent,
|
|
926
|
-
});
|
|
927
969
|
delete sourceContainer[blocksFieldName][source.id];
|
|
928
970
|
sourceContainer[blocksLayoutFieldname].items = removeFromArray(
|
|
929
971
|
sourceContainer[blocksLayoutFieldname].items,
|
|
@@ -957,23 +999,25 @@ export function moveBlockEnhanced(formData, { source, destination }) {
|
|
|
957
999
|
* @returns {object|undefined} - The container object if found, otherwise undefined.
|
|
958
1000
|
*/
|
|
959
1001
|
export const findContainer = (formData, { containerId }) => {
|
|
1002
|
+
const block =
|
|
1003
|
+
formData.blocks[containerId]?.data || formData.blocks[containerId];
|
|
960
1004
|
if (
|
|
961
|
-
|
|
962
|
-
Object.keys(
|
|
963
|
-
Object.keys(
|
|
1005
|
+
block &&
|
|
1006
|
+
Object.keys(block).includes('blocks') &&
|
|
1007
|
+
Object.keys(block).includes('blocks_layout')
|
|
964
1008
|
) {
|
|
965
|
-
return
|
|
1009
|
+
return block;
|
|
966
1010
|
}
|
|
967
1011
|
|
|
968
1012
|
let container;
|
|
969
1013
|
Object.keys(formData.blocks).every((blockId) => {
|
|
970
|
-
const
|
|
1014
|
+
const subBlock = formData.blocks[blockId].data || formData.blocks[blockId];
|
|
971
1015
|
if (
|
|
972
|
-
|
|
973
|
-
Object.keys(
|
|
974
|
-
Object.keys(
|
|
1016
|
+
subBlock &&
|
|
1017
|
+
Object.keys(subBlock).includes('blocks') &&
|
|
1018
|
+
Object.keys(subBlock).includes('blocks_layout')
|
|
975
1019
|
) {
|
|
976
|
-
container = findContainer(
|
|
1020
|
+
container = findContainer(subBlock, { containerId });
|
|
977
1021
|
}
|
|
978
1022
|
if (container) {
|
|
979
1023
|
return false;
|
|
@@ -985,6 +1029,47 @@ export const findContainer = (formData, { containerId }) => {
|
|
|
985
1029
|
return container;
|
|
986
1030
|
};
|
|
987
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Finds parent container of the specified blockId in the given formData.
|
|
1034
|
+
*
|
|
1035
|
+
* @param {object} formData - The form data object.
|
|
1036
|
+
* @param {object} options - The options object.
|
|
1037
|
+
* @param {string} options.blockId - The ID of the block to find.
|
|
1038
|
+
* @returns {object|undefined} - The container object if found, otherwise undefined.
|
|
1039
|
+
*/
|
|
1040
|
+
export const findParent = (formData, { blockId }) => {
|
|
1041
|
+
const block = formData.data || formData;
|
|
1042
|
+
|
|
1043
|
+
if (block && block.blocks && Object.keys(block.blocks).includes(blockId)) {
|
|
1044
|
+
return block;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
let found = false;
|
|
1048
|
+
|
|
1049
|
+
if (block && block.blocks) {
|
|
1050
|
+
Object.keys(block.blocks).every((subBlockId) => {
|
|
1051
|
+
const subBlock =
|
|
1052
|
+
block.blocks[subBlockId].data || block.blocks[subBlockId];
|
|
1053
|
+
if (subBlock && subBlock.blocks) {
|
|
1054
|
+
if (Object.keys(subBlock.blocks).includes(blockId)) {
|
|
1055
|
+
found = subBlock;
|
|
1056
|
+
}
|
|
1057
|
+
const parent = findParent(subBlock, { blockId });
|
|
1058
|
+
if (parent) {
|
|
1059
|
+
found = parent;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (found) {
|
|
1063
|
+
return false;
|
|
1064
|
+
} else {
|
|
1065
|
+
return true;
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
return found;
|
|
1071
|
+
};
|
|
1072
|
+
|
|
988
1073
|
const _dummyIntl = {
|
|
989
1074
|
formatMessage() {},
|
|
990
1075
|
};
|