@strapi/admin 4.10.4 → 4.11.0-alpha.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/admin/src/content-manager/components/DynamicTable/BulkActionsBar/index.js +307 -0
- package/admin/src/content-manager/components/DynamicTable/index.js +20 -4
- package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +3 -2
- package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/index.js +0 -1
- package/admin/src/content-manager/pages/ListView/index.js +118 -2
- package/admin/src/content-manager/utils/index.js +2 -0
- package/admin/src/content-manager/{components/EditViewDataManagerProvider/utils → utils}/schema.js +1 -1
- package/admin/src/injectionZones.js +6 -1
- package/admin/src/translations/en.json +5 -1
- package/build/{1387.437eb420.chunk.js → 1387.84b454d3.chunk.js} +1 -1
- package/build/{1657.85034334.chunk.js → 1657.45231968.chunk.js} +52 -52
- package/build/3081.bcf9a12f.chunk.js +108 -0
- package/build/3562.57745ccd.chunk.js +50 -0
- package/build/462.8fff7f3b.chunk.js +71 -0
- package/build/{4628.9cbb6df5.chunk.js → 4628.20631dd1.chunk.js} +1 -1
- package/build/5542.b8240e3f.chunk.js +70 -0
- package/build/{5563.44e0ef15.chunk.js → 5563.986609ed.chunk.js} +22 -22
- package/build/6404.68405699.chunk.js +100 -0
- package/build/6970.5159b068.chunk.js +1 -0
- package/build/{7259.3ed2b60e.chunk.js → 7259.7a48aa2f.chunk.js} +1 -1
- package/build/7725.1633e06f.chunk.js +213 -0
- package/build/{Admin-authenticatedApp.dbb99d34.chunk.js → Admin-authenticatedApp.27a2329f.chunk.js} +2 -2
- package/build/Admin_homePage.9f7c0bb1.chunk.js +73 -0
- package/build/Admin_marketplace.5f7b89e5.chunk.js +55 -0
- package/build/Admin_pluginsPage.9e6fa51c.chunk.js +6 -0
- package/build/{Admin_profilePage.1687246a.chunk.js → Admin_profilePage.1b337b73.chunk.js} +1 -1
- package/build/Admin_settingsPage.9d0419bc.chunk.js +79 -0
- package/build/{Upload_ConfigureTheView.aa64ed9a.chunk.js → Upload_ConfigureTheView.d429a7fc.chunk.js} +1 -1
- package/build/{admin-app.d4fd9379.chunk.js → admin-app.8e1f56bc.chunk.js} +5 -5
- package/build/{admin-edit-roles-page.6fe9de49.chunk.js → admin-edit-roles-page.d0c9497b.chunk.js} +41 -43
- package/build/{admin-edit-users.49363035.chunk.js → admin-edit-users.2aae89f5.chunk.js} +2 -2
- package/build/admin-roles-list.c759daa3.chunk.js +31 -0
- package/build/{admin-users.16bb6e77.chunk.js → admin-users.04a823ca.chunk.js} +15 -15
- package/build/audit-logs-settings-page.45cb4fb5.chunk.js +76 -0
- package/build/content-manager.cf467ecc.chunk.js +1123 -0
- package/build/content-type-builder-list-view.5ff685ec.chunk.js +214 -0
- package/build/{content-type-builder.51c64fe5.chunk.js → content-type-builder.4737a30c.chunk.js} +4 -4
- package/build/email-settings-page.dc07d518.chunk.js +10 -0
- package/build/{en-json.1f6af924.chunk.js → en-json.7edb00f6.chunk.js} +1 -1
- package/build/i18n-settings-page.8219dd99.chunk.js +60 -0
- package/build/i18n-translation-en-json.1ec7becf.chunk.js +1 -0
- package/build/index.html +1 -1
- package/build/main.14dca275.js +2592 -0
- package/build/{review-workflows-settings.26409ce4.chunk.js → review-workflows-settings.6a662ebd.chunk.js} +2 -2
- package/build/{runtime~main.a096f3d0.js → runtime~main.1074f2bb.js} +2 -2
- package/build/{sso-settings-page.c9d7c8df.chunk.js → sso-settings-page.f44d95d8.chunk.js} +1 -1
- package/build/upload-settings.dd2d987c.chunk.js +13 -0
- package/build/upload.c8479232.chunk.js +33 -0
- package/build/users-advanced-settings-page.c36cfd59.chunk.js +8 -0
- package/build/users-email-settings-page.2716ce8e.chunk.js +23 -0
- package/build/users-providers-settings-page.0d6304a5.chunk.js +28 -0
- package/build/webhook-edit-page.f4db86f3.chunk.js +75 -0
- package/build/webhook-list-page.30d73114.chunk.js +71 -0
- package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +6 -1
- package/package.json +11 -11
- package/admin/src/content-manager/components/DynamicTable/ConfirmDialogDeleteAll/index.js +0 -73
- package/build/3081.c2cdfac8.chunk.js +0 -108
- package/build/3816.2b58bc1a.chunk.js +0 -214
- package/build/462.a073ff1f.chunk.js +0 -71
- package/build/5542.5328b662.chunk.js +0 -71
- package/build/617.ce23dea8.chunk.js +0 -155
- package/build/6858.85d76858.chunk.js +0 -50
- package/build/6970.e8422af3.chunk.js +0 -1
- package/build/Admin_homePage.e15dcf28.chunk.js +0 -73
- package/build/Admin_marketplace.87f51b29.chunk.js +0 -55
- package/build/Admin_pluginsPage.7df6b5a9.chunk.js +0 -6
- package/build/Admin_settingsPage.20884a40.chunk.js +0 -79
- package/build/admin-roles-list.a323aa9f.chunk.js +0 -31
- package/build/audit-logs-settings-page.48b921f9.chunk.js +0 -129
- package/build/content-manager.a837bfcd.chunk.js +0 -1123
- package/build/content-type-builder-list-view.1e821eb9.chunk.js +0 -215
- package/build/email-settings-page.5bc22e5a.chunk.js +0 -11
- package/build/i18n-settings-page.1581894e.chunk.js +0 -114
- package/build/i18n-translation-en-json.60af6722.chunk.js +0 -1
- package/build/main.31bdf27e.js +0 -2679
- package/build/upload-settings.4d962053.chunk.js +0 -14
- package/build/upload.4dcd513f.chunk.js +0 -34
- package/build/users-advanced-settings-page.dd2a4954.chunk.js +0 -9
- package/build/users-email-settings-page.510648ab.chunk.js +0 -24
- package/build/users-providers-settings-page.0ee7d54f.chunk.js +0 -29
- package/build/webhook-edit-page.36c9f20d.chunk.js +0 -128
- package/build/webhook-list-page.0983d83f.chunk.js +0 -71
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Button, Dialog, DialogBody, DialogFooter, Flex, Typography } from '@strapi/design-system';
|
|
4
|
+
import { Check, ExclamationMarkCircle, Trash } from '@strapi/icons';
|
|
5
|
+
import { useIntl } from 'react-intl';
|
|
6
|
+
import { useSelector } from 'react-redux';
|
|
7
|
+
import { useTracking } from '@strapi/helper-plugin';
|
|
8
|
+
import { getTrad } from '../../../utils';
|
|
9
|
+
import InjectionZoneList from '../../InjectionZoneList';
|
|
10
|
+
import { listViewDomain } from '../../../pages/ListView/selectors';
|
|
11
|
+
|
|
12
|
+
const ConfirmBulkActionDialog = ({ onToggleDialog, isOpen, dialogBody, endAction }) => {
|
|
13
|
+
const { formatMessage } = useIntl();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Dialog
|
|
17
|
+
onClose={onToggleDialog}
|
|
18
|
+
title={formatMessage({
|
|
19
|
+
id: 'app.components.ConfirmDialog.title',
|
|
20
|
+
defaultMessage: 'Confirmation',
|
|
21
|
+
})}
|
|
22
|
+
labelledBy="confirmation"
|
|
23
|
+
describedBy="confirm-description"
|
|
24
|
+
isOpen={isOpen}
|
|
25
|
+
>
|
|
26
|
+
<DialogBody icon={<ExclamationMarkCircle />}>
|
|
27
|
+
<Flex direction="column" alignItems="stretch" gap={2}>
|
|
28
|
+
{dialogBody}
|
|
29
|
+
</Flex>
|
|
30
|
+
</DialogBody>
|
|
31
|
+
<DialogFooter
|
|
32
|
+
startAction={
|
|
33
|
+
<Button onClick={onToggleDialog} variant="tertiary">
|
|
34
|
+
{formatMessage({
|
|
35
|
+
id: 'app.components.Button.cancel',
|
|
36
|
+
defaultMessage: 'Cancel',
|
|
37
|
+
})}
|
|
38
|
+
</Button>
|
|
39
|
+
}
|
|
40
|
+
endAction={endAction}
|
|
41
|
+
/>
|
|
42
|
+
</Dialog>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
ConfirmBulkActionDialog.propTypes = {
|
|
47
|
+
isOpen: PropTypes.bool.isRequired,
|
|
48
|
+
onToggleDialog: PropTypes.func.isRequired,
|
|
49
|
+
dialogBody: PropTypes.node.isRequired,
|
|
50
|
+
endAction: PropTypes.node.isRequired,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const confirmDialogsPropTypes = {
|
|
54
|
+
isConfirmButtonLoading: PropTypes.bool.isRequired,
|
|
55
|
+
isOpen: PropTypes.bool.isRequired,
|
|
56
|
+
onConfirm: PropTypes.func.isRequired,
|
|
57
|
+
onToggleDialog: PropTypes.func.isRequired,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const ConfirmDialogPublishAll = ({ isOpen, onToggleDialog, isConfirmButtonLoading, onConfirm }) => {
|
|
61
|
+
const { formatMessage } = useIntl();
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<ConfirmBulkActionDialog
|
|
65
|
+
isOpen={isOpen}
|
|
66
|
+
onToggleDialog={onToggleDialog}
|
|
67
|
+
dialogBody={
|
|
68
|
+
<>
|
|
69
|
+
<Typography id="confirm-description" textAlign="center">
|
|
70
|
+
{formatMessage({
|
|
71
|
+
id: getTrad('popUpWarning.bodyMessage.contentType.publish.all'),
|
|
72
|
+
defaultMessage: 'Are you sure you want to publish these entries?',
|
|
73
|
+
})}
|
|
74
|
+
</Typography>
|
|
75
|
+
<InjectionZoneList area="contentManager.listView.publishModalAdditionalInfos" />
|
|
76
|
+
</>
|
|
77
|
+
}
|
|
78
|
+
endAction={
|
|
79
|
+
<Button
|
|
80
|
+
onClick={onConfirm}
|
|
81
|
+
variant="secondary"
|
|
82
|
+
startIcon={<Check />}
|
|
83
|
+
loading={isConfirmButtonLoading}
|
|
84
|
+
>
|
|
85
|
+
{formatMessage({
|
|
86
|
+
id: 'app.utils.publish',
|
|
87
|
+
defaultMessage: 'Publish',
|
|
88
|
+
})}
|
|
89
|
+
</Button>
|
|
90
|
+
}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
ConfirmDialogPublishAll.propTypes = confirmDialogsPropTypes;
|
|
96
|
+
|
|
97
|
+
const ConfirmDialogUnpublishAll = ({
|
|
98
|
+
isOpen,
|
|
99
|
+
onToggleDialog,
|
|
100
|
+
isConfirmButtonLoading,
|
|
101
|
+
onConfirm,
|
|
102
|
+
}) => {
|
|
103
|
+
const { formatMessage } = useIntl();
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<ConfirmBulkActionDialog
|
|
107
|
+
isOpen={isOpen}
|
|
108
|
+
onToggleDialog={onToggleDialog}
|
|
109
|
+
dialogBody={
|
|
110
|
+
<>
|
|
111
|
+
<Typography id="confirm-description" textAlign="center">
|
|
112
|
+
{formatMessage({
|
|
113
|
+
id: getTrad('popUpWarning.bodyMessage.contentType.unpublish.all'),
|
|
114
|
+
defaultMessage: 'Are you sure you want to unpublish these entries?',
|
|
115
|
+
})}
|
|
116
|
+
</Typography>
|
|
117
|
+
<InjectionZoneList area="contentManager.listView.unpublishModalAdditionalInfos" />
|
|
118
|
+
</>
|
|
119
|
+
}
|
|
120
|
+
endAction={
|
|
121
|
+
<Button
|
|
122
|
+
onClick={onConfirm}
|
|
123
|
+
variant="secondary"
|
|
124
|
+
startIcon={<Check />}
|
|
125
|
+
loading={isConfirmButtonLoading}
|
|
126
|
+
>
|
|
127
|
+
{formatMessage({
|
|
128
|
+
id: 'app.utils.unpublish',
|
|
129
|
+
defaultMessage: 'Unpublish',
|
|
130
|
+
})}
|
|
131
|
+
</Button>
|
|
132
|
+
}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
ConfirmDialogUnpublishAll.propTypes = confirmDialogsPropTypes;
|
|
138
|
+
|
|
139
|
+
const ConfirmDialogDeleteAll = ({ isOpen, onToggleDialog, isConfirmButtonLoading, onConfirm }) => {
|
|
140
|
+
const { formatMessage } = useIntl();
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<ConfirmBulkActionDialog
|
|
144
|
+
isOpen={isOpen}
|
|
145
|
+
onToggleDialog={onToggleDialog}
|
|
146
|
+
dialogBody={
|
|
147
|
+
<>
|
|
148
|
+
<Typography id="confirm-description" textAlign="center">
|
|
149
|
+
{formatMessage({
|
|
150
|
+
id: getTrad('popUpWarning.bodyMessage.contentType.delete.all'),
|
|
151
|
+
defaultMessage: 'Are you sure you want to delete these entries?',
|
|
152
|
+
})}
|
|
153
|
+
</Typography>
|
|
154
|
+
<InjectionZoneList area="contentManager.listView.deleteModalAdditionalInfos" />
|
|
155
|
+
</>
|
|
156
|
+
}
|
|
157
|
+
endAction={
|
|
158
|
+
<Button
|
|
159
|
+
onClick={onConfirm}
|
|
160
|
+
variant="danger-light"
|
|
161
|
+
startIcon={<Trash />}
|
|
162
|
+
id="confirm-delete"
|
|
163
|
+
loading={isConfirmButtonLoading}
|
|
164
|
+
>
|
|
165
|
+
{formatMessage({
|
|
166
|
+
id: 'app.components.Button.confirm',
|
|
167
|
+
defaultMessage: 'Confirm',
|
|
168
|
+
})}
|
|
169
|
+
</Button>
|
|
170
|
+
}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
ConfirmDialogDeleteAll.propTypes = confirmDialogsPropTypes;
|
|
176
|
+
|
|
177
|
+
const BulkActionsBar = ({
|
|
178
|
+
showPublish,
|
|
179
|
+
showDelete,
|
|
180
|
+
onConfirmDeleteAll,
|
|
181
|
+
onConfirmPublishAll,
|
|
182
|
+
onConfirmUnpublishAll,
|
|
183
|
+
selectedEntries,
|
|
184
|
+
clearSelectedEntries,
|
|
185
|
+
}) => {
|
|
186
|
+
const { formatMessage } = useIntl();
|
|
187
|
+
const { trackUsage } = useTracking();
|
|
188
|
+
const { data } = useSelector(listViewDomain());
|
|
189
|
+
|
|
190
|
+
const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
|
|
191
|
+
const [dialogToOpen, setDialogToOpen] = useState(null);
|
|
192
|
+
|
|
193
|
+
// Filters for Bulk actions
|
|
194
|
+
const selectedEntriesObjects = data.filter((entry) => selectedEntries.includes(entry.id));
|
|
195
|
+
const publishButtonIsShown =
|
|
196
|
+
showPublish && selectedEntriesObjects.some((entry) => !entry.publishedAt);
|
|
197
|
+
const unpublishButtonIsShown =
|
|
198
|
+
showPublish && selectedEntriesObjects.some((entry) => entry.publishedAt);
|
|
199
|
+
|
|
200
|
+
const toggleDeleteModal = () => {
|
|
201
|
+
if (dialogToOpen === 'delete') {
|
|
202
|
+
setDialogToOpen(null);
|
|
203
|
+
} else {
|
|
204
|
+
setDialogToOpen('delete');
|
|
205
|
+
trackUsage('willBulkDeleteEntries');
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const togglePublishModal = () => {
|
|
210
|
+
if (dialogToOpen === 'publish') {
|
|
211
|
+
setDialogToOpen(null);
|
|
212
|
+
} else {
|
|
213
|
+
setDialogToOpen('publish');
|
|
214
|
+
trackUsage('willBulkPublishEntries');
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const toggleUnpublishModal = () => {
|
|
219
|
+
if (dialogToOpen === 'unpublish') {
|
|
220
|
+
setDialogToOpen(null);
|
|
221
|
+
} else {
|
|
222
|
+
setDialogToOpen('unpublish');
|
|
223
|
+
trackUsage('willBulkUnpublishEntries');
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const handleBulkAction = async (confirmAction, toggleModal) => {
|
|
228
|
+
try {
|
|
229
|
+
setIsConfirmButtonLoading(true);
|
|
230
|
+
await confirmAction(selectedEntries);
|
|
231
|
+
setIsConfirmButtonLoading(false);
|
|
232
|
+
toggleModal();
|
|
233
|
+
clearSelectedEntries();
|
|
234
|
+
} catch (error) {
|
|
235
|
+
setIsConfirmButtonLoading(false);
|
|
236
|
+
toggleModal();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleBulkDelete = () => handleBulkAction(onConfirmDeleteAll, toggleDeleteModal);
|
|
241
|
+
const handleBulkPublish = () => handleBulkAction(onConfirmPublishAll, togglePublishModal);
|
|
242
|
+
const handleBulkUnpublish = () => handleBulkAction(onConfirmUnpublishAll, toggleUnpublishModal);
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<>
|
|
246
|
+
{publishButtonIsShown && (
|
|
247
|
+
<>
|
|
248
|
+
<Button variant="tertiary" onClick={togglePublishModal}>
|
|
249
|
+
{formatMessage({ id: 'app.utils.publish', defaultMessage: 'Publish' })}
|
|
250
|
+
</Button>
|
|
251
|
+
<ConfirmDialogPublishAll
|
|
252
|
+
isOpen={dialogToOpen === 'publish'}
|
|
253
|
+
onToggleDialog={togglePublishModal}
|
|
254
|
+
isConfirmButtonLoading={isConfirmButtonLoading}
|
|
255
|
+
onConfirm={handleBulkPublish}
|
|
256
|
+
/>
|
|
257
|
+
</>
|
|
258
|
+
)}
|
|
259
|
+
{unpublishButtonIsShown && (
|
|
260
|
+
<>
|
|
261
|
+
<Button variant="tertiary" onClick={toggleUnpublishModal}>
|
|
262
|
+
{formatMessage({ id: 'app.utils.unpublish', defaultMessage: 'Unpublish' })}
|
|
263
|
+
</Button>
|
|
264
|
+
<ConfirmDialogUnpublishAll
|
|
265
|
+
isOpen={dialogToOpen === 'unpublish'}
|
|
266
|
+
onToggleDialog={toggleUnpublishModal}
|
|
267
|
+
isConfirmButtonLoading={isConfirmButtonLoading}
|
|
268
|
+
onConfirm={handleBulkUnpublish}
|
|
269
|
+
/>
|
|
270
|
+
</>
|
|
271
|
+
)}
|
|
272
|
+
{showDelete && (
|
|
273
|
+
<>
|
|
274
|
+
<Button variant="danger-light" onClick={toggleDeleteModal}>
|
|
275
|
+
{formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
|
|
276
|
+
</Button>
|
|
277
|
+
<ConfirmDialogDeleteAll
|
|
278
|
+
isOpen={dialogToOpen === 'delete'}
|
|
279
|
+
onToggleDialog={toggleDeleteModal}
|
|
280
|
+
isConfirmButtonLoading={isConfirmButtonLoading}
|
|
281
|
+
onConfirm={handleBulkDelete}
|
|
282
|
+
/>
|
|
283
|
+
</>
|
|
284
|
+
)}
|
|
285
|
+
</>
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
BulkActionsBar.defaultProps = {
|
|
290
|
+
showPublish: false,
|
|
291
|
+
showDelete: false,
|
|
292
|
+
onConfirmDeleteAll() {},
|
|
293
|
+
onConfirmPublishAll() {},
|
|
294
|
+
onConfirmUnpublishAll() {},
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
BulkActionsBar.propTypes = {
|
|
298
|
+
showPublish: PropTypes.bool,
|
|
299
|
+
showDelete: PropTypes.bool,
|
|
300
|
+
onConfirmDeleteAll: PropTypes.func,
|
|
301
|
+
onConfirmPublishAll: PropTypes.func,
|
|
302
|
+
onConfirmUnpublishAll: PropTypes.func,
|
|
303
|
+
selectedEntries: PropTypes.array.isRequired,
|
|
304
|
+
clearSelectedEntries: PropTypes.func.isRequired,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export default BulkActionsBar;
|
|
@@ -9,19 +9,22 @@ import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
|
|
|
9
9
|
import { selectDisplayedHeaders } from '../../pages/ListView/selectors';
|
|
10
10
|
import { getTrad } from '../../utils';
|
|
11
11
|
import TableRows from './TableRows';
|
|
12
|
-
import ConfirmDialogDeleteAll from './ConfirmDialogDeleteAll';
|
|
13
12
|
import ConfirmDialogDelete from './ConfirmDialogDelete';
|
|
14
13
|
import { PublicationState } from './CellContent/PublicationState/PublicationState';
|
|
14
|
+
import BulkActionsBar from './BulkActionsBar';
|
|
15
15
|
|
|
16
16
|
const DynamicTable = ({
|
|
17
17
|
canCreate,
|
|
18
18
|
canDelete,
|
|
19
|
+
canPublish,
|
|
19
20
|
contentTypeName,
|
|
20
21
|
action,
|
|
21
22
|
isBulkable,
|
|
22
23
|
isLoading,
|
|
23
24
|
onConfirmDelete,
|
|
24
25
|
onConfirmDeleteAll,
|
|
26
|
+
onConfirmPublishAll,
|
|
27
|
+
onConfirmUnpublishAll,
|
|
25
28
|
layout,
|
|
26
29
|
rows,
|
|
27
30
|
}) => {
|
|
@@ -89,17 +92,27 @@ const DynamicTable = ({
|
|
|
89
92
|
|
|
90
93
|
return (
|
|
91
94
|
<Table
|
|
92
|
-
components={{ ConfirmDialogDelete
|
|
95
|
+
components={{ ConfirmDialogDelete }}
|
|
93
96
|
contentType={contentTypeName}
|
|
94
97
|
action={action}
|
|
95
98
|
isLoading={isLoading}
|
|
96
99
|
headers={tableHeaders}
|
|
97
100
|
onConfirmDelete={onConfirmDelete}
|
|
98
|
-
onConfirmDeleteAll={onConfirmDeleteAll}
|
|
99
101
|
onOpenDeleteAllModalTrackedEvent="willBulkDeleteEntries"
|
|
100
102
|
rows={rows}
|
|
101
103
|
withBulkActions
|
|
102
|
-
withMainAction={canDelete && isBulkable}
|
|
104
|
+
withMainAction={(canDelete || canPublish) && isBulkable}
|
|
105
|
+
renderBulkActionsBar={({ selectedEntries, clearSelectedEntries }) => (
|
|
106
|
+
<BulkActionsBar
|
|
107
|
+
showPublish={canPublish && hasDraftAndPublish}
|
|
108
|
+
showDelete={canDelete}
|
|
109
|
+
onConfirmDeleteAll={onConfirmDeleteAll}
|
|
110
|
+
onConfirmPublishAll={onConfirmPublishAll}
|
|
111
|
+
onConfirmUnpublishAll={onConfirmUnpublishAll}
|
|
112
|
+
selectedEntries={selectedEntries}
|
|
113
|
+
clearSelectedEntries={clearSelectedEntries}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
103
116
|
>
|
|
104
117
|
<TableRows
|
|
105
118
|
canCreate={canCreate}
|
|
@@ -121,6 +134,7 @@ DynamicTable.defaultProps = {
|
|
|
121
134
|
DynamicTable.propTypes = {
|
|
122
135
|
canCreate: PropTypes.bool.isRequired,
|
|
123
136
|
canDelete: PropTypes.bool.isRequired,
|
|
137
|
+
canPublish: PropTypes.bool.isRequired,
|
|
124
138
|
contentTypeName: PropTypes.string.isRequired,
|
|
125
139
|
action: PropTypes.node,
|
|
126
140
|
isBulkable: PropTypes.bool.isRequired,
|
|
@@ -139,6 +153,8 @@ DynamicTable.propTypes = {
|
|
|
139
153
|
}).isRequired,
|
|
140
154
|
onConfirmDelete: PropTypes.func.isRequired,
|
|
141
155
|
onConfirmDeleteAll: PropTypes.func.isRequired,
|
|
156
|
+
onConfirmPublishAll: PropTypes.func.isRequired,
|
|
157
|
+
onConfirmUnpublishAll: PropTypes.func.isRequired,
|
|
142
158
|
rows: PropTypes.array.isRequired,
|
|
143
159
|
};
|
|
144
160
|
|
|
@@ -21,12 +21,13 @@ import {
|
|
|
21
21
|
getAPIInnerErrors,
|
|
22
22
|
} from '@strapi/helper-plugin';
|
|
23
23
|
|
|
24
|
-
import { getTrad } from '../../utils';
|
|
24
|
+
import { createYupSchema, getTrad } from '../../utils';
|
|
25
25
|
|
|
26
26
|
import selectCrudReducer from '../../sharedReducers/crudReducer/selectors';
|
|
27
27
|
|
|
28
28
|
import reducer, { initialState } from './reducer';
|
|
29
|
-
import { cleanData
|
|
29
|
+
import { cleanData } from './utils';
|
|
30
|
+
|
|
30
31
|
import { clearSetModifiedDataOnly } from '../../sharedReducers/crudReducer/actions';
|
|
31
32
|
import { usePrev } from '../../hooks';
|
|
32
33
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
useTracking,
|
|
22
22
|
Link,
|
|
23
23
|
useAPIErrorHandler,
|
|
24
|
+
getYupInnerErrors,
|
|
24
25
|
} from '@strapi/helper-plugin';
|
|
25
26
|
|
|
26
27
|
import {
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
} from '@strapi/design-system';
|
|
36
37
|
|
|
37
38
|
import { ArrowLeft, Plus, Cog } from '@strapi/icons';
|
|
39
|
+
import { useMutation } from 'react-query';
|
|
38
40
|
|
|
39
41
|
import DynamicTable from '../../components/DynamicTable';
|
|
40
42
|
import AttributeFilter from '../../components/AttributeFilter';
|
|
@@ -42,7 +44,7 @@ import { InjectionZone } from '../../../shared/components';
|
|
|
42
44
|
|
|
43
45
|
import permissions from '../../../permissions';
|
|
44
46
|
|
|
45
|
-
import { getRequestUrl, getTrad } from '../../utils';
|
|
47
|
+
import { createYupSchema, getRequestUrl, getTrad } from '../../utils';
|
|
46
48
|
|
|
47
49
|
import FieldPicker from './FieldPicker';
|
|
48
50
|
import PaginationFooter from './PaginationFooter';
|
|
@@ -64,6 +66,7 @@ function ListView({
|
|
|
64
66
|
canCreate,
|
|
65
67
|
canDelete,
|
|
66
68
|
canRead,
|
|
69
|
+
canPublish,
|
|
67
70
|
data,
|
|
68
71
|
getData,
|
|
69
72
|
getDataSucceeded,
|
|
@@ -100,6 +103,50 @@ function ListView({
|
|
|
100
103
|
const fetchClient = useFetchClient();
|
|
101
104
|
const { post, del } = fetchClient;
|
|
102
105
|
|
|
106
|
+
const bulkPublishMutation = useMutation(
|
|
107
|
+
(data) =>
|
|
108
|
+
post(`/content-manager/collection-types/${contentType.uid}/actions/bulkPublish`, data),
|
|
109
|
+
{
|
|
110
|
+
onSuccess() {
|
|
111
|
+
toggleNotification({
|
|
112
|
+
type: 'success',
|
|
113
|
+
message: { id: 'content-manager.success.record.publish', defaultMessage: 'Published' },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
fetchData(`/content-manager/collection-types/${slug}${params}`);
|
|
117
|
+
},
|
|
118
|
+
onError(error) {
|
|
119
|
+
toggleNotification({
|
|
120
|
+
type: 'warning',
|
|
121
|
+
message: formatAPIError(error),
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
const bulkUnpublishMutation = useMutation(
|
|
127
|
+
(data) =>
|
|
128
|
+
post(`/content-manager/collection-types/${contentType.uid}/actions/bulkUnpublish`, data),
|
|
129
|
+
{
|
|
130
|
+
onSuccess() {
|
|
131
|
+
toggleNotification({
|
|
132
|
+
type: 'success',
|
|
133
|
+
message: {
|
|
134
|
+
id: 'content-manager.success.record.unpublish',
|
|
135
|
+
defaultMessage: 'Unpublished',
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
fetchData(`/content-manager/collection-types/${slug}${params}`);
|
|
140
|
+
},
|
|
141
|
+
onError(error) {
|
|
142
|
+
toggleNotification({
|
|
143
|
+
type: 'warning',
|
|
144
|
+
message: formatAPIError(error),
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
103
150
|
// FIXME
|
|
104
151
|
// Using a ref to avoid requests being fired multiple times on slug on change
|
|
105
152
|
// We need it because the hook as mulitple dependencies so it may run before the permissions have checked
|
|
@@ -200,6 +247,70 @@ function ListView({
|
|
|
200
247
|
[slug, params, fetchData, toggleNotification, formatAPIError, del]
|
|
201
248
|
);
|
|
202
249
|
|
|
250
|
+
/**
|
|
251
|
+
* @param {number[]} selectedEntries - Array of ids to publish
|
|
252
|
+
* @returns {{validIds: number[], errors: Object.<number, string>}} - Returns an object with the valid ids and the errors
|
|
253
|
+
*/
|
|
254
|
+
const validateEntriesToPublish = async (selectedEntries) => {
|
|
255
|
+
const validations = { validIds: [], errors: {} };
|
|
256
|
+
// Create the validation schema based on the contentType
|
|
257
|
+
const schema = createYupSchema(
|
|
258
|
+
contentType,
|
|
259
|
+
{ components: layout.components },
|
|
260
|
+
{ isDraft: false }
|
|
261
|
+
);
|
|
262
|
+
// Get the selected entries
|
|
263
|
+
const entries = data.filter((entry) => {
|
|
264
|
+
return selectedEntries.includes(entry.id);
|
|
265
|
+
});
|
|
266
|
+
// Validate each entry and map the unresolved promises
|
|
267
|
+
const validationPromises = entries.map((entry) =>
|
|
268
|
+
schema.validate(entry, { abortEarly: false })
|
|
269
|
+
);
|
|
270
|
+
// Resolve all the promises in one go
|
|
271
|
+
const resolvedPromises = await Promise.allSettled(validationPromises);
|
|
272
|
+
// Set the validations
|
|
273
|
+
resolvedPromises.forEach((promise) => {
|
|
274
|
+
if (promise.status === 'rejected') {
|
|
275
|
+
const entityId = promise.reason.value.id;
|
|
276
|
+
validations.errors[entityId] = getYupInnerErrors(promise.reason);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (promise.status === 'fulfilled') {
|
|
280
|
+
validations.validIds.push(promise.value.id);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return validations;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const handleConfirmPublishAllData = async (selectedEntries) => {
|
|
288
|
+
const validations = await validateEntriesToPublish(selectedEntries);
|
|
289
|
+
|
|
290
|
+
if (Object.values(validations.errors).length) {
|
|
291
|
+
toggleNotification({
|
|
292
|
+
type: 'warning',
|
|
293
|
+
title: {
|
|
294
|
+
id: 'content-manager.listView.validation.errors.title',
|
|
295
|
+
defaultMessage: 'Action required',
|
|
296
|
+
},
|
|
297
|
+
message: {
|
|
298
|
+
id: 'content-manager.listView.validation.errors.message',
|
|
299
|
+
defaultMessage:
|
|
300
|
+
'Please make sure all fields are valid before publishing (required field, min/max character limit, etc.)',
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
throw new Error('Validation error');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return bulkPublishMutation.mutateAsync({ ids: selectedEntries });
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const handleConfirmUnpublishAllData = (selectedEntries) => {
|
|
311
|
+
return bulkUnpublishMutation.mutateAsync({ ids: selectedEntries });
|
|
312
|
+
};
|
|
313
|
+
|
|
203
314
|
useEffect(() => {
|
|
204
315
|
const CancelToken = axios.CancelToken;
|
|
205
316
|
const source = CancelToken.source();
|
|
@@ -331,9 +442,12 @@ function ListView({
|
|
|
331
442
|
<DynamicTable
|
|
332
443
|
canCreate={canCreate}
|
|
333
444
|
canDelete={canDelete}
|
|
445
|
+
canPublish={canPublish}
|
|
334
446
|
contentTypeName={headerLayoutTitle}
|
|
335
|
-
onConfirmDeleteAll={handleConfirmDeleteAllData}
|
|
336
447
|
onConfirmDelete={handleConfirmDeleteData}
|
|
448
|
+
onConfirmDeleteAll={handleConfirmDeleteAllData}
|
|
449
|
+
onConfirmPublishAll={handleConfirmPublishAllData}
|
|
450
|
+
onConfirmUnpublishAll={handleConfirmUnpublishAllData}
|
|
337
451
|
isBulkable={isBulkable}
|
|
338
452
|
isLoading={isLoading}
|
|
339
453
|
// FIXME: remove the layout props drilling
|
|
@@ -355,10 +469,12 @@ ListView.propTypes = {
|
|
|
355
469
|
canCreate: PropTypes.bool.isRequired,
|
|
356
470
|
canDelete: PropTypes.bool.isRequired,
|
|
357
471
|
canRead: PropTypes.bool.isRequired,
|
|
472
|
+
canPublish: PropTypes.bool.isRequired,
|
|
358
473
|
data: PropTypes.array.isRequired,
|
|
359
474
|
layout: PropTypes.exact({
|
|
360
475
|
components: PropTypes.object.isRequired,
|
|
361
476
|
contentType: PropTypes.shape({
|
|
477
|
+
uid: PropTypes.string.isRequired,
|
|
362
478
|
attributes: PropTypes.object.isRequired,
|
|
363
479
|
metadatas: PropTypes.object.isRequired,
|
|
364
480
|
info: PropTypes.shape({ displayName: PropTypes.string.isRequired }).isRequired,
|
|
@@ -18,3 +18,5 @@ export { default as mergeMetasWithSchema } from './mergeMetasWithSchema';
|
|
|
18
18
|
|
|
19
19
|
export { default as removeKeyInObject } from './removeKeyInObject';
|
|
20
20
|
export { default as removePasswordFieldsFromData } from './removePasswordFieldsFromData';
|
|
21
|
+
|
|
22
|
+
export { default as createYupSchema } from './schema';
|
package/admin/src/content-manager/{components/EditViewDataManagerProvider/utils → utils}/schema.js
RENAMED
|
@@ -7,7 +7,7 @@ import toNumber from 'lodash/toNumber';
|
|
|
7
7
|
import * as yup from 'yup';
|
|
8
8
|
import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
|
|
9
9
|
|
|
10
|
-
import isFieldTypeNumber from '
|
|
10
|
+
import isFieldTypeNumber from './isFieldTypeNumber';
|
|
11
11
|
|
|
12
12
|
yup.addMethod(yup.mixed, 'defined', function () {
|
|
13
13
|
return this.test('defined', errorsTrads.required, (value) => value !== undefined);
|
|
@@ -13,7 +13,12 @@ const injectionZones = {
|
|
|
13
13
|
},
|
|
14
14
|
contentManager: {
|
|
15
15
|
editView: { informations: [], 'right-links': [] },
|
|
16
|
-
listView: {
|
|
16
|
+
listView: {
|
|
17
|
+
actions: [],
|
|
18
|
+
deleteModalAdditionalInfos: [],
|
|
19
|
+
publishModalAdditionalInfos: [],
|
|
20
|
+
unpublishModalAdditionalInfos: [],
|
|
21
|
+
},
|
|
17
22
|
},
|
|
18
23
|
};
|
|
19
24
|
|
|
@@ -793,7 +793,9 @@
|
|
|
793
793
|
"content-manager.plugin.description.long": "Quick way to see, edit and delete the data in your database.",
|
|
794
794
|
"content-manager.plugin.description.short": "Quick way to see, edit and delete the data in your database.",
|
|
795
795
|
"content-manager.popUpWarning.bodyMessage.contentType.delete": "Are you sure to delete Content-Type?",
|
|
796
|
-
"content-manager.popUpWarning.bodyMessage.contentType.delete.all": "Are you sure to delete
|
|
796
|
+
"content-manager.popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete these entries?",
|
|
797
|
+
"content-manager.popUpWarning.bodyMessage.contentType.publish.all": "Are you sure you want to publish these entries?",
|
|
798
|
+
"content-manager.popUpWarning.bodyMessage.contentType.unpublish.all": "Are you sure you want to unpublish these entries?",
|
|
797
799
|
"content-manager.popUpWarning.warning.has-draft-relations.title": "Confirmation",
|
|
798
800
|
"content-manager.popUpWarning.warning.publish-question": "Do you still want to publish?",
|
|
799
801
|
"content-manager.popUpWarning.warning.unpublish": "If you don't publish this content, it will automatically turn into a Draft.",
|
|
@@ -816,6 +818,8 @@
|
|
|
816
818
|
"content-manager.success.record.save": "Saved",
|
|
817
819
|
"content-manager.success.record.unpublish": "Unpublished",
|
|
818
820
|
"content-manager.utils.data-loaded": "The {number, plural, =1 {entry has} other {entries have}} successfully been loaded",
|
|
821
|
+
"content-manager.listView.validation.errors.title": "Action required",
|
|
822
|
+
"content-manager.listView.validation.errors.message": "Please make sure all fields are valid before publishing (required field, min/max character limit, etc.)",
|
|
819
823
|
"dark": "Dark",
|
|
820
824
|
"form.button.continue": "Continue",
|
|
821
825
|
"form.button.done": "Done",
|