@plone/volto 16.23.0 → 16.25.0
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.draft +8 -9
- package/.yarn/install-state.gz +0 -0
- package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
- package/.yarn/releases/yarn-3.6.3.cjs +874 -0
- package/CHANGELOG.md +35 -0
- package/cypress.config.js +17 -0
- package/locales/ca/LC_MESSAGES/volto.po +31 -5
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +31 -5
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +30 -4
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +31 -5
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +30 -4
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +30 -4
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +31 -5
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +30 -4
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +30 -4
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +31 -5
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +31 -5
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +30 -4
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +31 -5
- package/locales/ro.json +1 -1
- package/locales/volto.pot +30 -4
- package/locales/zh_CN/LC_MESSAGES/volto.po +31 -5
- package/locales/zh_CN.json +1 -1
- package/package.json +2 -2
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/editor/plugins/Link/render.jsx +5 -6
- package/src/components/manage/Blocks/Listing/ListingData.jsx +3 -3
- package/src/components/manage/Blocks/Listing/ListingData.test.jsx +2 -0
- package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +1 -1
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +42 -25
- package/src/components/manage/Blocks/ToC/View.jsx +1 -0
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +65 -38
- package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +95 -5
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +116 -103
- package/src/components/manage/Sharing/Sharing.jsx +39 -16
- package/src/components/manage/Widgets/QueryWidget.jsx +6 -2
- package/src/helpers/MessageLabels/MessageLabels.js +26 -4
- package/src/reducers/navigation/navigation.js +5 -5
- package/src/reducers/navigation/navigation.test.js +30 -0
- package/styles/Microsoft/AMPM.yml +9 -0
- package/styles/Microsoft/Accessibility.yml +25 -0
- package/styles/Microsoft/Acronyms.yml +64 -0
- package/styles/Microsoft/Adverbs.yml +270 -0
- package/styles/Microsoft/Auto.yml +11 -0
- package/styles/Microsoft/Avoid.yml +14 -0
- package/styles/Microsoft/ComplexWords.yml +120 -0
- package/styles/Microsoft/Contractions.yml +50 -0
- package/styles/Microsoft/Dashes.yml +13 -0
- package/styles/Microsoft/DateFormat.yml +8 -0
- package/styles/Microsoft/DateNumbers.yml +40 -0
- package/styles/Microsoft/DateOrder.yml +8 -0
- package/styles/Microsoft/Ellipses.yml +9 -0
- package/styles/Microsoft/FirstPerson.yml +16 -0
- package/styles/Microsoft/Foreign.yml +13 -0
- package/styles/Microsoft/Gender.yml +8 -0
- package/styles/Microsoft/GenderBias.yml +44 -0
- package/styles/Microsoft/GeneralURL.yml +11 -0
- package/styles/Microsoft/HeadingAcronyms.yml +7 -0
- package/styles/Microsoft/HeadingColons.yml +8 -0
- package/styles/Microsoft/HeadingPunctuation.yml +13 -0
- package/styles/Microsoft/Headings.yml +28 -0
- package/styles/Microsoft/Hyphens.yml +14 -0
- package/styles/Microsoft/Negative.yml +13 -0
- package/styles/Microsoft/Ordinal.yml +13 -0
- package/styles/Microsoft/OxfordComma.yml +8 -0
- package/styles/Microsoft/Passive.yml +183 -0
- package/styles/Microsoft/Percentages.yml +7 -0
- package/styles/Microsoft/Quotes.yml +7 -0
- package/styles/Microsoft/RangeFormat.yml +13 -0
- package/styles/Microsoft/RangeTime.yml +13 -0
- package/styles/Microsoft/Ranges.yml +7 -0
- package/styles/Microsoft/Semicolon.yml +8 -0
- package/styles/Microsoft/SentenceLength.yml +7 -0
- package/styles/Microsoft/Spacing.yml +8 -0
- package/styles/Microsoft/Suspended.yml +7 -0
- package/styles/Microsoft/Terms.yml +41 -0
- package/styles/Microsoft/URLFormat.yml +10 -0
- package/styles/Microsoft/Units.yml +16 -0
- package/styles/Microsoft/Vocab.yml +25 -0
- package/styles/Microsoft/We.yml +11 -0
- package/styles/Microsoft/Wordiness.yml +122 -0
- package/styles/Microsoft/meta.json +4 -0
- package/theme/themes/pastanaga/elements/input.overrides +5 -1
- package/theme/themes/pastanaga/extras/login.less +2 -0
|
@@ -7,8 +7,14 @@ import React, { Component } from 'react';
|
|
|
7
7
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
8
8
|
import { Dropdown, Table, Checkbox } from 'semantic-ui-react';
|
|
9
9
|
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
10
|
-
import
|
|
10
|
+
import editSVG from '@plone/volto/icons/editing.svg';
|
|
11
|
+
import { Icon, ModalForm, Toast } from '@plone/volto/components';
|
|
12
|
+
import { updateUser } from '@plone/volto/actions';
|
|
11
13
|
import ploneSVG from '@plone/volto/icons/plone.svg';
|
|
14
|
+
import { compose } from 'redux';
|
|
15
|
+
import { connect } from 'react-redux';
|
|
16
|
+
import { messages } from '@plone/volto/helpers';
|
|
17
|
+
import { toast } from 'react-toastify';
|
|
12
18
|
|
|
13
19
|
/**
|
|
14
20
|
* UsersControlpanelUser class.
|
|
@@ -43,9 +49,14 @@ class RenderUsers extends Component {
|
|
|
43
49
|
*/
|
|
44
50
|
constructor(props) {
|
|
45
51
|
super(props);
|
|
46
|
-
this.state = {
|
|
52
|
+
this.state = {
|
|
53
|
+
user: {},
|
|
54
|
+
};
|
|
47
55
|
this.onChange = this.onChange.bind(this);
|
|
56
|
+
this.onEditUserError = this.onEditUserError.bind(this);
|
|
57
|
+
this.onEditUserSubmit = this.onEditUserSubmit.bind(this);
|
|
48
58
|
}
|
|
59
|
+
|
|
49
60
|
/**
|
|
50
61
|
* @param {*} event
|
|
51
62
|
* @param {*} { value }
|
|
@@ -56,6 +67,49 @@ class RenderUsers extends Component {
|
|
|
56
67
|
const [user, role] = value.split('&role=');
|
|
57
68
|
this.props.updateUser(user, role);
|
|
58
69
|
}
|
|
70
|
+
|
|
71
|
+
componentDidUpdate(prevProps) {
|
|
72
|
+
if (
|
|
73
|
+
prevProps.updateRequest.loading &&
|
|
74
|
+
this.props.updateRequest.loaded &&
|
|
75
|
+
this.state?.user?.id === this.props?.user?.id
|
|
76
|
+
) {
|
|
77
|
+
this.setState({ user: {} });
|
|
78
|
+
this.props.listUsers();
|
|
79
|
+
return toast.success(
|
|
80
|
+
<Toast
|
|
81
|
+
success
|
|
82
|
+
title={this.props.intl.formatMessage(messages.success)}
|
|
83
|
+
content={this.props.intl.formatMessage(messages.updateUserSuccess)}
|
|
84
|
+
/>,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onEditUserSubmit(data, callback) {
|
|
90
|
+
// Do not handle groups and roles in this form
|
|
91
|
+
delete data.groups;
|
|
92
|
+
delete data.roles;
|
|
93
|
+
this.props.updateUserData(data.id, data);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
onEditUserError() {
|
|
97
|
+
return toast.error(
|
|
98
|
+
<Toast
|
|
99
|
+
error
|
|
100
|
+
title={this.props.intl.formatMessage(messages.error)}
|
|
101
|
+
content={this.props.intl.formatMessage(
|
|
102
|
+
messages.addUserFormPasswordAndSendPasswordTogetherNotAllowed,
|
|
103
|
+
)}
|
|
104
|
+
/>,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onClickEdit(props) {
|
|
109
|
+
const { formData } = props;
|
|
110
|
+
this.setState({ user: { ...formData } });
|
|
111
|
+
}
|
|
112
|
+
|
|
59
113
|
/**
|
|
60
114
|
* Render method.
|
|
61
115
|
* @method render
|
|
@@ -67,7 +121,8 @@ class RenderUsers extends Component {
|
|
|
67
121
|
<Table.Cell className="fullname">
|
|
68
122
|
{this.props.user.fullname
|
|
69
123
|
? this.props.user.fullname
|
|
70
|
-
: this.props.user.username}
|
|
124
|
+
: this.props.user.username}{' '}
|
|
125
|
+
({this.props.user.username})
|
|
71
126
|
</Table.Cell>
|
|
72
127
|
{this.props.roles.map((role) => (
|
|
73
128
|
<Table.Cell key={role.id}>
|
|
@@ -91,7 +146,20 @@ class RenderUsers extends Component {
|
|
|
91
146
|
<Table.Cell textAlign="right">
|
|
92
147
|
<Dropdown icon="ellipsis horizontal">
|
|
93
148
|
<Dropdown.Menu className="left">
|
|
149
|
+
{this.props.userschema && (
|
|
150
|
+
<Dropdown.Item
|
|
151
|
+
id="edit-user-button"
|
|
152
|
+
onClick={() => {
|
|
153
|
+
this.onClickEdit({ formData: this.props.user });
|
|
154
|
+
}}
|
|
155
|
+
value={this.props.user['@id']}
|
|
156
|
+
>
|
|
157
|
+
<Icon name={editSVG} size="15px" />
|
|
158
|
+
<FormattedMessage id="Edit" defaultMessage="Edit" />
|
|
159
|
+
</Dropdown.Item>
|
|
160
|
+
)}
|
|
94
161
|
<Dropdown.Item
|
|
162
|
+
id="delete-user-button"
|
|
95
163
|
onClick={this.props.onDelete}
|
|
96
164
|
value={this.props.user['@id']}
|
|
97
165
|
>
|
|
@@ -101,9 +169,31 @@ class RenderUsers extends Component {
|
|
|
101
169
|
</Dropdown.Menu>
|
|
102
170
|
</Dropdown>
|
|
103
171
|
</Table.Cell>
|
|
172
|
+
{Object.keys(this.state.user).length > 0 &&
|
|
173
|
+
this.props.userschema.loaded && (
|
|
174
|
+
<ModalForm
|
|
175
|
+
className="modal"
|
|
176
|
+
onSubmit={this.onEditUserSubmit}
|
|
177
|
+
submitError={this.state.editUserError}
|
|
178
|
+
formData={this.state.user}
|
|
179
|
+
onCancel={() => this.setState({ user: {} })}
|
|
180
|
+
title={this.props.intl.formatMessage(
|
|
181
|
+
messages.updateUserFormTitle,
|
|
182
|
+
)}
|
|
183
|
+
loading={this.props.updateRequest.loading}
|
|
184
|
+
schema={this.props.userschema.userschema}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
104
187
|
</Table.Row>
|
|
105
188
|
);
|
|
106
189
|
}
|
|
107
190
|
}
|
|
108
|
-
|
|
109
|
-
|
|
191
|
+
export default compose(
|
|
192
|
+
injectIntl,
|
|
193
|
+
connect(
|
|
194
|
+
(state, props) => ({
|
|
195
|
+
updateRequest: state.users?.update,
|
|
196
|
+
}),
|
|
197
|
+
{ updateUserData: updateUser },
|
|
198
|
+
),
|
|
199
|
+
)(RenderUsers);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getControlpanel,
|
|
12
12
|
updateUser,
|
|
13
13
|
updateGroup,
|
|
14
|
+
getUserSchema,
|
|
14
15
|
} from '@plone/volto/actions';
|
|
15
16
|
import {
|
|
16
17
|
Icon,
|
|
@@ -98,6 +99,7 @@ class UsersControlpanel extends Component {
|
|
|
98
99
|
this.updateUserRole = this.updateUserRole.bind(this);
|
|
99
100
|
this.state = {
|
|
100
101
|
search: '',
|
|
102
|
+
isLoading: false,
|
|
101
103
|
showAddUser: false,
|
|
102
104
|
showAddUserErrorConfirm: false,
|
|
103
105
|
addUserError: '',
|
|
@@ -121,6 +123,7 @@ class UsersControlpanel extends Component {
|
|
|
121
123
|
entries: this.props.users,
|
|
122
124
|
});
|
|
123
125
|
}
|
|
126
|
+
await this.props.getUserSchema();
|
|
124
127
|
};
|
|
125
128
|
|
|
126
129
|
// Because username field needs to be disabled if email login is enabled!
|
|
@@ -181,9 +184,19 @@ class UsersControlpanel extends Component {
|
|
|
181
184
|
*/
|
|
182
185
|
onSearch(event) {
|
|
183
186
|
event.preventDefault();
|
|
184
|
-
this.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
this.setState({ isLoading: true });
|
|
188
|
+
this.props
|
|
189
|
+
.listUsers({
|
|
190
|
+
search: this.state.search,
|
|
191
|
+
})
|
|
192
|
+
.then(() => {
|
|
193
|
+
this.setState({ isLoading: false });
|
|
194
|
+
})
|
|
195
|
+
.catch((error) => {
|
|
196
|
+
this.setState({ isLoading: false });
|
|
197
|
+
// eslint-disable-next-line no-console
|
|
198
|
+
console.error('Error searching users', error);
|
|
199
|
+
});
|
|
187
200
|
}
|
|
188
201
|
|
|
189
202
|
/**
|
|
@@ -265,12 +278,28 @@ class UsersControlpanel extends Component {
|
|
|
265
278
|
* @returns {undefined}
|
|
266
279
|
*/
|
|
267
280
|
onAddUserSubmit(data, callback) {
|
|
268
|
-
const { groups, sendPasswordReset } = data;
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
const { groups, sendPasswordReset, password } = data;
|
|
282
|
+
if (
|
|
283
|
+
sendPasswordReset !== undefined &&
|
|
284
|
+
sendPasswordReset === true &&
|
|
285
|
+
password !== undefined
|
|
286
|
+
) {
|
|
287
|
+
toast.error(
|
|
288
|
+
<Toast
|
|
289
|
+
error
|
|
290
|
+
title={this.props.intl.formatMessage(messages.error)}
|
|
291
|
+
content={this.props.intl.formatMessage(
|
|
292
|
+
messages.addUserFormPasswordAndSendPasswordTogetherNotAllowed,
|
|
293
|
+
)}
|
|
294
|
+
/>,
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
if (groups && groups.length > 0) this.addUserToGroup(data);
|
|
298
|
+
this.props.createUser(data, sendPasswordReset);
|
|
299
|
+
this.setState({
|
|
300
|
+
addUserSetFormDataCallback: callback,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
274
303
|
}
|
|
275
304
|
|
|
276
305
|
/**
|
|
@@ -392,6 +421,65 @@ class UsersControlpanel extends Component {
|
|
|
392
421
|
let usernameToDelete = this.state.userToDelete
|
|
393
422
|
? this.state.userToDelete.username
|
|
394
423
|
: '';
|
|
424
|
+
// Copy the userschema using JSON serialization/deserialization
|
|
425
|
+
// this is really ugly, but if we don't do this the original value
|
|
426
|
+
// of the userschema is changed and it is used like that through
|
|
427
|
+
// the lifecycle of the application
|
|
428
|
+
let adduserschema = {};
|
|
429
|
+
if (this.props?.userschema?.loaded) {
|
|
430
|
+
adduserschema = JSON.parse(
|
|
431
|
+
JSON.stringify(this.props?.userschema?.userschema),
|
|
432
|
+
);
|
|
433
|
+
adduserschema.properties['username'] = {
|
|
434
|
+
title: this.props.intl.formatMessage(messages.addUserFormUsernameTitle),
|
|
435
|
+
type: 'string',
|
|
436
|
+
description: this.props.intl.formatMessage(
|
|
437
|
+
messages.addUserFormUsernameDescription,
|
|
438
|
+
),
|
|
439
|
+
};
|
|
440
|
+
adduserschema.properties['password'] = {
|
|
441
|
+
title: this.props.intl.formatMessage(messages.addUserFormPasswordTitle),
|
|
442
|
+
type: 'password',
|
|
443
|
+
description: this.props.intl.formatMessage(
|
|
444
|
+
messages.addUserFormPasswordDescription,
|
|
445
|
+
),
|
|
446
|
+
widget: 'password',
|
|
447
|
+
};
|
|
448
|
+
adduserschema.properties['sendPasswordReset'] = {
|
|
449
|
+
title: this.props.intl.formatMessage(
|
|
450
|
+
messages.addUserFormSendPasswordResetTitle,
|
|
451
|
+
),
|
|
452
|
+
type: 'boolean',
|
|
453
|
+
};
|
|
454
|
+
adduserschema.properties['roles'] = {
|
|
455
|
+
title: this.props.intl.formatMessage(messages.addUserFormRolesTitle),
|
|
456
|
+
type: 'array',
|
|
457
|
+
choices: this.props.roles.map((role) => [role.id, role.title]),
|
|
458
|
+
noValueOption: false,
|
|
459
|
+
};
|
|
460
|
+
adduserschema.properties['groups'] = {
|
|
461
|
+
title: this.props.intl.formatMessage(messages.addUserGroupNameTitle),
|
|
462
|
+
type: 'array',
|
|
463
|
+
choices: this.props.groups.map((group) => [group.id, group.id]),
|
|
464
|
+
noValueOption: false,
|
|
465
|
+
};
|
|
466
|
+
if (
|
|
467
|
+
adduserschema.fieldsets &&
|
|
468
|
+
adduserschema.fieldsets.length > 0 &&
|
|
469
|
+
!adduserschema.fieldsets[0]['fields'].includes('username')
|
|
470
|
+
) {
|
|
471
|
+
adduserschema.fieldsets[0]['fields'] = adduserschema.fieldsets[0][
|
|
472
|
+
'fields'
|
|
473
|
+
].concat([
|
|
474
|
+
'username',
|
|
475
|
+
'password',
|
|
476
|
+
'sendPasswordReset',
|
|
477
|
+
'roles',
|
|
478
|
+
'groups',
|
|
479
|
+
]);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
395
483
|
return (
|
|
396
484
|
<Container className="users-control-panel">
|
|
397
485
|
<Helmet title={this.props.intl.formatMessage(messages.users)} />
|
|
@@ -418,7 +506,7 @@ class UsersControlpanel extends Component {
|
|
|
418
506
|
onConfirm={this.onDeleteOk}
|
|
419
507
|
size={null}
|
|
420
508
|
/>
|
|
421
|
-
{this.state.showAddUser ? (
|
|
509
|
+
{this.props?.userschema?.loaded && this.state.showAddUser ? (
|
|
422
510
|
<ModalForm
|
|
423
511
|
open={this.state.showAddUser}
|
|
424
512
|
className="modal"
|
|
@@ -429,96 +517,7 @@ class UsersControlpanel extends Component {
|
|
|
429
517
|
}
|
|
430
518
|
title={this.props.intl.formatMessage(messages.addUserFormTitle)}
|
|
431
519
|
loading={this.props.createRequest.loading}
|
|
432
|
-
schema={
|
|
433
|
-
fieldsets: [
|
|
434
|
-
{
|
|
435
|
-
id: 'default',
|
|
436
|
-
title: 'FIXME: User Data',
|
|
437
|
-
fields: [
|
|
438
|
-
...(!this.state.loginUsingEmail ? ['username'] : []),
|
|
439
|
-
'fullname',
|
|
440
|
-
'email',
|
|
441
|
-
'password',
|
|
442
|
-
'sendPasswordReset',
|
|
443
|
-
'roles',
|
|
444
|
-
'groups',
|
|
445
|
-
],
|
|
446
|
-
},
|
|
447
|
-
],
|
|
448
|
-
properties: {
|
|
449
|
-
...(!this.state.loginUsingEmail
|
|
450
|
-
? {
|
|
451
|
-
username: {
|
|
452
|
-
title: this.props.intl.formatMessage(
|
|
453
|
-
messages.addUserFormUsernameTitle,
|
|
454
|
-
),
|
|
455
|
-
type: 'string',
|
|
456
|
-
description: this.props.intl.formatMessage(
|
|
457
|
-
messages.addUserFormUsernameDescription,
|
|
458
|
-
),
|
|
459
|
-
},
|
|
460
|
-
}
|
|
461
|
-
: {}),
|
|
462
|
-
fullname: {
|
|
463
|
-
title: this.props.intl.formatMessage(
|
|
464
|
-
messages.addUserFormFullnameTitle,
|
|
465
|
-
),
|
|
466
|
-
type: 'string',
|
|
467
|
-
description: this.props.intl.formatMessage(
|
|
468
|
-
messages.addUserFormFullnameDescription,
|
|
469
|
-
),
|
|
470
|
-
},
|
|
471
|
-
email: {
|
|
472
|
-
title: this.props.intl.formatMessage(
|
|
473
|
-
messages.addUserFormEmailTitle,
|
|
474
|
-
),
|
|
475
|
-
type: 'string',
|
|
476
|
-
description: this.props.intl.formatMessage(
|
|
477
|
-
messages.addUserFormEmailDescription,
|
|
478
|
-
),
|
|
479
|
-
widget: 'email',
|
|
480
|
-
},
|
|
481
|
-
password: {
|
|
482
|
-
title: this.props.intl.formatMessage(
|
|
483
|
-
messages.addUserFormPasswordTitle,
|
|
484
|
-
),
|
|
485
|
-
type: 'password',
|
|
486
|
-
description: this.props.intl.formatMessage(
|
|
487
|
-
messages.addUserFormPasswordDescription,
|
|
488
|
-
),
|
|
489
|
-
widget: 'password',
|
|
490
|
-
},
|
|
491
|
-
sendPasswordReset: {
|
|
492
|
-
title: this.props.intl.formatMessage(
|
|
493
|
-
messages.addUserFormSendPasswordResetTitle,
|
|
494
|
-
),
|
|
495
|
-
type: 'boolean',
|
|
496
|
-
},
|
|
497
|
-
roles: {
|
|
498
|
-
title: this.props.intl.formatMessage(
|
|
499
|
-
messages.addUserFormRolesTitle,
|
|
500
|
-
),
|
|
501
|
-
type: 'array',
|
|
502
|
-
choices: this.props.roles.map((role) => [
|
|
503
|
-
role.id,
|
|
504
|
-
role.title,
|
|
505
|
-
]),
|
|
506
|
-
noValueOption: false,
|
|
507
|
-
},
|
|
508
|
-
groups: {
|
|
509
|
-
title: this.props.intl.formatMessage(
|
|
510
|
-
messages.addUserGroupNameTitle,
|
|
511
|
-
),
|
|
512
|
-
type: 'array',
|
|
513
|
-
choices: this.props.groups.map((group) => [
|
|
514
|
-
group.id,
|
|
515
|
-
group.id,
|
|
516
|
-
]),
|
|
517
|
-
noValueOption: false,
|
|
518
|
-
},
|
|
519
|
-
},
|
|
520
|
-
required: ['username', 'email'],
|
|
521
|
-
}}
|
|
520
|
+
schema={adduserschema}
|
|
522
521
|
/>
|
|
523
522
|
) : null}
|
|
524
523
|
</div>
|
|
@@ -547,7 +546,11 @@ class UsersControlpanel extends Component {
|
|
|
547
546
|
<Form.Field>
|
|
548
547
|
<Input
|
|
549
548
|
name="SearchableText"
|
|
550
|
-
action={{
|
|
549
|
+
action={{
|
|
550
|
+
icon: 'search',
|
|
551
|
+
loading: this.state.isLoading,
|
|
552
|
+
disabled: this.state.isLoading,
|
|
553
|
+
}}
|
|
551
554
|
placeholder={this.props.intl.formatMessage(
|
|
552
555
|
messages.searchUsers,
|
|
553
556
|
)}
|
|
@@ -558,7 +561,8 @@ class UsersControlpanel extends Component {
|
|
|
558
561
|
</Form>
|
|
559
562
|
</Segment>
|
|
560
563
|
<Form>
|
|
561
|
-
|
|
564
|
+
{((this.props.many_users && this.state.entries.length > 0) ||
|
|
565
|
+
!this.props.many_users) && (
|
|
562
566
|
<Table padded striped attached unstackable>
|
|
563
567
|
<Table.Header>
|
|
564
568
|
<Table.Row>
|
|
@@ -592,11 +596,18 @@ class UsersControlpanel extends Component {
|
|
|
592
596
|
user={user}
|
|
593
597
|
updateUser={this.updateUserRole}
|
|
594
598
|
inheritedRole={this.props.inheritedRole}
|
|
599
|
+
userschema={this.props.userschema}
|
|
600
|
+
listUsers={this.props.listUsers}
|
|
595
601
|
/>
|
|
596
602
|
))}
|
|
597
603
|
</Table.Body>
|
|
598
604
|
</Table>
|
|
599
|
-
|
|
605
|
+
)}
|
|
606
|
+
{this.state.entries.length === 0 && this.state.search && (
|
|
607
|
+
<Segment>
|
|
608
|
+
{this.props.intl.formatMessage(messages.userSearchNoResults)}
|
|
609
|
+
</Segment>
|
|
610
|
+
)}
|
|
600
611
|
<div className="contents-pagination">
|
|
601
612
|
<Pagination
|
|
602
613
|
current={this.state.currentPage}
|
|
@@ -684,6 +695,7 @@ export default compose(
|
|
|
684
695
|
createRequest: state.users.create,
|
|
685
696
|
loadRolesRequest: state.roles,
|
|
686
697
|
inheritedRole: state.authRole.authenticatedRole,
|
|
698
|
+
userschema: state.userschema,
|
|
687
699
|
controlPanelData: state.controlpanels?.controlpanel,
|
|
688
700
|
}),
|
|
689
701
|
(dispatch) =>
|
|
@@ -697,6 +709,7 @@ export default compose(
|
|
|
697
709
|
createUser,
|
|
698
710
|
updateUser,
|
|
699
711
|
updateGroup,
|
|
712
|
+
getUserSchema,
|
|
700
713
|
},
|
|
701
714
|
dispatch,
|
|
702
715
|
),
|
|
@@ -145,6 +145,7 @@ class SharingComponent extends Component {
|
|
|
145
145
|
this.onToggleInherit = this.onToggleInherit.bind(this);
|
|
146
146
|
this.state = {
|
|
147
147
|
search: '',
|
|
148
|
+
isLoading: false,
|
|
148
149
|
inherit: props.inherit,
|
|
149
150
|
entries: props.entries,
|
|
150
151
|
isClient: false,
|
|
@@ -225,7 +226,17 @@ class SharingComponent extends Component {
|
|
|
225
226
|
*/
|
|
226
227
|
onSearch(event) {
|
|
227
228
|
event.preventDefault();
|
|
228
|
-
this.
|
|
229
|
+
this.setState({ isLoading: true });
|
|
230
|
+
this.props
|
|
231
|
+
.getSharing(getBaseUrl(this.props.pathname), this.state.search)
|
|
232
|
+
.then(() => {
|
|
233
|
+
this.setState({ isLoading: false });
|
|
234
|
+
})
|
|
235
|
+
.catch((error) => {
|
|
236
|
+
this.setState({ isLoading: false });
|
|
237
|
+
// eslint-disable-next-line no-console
|
|
238
|
+
console.error('Error searching users or groups', error);
|
|
239
|
+
});
|
|
229
240
|
}
|
|
230
241
|
|
|
231
242
|
/**
|
|
@@ -296,7 +307,10 @@ class SharingComponent extends Component {
|
|
|
296
307
|
<Container id="page-sharing">
|
|
297
308
|
<Helmet title={this.props.intl.formatMessage(messages.sharing)} />
|
|
298
309
|
<Segment.Group raised>
|
|
299
|
-
<Pluggable
|
|
310
|
+
<Pluggable
|
|
311
|
+
name="sharing-component"
|
|
312
|
+
params={{ isLoading: this.state.isLoading }}
|
|
313
|
+
/>
|
|
300
314
|
<Plug pluggable="sharing-component" id="sharing-component-title">
|
|
301
315
|
<Segment className="primary">
|
|
302
316
|
<FormattedMessage
|
|
@@ -318,20 +332,29 @@ class SharingComponent extends Component {
|
|
|
318
332
|
</Segment>
|
|
319
333
|
</Plug>
|
|
320
334
|
<Plug pluggable="sharing-component" id="sharing-component-search">
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
{({ isLoading }) => {
|
|
336
|
+
return (
|
|
337
|
+
<Segment>
|
|
338
|
+
<Form onSubmit={this.onSearch}>
|
|
339
|
+
<Form.Field>
|
|
340
|
+
<Input
|
|
341
|
+
name="SearchableText"
|
|
342
|
+
action={{
|
|
343
|
+
icon: 'search',
|
|
344
|
+
loading: isLoading,
|
|
345
|
+
disabled: isLoading,
|
|
346
|
+
}}
|
|
347
|
+
placeholder={this.props.intl.formatMessage(
|
|
348
|
+
messages.searchForUserOrGroup,
|
|
349
|
+
)}
|
|
350
|
+
onChange={this.onChangeSearch}
|
|
351
|
+
id="sharing-component-search"
|
|
352
|
+
/>
|
|
353
|
+
</Form.Field>
|
|
354
|
+
</Form>
|
|
355
|
+
</Segment>
|
|
356
|
+
);
|
|
357
|
+
}}
|
|
335
358
|
</Plug>
|
|
336
359
|
<Plug
|
|
337
360
|
pluggable="sharing-component"
|
|
@@ -314,7 +314,8 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
314
314
|
label: field[1].title,
|
|
315
315
|
value: field[0],
|
|
316
316
|
isDisabled: (value || []).some(
|
|
317
|
-
(v) =>
|
|
317
|
+
(v) =>
|
|
318
|
+
v['i'] !== 'path' && v['i'] === field[0],
|
|
318
319
|
),
|
|
319
320
|
}),
|
|
320
321
|
),
|
|
@@ -444,8 +445,11 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
444
445
|
(field) => ({
|
|
445
446
|
label: field[1].title,
|
|
446
447
|
value: field[0],
|
|
448
|
+
// disable selecting indexes that are already used,
|
|
449
|
+
// except for path, which has explicit support
|
|
450
|
+
// in the backend for multipath queries
|
|
447
451
|
isDisabled: (value || []).some(
|
|
448
|
-
(v) => v['i'] === field[0],
|
|
452
|
+
(v) => v['i'] !== 'path' && v['i'] === field[0],
|
|
449
453
|
),
|
|
450
454
|
}),
|
|
451
455
|
),
|
|
@@ -172,6 +172,28 @@ export const messages = defineMessages({
|
|
|
172
172
|
id: 'Roles',
|
|
173
173
|
defaultMessage: 'Roles',
|
|
174
174
|
},
|
|
175
|
+
addUserFormPasswordAndSendPasswordTogetherNotAllowed: {
|
|
176
|
+
id:
|
|
177
|
+
'It is not allowed to define both the password and to request sending the password reset message by e-mail. You need to select one of them.',
|
|
178
|
+
defaultMessage:
|
|
179
|
+
'It is not allowed to define both the password and to request sending the password reset message by e-mail. You need to select one of them.',
|
|
180
|
+
},
|
|
181
|
+
userSearchNoResults: {
|
|
182
|
+
id: 'There are no users with the searched criteria',
|
|
183
|
+
defaultMessage: 'There are no users with the searched criteria',
|
|
184
|
+
},
|
|
185
|
+
groupSearchNoResults: {
|
|
186
|
+
id: 'There are no groups with the searched criteria',
|
|
187
|
+
defaultMessage: 'There are no groups with the searched criteria',
|
|
188
|
+
},
|
|
189
|
+
updateUserFormTitle: {
|
|
190
|
+
id: 'Update User',
|
|
191
|
+
defaultMessage: 'Update User',
|
|
192
|
+
},
|
|
193
|
+
updateUserSuccess: {
|
|
194
|
+
id: 'User updated successfuly',
|
|
195
|
+
defaultMessage: 'User updated successfuly',
|
|
196
|
+
},
|
|
175
197
|
updateRoles: {
|
|
176
198
|
id: 'User roles updated',
|
|
177
199
|
defaultMessage: 'User roles updated',
|
|
@@ -234,19 +256,19 @@ export const messages = defineMessages({
|
|
|
234
256
|
},
|
|
235
257
|
copyBlocks: {
|
|
236
258
|
id: 'Copy blocks',
|
|
237
|
-
|
|
259
|
+
defaultMessage: 'Copy blocks',
|
|
238
260
|
},
|
|
239
261
|
cutBlocks: {
|
|
240
262
|
id: 'Cut blocks',
|
|
241
|
-
|
|
263
|
+
defaultMessage: 'Cut blocks',
|
|
242
264
|
},
|
|
243
265
|
pasteBlocks: {
|
|
244
266
|
id: 'Paste blocks',
|
|
245
|
-
|
|
267
|
+
defaultMessage: 'Paste blocks',
|
|
246
268
|
},
|
|
247
269
|
deleteBlocks: {
|
|
248
270
|
id: 'Delete blocks',
|
|
249
|
-
|
|
271
|
+
defaultMessage: 'Delete blocks',
|
|
250
272
|
},
|
|
251
273
|
showAllUserButton: {
|
|
252
274
|
id: 'Show All',
|
|
@@ -73,11 +73,11 @@ export default function navigation(state = initialState, action = {}) {
|
|
|
73
73
|
}
|
|
74
74
|
return state;
|
|
75
75
|
case `${GET_NAVIGATION}_SUCCESS`:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
if (!
|
|
76
|
+
// Even if the expander is set or not, if the GET_NAVIGATION is
|
|
77
|
+
// called, we want it to store the data if the actions data is
|
|
78
|
+
// not set in the expander data (['@components']) but in the "normal"
|
|
79
|
+
// action result (we look for the object property returned by the endpoint)
|
|
80
|
+
if (!action.result?.['@components'] && action.result?.items) {
|
|
81
81
|
return {
|
|
82
82
|
...state,
|
|
83
83
|
error: null,
|
|
@@ -172,4 +172,34 @@ describe('Navigation reducer (NAVIGATION)GET_CONTENT', () => {
|
|
|
172
172
|
loading: false,
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
|
+
|
|
176
|
+
it('should handle (NAVIGATION)GET_NAVIGATION_SUCCESS (standalone with apiExpander enabled)', () => {
|
|
177
|
+
expect(
|
|
178
|
+
navigation(undefined, {
|
|
179
|
+
type: `${GET_NAVIGATION}_SUCCESS`,
|
|
180
|
+
result: {
|
|
181
|
+
items: [
|
|
182
|
+
{
|
|
183
|
+
title: 'Welcome to Plone!',
|
|
184
|
+
description:
|
|
185
|
+
'Congratulations! You have successfully installed Plone.',
|
|
186
|
+
'@id': `${settings.apiPath}/front-page`,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
).toEqual({
|
|
192
|
+
error: null,
|
|
193
|
+
items: [
|
|
194
|
+
{
|
|
195
|
+
title: 'Welcome to Plone!',
|
|
196
|
+
description:
|
|
197
|
+
'Congratulations! You have successfully installed Plone.',
|
|
198
|
+
url: '/front-page',
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
loaded: true,
|
|
202
|
+
loading: false,
|
|
203
|
+
});
|
|
204
|
+
});
|
|
175
205
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
extends: existence
|
|
2
|
+
message: Use 'AM' or 'PM' (preceded by a space).
|
|
3
|
+
link: https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/date-time-terms
|
|
4
|
+
level: error
|
|
5
|
+
nonword: true
|
|
6
|
+
tokens:
|
|
7
|
+
- '\d{1,2}[AP]M'
|
|
8
|
+
- '\d{1,2} ?[ap]m'
|
|
9
|
+
- '\d{1,2} ?[aApP]\.[mM]\.'
|