@plone/volto 19.0.0-alpha.5 → 19.0.0-alpha.7

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.
Files changed (123) hide show
  1. package/.eslintrc +20 -0
  2. package/CHANGELOG.md +73 -0
  3. package/README.md +2 -2
  4. package/cypress/support/commands.js +5 -6
  5. package/locales/af.json +1 -1
  6. package/locales/ar.json +1 -1
  7. package/locales/bg.json +1 -1
  8. package/locales/bn.json +1 -1
  9. package/locales/ca/LC_MESSAGES/volto.po +25 -5
  10. package/locales/ca.json +1 -1
  11. package/locales/cs.json +1 -1
  12. package/locales/cy.json +1 -1
  13. package/locales/da.json +1 -1
  14. package/locales/de/LC_MESSAGES/volto.po +25 -5
  15. package/locales/de.json +1 -1
  16. package/locales/el.json +1 -1
  17. package/locales/en/LC_MESSAGES/volto.po +25 -5
  18. package/locales/en.json +1 -1
  19. package/locales/en_AU.json +1 -1
  20. package/locales/en_GB.json +1 -1
  21. package/locales/eo.json +1 -1
  22. package/locales/es/LC_MESSAGES/volto.po +25 -5
  23. package/locales/es.json +1 -1
  24. package/locales/et.json +1 -1
  25. package/locales/eu/LC_MESSAGES/volto.po +25 -5
  26. package/locales/eu.json +1 -1
  27. package/locales/fa.json +1 -1
  28. package/locales/fi/LC_MESSAGES/volto.po +25 -5
  29. package/locales/fi.json +1 -1
  30. package/locales/fr/LC_MESSAGES/volto.po +25 -5
  31. package/locales/fr.json +1 -1
  32. package/locales/fu.json +1 -1
  33. package/locales/gl.json +1 -1
  34. package/locales/he.json +1 -1
  35. package/locales/hi/LC_MESSAGES/volto.po +25 -5
  36. package/locales/hi.json +1 -1
  37. package/locales/hr.json +1 -1
  38. package/locales/hu.json +1 -1
  39. package/locales/hy.json +1 -1
  40. package/locales/id.json +1 -1
  41. package/locales/it/LC_MESSAGES/volto.po +26 -6
  42. package/locales/it.json +1 -1
  43. package/locales/ja/LC_MESSAGES/volto.po +25 -5
  44. package/locales/ja.json +1 -1
  45. package/locales/ka.json +1 -1
  46. package/locales/kn.json +1 -1
  47. package/locales/ko.json +1 -1
  48. package/locales/lt.json +1 -1
  49. package/locales/lv.json +1 -1
  50. package/locales/mi.json +1 -1
  51. package/locales/mk.json +1 -1
  52. package/locales/my.json +1 -1
  53. package/locales/nb_NO.json +1 -1
  54. package/locales/nl/LC_MESSAGES/volto.po +25 -5
  55. package/locales/nl.json +1 -1
  56. package/locales/nn.json +1 -1
  57. package/locales/pl.json +1 -1
  58. package/locales/pt/LC_MESSAGES/volto.po +25 -5
  59. package/locales/pt.json +1 -1
  60. package/locales/pt_BR/LC_MESSAGES/volto.po +36 -16
  61. package/locales/pt_BR.json +1 -1
  62. package/locales/rm.json +1 -1
  63. package/locales/ro/LC_MESSAGES/volto.po +25 -5
  64. package/locales/ro.json +1 -1
  65. package/locales/ru/LC_MESSAGES/volto.po +25 -5
  66. package/locales/ru.json +1 -1
  67. package/locales/sk.json +1 -1
  68. package/locales/sl.json +1 -1
  69. package/locales/sm.json +1 -1
  70. package/locales/sq.json +1 -1
  71. package/locales/sr.json +1 -1
  72. package/locales/sr@cyrl.json +1 -1
  73. package/locales/sr@latn.json +1 -1
  74. package/locales/sv.json +1 -1
  75. package/locales/ta.json +1 -1
  76. package/locales/te.json +1 -1
  77. package/locales/th.json +1 -1
  78. package/locales/to.json +1 -1
  79. package/locales/tr.json +1 -1
  80. package/locales/uk.json +1 -1
  81. package/locales/vi.json +1 -1
  82. package/locales/volto.pot +26 -6
  83. package/locales/zh_CN/LC_MESSAGES/volto.po +25 -5
  84. package/locales/zh_CN.json +1 -1
  85. package/locales/zh_Hant.json +1 -1
  86. package/locales/zh_Hant_HK.json +1 -1
  87. package/package.json +10 -10
  88. package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +9 -4
  89. package/src/components/manage/Blocks/LeadImage/Edit.jsx +2 -2
  90. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +1 -1
  91. package/src/components/manage/Blocks/Maps/Edit.jsx +2 -1
  92. package/src/components/manage/Blocks/Teaser/Data.jsx +20 -6
  93. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +1 -1
  94. package/src/components/manage/Blocks/Video/Edit.jsx +2 -1
  95. package/src/components/manage/Contents/Contents.jsx +20 -2
  96. package/src/components/manage/Controlpanels/ContentType.jsx +1 -1
  97. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +3 -2
  98. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +156 -175
  99. package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +2 -1
  100. package/src/components/manage/TemplateChooser/TemplateChooser.jsx +2 -1
  101. package/src/components/manage/Toolbar/PersonalTools.jsx +2 -1
  102. package/src/components/manage/Widgets/DatetimeWidget.jsx +5 -0
  103. package/src/components/manage/Widgets/FileWidget.jsx +14 -8
  104. package/src/components/manage/Widgets/ImageWidget.jsx +2 -2
  105. package/src/components/manage/Widgets/InternalUrlWidget.jsx +2 -0
  106. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +3 -0
  107. package/src/components/manage/Widgets/UrlWidget.jsx +2 -0
  108. package/src/components/theme/Avatar/Avatar.jsx +2 -1
  109. package/src/components/theme/PreviewImage/PreviewImage.jsx +1 -1
  110. package/src/components/theme/Widgets/ImageWidget.jsx +2 -1
  111. package/src/helpers/Content/withClientSideContent.jsx +35 -0
  112. package/src/helpers/Html/Html.jsx +1 -9
  113. package/src/helpers/MessageLabels/MessageLabels.js +5 -0
  114. package/src/middleware/api.js +3 -3
  115. package/src/routes.js +3 -1
  116. package/theme/themes/pastanaga/extras/contents.less +12 -0
  117. package/theme/themes/pastanaga/extras/main.less +4 -0
  118. package/types/components/manage/Controlpanels/Users/RenderUsers.d.ts +18 -2
  119. package/types/components/manage/Controlpanels/index.d.ts +1 -1
  120. package/types/helpers/Content/withClientSideContent.d.ts +1 -0
  121. package/types/helpers/MessageLabels/MessageLabels.d.ts +68 -62
  122. package/src/helpers/Url/bulkFlattenToAppURL.test.ts +0 -122
  123. package/src/helpers/Url/bulkFlattenToAppURL.ts +0 -24
