@plone/volto 18.0.0-alpha.18 → 18.0.0-alpha.19
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 +31 -1
- package/locales/ca/LC_MESSAGES/volto.po +17 -42
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +15 -40
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +14 -39
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +15 -40
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +15 -40
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +15 -40
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +15 -40
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +15 -40
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +15 -40
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +14 -39
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +15 -40
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +15 -40
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +15 -40
- package/locales/ro.json +1 -1
- package/locales/volto.pot +15 -40
- package/locales/zh_CN/LC_MESSAGES/volto.po +15 -40
- package/locales/zh_CN.json +1 -1
- package/package.json +10 -20
- package/src/components/index.js +0 -6
- package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +8 -2
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +2 -2
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +33 -5
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx +12 -0
- package/src/components/manage/Controlpanels/Groups/RenderGroups.jsx +22 -11
- package/src/components/manage/Controlpanels/Groups/RenderGroups.test.jsx +21 -0
- package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +30 -21
- package/src/components/manage/Controlpanels/Users/RenderUsers.test.jsx +27 -1
- package/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx +29 -7
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +51 -3
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +8 -0
- package/src/config/Blocks.jsx +63 -67
- package/src/config/Loadables.jsx +0 -22
- package/src/config/Widgets.jsx +0 -2
- package/src/config/index.js +0 -13
- package/src/helpers/User/User.js +29 -0
- package/src/helpers/index.js +6 -1
- package/test-setup-config.js +0 -30
- package/types/components/index.d.ts +0 -6
- package/types/config/Blocks.d.ts +0 -51
- package/types/config/Loadables.d.ts +0 -10
- package/types/config/Widgets.d.ts +0 -2
- package/types/helpers/User/User.d.ts +18 -0
- package/types/helpers/index.d.ts +1 -1
- package/webpack-plugins/webpack-bundle-analyze-plugin.js +1 -1
- package/src/components/manage/AnchorPlugin/components/Link/index.jsx +0 -37
- package/src/components/manage/AnchorPlugin/components/LinkButton/index.jsx +0 -126
- package/src/components/manage/AnchorPlugin/index.jsx +0 -82
- package/src/components/manage/AnchorPlugin/linkStrategy.js +0 -21
- package/src/components/manage/AnchorPlugin/utils/EditorUtils.js +0 -47
- package/src/components/manage/Blocks/HeroImageLeft/Data.jsx +0 -29
- package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +0 -493
- package/src/components/manage/Blocks/HeroImageLeft/Edit.test.jsx +0 -58
- package/src/components/manage/Blocks/HeroImageLeft/View.jsx +0 -37
- package/src/components/manage/Blocks/HeroImageLeft/View.test.jsx +0 -9
- package/src/components/manage/Blocks/HeroImageLeft/schema.js +0 -43
- package/src/components/manage/Blocks/Table/Cell.jsx +0 -206
- package/src/components/manage/Blocks/Table/Cell.test.jsx +0 -19
- package/src/components/manage/Blocks/Table/Edit.jsx +0 -748
- package/src/components/manage/Blocks/Table/Edit.test.jsx +0 -44
- package/src/components/manage/Blocks/Table/Readme.md +0 -5
- package/src/components/manage/Blocks/Table/View.jsx +0 -51
- package/src/components/manage/Blocks/Table/View.test.jsx +0 -41
- package/src/components/manage/Blocks/Text/Edit.jsx +0 -372
- package/src/components/manage/Blocks/Text/Edit.test.jsx +0 -46
- package/src/components/manage/Blocks/Text/Readme.md +0 -5
- package/src/components/manage/Blocks/Text/Schema.jsx +0 -31
- package/src/components/manage/Blocks/Text/View.jsx +0 -26
- package/src/components/manage/Blocks/Text/View.test.jsx +0 -28
- package/src/components/manage/LinkDetectionPlugin/link-detection-plugin.jsx +0 -227
- package/src/components/manage/LinkDetectionPlugin/utils.js +0 -12
- package/src/components/manage/Widgets/WysiwygWidget.jsx +0 -350
- package/src/components/manage/Widgets/WysiwygWidget.stories.jsx +0 -24
- package/src/components/manage/Widgets/WysiwygWidget.test.jsx +0 -37
- package/src/config/RichTextEditor/Blocks.jsx +0 -29
- package/src/config/RichTextEditor/FromHTML.jsx +0 -8
- package/src/config/RichTextEditor/Plugins.jsx +0 -59
- package/src/config/RichTextEditor/Styles.jsx +0 -69
- package/src/config/RichTextEditor/ToHTML.jsx +0 -262
- package/src/config/RichTextEditor/index.js +0 -25
|
@@ -9,6 +9,7 @@ import { Dropdown, Table, Checkbox } from 'semantic-ui-react';
|
|
|
9
9
|
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
10
10
|
import ploneSVG from '@plone/volto/icons/plone.svg';
|
|
11
11
|
import { Icon } from '@plone/volto/components';
|
|
12
|
+
import { canAssignRole } from '@plone/volto/helpers';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* UsersControlpanelGroups class.
|
|
@@ -38,6 +39,7 @@ class RenderGroups extends Component {
|
|
|
38
39
|
).isRequired,
|
|
39
40
|
inheritedRole: PropTypes.array,
|
|
40
41
|
onDelete: PropTypes.func.isRequired,
|
|
42
|
+
isUserManager: PropTypes.bool.isRequired,
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -69,6 +71,12 @@ class RenderGroups extends Component {
|
|
|
69
71
|
isAuthGroup = (roleId) => {
|
|
70
72
|
return this.props.inheritedRole.includes(roleId);
|
|
71
73
|
};
|
|
74
|
+
|
|
75
|
+
canDeleteGroup() {
|
|
76
|
+
if (this.props.isUserManager) return true;
|
|
77
|
+
return !this.props.group.roles.includes('Manager');
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
/**
|
|
73
81
|
* Render method.
|
|
74
82
|
* @method render
|
|
@@ -98,22 +106,25 @@ class RenderGroups extends Component {
|
|
|
98
106
|
}
|
|
99
107
|
onChange={this.onChange}
|
|
100
108
|
value={`${this.props.group.id}&role=${role.id}`}
|
|
109
|
+
disabled={!canAssignRole(this.props.isUserManager, role)}
|
|
101
110
|
/>
|
|
102
111
|
)}
|
|
103
112
|
</Table.Cell>
|
|
104
113
|
))}
|
|
105
114
|
<Table.Cell textAlign="right">
|
|
106
|
-
|
|
107
|
-
<Dropdown
|
|
108
|
-
<Dropdown.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
{this.canDeleteGroup() && (
|
|
116
|
+
<Dropdown icon="ellipsis horizontal">
|
|
117
|
+
<Dropdown.Menu className="left">
|
|
118
|
+
<Dropdown.Item
|
|
119
|
+
onClick={this.props.onDelete}
|
|
120
|
+
value={this.props.group['@id']}
|
|
121
|
+
>
|
|
122
|
+
<Icon name={trashSVG} size="15px" />
|
|
123
|
+
<FormattedMessage id="Delete" defaultMessage="Delete" />
|
|
124
|
+
</Dropdown.Item>
|
|
125
|
+
</Dropdown.Menu>
|
|
126
|
+
</Dropdown>
|
|
127
|
+
)}
|
|
117
128
|
</Table.Cell>
|
|
118
129
|
</Table.Row>
|
|
119
130
|
);
|
|
@@ -49,6 +49,27 @@ describe('UsersControlpanelGroups', () => {
|
|
|
49
49
|
group={testGroups}
|
|
50
50
|
roles={testRoles}
|
|
51
51
|
onDelete={() => {}}
|
|
52
|
+
isUserManager={true}
|
|
53
|
+
/>
|
|
54
|
+
</Provider>,
|
|
55
|
+
);
|
|
56
|
+
const json = component.toJSON();
|
|
57
|
+
expect(json).toMatchSnapshot();
|
|
58
|
+
});
|
|
59
|
+
it('renders a UsersControlpanelGroups component with no Manager user', () => {
|
|
60
|
+
const store = mockStore({
|
|
61
|
+
intl: {
|
|
62
|
+
locale: 'en',
|
|
63
|
+
messages: {},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const component = renderer.create(
|
|
67
|
+
<Provider store={store}>
|
|
68
|
+
<RenderGroups
|
|
69
|
+
group={testGroups}
|
|
70
|
+
roles={testRoles}
|
|
71
|
+
onDelete={() => {}}
|
|
72
|
+
isUserManager={false}
|
|
52
73
|
/>
|
|
53
74
|
</Provider>,
|
|
54
75
|
);
|
|
@@ -13,7 +13,7 @@ import { updateUser } from '@plone/volto/actions';
|
|
|
13
13
|
import ploneSVG from '@plone/volto/icons/plone.svg';
|
|
14
14
|
import { compose } from 'redux';
|
|
15
15
|
import { connect } from 'react-redux';
|
|
16
|
-
import { messages } from '@plone/volto/helpers';
|
|
16
|
+
import { messages, canAssignRole } from '@plone/volto/helpers';
|
|
17
17
|
import { toast } from 'react-toastify';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -39,6 +39,7 @@ class RenderUsers extends Component {
|
|
|
39
39
|
}),
|
|
40
40
|
).isRequired,
|
|
41
41
|
onDelete: PropTypes.func.isRequired,
|
|
42
|
+
isUserManager: PropTypes.bool.isRequired,
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
/**
|
|
@@ -110,6 +111,11 @@ class RenderUsers extends Component {
|
|
|
110
111
|
this.setState({ user: { ...formData } });
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
canDeleteUser() {
|
|
115
|
+
if (this.props.isUserManager) return true;
|
|
116
|
+
return !this.props.user.roles.includes('Manager');
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
/**
|
|
114
120
|
* Render method.
|
|
115
121
|
* @method render
|
|
@@ -139,35 +145,38 @@ class RenderUsers extends Component {
|
|
|
139
145
|
checked={this.props.user.roles.includes(role.id)}
|
|
140
146
|
onChange={this.onChange}
|
|
141
147
|
value={`${this.props.user.id}&role=${role.id}`}
|
|
148
|
+
disabled={!canAssignRole(this.props.isUserManager, role)}
|
|
142
149
|
/>
|
|
143
150
|
)}
|
|
144
151
|
</Table.Cell>
|
|
145
152
|
))}
|
|
146
153
|
<Table.Cell textAlign="right">
|
|
147
|
-
|
|
148
|
-
<Dropdown
|
|
149
|
-
|
|
154
|
+
{this.canDeleteUser() && (
|
|
155
|
+
<Dropdown icon="ellipsis horizontal">
|
|
156
|
+
<Dropdown.Menu className="left">
|
|
157
|
+
{this.props.userschema && (
|
|
158
|
+
<Dropdown.Item
|
|
159
|
+
id="edit-user-button"
|
|
160
|
+
onClick={() => {
|
|
161
|
+
this.onClickEdit({ formData: this.props.user });
|
|
162
|
+
}}
|
|
163
|
+
value={this.props.user['@id']}
|
|
164
|
+
>
|
|
165
|
+
<Icon name={editSVG} size="15px" />
|
|
166
|
+
<FormattedMessage id="Edit" defaultMessage="Edit" />
|
|
167
|
+
</Dropdown.Item>
|
|
168
|
+
)}
|
|
150
169
|
<Dropdown.Item
|
|
151
|
-
id="
|
|
152
|
-
onClick={
|
|
153
|
-
this.onClickEdit({ formData: this.props.user });
|
|
154
|
-
}}
|
|
170
|
+
id="delete-user-button"
|
|
171
|
+
onClick={this.props.onDelete}
|
|
155
172
|
value={this.props.user['@id']}
|
|
156
173
|
>
|
|
157
|
-
<Icon name={
|
|
158
|
-
<FormattedMessage id="
|
|
174
|
+
<Icon name={trashSVG} size="15px" />
|
|
175
|
+
<FormattedMessage id="Delete" defaultMessage="Delete" />
|
|
159
176
|
</Dropdown.Item>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
onClick={this.props.onDelete}
|
|
164
|
-
value={this.props.user['@id']}
|
|
165
|
-
>
|
|
166
|
-
<Icon name={trashSVG} size="15px" />
|
|
167
|
-
<FormattedMessage id="Delete" defaultMessage="Delete" />
|
|
168
|
-
</Dropdown.Item>
|
|
169
|
-
</Dropdown.Menu>
|
|
170
|
-
</Dropdown>
|
|
177
|
+
</Dropdown.Menu>
|
|
178
|
+
</Dropdown>
|
|
179
|
+
)}
|
|
171
180
|
</Table.Cell>
|
|
172
181
|
{Object.keys(this.state.user).length > 0 &&
|
|
173
182
|
this.props.userschema.loaded && (
|
|
@@ -47,7 +47,33 @@ describe('UsersControlpanelUser', () => {
|
|
|
47
47
|
});
|
|
48
48
|
const component = renderer.create(
|
|
49
49
|
<Provider store={store}>
|
|
50
|
-
<RenderUsers
|
|
50
|
+
<RenderUsers
|
|
51
|
+
user={testUser}
|
|
52
|
+
roles={testRoles}
|
|
53
|
+
onDelete={() => {}}
|
|
54
|
+
isUserManager={true}
|
|
55
|
+
/>
|
|
56
|
+
</Provider>,
|
|
57
|
+
);
|
|
58
|
+
const json = component.toJSON();
|
|
59
|
+
expect(json).toMatchSnapshot();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('renders a UsersControlpanelUser component with options disabled if not allowed', () => {
|
|
63
|
+
const store = mockStore({
|
|
64
|
+
intl: {
|
|
65
|
+
locale: 'en',
|
|
66
|
+
messages: {},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
const component = renderer.create(
|
|
70
|
+
<Provider store={store}>
|
|
71
|
+
<RenderUsers
|
|
72
|
+
user={testUser}
|
|
73
|
+
roles={testRoles}
|
|
74
|
+
onDelete={() => {}}
|
|
75
|
+
isUserManager={false}
|
|
76
|
+
/>
|
|
51
77
|
</Provider>,
|
|
52
78
|
);
|
|
53
79
|
const json = component.toJSON();
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { cloneDeep, uniqBy } from 'lodash';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
|
-
import { useSelector, useDispatch } from 'react-redux';
|
|
4
|
+
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
|
5
|
+
import jwtDecode from 'jwt-decode';
|
|
5
6
|
import { toast } from 'react-toastify';
|
|
6
7
|
import { Button, Checkbox } from 'semantic-ui-react';
|
|
7
|
-
import { messages } from '@plone/volto/helpers';
|
|
8
|
-
import { listGroups } from '@plone/volto/actions';
|
|
8
|
+
import { messages, isManager, canAssignGroup } from '@plone/volto/helpers';
|
|
9
|
+
import { listGroups, getUser } from '@plone/volto/actions';
|
|
9
10
|
import { Icon, Toast } from '@plone/volto/components';
|
|
10
11
|
import { updateGroup, listUsers } from '@plone/volto/actions';
|
|
11
12
|
|
|
@@ -25,6 +26,16 @@ const ListingTemplate = ({
|
|
|
25
26
|
const pageSize = 25;
|
|
26
27
|
const [userLimit, setUserLimit] = useState(pageSize);
|
|
27
28
|
|
|
29
|
+
const token = useSelector((state) => state.userSession.token, shallowEqual);
|
|
30
|
+
const user = useSelector((state) => state.users.user);
|
|
31
|
+
const userId = token ? jwtDecode(token).sub : '';
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
dispatch(getUser(userId));
|
|
35
|
+
}, [dispatch, userId]);
|
|
36
|
+
|
|
37
|
+
const isUserManager = isManager(user);
|
|
38
|
+
|
|
28
39
|
// y axis
|
|
29
40
|
let items = useSelector((state) => state.users.users);
|
|
30
41
|
let show_users =
|
|
@@ -51,12 +62,17 @@ const ListingTemplate = ({
|
|
|
51
62
|
|
|
52
63
|
// x axis
|
|
53
64
|
let groups = useSelector((state) => state.groups.groups);
|
|
65
|
+
|
|
66
|
+
const getRoles = (group_id) => {
|
|
67
|
+
return groups.find((group) => group.id === group_id)?.roles || [];
|
|
68
|
+
};
|
|
69
|
+
|
|
54
70
|
let show_matrix_options =
|
|
55
71
|
!many_groups ||
|
|
56
72
|
(many_groups && query_group.length > 1) ||
|
|
57
73
|
groups_filter.length > 0 ||
|
|
58
74
|
add_joined_groups;
|
|
59
|
-
let matrix_options; // list of Objects (value, label)
|
|
75
|
+
let matrix_options; // list of Objects (value, label, roles)
|
|
60
76
|
if (show_matrix_options) {
|
|
61
77
|
matrix_options =
|
|
62
78
|
!many_groups || (many_groups && query_group.length > 1)
|
|
@@ -90,6 +106,10 @@ const ListingTemplate = ({
|
|
|
90
106
|
}
|
|
91
107
|
return 0;
|
|
92
108
|
});
|
|
109
|
+
matrix_options = matrix_options.map((matrix_option) => ({
|
|
110
|
+
...matrix_option,
|
|
111
|
+
roles: getRoles(matrix_option.value),
|
|
112
|
+
}));
|
|
93
113
|
} else {
|
|
94
114
|
matrix_options = [];
|
|
95
115
|
}
|
|
@@ -126,7 +146,7 @@ const ListingTemplate = ({
|
|
|
126
146
|
},
|
|
127
147
|
}),
|
|
128
148
|
)
|
|
129
|
-
.then((
|
|
149
|
+
.then(() => {
|
|
130
150
|
singleClick &&
|
|
131
151
|
dispatch(
|
|
132
152
|
listUsers({
|
|
@@ -209,13 +229,14 @@ const ListingTemplate = ({
|
|
|
209
229
|
<Checkbox
|
|
210
230
|
className="toggle-target"
|
|
211
231
|
defaultChecked={false}
|
|
212
|
-
onChange={(
|
|
232
|
+
onChange={(_event, { checked }) =>
|
|
213
233
|
onSelectAllHandler(
|
|
214
234
|
matrix_option.value,
|
|
215
235
|
items.map((el) => el.id),
|
|
216
236
|
checked,
|
|
217
237
|
)
|
|
218
238
|
}
|
|
239
|
+
disabled={!canAssignGroup(isUserManager, matrix_option)}
|
|
219
240
|
/>
|
|
220
241
|
</div>
|
|
221
242
|
))}
|
|
@@ -251,13 +272,14 @@ const ListingTemplate = ({
|
|
|
251
272
|
checked={item.groups?.items
|
|
252
273
|
?.map((el) => el.id)
|
|
253
274
|
.includes(matrix_option.value)}
|
|
254
|
-
onChange={(
|
|
275
|
+
onChange={(_event, { checked }) => {
|
|
255
276
|
onSelectOptionHandler(
|
|
256
277
|
{ y: matrix_option.value, x: item.id },
|
|
257
278
|
checked,
|
|
258
279
|
true,
|
|
259
280
|
);
|
|
260
281
|
}}
|
|
282
|
+
disabled={!canAssignGroup(isUserManager, matrix_option)}
|
|
261
283
|
/>
|
|
262
284
|
))}
|
|
263
285
|
</div>
|
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
updateUser,
|
|
13
13
|
updateGroup,
|
|
14
14
|
getUserSchema,
|
|
15
|
+
getUser,
|
|
15
16
|
} from '@plone/volto/actions';
|
|
17
|
+
import jwtDecode from 'jwt-decode';
|
|
16
18
|
import {
|
|
17
19
|
Icon,
|
|
18
20
|
ModalForm,
|
|
@@ -23,7 +25,12 @@ import {
|
|
|
23
25
|
Error,
|
|
24
26
|
} from '@plone/volto/components';
|
|
25
27
|
import { Link } from 'react-router-dom';
|
|
26
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
Helmet,
|
|
30
|
+
messages,
|
|
31
|
+
isManager,
|
|
32
|
+
canAssignGroup,
|
|
33
|
+
} from '@plone/volto/helpers';
|
|
27
34
|
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
28
35
|
import addUserSvg from '@plone/volto/icons/add-user.svg';
|
|
29
36
|
import saveSVG from '@plone/volto/icons/save.svg';
|
|
@@ -77,6 +84,19 @@ class UsersControlpanel extends Component {
|
|
|
77
84
|
roles: PropTypes.arrayOf(PropTypes.string),
|
|
78
85
|
}),
|
|
79
86
|
).isRequired,
|
|
87
|
+
user: PropTypes.shape({
|
|
88
|
+
'@id': PropTypes.string,
|
|
89
|
+
id: PropTypes.string,
|
|
90
|
+
description: PropTypes.string,
|
|
91
|
+
email: PropTypes.string,
|
|
92
|
+
fullname: PropTypes.string,
|
|
93
|
+
groups: PropTypes.object,
|
|
94
|
+
location: PropTypes.string,
|
|
95
|
+
portrait: PropTypes.string,
|
|
96
|
+
home_page: PropTypes.string,
|
|
97
|
+
roles: PropTypes.arrayOf(PropTypes.string),
|
|
98
|
+
username: PropTypes.string,
|
|
99
|
+
}).isRequired,
|
|
80
100
|
};
|
|
81
101
|
|
|
82
102
|
/**
|
|
@@ -124,6 +144,7 @@ class UsersControlpanel extends Component {
|
|
|
124
144
|
});
|
|
125
145
|
}
|
|
126
146
|
await this.props.getUserSchema();
|
|
147
|
+
await this.props.getUser(this.props.userId);
|
|
127
148
|
};
|
|
128
149
|
|
|
129
150
|
// Because username field needs to be disabled if email login is enabled!
|
|
@@ -406,6 +427,20 @@ class UsersControlpanel extends Component {
|
|
|
406
427
|
}
|
|
407
428
|
}
|
|
408
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Filters the roles a user can assign when adding a user.
|
|
432
|
+
* @method canAssignAdd
|
|
433
|
+
* @returns {arry}
|
|
434
|
+
*/
|
|
435
|
+
canAssignAdd(isManager) {
|
|
436
|
+
if (isManager) return this.props.roles;
|
|
437
|
+
return this.props.user?.roles
|
|
438
|
+
? this.props.roles.filter((role) =>
|
|
439
|
+
this.props.user.roles.includes(role.id),
|
|
440
|
+
)
|
|
441
|
+
: [];
|
|
442
|
+
}
|
|
443
|
+
|
|
409
444
|
/**
|
|
410
445
|
* Render method.
|
|
411
446
|
* @method render
|
|
@@ -426,7 +461,9 @@ class UsersControlpanel extends Component {
|
|
|
426
461
|
// of the userschema is changed and it is used like that through
|
|
427
462
|
// the lifecycle of the application
|
|
428
463
|
let adduserschema = {};
|
|
464
|
+
let isUserManager = false;
|
|
429
465
|
if (this.props?.userschema?.loaded) {
|
|
466
|
+
isUserManager = isManager(this.props.user);
|
|
430
467
|
adduserschema = JSON.parse(
|
|
431
468
|
JSON.stringify(this.props?.userschema?.userschema),
|
|
432
469
|
);
|
|
@@ -454,13 +491,18 @@ class UsersControlpanel extends Component {
|
|
|
454
491
|
adduserschema.properties['roles'] = {
|
|
455
492
|
title: this.props.intl.formatMessage(messages.addUserFormRolesTitle),
|
|
456
493
|
type: 'array',
|
|
457
|
-
choices: this.
|
|
494
|
+
choices: this.canAssignAdd(isUserManager).map((role) => [
|
|
495
|
+
role.id,
|
|
496
|
+
role.title,
|
|
497
|
+
]),
|
|
458
498
|
noValueOption: false,
|
|
459
499
|
};
|
|
460
500
|
adduserschema.properties['groups'] = {
|
|
461
501
|
title: this.props.intl.formatMessage(messages.addUserGroupNameTitle),
|
|
462
502
|
type: 'array',
|
|
463
|
-
choices: this.props.groups
|
|
503
|
+
choices: this.props.groups
|
|
504
|
+
.filter((group) => canAssignGroup(isUserManager, group))
|
|
505
|
+
.map((group) => [group.id, group.id]),
|
|
464
506
|
noValueOption: false,
|
|
465
507
|
};
|
|
466
508
|
if (
|
|
@@ -598,6 +640,7 @@ class UsersControlpanel extends Component {
|
|
|
598
640
|
inheritedRole={this.props.inheritedRole}
|
|
599
641
|
userschema={this.props.userschema}
|
|
600
642
|
listUsers={this.props.listUsers}
|
|
643
|
+
isUserManager={isUserManager}
|
|
601
644
|
/>
|
|
602
645
|
))}
|
|
603
646
|
</Table.Body>
|
|
@@ -686,6 +729,10 @@ export default compose(
|
|
|
686
729
|
(state, props) => ({
|
|
687
730
|
roles: state.roles.roles,
|
|
688
731
|
users: state.users.users,
|
|
732
|
+
user: state.users.user,
|
|
733
|
+
userId: state.userSession.token
|
|
734
|
+
? jwtDecode(state.userSession.token).sub
|
|
735
|
+
: '',
|
|
689
736
|
groups: state.groups.groups,
|
|
690
737
|
many_users: state.controlpanels?.controlpanel?.data?.many_users,
|
|
691
738
|
many_groups: state.controlpanels?.controlpanel?.data?.many_groups,
|
|
@@ -710,6 +757,7 @@ export default compose(
|
|
|
710
757
|
updateUser,
|
|
711
758
|
updateGroup,
|
|
712
759
|
getUserSchema,
|
|
760
|
+
getUser,
|
|
713
761
|
},
|
|
714
762
|
dispatch,
|
|
715
763
|
),
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import jwt from 'jsonwebtoken';
|
|
5
6
|
|
|
6
7
|
import UsersControlpanel from './UsersControlpanel';
|
|
7
8
|
|
|
@@ -11,10 +12,17 @@ jest.mock('../../Toolbar/Toolbar', () => jest.fn(() => <div id="Portal" />));
|
|
|
11
12
|
describe('UsersControlpanel', () => {
|
|
12
13
|
it('renders a user control component', () => {
|
|
13
14
|
const store = mockStore({
|
|
15
|
+
userSession: {
|
|
16
|
+
token: jwt.sign({ sub: 'john' }, 'secret'),
|
|
17
|
+
},
|
|
14
18
|
roles: { roles: [] },
|
|
15
19
|
users: {
|
|
16
20
|
users: [],
|
|
17
21
|
create: { loading: false },
|
|
22
|
+
user: {
|
|
23
|
+
roles: ['Manager'],
|
|
24
|
+
'@id': 'admin',
|
|
25
|
+
},
|
|
18
26
|
},
|
|
19
27
|
groups: {
|
|
20
28
|
groups: [],
|
package/src/config/Blocks.jsx
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import { defineMessages } from 'react-intl';
|
|
2
|
+
import { cloneDeep } from 'lodash';
|
|
2
3
|
|
|
3
4
|
import ViewTitleBlock from '@plone/volto/components/manage/Blocks/Title/View';
|
|
4
5
|
import ViewDescriptionBlock from '@plone/volto/components/manage/Blocks/Description/View';
|
|
5
6
|
import ViewToCBlock from '@plone/volto/components/manage/Blocks/ToC/View';
|
|
6
|
-
import ViewTextBlock from '@plone/volto/components/manage/Blocks/Text/View';
|
|
7
7
|
import ViewImageBlock from '@plone/volto/components/manage/Blocks/Image/View';
|
|
8
8
|
import ViewLeadImageBlock from '@plone/volto/components/manage/Blocks/LeadImage/View';
|
|
9
9
|
import ViewListingBlock from '@plone/volto/components/manage/Blocks/Listing/View';
|
|
10
10
|
import ViewVideoBlock from '@plone/volto/components/manage/Blocks/Video/View';
|
|
11
|
-
import ViewHeroImageLeftBlock from '@plone/volto/components/manage/Blocks/HeroImageLeft/View';
|
|
12
11
|
import ViewMapBlock from '@plone/volto/components/manage/Blocks/Maps/View';
|
|
13
12
|
import ViewHTMLBlock from '@plone/volto/components/manage/Blocks/HTML/View';
|
|
14
|
-
import ViewTableBlock from '@plone/volto/components/manage/Blocks/Table/View';
|
|
15
13
|
|
|
16
14
|
import EditTitleBlock from '@plone/volto/components/manage/Blocks/Title/Edit';
|
|
17
15
|
import EditDescriptionBlock from '@plone/volto/components/manage/Blocks/Description/Edit';
|
|
18
16
|
import EditToCBlock from '@plone/volto/components/manage/Blocks/ToC/Edit';
|
|
19
|
-
import EditTextBlock from '@plone/volto/components/manage/Blocks/Text/Edit';
|
|
20
17
|
import EditImageBlock from '@plone/volto/components/manage/Blocks/Image/Edit';
|
|
21
18
|
import EditLeadImageBlock from '@plone/volto/components/manage/Blocks/LeadImage/Edit';
|
|
22
19
|
import EditListingBlock from '@plone/volto/components/manage/Blocks/Listing/Edit';
|
|
@@ -25,20 +22,15 @@ import GalleryNoResultsComponent from '@plone/volto/components/manage/Blocks/Lis
|
|
|
25
22
|
import DefaultListingBlockTemplate from '@plone/volto/components/manage/Blocks/Listing/DefaultTemplate';
|
|
26
23
|
import SummaryListingBlockTemplate from '@plone/volto/components/manage/Blocks/Listing/SummaryTemplate';
|
|
27
24
|
import EditVideoBlock from '@plone/volto/components/manage/Blocks/Video/Edit';
|
|
28
|
-
import EditHeroImageLeftBlock from '@plone/volto/components/manage/Blocks/HeroImageLeft/Edit';
|
|
29
25
|
import EditMapBlock from '@plone/volto/components/manage/Blocks/Maps/Edit';
|
|
30
26
|
import EditHTMLBlock from '@plone/volto/components/manage/Blocks/HTML/Edit';
|
|
31
|
-
import EditTableBlock from '@plone/volto/components/manage/Blocks/Table/Edit';
|
|
32
27
|
|
|
33
28
|
import descriptionSVG from '@plone/volto/icons/description.svg';
|
|
34
29
|
import titleSVG from '@plone/volto/icons/text.svg';
|
|
35
|
-
import textSVG from '@plone/volto/icons/subtext.svg';
|
|
36
30
|
import cameraSVG from '@plone/volto/icons/camera.svg';
|
|
37
31
|
import videoSVG from '@plone/volto/icons/videocamera.svg';
|
|
38
32
|
import globeSVG from '@plone/volto/icons/globe.svg';
|
|
39
33
|
import codeSVG from '@plone/volto/icons/code.svg';
|
|
40
|
-
import heroSVG from '@plone/volto/icons/hero.svg';
|
|
41
|
-
import tableSVG from '@plone/volto/icons/table.svg';
|
|
42
34
|
import listingBlockSVG from '@plone/volto/icons/content-listing.svg';
|
|
43
35
|
import tocSVG from '@plone/volto/icons/list-bullet.svg';
|
|
44
36
|
import searchSVG from '@plone/volto/icons/zoom.svg';
|
|
@@ -47,7 +39,6 @@ import imagesSVG from '@plone/volto/icons/images.svg';
|
|
|
47
39
|
|
|
48
40
|
import ImageGalleryListingBlockTemplate from '@plone/volto/components/manage/Blocks/Listing/ImageGallery';
|
|
49
41
|
import BlockSettingsSchema from '@plone/volto/components/manage/Blocks/Block/Schema';
|
|
50
|
-
import TextSettingsSchema from '@plone/volto/components/manage/Blocks/Text/Schema';
|
|
51
42
|
import ImageSettingsSchema from '@plone/volto/components/manage/Blocks/Image/LayoutSchema';
|
|
52
43
|
import ToCSettingsSchema from '@plone/volto/components/manage/Blocks/ToC/Schema';
|
|
53
44
|
|
|
@@ -79,7 +70,6 @@ import { getImageBlockSizes } from '@plone/volto/components/manage/Blocks/Image/
|
|
|
79
70
|
import { getLeadImageBlockSizes } from '@plone/volto/components/manage/Blocks/LeadImage/utils';
|
|
80
71
|
|
|
81
72
|
// block sidebar schemas (not the Dexterity Layout block settings schemas)
|
|
82
|
-
import HeroImageLeftBlockSchema from '@plone/volto/components/manage/Blocks/HeroImageLeft/schema';
|
|
83
73
|
import ListingBlockSchema from '@plone/volto/components/manage/Blocks/Listing/schema';
|
|
84
74
|
import SearchBlockSchema from '@plone/volto/components/manage/Blocks/Search/schema';
|
|
85
75
|
|
|
@@ -191,6 +181,63 @@ defineMessages({
|
|
|
191
181
|
id: 'toggleFacet',
|
|
192
182
|
defaultMessage: 'Toggle',
|
|
193
183
|
},
|
|
184
|
+
// BBB Table messages
|
|
185
|
+
Table: {
|
|
186
|
+
id: 'Table',
|
|
187
|
+
defaultMessage: 'Table',
|
|
188
|
+
},
|
|
189
|
+
cell: {
|
|
190
|
+
id: 'Cell',
|
|
191
|
+
defaultMessage: 'Cell',
|
|
192
|
+
},
|
|
193
|
+
insertRowBefore: {
|
|
194
|
+
id: 'Insert row before',
|
|
195
|
+
defaultMessage: 'Insert row before',
|
|
196
|
+
},
|
|
197
|
+
insertRowAfter: {
|
|
198
|
+
id: 'Insert row after',
|
|
199
|
+
defaultMessage: 'Insert row after',
|
|
200
|
+
},
|
|
201
|
+
deleteRow: {
|
|
202
|
+
id: 'Delete row',
|
|
203
|
+
defaultMessage: 'Delete row',
|
|
204
|
+
},
|
|
205
|
+
insertColBefore: {
|
|
206
|
+
id: 'Insert col before',
|
|
207
|
+
defaultMessage: 'Insert col before',
|
|
208
|
+
},
|
|
209
|
+
insertColAfter: {
|
|
210
|
+
id: 'Insert col after',
|
|
211
|
+
defaultMessage: 'Insert col after',
|
|
212
|
+
},
|
|
213
|
+
deleteCol: {
|
|
214
|
+
id: 'Delete col',
|
|
215
|
+
defaultMessage: 'Delete col',
|
|
216
|
+
},
|
|
217
|
+
fixed: {
|
|
218
|
+
id: 'Fixed width table cells',
|
|
219
|
+
defaultMessage: 'Fixed width columns',
|
|
220
|
+
},
|
|
221
|
+
compact: {
|
|
222
|
+
id: 'Make the table compact',
|
|
223
|
+
defaultMessage: 'Reduce cell padding',
|
|
224
|
+
},
|
|
225
|
+
basic: {
|
|
226
|
+
id: 'Reduce complexity',
|
|
227
|
+
defaultMessage: 'Minimalistic table design',
|
|
228
|
+
},
|
|
229
|
+
celled: {
|
|
230
|
+
id: 'Divide each row into separate cells',
|
|
231
|
+
defaultMessage: 'Add border to inner columns',
|
|
232
|
+
},
|
|
233
|
+
striped: {
|
|
234
|
+
id: 'Stripe alternate rows with color',
|
|
235
|
+
defaultMessage: 'Alternate row background color',
|
|
236
|
+
},
|
|
237
|
+
headerCell: {
|
|
238
|
+
id: 'Header cell',
|
|
239
|
+
defaultMessage: 'Header cell',
|
|
240
|
+
},
|
|
194
241
|
});
|
|
195
242
|
|
|
196
243
|
const groupBlocksOrder = [
|
|
@@ -230,25 +277,6 @@ const blocksConfig = {
|
|
|
230
277
|
blockHasOwnFocusManagement: true,
|
|
231
278
|
sidebarTab: 0,
|
|
232
279
|
},
|
|
233
|
-
text: {
|
|
234
|
-
id: 'text',
|
|
235
|
-
title: 'Text',
|
|
236
|
-
icon: textSVG,
|
|
237
|
-
group: 'text',
|
|
238
|
-
view: ViewTextBlock,
|
|
239
|
-
edit: EditTextBlock,
|
|
240
|
-
schema: TextSettingsSchema,
|
|
241
|
-
restricted: false,
|
|
242
|
-
mostUsed: false,
|
|
243
|
-
blockHasOwnFocusManagement: true,
|
|
244
|
-
sidebarTab: 0,
|
|
245
|
-
blockHasValue: (data) => {
|
|
246
|
-
const isEmpty =
|
|
247
|
-
!data.text ||
|
|
248
|
-
(data.text?.blocks?.length === 1 && data.text.blocks[0].text === '');
|
|
249
|
-
return !isEmpty;
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
280
|
image: {
|
|
253
281
|
id: 'image',
|
|
254
282
|
title: 'Image',
|
|
@@ -335,21 +363,6 @@ const blocksConfig = {
|
|
|
335
363
|
mostUsed: false,
|
|
336
364
|
sidebarTab: 1,
|
|
337
365
|
},
|
|
338
|
-
hero: {
|
|
339
|
-
id: 'hero',
|
|
340
|
-
title: 'Hero',
|
|
341
|
-
icon: heroSVG,
|
|
342
|
-
group: 'common',
|
|
343
|
-
view: ViewHeroImageLeftBlock,
|
|
344
|
-
edit: EditHeroImageLeftBlock,
|
|
345
|
-
schema: BlockSettingsSchema,
|
|
346
|
-
blockSchema: HeroImageLeftBlockSchema,
|
|
347
|
-
restricted: false,
|
|
348
|
-
mostUsed: false,
|
|
349
|
-
blockHasOwnFocusManagement: true,
|
|
350
|
-
sidebarTab: 1,
|
|
351
|
-
},
|
|
352
|
-
|
|
353
366
|
maps: {
|
|
354
367
|
id: 'maps',
|
|
355
368
|
title: 'Maps',
|
|
@@ -374,19 +387,6 @@ const blocksConfig = {
|
|
|
374
387
|
mostUsed: false,
|
|
375
388
|
sidebarTab: 0,
|
|
376
389
|
},
|
|
377
|
-
table: {
|
|
378
|
-
id: 'table',
|
|
379
|
-
title: 'Table',
|
|
380
|
-
icon: tableSVG,
|
|
381
|
-
group: 'common',
|
|
382
|
-
view: ViewTableBlock,
|
|
383
|
-
edit: EditTableBlock,
|
|
384
|
-
schema: BlockSettingsSchema,
|
|
385
|
-
restricted: false,
|
|
386
|
-
mostUsed: false,
|
|
387
|
-
blockHasOwnFocusManagement: true,
|
|
388
|
-
sidebarTab: 1,
|
|
389
|
-
},
|
|
390
390
|
search: {
|
|
391
391
|
id: 'search',
|
|
392
392
|
title: 'Search',
|
|
@@ -519,15 +519,11 @@ const blocksConfig = {
|
|
|
519
519
|
// for the grid block, since we need to modify how the inner teaser
|
|
520
520
|
// block behave in it (= no schemaEnhancer fields for teasers inside a grid)
|
|
521
521
|
// Afterwards, it can be further customized in add-ons using the same technique.
|
|
522
|
-
blocksConfig.gridBlock.blocksConfig =
|
|
523
|
-
blocksConfig.gridBlock.blocksConfig.teaser =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
blocksConfig.gridBlock.blocksConfig.image = {
|
|
528
|
-
...blocksConfig.image,
|
|
529
|
-
schemaEnhancer: gridImageDisableSizeAndPositionHandlersSchema,
|
|
530
|
-
};
|
|
522
|
+
blocksConfig.gridBlock.blocksConfig = cloneDeep(blocksConfig);
|
|
523
|
+
blocksConfig.gridBlock.blocksConfig.teaser.schemaEnhancer =
|
|
524
|
+
gridTeaserDisableStylingSchema;
|
|
525
|
+
blocksConfig.gridBlock.blocksConfig.image.schemaEnhancer =
|
|
526
|
+
gridImageDisableSizeAndPositionHandlersSchema;
|
|
531
527
|
|
|
532
528
|
const requiredBlocks = ['title'];
|
|
533
529
|
|