@@ -29,6 +29,16 @@ const messages = defineMessages({
29
29
  },
30
30
  });
31
31
 
32
+ function getImageField(resp) {
33
+ if (!resp) return null;
34
+
35
+ if (resp.preview_image_link) return 'preview_image_link';
36
+ if (resp.preview_image) return 'preview_image';
37
+ if (resp.image) return 'image';
38
+
39
+ return null;
40
+ }
41
+
32
42
  const TeaserData = (props) => {
33
43
  const {
34
44
  block,
@@ -58,16 +68,20 @@ const TeaserData = (props) => {
58
68
  '@type': resp?.['@type'],
59
69
  Description: resp?.description,
60
70
  Title: resp.title,
61
- hasPreviewImage: resp?.preview_image ? true : false,
71
+ hasPreviewImage: getImageField(resp) ? true : false,
62
72
  head_title: resp.head_title ?? null,
63
- image_field: resp?.preview_image
64
- ? 'preview_image'
65
- : resp?.image
66
- ? 'image'
67
- : null,
73
+ image_field: getImageField(resp),
68
74
  image_scales: {
69
75
  preview_image: [resp?.preview_image],
70
76
  image: [resp?.image],
77
+ preview_image_link: resp?.preview_image_link
78
+ ? [
79
+ {
80
+ ...resp?.preview_image_link?.['image_scales']?.image?.[0],
81
+ base_path: resp?.preview_image_link?.['@id'],
82
+ },
83
+ ]
84
+ : [],
71
85
  },
72
86
  title: resp.title,
73
87
  };
@@ -33,7 +33,7 @@ const TeaserDefaultTemplate = (props) => {
33
33
  {!href && isEditMode && (
34
34
  <Message>
35
35
  <div className="teaser-item placeholder">
36
- <img src={imageBlockSVG} alt="" />
36
+ <Image src={imageBlockSVG} alt="" />
37
37
  <p>{intl.formatMessage(messages.PleaseChooseContent)}</p>
38
38
  </div>
39
39
  </Message>
@@ -12,6 +12,7 @@ import aheadSVG from '@plone/volto/icons/ahead.svg';
12
12
  import videoBlockSVG from '@plone/volto/components/manage/Blocks/Video/block-video.svg';
13
13
  import Body from '@plone/volto/components/manage/Blocks/Video/Body';
14
14
  import { withBlockExtensions } from '@plone/volto/helpers/Extensions';
15
+ import Image from '@plone/volto/components/theme/Image/Image';
15
16
 
16
17
  const messages = defineMessages({
17
18
  VideoFormDescription: {
@@ -82,7 +83,7 @@ const Edit = (props) => {
82
83
  ) : (
83
84
  <Message>
84
85
  <center>
85
- <img src={videoBlockSVG} alt="" />
86
+ <Image src={videoBlockSVG} alt="" />
86
87
  <div className="toolbar-inner">
87
88
  <Input
88
89
  onKeyDown={onKeyDownVariantMenuForm}
@@ -264,6 +264,10 @@ const messages = defineMessages({
264
264
  id: 'All',
265
265
  defaultMessage: 'All',
266
266
  },
267
+ resultCount: {
268
+ id: 'resultCount',
269
+ defaultMessage: 'Result count',
270
+ },
267
271
  });
268
272
 
269
273
  /**
@@ -1218,8 +1222,9 @@ class Contents extends Component {
1218
1222
  as={Button}
1219
1223
  onClick={this.upload}
1220
1224
  className="upload"
1225
+ aria-controls="contents-table-wrapper"
1221
1226
  aria-label={this.props.intl.formatMessage(
1222
- messages.upload,
1227
+ messages.filter,
1223
1228
  )}
1224
1229
  >
1225
1230
  <Icon
@@ -1551,7 +1556,20 @@ class Contents extends Component {
1551
1556
  </Dropdown.Menu>
1552
1557
  </Dropdown>
1553
1558
  </Segment>
1554
- <div className="contents-table-wrapper">
1559
+ <div
1560
+ id="contents-table-wrapper"
1561
+ className="contents-table-wrapper"
1562
+ role="region"
1563
+ >
1564
+ <span
1565
+ aria-live="polite"
1566
+ className="search-feedback"
1567
+ role="status"
1568
+ >
1569
+ {`${this.props.intl.formatMessage(
1570
+ messages.resultCount,
1571
+ )}: ${this.props.total || 0}`}
1572
+ </span>
1555
1573
  <Table selectable compact singleLine attached>
1556
1574
  <Table.Header>
1557
1575
  <Table.Row>
@@ -182,7 +182,7 @@ class ContentType extends Component {
182
182
  return <Error error={this.state.error} />;
183
183
  }
184
184
 
185
- if (this.props.controlpanel) {
185
+ if (this.props.controlpanel?.data) {
186
186
  let controlpanel = this.props.controlpanel;
187
187
  if (controlpanel?.data?.filter_content_types === false) {
188
188
  controlpanel.data.filter_content_types = { title: 'all', token: 'all' };
@@ -494,8 +494,9 @@ class GroupsControlpanel extends Component {
494
494
  messages.addGroupsFormGroupNameTitle,
495
495
  ),
496
496
  type: 'string',
497
- description:
498
- 'A unique identifier for the group. Can not be changed after creation.',
497
+ description: this.props.intl.formatMessage(
498
+ messages.addGroupsFormGroupNameDescription,
499
+ ),
499
500
  },
500
501
  email: {
501
502
  title: this.props.intl.formatMessage(
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Users controlpanel user.
3
- * @module components/manage/Controlpanels/UsersControlpanelUser
2
+ * RenderUsers component.
3
+ * @module components/manage/Controlpanels/Users/RenderUsers
4
4
  */
5
5
  import PropTypes from 'prop-types';
6
- import React, { Component } from 'react';
7
- import { FormattedMessage, injectIntl } from 'react-intl';
6
+ import { useState } from 'react';
7
+ import { FormattedMessage, useIntl } from 'react-intl';
8
+ import { useDispatch, useSelector } from 'react-redux';
8
9
  import { Dropdown, Table, Checkbox } from 'semantic-ui-react';
9
10
  import trashSVG from '@plone/volto/icons/delete.svg';
10
11
  import editSVG from '@plone/volto/icons/editing.svg';
@@ -13,199 +14,179 @@ import Toast from '@plone/volto/components/manage/Toast/Toast';
13
14
  import { ModalForm } from '@plone/volto/components/manage/Form';
14
15
  import { updateUser } from '@plone/volto/actions/users/users';
15
16
  import ploneSVG from '@plone/volto/icons/plone.svg';
16
- import { compose } from 'redux';
17
- import { connect } from 'react-redux';
18
17
  import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
19
18
  import { canAssignRole } from '@plone/volto/helpers/User/User';
20
19
  import { toast } from 'react-toastify';
21
20
 
22
21
  /**
23
- * UsersControlpanelUser class.
24
- * @class UsersControlpanelUser
25
- * @extends Component
22
+ * RenderUsers functional component.
23
+ * @function RenderUsers
26
24
  */
27
- class RenderUsers extends Component {
28
- /**
29
- * Property types.
30
- * @property {Object} propTypes Property types.
31
- * @static
32
- */
33
- static propTypes = {
34
- user: PropTypes.shape({
35
- username: PropTypes.string,
36
- fullname: PropTypes.string,
37
- roles: PropTypes.arrayOf(PropTypes.string),
38
- }).isRequired,
39
- roles: PropTypes.arrayOf(
40
- PropTypes.shape({
41
- id: PropTypes.string,
42
- }),
43
- ).isRequired,
44
- onDelete: PropTypes.func.isRequired,
45
- isUserManager: PropTypes.bool.isRequired,
46
- };
25
+ const RenderUsers = (props) => {
26
+ const [user, setUser] = useState({});
47
27
 
48
- /**
49
- * Constructor
50
- * @method constructor
51
- * @param {Object} props Component properties
52
- * @constructs Sharing
53
- */
54
- constructor(props) {
55
- super(props);
56
- this.state = {
57
- user: {},
58
- };
59
- this.onChange = this.onChange.bind(this);
60
- this.onEditUserError = this.onEditUserError.bind(this);
61
- this.onEditUserSubmit = this.onEditUserSubmit.bind(this);
62
- }
28
+ const intl = useIntl();
29
+ const dispatch = useDispatch();
30
+ const updateRequest = useSelector((state) => state.users?.update);
31
+
32
+ const {
33
+ user: propsUser,
34
+ listUsers,
35
+ updateUser: updateUserRole,
36
+ isUserManager,
37
+ roles,
38
+ inheritedRole,
39
+ userschema,
40
+ onDelete,
41
+ } = props;
42
+
43
+ // Use dispatch to call updateUser action
44
+ const updateUserData = (userId, userData) => {
45
+ dispatch(updateUser(userId, userData))
46
+ .then(() => {
47
+ // Handle success
48
+ setUser({});
49
+ if (listUsers) {
50
+ listUsers();
51
+ }
52
+ toast.success(
53
+ <Toast
54
+ success
55
+ title={intl.formatMessage(messages.success)}
56
+ content={intl.formatMessage(messages.updateUserSuccess)}
57
+ />,
58
+ );
59
+ })
60
+ .catch(() => {
61
+ // Handle error
62
+ toast.error(
63
+ <Toast
64
+ error
65
+ title={intl.formatMessage(messages.error)}
66
+ content={intl.formatMessage(messages.thereWereSomeErrors)}
67
+ />,
68
+ );
69
+ });
70
+ };
63
71
 
64
72
  /**
65
73
  * @param {*} event
66
74
  * @param {*} { value }
67
- * @memberof UsersControlpanelUser
68
75
  */
76
+ const onChange = (_event, { value }) => {
77
+ const [userId, role] = value.split('&role=');
78
+ updateUserRole(userId, role);
79
+ };
69
80
 
70
- onChange(event, { value }) {
71
- const [user, role] = value.split('&role=');
72
- this.props.updateUser(user, role);
73
- }
74
-
75
- componentDidUpdate(prevProps) {
76
- if (
77
- prevProps.updateRequest.loading &&
78
- this.props.updateRequest.loaded &&
79
- this.state?.user?.id === this.props?.user?.id
80
- ) {
81
- this.setState({ user: {} });
82
- this.props.listUsers();
83
- return toast.success(
84
- <Toast
85
- success
86
- title={this.props.intl.formatMessage(messages.success)}
87
- content={this.props.intl.formatMessage(messages.updateUserSuccess)}
88
- />,
89
- );
90
- }
91
- }
92
-
93
- onEditUserSubmit(data, callback) {
81
+ const onEditUserSubmit = (data) => {
94
82
  // Do not handle groups and roles in this form
95
83
  delete data.groups;
96
84
  delete data.roles;
97
- this.props.updateUserData(data.id, data);
98
- }
99
-
100
- onEditUserError() {
101
- return toast.error(
102
- <Toast
103
- error
104
- title={this.props.intl.formatMessage(messages.error)}
105
- content={this.props.intl.formatMessage(
106
- messages.addUserFormPasswordAndSendPasswordTogetherNotAllowed,
107
- )}
108
- />,
109
- );
110
- }
85
+ updateUserData(data.id, data);
86
+ };
111
87
 
112
- onClickEdit(props) {
113
- const { formData } = props;
114
- this.setState({ user: { ...formData } });
115
- }
88
+ const onClickEdit = (formProps) => {
89
+ const { formData } = formProps;
90
+ setUser({ ...formData });
91
+ };
116
92
 
117
- canDeleteUser() {
118
- if (this.props.isUserManager) return true;
119
- return !this.props.user.roles.includes('Manager');
120
- }
93
+ const canDeleteUser = () => {
94
+ if (isUserManager) return true;
95
+ return !propsUser.roles.includes('Manager');
96
+ };
121
97
 
122
- /**
123
- * Render method.
124
- * @method render
125
- * @returns {string} Markup for the component.
126
- */
127
- render() {
128
- return (
129
- <Table.Row key={this.props.user.username}>
130
- <Table.Cell className="fullname">
131
- {this.props.user.fullname
132
- ? this.props.user.fullname
133
- : this.props.user.username}{' '}
134
- ({this.props.user.username})
98
+ return (
99
+ <Table.Row key={propsUser.username}>
100
+ <Table.Cell className="fullname">
101
+ {propsUser.fullname ? propsUser.fullname : propsUser.username} (
102
+ {propsUser.username})
103
+ </Table.Cell>
104
+ {roles.map((role) => (
105
+ <Table.Cell key={role.id}>
106
+ {inheritedRole && inheritedRole.includes(role.id) ? (
107
+ <Icon
108
+ name={ploneSVG}
109
+ size="20px"
110
+ color="#007EB1"
111
+ title={'plone-svg'}
112
+ />
113
+ ) : (
114
+ <Checkbox
115
+ checked={propsUser.roles.includes(role.id)}
116
+ onChange={onChange}
117
+ value={`${propsUser.id}&role=${role.id}`}
118
+ disabled={!canAssignRole(isUserManager, role)}
119
+ />
120
+ )}
135
121
  </Table.Cell>
136
- {this.props.roles.map((role) => (
137
- <Table.Cell key={role.id}>
138
- {this.props.inheritedRole &&
139
- this.props.inheritedRole.includes(role.id) ? (
140
- <Icon
141
- name={ploneSVG}
142
- size="20px"
143
- color="#007EB1"
144
- title={'plone-svg'}
145
- />
146
- ) : (
147
- <Checkbox
148
- checked={this.props.user.roles.includes(role.id)}
149
- onChange={this.onChange}
150
- value={`${this.props.user.id}&role=${role.id}`}
151
- disabled={!canAssignRole(this.props.isUserManager, role)}
152
- />
153
- )}
154
- </Table.Cell>
155
- ))}
156
- <Table.Cell textAlign="right">
157
- {this.canDeleteUser() && (
158
- <Dropdown icon="ellipsis horizontal">
159
- <Dropdown.Menu className="left">
160
- {this.props.userschema && (
161
- <Dropdown.Item
162
- id="edit-user-button"
163
- onClick={() => {
164
- this.onClickEdit({ formData: this.props.user });
165
- }}
166
- value={this.props.user['@id']}
167
- >
168
- <Icon name={editSVG} size="15px" />
169
- <FormattedMessage id="Edit" defaultMessage="Edit" />
170
- </Dropdown.Item>
171
- )}
122
+ ))}
123
+ <Table.Cell textAlign="right">
124
+ {canDeleteUser() && (
125
+ <Dropdown icon="ellipsis horizontal">
126
+ <Dropdown.Menu className="left">
127
+ {userschema && (
172
128
  <Dropdown.Item
173
- id="delete-user-button"
174
- onClick={this.props.onDelete}
175
- value={this.props.user['@id']}
129
+ id="edit-user-button"
130
+ onClick={() => {
131
+ onClickEdit({ formData: propsUser });
132
+ }}
133
+ value={propsUser['@id']}
176
134
  >
177
- <Icon name={trashSVG} size="15px" />
178
- <FormattedMessage id="Delete" defaultMessage="Delete" />
135
+ <Icon name={editSVG} size="15px" />
136
+ <FormattedMessage id="Edit" defaultMessage="Edit" />
179
137
  </Dropdown.Item>
180
- </Dropdown.Menu>
181
- </Dropdown>
182
- )}
183
- </Table.Cell>
184
- {Object.keys(this.state.user).length > 0 &&
185
- this.props.userschema.loaded && (
186
- <ModalForm
187
- className="modal"
188
- onSubmit={this.onEditUserSubmit}
189
- submitError={this.state.editUserError}
190
- formData={this.state.user}
191
- onCancel={() => this.setState({ user: {} })}
192
- title={this.props.intl.formatMessage(
193
- messages.updateUserFormTitle,
194
138
  )}
195
- loading={this.props.updateRequest.loading}
196
- schema={this.props.userschema.userschema}
197
- />
198
- )}
199
- </Table.Row>
200
- );
201
- }
202
- }
203
- export default compose(
204
- injectIntl,
205
- connect(
206
- (state, props) => ({
207
- updateRequest: state.users?.update,
139
+ <Dropdown.Item
140
+ id="delete-user-button"
141
+ onClick={onDelete}
142
+ value={propsUser['@id']}
143
+ >
144
+ <Icon name={trashSVG} size="15px" />
145
+ <FormattedMessage id="Delete" defaultMessage="Delete" />
146
+ </Dropdown.Item>
147
+ </Dropdown.Menu>
148
+ </Dropdown>
149
+ )}
150
+ </Table.Cell>
151
+ {Object.keys(user).length > 0 && userschema.loaded && (
152
+ <ModalForm
153
+ className="modal"
154
+ onSubmit={onEditUserSubmit}
155
+ submitError={user.editUserError}
156
+ formData={user}
157
+ onCancel={() => setUser({})}
158
+ title={intl.formatMessage(messages.updateUserFormTitle)}
159
+ loading={updateRequest.loading}
160
+ schema={userschema.userschema}
161
+ />
162
+ )}
163
+ </Table.Row>
164
+ );
165
+ };
166
+
167
+ // PropTypes to the component
168
+ RenderUsers.propTypes = {
169
+ user: PropTypes.shape({
170
+ id: PropTypes.string,
171
+ username: PropTypes.string,
172
+ fullname: PropTypes.string,
173
+ roles: PropTypes.arrayOf(PropTypes.string),
174
+ '@id': PropTypes.string,
175
+ }).isRequired,
176
+ roles: PropTypes.arrayOf(
177
+ PropTypes.shape({
178
+ id: PropTypes.string,
208
179
  }),
209
- { updateUserData: updateUser },
210
- ),
211
- )(RenderUsers);
180
+ ).isRequired,
181
+ onDelete: PropTypes.func.isRequired,
182
+ isUserManager: PropTypes.bool.isRequired,
183
+ listUsers: PropTypes.func,
184
+ updateUser: PropTypes.func.isRequired,
185
+ inheritedRole: PropTypes.arrayOf(PropTypes.string),
186
+ userschema: PropTypes.shape({
187
+ loaded: PropTypes.bool,
188
+ userschema: PropTypes.object,
189
+ }),
190
+ };
191
+
192
+ export default RenderUsers;
@@ -3,6 +3,7 @@ import { Button, Segment, Popup } from 'semantic-ui-react';
3
3
  import { useIntl, defineMessages } from 'react-intl';
4
4
  import cx from 'classnames';
5
5
  import Icon from '@plone/volto/components/theme/Icon/Icon';
6
+ import Image from '@plone/volto/components/theme/Image/Image';
6
7
  import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
7
8
  import { getContentIcon } from '@plone/volto/helpers/Content/Content';
8
9
  import config from '@plone/volto/registry';
@@ -69,7 +70,7 @@ const ObjectBrowserNav = ({
69
70
  }
70
71
  >
71
72
  {item['@type'] === 'Image' ? (
72
- <img
73
+ <Image
73
74
  src={`${item['@id']}/@@images/image/preview`}
74
75
  alt={item.title}
75
76
  style={{
@@ -1,6 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { Button, Grid, Message } from 'semantic-ui-react';
4
+ import Image from '@plone/volto/components/theme/Image/Image';
4
5
 
5
6
  const TemplateChooser = ({ templates, onSelectTemplate }) => {
6
7
  const intl = useIntl();
@@ -15,7 +16,7 @@ const TemplateChooser = ({ templates, onSelectTemplate }) => {
15
16
  className="template-chooser-item"
16
17
  onClick={() => onSelectTemplate(index)}
17
18
  >
18
- <img src={template.image} alt="" />
19
+ <Image src={template.image} alt="" />
19
20
  <div className="template-chooser-title">
20
21
  {intl.formatMessage({
21
22
  id: template.id,
@@ -7,6 +7,7 @@ import cx from 'classnames';
7
7
  import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
8
8
 
9
9
  import Icon from '@plone/volto/components/theme/Icon/Icon';
10
+ import Image from '@plone/volto/components/theme/Image/Image';
10
11
  import { getUser } from '@plone/volto/actions/users/users';
11
12
  import { Pluggable } from '@plone/volto/components/manage/Pluggable';
12
13
  import { expandToBackendURL, getBaseUrl } from '@plone/volto/helpers/Url/Url';
@@ -96,7 +97,7 @@ const PersonalTools = (props) => {
96
97
  </header>
97
98
  <div className={cx('avatar', { default: !user.portrait })}>
98
99
  {user.portrait ? (
99
- <img
100
+ <Image
100
101
  src={expandToBackendURL(user.portrait)}
101
102
  alt={intl.formatMessage(messages.userAvatar)}
102
103
  />
@@ -27,6 +27,10 @@ const messages = defineMessages({
27
27
  id: 'Time',
28
28
  defaultMessage: 'Time',
29
29
  },
30
+ clearDateTime: {
31
+ id: 'Clear date/time',
32
+ defaultMessage: 'Clear date and time',
33
+ },
30
34
  });
31
35
 
32
36
  const PrevIcon = () => (
@@ -207,6 +211,7 @@ const DatetimeWidgetComponent = (props) => {
207
211
  disabled={isDisabled || !datetime}
208
212
  onClick={onResetDates}
209
213
  className="item ui noborder button"
214
+ aria-label={intl.formatMessage(messages.clearDateTime)}
210
215
  >
211
216
  <Icon name={clearSVG} size="24px" className="close" />
212
217
  </button>
@@ -15,7 +15,6 @@ import UniversalLink from '@plone/volto/components/manage/UniversalLink/Universa
15
15
  import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
16
16
  import Image from '@plone/volto/components/theme/Image/Image';
17
17
  import loadable from '@loadable/component';
18
- import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
19
18
  import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
20
19
  import { defineMessages, useIntl } from 'react-intl';
21
20
  import { toast } from 'react-toastify';
@@ -90,18 +89,25 @@ const FileWidget = (props) => {
90
89
  const [fileType, setFileType] = React.useState(false);
91
90
  const intl = useIntl();
92
91
 
92
+ const imgAttrs = React.useMemo(() => {
93
+ const data = {};
94
+ if (value?.download) {
95
+ data.item = {
96
+ '@id': value.download.substring(0, value.download.indexOf('/@@images')),
97
+ image: value,
98
+ };
99
+ } else if (value?.data) {
100
+ data.src = `data:${value['content-type']};${value.encoding},${value.data}`;
101
+ }
102
+ return data;
103
+ }, [value]);
104
+
93
105
  React.useEffect(() => {
94
106
  if (value && imageMimetypes.includes(value['content-type'])) {
95
107
  setFileType(true);
96
108
  }
97
109
  }, [value]);
98
110
 
99
- const imgsrc = value?.download
100
- ? `${flattenToAppURL(value?.download)}?id=${Date.now()}`
101
- : null || value?.data
102
- ? `data:${value['content-type']};${value.encoding},${value.data}`
103
- : null;
104
-
105
111
  /**
106
112
  * Drop handler
107
113
  * @method onDrop
@@ -175,7 +181,7 @@ const FileWidget = (props) => {
175
181
  <Image
176
182
  className="image-preview small ui image"
177
183
  id={`field-${id}-image`}
178
- src={imgsrc}
184
+ {...imgAttrs}
179
185
  />
180
186
  ) : (
181
187
  <div className="dropzone-placeholder">
@@ -291,7 +291,7 @@ const UnconnectedImageInput = (props) => {
291
291
  {isRelationChoice ? (
292
292
  <Image item={value} width="fit-content" height="auto" loading="lazy" />
293
293
  ) : (
294
- <img
294
+ <Image
295
295
  className={props.className}
296
296
  src={
297
297
  isInternalURL(imageValue)
@@ -341,7 +341,7 @@ const UnconnectedImageInput = (props) => {
341
341
  </Loader>
342
342
  </Dimmer>
343
343
  )}
344
- <img src={imageBlockSVG} alt="" className="placeholder" />
344
+ <Image src={imageBlockSVG} alt="" className="placeholder" />
345
345
  <p>{description || intl.formatMessage(messages.addImage)}</p>
346
346
  <div className="toolbar-wrapper">
347
347
  <div className="toolbar-inner" ref={linkEditor.anchorNode}>
@@ -100,6 +100,7 @@ export const InternalUrlWidget = (props) => {
100
100
  {value?.length > 0 ? (
101
101
  <Button.Group>
102
102
  <Button
103
+ type="button"
103
104
  basic
104
105
  className="cancel"
105
106
  aria-label="clearUrlBrowser"
@@ -115,6 +116,7 @@ export const InternalUrlWidget = (props) => {
115
116
  ) : (
116
117
  <Button.Group>
117
118
  <Button
119
+ type="button"
118
120
  basic
119
121
  icon
120
122
  aria-label="openUrlBrowser"