strapi-plugin-comments 2.0.0-beta.1 → 2.0.0-beta.5
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/README.md +16 -5
- package/admin/src/components/DiscussionThread/index.js +3 -1
- package/admin/src/components/DiscussionThreadItem/index.js +4 -4
- package/admin/src/components/DiscussionThreadItem/styles.js +1 -0
- package/admin/src/components/DiscussionThreadItemActions/index.js +30 -44
- package/admin/src/components/DiscussionThreadItemActions/styles.js +0 -4
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +72 -0
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/styles.js +6 -0
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +13 -9
- package/admin/src/components/Nav/index.js +5 -1
- package/admin/src/components/ReportsReviewModal/index.js +0 -2
- package/admin/src/components/ReportsReviewTable/index.js +9 -5
- package/admin/src/components/icons/index.js +6 -0
- package/admin/src/components/icons/lock.js +22 -0
- package/admin/src/components/icons/review.js +18 -0
- package/admin/src/components/icons/unlock.js +20 -0
- package/admin/src/index.js +6 -4
- package/admin/src/pages/App/index.js +42 -3
- package/admin/src/pages/App/reducer/actions.js +8 -0
- package/admin/src/pages/App/reducer/constants.js +4 -0
- package/admin/src/pages/App/reducer/index.js +26 -0
- package/admin/src/pages/App/reducer/init.js +7 -0
- package/admin/src/pages/App/reducer/selectors.js +35 -0
- package/admin/src/pages/App/utils/api.js +12 -0
- package/admin/src/pages/Details/components/DetailsEntity/index.js +66 -0
- package/admin/src/pages/Details/components/DetailsFilters/index.js +36 -0
- package/admin/src/pages/Details/components/DetailsFilters/styles.js +8 -0
- package/admin/src/pages/Details/index.js +41 -14
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +190 -0
- package/admin/src/pages/Discover/components/DiscoverTableRow/styles.js +8 -0
- package/admin/src/pages/Discover/index.js +25 -50
- package/admin/src/pages/Discover/utils/api.js +2 -11
- package/admin/src/pages/{Details/utils → utils}/api.js +15 -3
- package/admin/src/reducers.js +8 -0
- package/admin/src/translations/en.json +16 -4
- package/admin/src/utils/constants.js +3 -6
- package/admin/src/utils/getMessage.js +10 -3
- package/admin/src/utils/getRelatedGroups.js +1 -3
- package/admin/src/utils/handleAPIError.js +1 -1
- package/admin/src/utils/index.js +4 -2
- package/admin/src/utils/renderEntryTitle.js +14 -0
- package/admin/src/utils/resolveCommentStatus.js +30 -0
- package/admin/src/utils/resolveCommentStatusColor.js +22 -0
- package/package.json +7 -3
- package/server/config/index.js +6 -1
- package/server/controllers/admin.js +8 -24
- package/server/controllers/client.js +11 -2
- package/server/services/admin.js +75 -14
- package/server/services/client.js +22 -14
- package/server/services/common.js +41 -70
- package/server/services/utils/functions.js +7 -14
- package/server/utils/constants.js +3 -3
- package/admin/src/components/EntityDetails/index.js +0 -53
package/README.md
CHANGED
|
@@ -50,9 +50,15 @@ Enjoy 🎉
|
|
|
50
50
|
|
|
51
51
|
Complete installation requirements are exact same as for Strapi itself and can be found in the documentation under [Installation Requirements](https://docs.strapi.io/developer-docs/latest/getting-started/introduction.html).
|
|
52
52
|
|
|
53
|
+
**Minimum environment requirements**
|
|
54
|
+
- Node.js `>=14.x.x`
|
|
55
|
+
- NPM `>=7.x.x`
|
|
56
|
+
|
|
57
|
+
In our minimum support we're following [official Node.js releases timelines](https://nodejs.org/en/about/releases/).
|
|
58
|
+
|
|
53
59
|
**Supported Strapi versions**:
|
|
54
60
|
|
|
55
|
-
- Strapi v4.0.
|
|
61
|
+
- Strapi v4.0.4 (recently tested)
|
|
56
62
|
|
|
57
63
|
(This plugin is not working with v3.x and not may work with the older Strapi v4 versions, but these are not tested nor officially supported at this time.)
|
|
58
64
|
|
|
@@ -74,8 +80,13 @@ To setup amend default plugin configuration we recommend to put following snippe
|
|
|
74
80
|
```js
|
|
75
81
|
...
|
|
76
82
|
comments: {
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
badWords: false,
|
|
84
|
+
moderatorRoles: ["Authenticated"],
|
|
85
|
+
approvalFlow: ['api::page.page'],
|
|
86
|
+
entryLabel: {
|
|
87
|
+
'*': ['Title', 'title', 'Name', 'name', 'Subject', 'subject'],
|
|
88
|
+
'api::page.page': ['MyField'],
|
|
89
|
+
},
|
|
79
90
|
},
|
|
80
91
|
...
|
|
81
92
|
```
|
|
@@ -83,6 +94,8 @@ To setup amend default plugin configuration we recommend to put following snippe
|
|
|
83
94
|
### Properties
|
|
84
95
|
- `badWords` - Enabled support for [bad words filtering](https://www.npmjs.com/package/bad-words). Can be turned off or overwritten using [options reference](https://www.npmjs.com/package/bad-words#constructor). Default value: `true`.
|
|
85
96
|
- `moderatorRoles` - Optional list of names of roles. Users with those roles will be notified by email when a new abuse report is created. This feature requires a built-in [Strapi email plugin](https://docs.strapi.io/developer-docs/latest/plugins/email.html) configured.
|
|
97
|
+
- `approvalFlow` - list of Content Types which are supporting approval flow. Values must be in format like `'api::<collection name>.<content type name>'`. For not included, posted comments are going to be immediately visible.
|
|
98
|
+
- `entryLabel` - ordered list of property names per Content Type to generate related entity label. Keys must be in format like `'api::<collection name>.<content type name>'`. Default formatting set as `*`.
|
|
86
99
|
|
|
87
100
|
## Additional GQL Configuration
|
|
88
101
|
|
|
@@ -215,7 +228,6 @@ Posts a Comment related to specified instance of Content Type like for example `
|
|
|
215
228
|
},
|
|
216
229
|
"content": "My sample response",
|
|
217
230
|
"threadOf": 2, // id of comment we would like to start / continue the thread (Optional)
|
|
218
|
-
"related": "api::page.page:1"
|
|
219
231
|
}
|
|
220
232
|
```
|
|
221
233
|
|
|
@@ -225,7 +237,6 @@ Posts a Comment related to specified instance of Content Type like for example `
|
|
|
225
237
|
"authorUser": 1, // id of a author user. Optional in case of 'enableUsers: true' in the plugin configuration
|
|
226
238
|
"content": "My sample response",
|
|
227
239
|
"threadOf": 2, // id of comment we would like to start / continue the thread (Optional)
|
|
228
|
-
"related": "api::page.page:1"
|
|
229
240
|
}
|
|
230
241
|
```
|
|
231
242
|
|
|
@@ -34,12 +34,14 @@ const DiscussionThread = ({ level = [], selected = {}, isReloading }) => {
|
|
|
34
34
|
<Divider />
|
|
35
35
|
</Box>
|
|
36
36
|
<Flex as="ul" direction="column" alignItems="flex-start">
|
|
37
|
-
{ rootThread && (<DiscussionThreadItem {...rootThread} isThreadAuthor root />) }
|
|
37
|
+
{ rootThread && (<DiscussionThreadItem {...rootThread} isThreadAuthor root pinned />) }
|
|
38
38
|
{ level.map(item => {
|
|
39
39
|
const isSelected = selected.id === item.id;
|
|
40
40
|
const isThreadAuthor = !isNil(selected?.threadOf?.authorId) && (selected?.threadOf?.authorId === item.authorId);
|
|
41
41
|
return (<DiscussionThreadItem
|
|
42
|
+
key={`comment-${item.id}`}
|
|
42
43
|
{...item}
|
|
44
|
+
root={isNil(rootThread)}
|
|
43
45
|
blockedThread={rootThread?.blockedThread || item.blockedThread}
|
|
44
46
|
isSelected={isSelected}
|
|
45
47
|
isThreadAuthor={isThreadAuthor}
|
|
@@ -21,7 +21,7 @@ import { getMessage, getUrl } from '../../utils';
|
|
|
21
21
|
import DiscussionThreadItemWarnings from '../DiscussionThreadItemWarnings';
|
|
22
22
|
|
|
23
23
|
const DiscussionThreadItem = (item) => {
|
|
24
|
-
const { id, isSelected, isThreadAuthor, content, root, preview, blocked, blockedThread, gotThread, threadFirstItemId, authorUser, authorAvatar, authorName } = item;
|
|
24
|
+
const { id, isSelected, isThreadAuthor, content, root, preview, pinned, blocked, removed, blockedThread, gotThread, threadFirstItemId, authorUser, authorAvatar, authorName } = item;
|
|
25
25
|
|
|
26
26
|
let background = 'neutral100';
|
|
27
27
|
let borderColor = 'neutral200';
|
|
@@ -62,16 +62,16 @@ const DiscussionThreadItem = (item) => {
|
|
|
62
62
|
<Box as={Flex} grow={1} alignItems="center">
|
|
63
63
|
<Typography variant="omega">{ content }</Typography>
|
|
64
64
|
</Box>
|
|
65
|
-
{ !preview && (<DiscussionThreadItemActions { ...item } root={root} />) }
|
|
65
|
+
{ !preview && (<DiscussionThreadItemActions { ...item } root={root || pinned} />) }
|
|
66
66
|
</DiscussionThreadItemBoxContent>
|
|
67
|
-
{ !(preview || isBlocked) && (<DiscussionThreadItemWarnings { ...item } />) }
|
|
67
|
+
{ !(preview || isBlocked || removed) && (<DiscussionThreadItemWarnings { ...item } gotThread={gotThread || root} />) }
|
|
68
68
|
</Flex>
|
|
69
69
|
<Divider background={dividerColor} />
|
|
70
70
|
<DiscussionThreadItemMeta { ...item } />
|
|
71
71
|
</DiscussionThreadItemBox>
|
|
72
72
|
</DiscussionThreadItemContent>
|
|
73
73
|
|
|
74
|
-
{ (gotThread && !preview) && (<DiscussionThreadItemLinkBox direction="
|
|
74
|
+
{ (gotThread && !(removed || preview || pinned)) && (<DiscussionThreadItemLinkBox direction="column" alignItems="flex-end">
|
|
75
75
|
<Link to={getUrl(`discover/${threadFirstItemId}`)} endIcon={<ArrowRight />}>
|
|
76
76
|
{ getMessage('page.details.panel.discussion.nav.drilldown') }
|
|
77
77
|
</Link>
|
|
@@ -4,21 +4,23 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, { useState
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
8
|
import PropTypes from 'prop-types';
|
|
9
9
|
import { useMutation, useQueryClient } from 'react-query';
|
|
10
10
|
import { isNil } from 'lodash';
|
|
11
11
|
import { Button } from '@strapi/design-system/Button';
|
|
12
12
|
import { Flex } from '@strapi/design-system/Flex';
|
|
13
13
|
import { IconButton } from '@strapi/design-system/IconButton';
|
|
14
|
-
import { Lock, Check, Cross, Key } from '@strapi/icons';
|
|
15
14
|
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
16
|
-
import { getMessage, handleAPIError } from '../../utils';
|
|
17
|
-
import { DiscussionThreadItemActionsBadge,
|
|
15
|
+
import { getMessage, handleAPIError, resolveCommentStatus, resolveCommentStatusColor } from '../../utils';
|
|
16
|
+
import { DiscussionThreadItemActionsBadge, DiscussionThreadItemActionsWrapper } from './styles';
|
|
18
17
|
import ConfirmationDialog from '../ConfirmationDialog';
|
|
19
|
-
import { blockItem, blockItemThread, unblockItem, unblockItemThread } from '../../pages/
|
|
18
|
+
import { blockItem, blockItemThread, unblockItem, unblockItemThread } from '../../pages/utils/api';
|
|
19
|
+
import pluginId from '../../pluginId';
|
|
20
|
+
import { LockIcon, UnlockIcon } from '../icons';
|
|
21
|
+
import DiscussionThreadItemApprovalFlowActions from '../DiscussionThreadItemApprovalFlowActions';
|
|
20
22
|
|
|
21
|
-
const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatus, root }) => {
|
|
23
|
+
const DiscussionThreadItemActions = ({ id, blocked, removed, blockedThread, gotThread, pinned, approvalStatus, root }) => {
|
|
22
24
|
|
|
23
25
|
const [blockConfirmationVisible, setBlockConfirmationVisible] = useState(false);
|
|
24
26
|
const [blockThreadConfirmationVisible, setBlockThreadConfirmationVisible] = useState(false);
|
|
@@ -31,7 +33,7 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
|
|
|
31
33
|
await queryClient.invalidateQueries('get-details-data');
|
|
32
34
|
toggleNotification({
|
|
33
35
|
type: 'success',
|
|
34
|
-
message
|
|
36
|
+
message: `${pluginId}.${message}`,
|
|
35
37
|
});
|
|
36
38
|
stateAction(false);
|
|
37
39
|
unlockApp();
|
|
@@ -69,35 +71,16 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
|
|
|
69
71
|
const gotApprovalFlow = !isNil(approvalStatus);
|
|
70
72
|
const needsApproval = gotApprovalFlow && (approvalStatus === 'PENDING');
|
|
71
73
|
const isBlocked = blocked || blockedThread;
|
|
74
|
+
const isRejected = gotApprovalFlow && (approvalStatus === 'REJECTED');
|
|
72
75
|
|
|
73
76
|
const resolveStatusBadge = () => {
|
|
74
|
-
|
|
75
|
-
let label = '';
|
|
76
|
-
|
|
77
|
-
if (isBlocked) {
|
|
78
|
-
color = 'danger';
|
|
79
|
-
label = 'blocked';
|
|
80
|
-
} else if (gotApprovalFlow) {
|
|
81
|
-
label = approvalStatus.toLowerCase();
|
|
82
|
-
switch (approvalStatus) {
|
|
83
|
-
case 'APPROVED':
|
|
84
|
-
color = 'success';
|
|
85
|
-
break;
|
|
86
|
-
case 'REJECTED':
|
|
87
|
-
color = 'danger';
|
|
88
|
-
break;
|
|
89
|
-
default:
|
|
90
|
-
color = 'alternative';
|
|
91
|
-
break;
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
77
|
+
const status = resolveCommentStatus({ removed, blocked, blockedThread, approvalStatus });
|
|
95
78
|
return {
|
|
96
|
-
badgeVisible: isBlocked || gotApprovalFlow,
|
|
97
|
-
badgeColor:
|
|
98
|
-
badgeLabel:
|
|
99
|
-
}
|
|
100
|
-
}
|
|
79
|
+
badgeVisible: isBlocked || gotApprovalFlow || removed,
|
|
80
|
+
badgeColor: resolveCommentStatusColor(status),
|
|
81
|
+
badgeLabel: status,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
101
84
|
|
|
102
85
|
const {badgeVisible, badgeColor, badgeLabel } = resolveStatusBadge();
|
|
103
86
|
|
|
@@ -127,24 +110,27 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
|
|
|
127
110
|
unblockItemThreadMutation.mutate(id);
|
|
128
111
|
};
|
|
129
112
|
|
|
113
|
+
if (removed || isRejected) {
|
|
114
|
+
return (<DiscussionThreadItemActionsWrapper as={Flex} direction="row">
|
|
115
|
+
<DiscussionThreadItemActionsBadge color={badgeColor} backgroundColor={`${badgeColor}100`} textColor={`${badgeColor}600`}>{getMessage(`page.common.item.status.${badgeLabel}`, badgeLabel)}</DiscussionThreadItemActionsBadge>
|
|
116
|
+
</DiscussionThreadItemActionsWrapper>);
|
|
117
|
+
}
|
|
118
|
+
|
|
130
119
|
return (<>
|
|
131
120
|
<DiscussionThreadItemActionsWrapper as={Flex} direction="row">
|
|
132
|
-
{ badgeVisible && (<DiscussionThreadItemActionsBadge color={badgeColor} backgroundColor={`${badgeColor}100`} textColor={`${badgeColor}600`}>{getMessage(`page.
|
|
133
|
-
{ !blockedThread && !(blocked || needsApproval) && (<IconButton onClick={handleBlockClick} loading={blockItemMutation.isLoading} icon={<
|
|
134
|
-
{ !blockedThread && blocked && (<IconButton onClick={handleUnblockClick} loading={unblockItemMutation.isLoading} icon={<
|
|
135
|
-
{ (!blockedThread &&
|
|
136
|
-
{ (blockedThread &&
|
|
137
|
-
{ needsApproval && (<
|
|
138
|
-
<IconButton icon={<Check />} label={getMessage('page.details.actions.comment.approve', 'Approve')} />
|
|
139
|
-
<IconButton icon={<Cross />} label={getMessage('page.details.actions.comment.reject', 'Reject')} />
|
|
140
|
-
</DiscussionThreadItemActionsGroupWrapper>) }
|
|
121
|
+
{ badgeVisible && (<DiscussionThreadItemActionsBadge color={badgeColor} backgroundColor={`${badgeColor}100`} textColor={`${badgeColor}600`}>{getMessage(`page.common.item.status.${badgeLabel}`, badgeLabel)}</DiscussionThreadItemActionsBadge>)}
|
|
122
|
+
{ !blockedThread && !(blocked || needsApproval) && (<IconButton onClick={handleBlockClick} loading={blockItemMutation.isLoading} icon={<LockIcon />} label={getMessage('page.details.actions.comment.block', 'Block')} style={(!blockedThread && root) ? { marginTop: '1px', marginRight: '.5rem' } : {}} />)}
|
|
123
|
+
{ !blockedThread && blocked && (<IconButton onClick={handleUnblockClick} loading={unblockItemMutation.isLoading} icon={<UnlockIcon />} label={getMessage('page.details.actions.comment.unblock', 'Unblock')} style={(blocked && !blockedThread) ? { marginTop: '1px', marginRight: '.5rem' } : {}} />)}
|
|
124
|
+
{ (!blockedThread && (gotThread || pinned)) && (<Button onClick={handleBlockThreadClick} startIcon={<LockIcon />} loading={blockItemThreadMutation.isLoading} variant="danger">{ getMessage('page.details.actions.thread.block', 'Block thread') }</Button>) }
|
|
125
|
+
{ (blockedThread && (gotThread || pinned)) && (<Button onClick={handleUnblockThreadClick} startIcon={<UnlockIcon />} loading={unblockItemThreadMutation.isLoading} variant="success">{ getMessage('page.details.actions.thread.unblock', 'Unblock thread') }</Button>) }
|
|
126
|
+
{ needsApproval && (<DiscussionThreadItemApprovalFlowActions id={id} queryToInvalidate="get-details-data" />) }
|
|
141
127
|
</DiscussionThreadItemActionsWrapper>
|
|
142
128
|
{ !blocked && (<ConfirmationDialog
|
|
143
129
|
isVisible={blockConfirmationVisible}
|
|
144
130
|
isActionAsync={blockItemMutation.isLoading}
|
|
145
131
|
header={getMessage('page.details.actions.comment.block.confirmation.header')}
|
|
146
132
|
labelConfirm={getMessage('page.details.actions.comment.block.confirmation.button.confirm')}
|
|
147
|
-
iconConfirm={<
|
|
133
|
+
iconConfirm={<LockIcon />}
|
|
148
134
|
onConfirm={handleBlockConfirm}
|
|
149
135
|
onCancel={handleBlockCancel}>
|
|
150
136
|
{ getMessage('page.details.actions.comment.block.confirmation.description') }
|
|
@@ -154,7 +140,7 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
|
|
|
154
140
|
isActionAsync={blockItemThreadMutation.isLoading}
|
|
155
141
|
header={getMessage('page.details.actions.thread.block.confirmation.header')}
|
|
156
142
|
labelConfirm={getMessage('page.details.actions.thread.block.confirmation.button.confirm')}
|
|
157
|
-
iconConfirm={<
|
|
143
|
+
iconConfirm={<LockIcon />}
|
|
158
144
|
onConfirm={handleBlockThreadConfirm}
|
|
159
145
|
onCancel={handleBlockThreadCancel}>
|
|
160
146
|
{ getMessage('page.details.actions.thread.block.confirmation.description') }
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Approval Flow Actions
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import { useMutation, useQueryClient } from 'react-query';
|
|
10
|
+
import { IconButton } from '@strapi/design-system/IconButton';
|
|
11
|
+
import { Check, Cross } from '@strapi/icons';
|
|
12
|
+
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
13
|
+
import { getMessage, handleAPIError } from '../../utils';
|
|
14
|
+
import { DiscussionThreadItemActionsGroupWrapper } from './styles';
|
|
15
|
+
import { approveItem, rejectItem } from '../../pages/utils/api';
|
|
16
|
+
import pluginId from '../../pluginId';
|
|
17
|
+
|
|
18
|
+
const DiscussionThreadItemApprovalFlowActions = ({ id, queryToInvalidate }) => {
|
|
19
|
+
|
|
20
|
+
const toggleNotification = useNotification();
|
|
21
|
+
const queryClient = useQueryClient();
|
|
22
|
+
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
23
|
+
|
|
24
|
+
const onSuccess = (message, stateAction = () => {}) => async () => {
|
|
25
|
+
if (queryToInvalidate) {
|
|
26
|
+
await queryClient.invalidateQueries(queryToInvalidate);
|
|
27
|
+
}
|
|
28
|
+
toggleNotification({
|
|
29
|
+
type: 'success',
|
|
30
|
+
message: `${pluginId}.${message}`,
|
|
31
|
+
});
|
|
32
|
+
unlockApp();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
36
|
+
|
|
37
|
+
const approveItemMutation = useMutation(approveItem, {
|
|
38
|
+
onSuccess: onSuccess('page.details.actions.comment.approve.confirmation.success'),
|
|
39
|
+
onError,
|
|
40
|
+
refetchActive: false,
|
|
41
|
+
});
|
|
42
|
+
const rejectItemMutation = useMutation(rejectItem, {
|
|
43
|
+
onSuccess: onSuccess('page.details.actions.comment.reject.confirmation.success'),
|
|
44
|
+
onError,
|
|
45
|
+
refetchActive: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
const handleApproveClick = () => {
|
|
50
|
+
lockApp();
|
|
51
|
+
approveItemMutation.mutate(id);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleRejectClick = () => {
|
|
55
|
+
lockApp();
|
|
56
|
+
rejectItemMutation.mutate(id);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (<DiscussionThreadItemActionsGroupWrapper>
|
|
60
|
+
<IconButton icon={<Check />} label={getMessage('page.details.actions.comment.approve', 'Approve')} onClick={handleApproveClick} />
|
|
61
|
+
<IconButton icon={<Cross />} label={getMessage('page.details.actions.comment.reject', 'Reject')} onClick={handleRejectClick} />
|
|
62
|
+
</DiscussionThreadItemActionsGroupWrapper>);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
DiscussionThreadItemApprovalFlowActions.propTypes = {
|
|
66
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
67
|
+
queryToInvalidate: PropTypes.string,
|
|
68
|
+
wrapped: PropTypes.bool,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default DiscussionThreadItemApprovalFlowActions;
|
|
72
|
+
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import React, { useState } from 'react';
|
|
8
|
-
import PropTypes from 'prop-types';
|
|
9
8
|
import { useMutation, useQueryClient } from 'react-query';
|
|
10
9
|
import { isEmpty, orderBy } from 'lodash';
|
|
11
10
|
import { Badge } from '@strapi/design-system/Badge';
|
|
@@ -14,18 +13,20 @@ import { Button } from '@strapi/design-system/Button';
|
|
|
14
13
|
import { Divider } from '@strapi/design-system/Divider';
|
|
15
14
|
import { Flex } from '@strapi/design-system/Flex';
|
|
16
15
|
import { Typography } from '@strapi/design-system/Typography';
|
|
17
|
-
import { ArrowRight, Lock } from '@strapi/icons';
|
|
18
16
|
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
19
17
|
import { getMessage, handleAPIError } from '../../utils';
|
|
20
|
-
import { blockItem, blockItemThread, resolveReport } from '../../pages/
|
|
18
|
+
import { blockItem, blockItemThread, resolveReport } from '../../pages/utils/api';
|
|
21
19
|
import { DiscussionThreadItemWarningsWrapper } from './styles';
|
|
22
20
|
import ReportsReviewModal from '../ReportsReviewModal';
|
|
23
21
|
import ReportsReviewTable from '../ReportsReviewTable';
|
|
22
|
+
import pluginId from '../../pluginId';
|
|
23
|
+
import { LockIcon, ReviewIcon } from '../icons';
|
|
24
24
|
|
|
25
25
|
const DiscussionThreadItemWarnings = (item) => {
|
|
26
|
-
const { id, reports } = item;
|
|
26
|
+
const { id, reports, gotThread } = item;
|
|
27
27
|
|
|
28
28
|
const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
|
|
29
|
+
const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
|
|
29
30
|
|
|
30
31
|
const toggleNotification = useNotification();
|
|
31
32
|
const queryClient = useQueryClient();
|
|
@@ -37,7 +38,7 @@ const DiscussionThreadItemWarnings = (item) => {
|
|
|
37
38
|
}
|
|
38
39
|
toggleNotification({
|
|
39
40
|
type: 'success',
|
|
40
|
-
message
|
|
41
|
+
message: `${pluginId}.${message}`,
|
|
41
42
|
});
|
|
42
43
|
stateAction(false);
|
|
43
44
|
unlockApp();
|
|
@@ -82,6 +83,8 @@ const DiscussionThreadItemWarnings = (item) => {
|
|
|
82
83
|
setReportsReviewVisible(false);
|
|
83
84
|
};
|
|
84
85
|
|
|
86
|
+
const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
|
|
87
|
+
|
|
85
88
|
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
|
|
86
89
|
const openReports = reports?.filter(_ => !_.resolved);
|
|
87
90
|
|
|
@@ -97,7 +100,7 @@ const DiscussionThreadItemWarnings = (item) => {
|
|
|
97
100
|
<Typography textColor="warning600" fontWeight="bold">{getMessage(`page.details.panel.discussion.warnings.reports.description`, 'abuse reports')}</Typography>
|
|
98
101
|
</Box>
|
|
99
102
|
</Box>
|
|
100
|
-
<Button onClick={handleReportsReviewClick} variant="ghost" endIcon={<
|
|
103
|
+
<Button onClick={handleReportsReviewClick} variant="ghost" endIcon={<ReviewIcon />}>
|
|
101
104
|
{ getMessage(`page.details.actions.comment.report.review`, 'review') }
|
|
102
105
|
</Button>
|
|
103
106
|
</DiscussionThreadItemWarningsWrapper>
|
|
@@ -106,12 +109,12 @@ const DiscussionThreadItemWarnings = (item) => {
|
|
|
106
109
|
isActionAsync={isLoading}
|
|
107
110
|
onClose={handleReportsReviewClose}
|
|
108
111
|
startActions={<>
|
|
109
|
-
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<
|
|
112
|
+
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
110
113
|
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
111
114
|
</Button>
|
|
112
|
-
<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<
|
|
115
|
+
{ gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
113
116
|
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
114
|
-
</Button>
|
|
117
|
+
</Button>) }
|
|
115
118
|
</>}
|
|
116
119
|
item={item}>
|
|
117
120
|
<Typography variant="sigma" textColor="neutral600" id="reports-list">
|
|
@@ -124,6 +127,7 @@ const DiscussionThreadItemWarnings = (item) => {
|
|
|
124
127
|
commentId={item.id}
|
|
125
128
|
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
126
129
|
mutation={resolveReportMutation}
|
|
130
|
+
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
127
131
|
/>
|
|
128
132
|
</ReportsReviewModal>
|
|
129
133
|
</>);
|
|
@@ -18,7 +18,11 @@ import getMessage from '../../utils/getMessage';
|
|
|
18
18
|
// import PropTypes from 'prop-types';
|
|
19
19
|
import getUrl from '../../utils/getUrl';
|
|
20
20
|
|
|
21
|
-
const Nav = ({ hasNewComments, hasNewReports }) => {
|
|
21
|
+
const Nav = ({ visible = false, hasNewComments, hasNewReports }) => {
|
|
22
|
+
if (!visible) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
return <SubNav ariaLabel="Comments sub nav">
|
|
23
27
|
<SubNavHeader label={getMessage('plugin.name')} />
|
|
24
28
|
<SubNavSections>
|
|
@@ -13,7 +13,6 @@ import DiscussionThreadItem from '../DiscussionThreadItem';
|
|
|
13
13
|
|
|
14
14
|
const ReportsReviewModal = ({
|
|
15
15
|
isVisible = false,
|
|
16
|
-
isActionAsync = false,
|
|
17
16
|
item,
|
|
18
17
|
children,
|
|
19
18
|
onClose,
|
|
@@ -34,7 +33,6 @@ const ReportsReviewModal = ({
|
|
|
34
33
|
|
|
35
34
|
ReportsReviewModal.propTypes = {
|
|
36
35
|
isVisible: PropTypes.bool,
|
|
37
|
-
isActionAsync: PropTypes.bool,
|
|
38
36
|
startActions: PropTypes.array,
|
|
39
37
|
endActions: PropTypes.array,
|
|
40
38
|
item: PropTypes.object.isRequired,
|
|
@@ -23,7 +23,8 @@ import { REPORT_REASON, REPORT_STATUS } from '../../utils/constants';
|
|
|
23
23
|
const ReportsReviewTable = ({
|
|
24
24
|
commentId,
|
|
25
25
|
items,
|
|
26
|
-
mutation
|
|
26
|
+
mutation,
|
|
27
|
+
onBlockButtonsStateChange,
|
|
27
28
|
}) => {
|
|
28
29
|
|
|
29
30
|
const [storedItems, setStoredItems] = useState([]);
|
|
@@ -65,10 +66,12 @@ const ReportsReviewTable = ({
|
|
|
65
66
|
});
|
|
66
67
|
console.log(item);
|
|
67
68
|
if (item) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const updatedItems = storedItems.map(_ => ({
|
|
70
|
+
..._,
|
|
71
|
+
resolved: reportId === _.id ? true : _.resolved,
|
|
72
|
+
}));
|
|
73
|
+
setStoredItems(updatedItems);
|
|
74
|
+
onBlockButtonsStateChange(updatedItems.filter(_ => !_.resolved).length === 0);
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -137,6 +140,7 @@ ReportsReviewTable.propTypes = {
|
|
|
137
140
|
commentId: PropTypes.string.isRequired,
|
|
138
141
|
items: PropTypes.array.isRequired,
|
|
139
142
|
mutation: PropTypes.func.isRequired,
|
|
143
|
+
onBlockButtonsStateChange: PropTypes.func.isRequired,
|
|
140
144
|
};
|
|
141
145
|
|
|
142
146
|
export default ReportsReviewTable;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
const initSize = 330;
|
|
5
|
+
|
|
6
|
+
const LockIcon = ({ width, height, size = 16 }) =>
|
|
7
|
+
<svg
|
|
8
|
+
width={width || size}
|
|
9
|
+
height={height || size}
|
|
10
|
+
viewBox={`0 0 ${width || size} ${height || size}`}
|
|
11
|
+
style={{ width: `${width || size}px`, height: `${height || size}px`, }}
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
13
|
+
<g style={ { transform: `scale(${(width || size)/initSize})` } }>
|
|
14
|
+
<path d="M165,330c63.411,0,115-51.589,115-115c0-29.771-11.373-56.936-30-77.379V85c0-46.869-38.131-85-85-85
|
|
15
|
+
S80.001,38.131,80.001,85v52.619C61.373,158.064,50,185.229,50,215C50,278.411,101.589,330,165,330z M180,219.986V240
|
|
16
|
+
c0,8.284-6.716,15-15,15s-15-6.716-15-15v-20.014c-6.068-4.565-10-11.824-10-19.986c0-13.785,11.215-25,25-25s25,11.215,25,25
|
|
17
|
+
C190,208.162,186.068,215.421,180,219.986z M110.001,85c0-30.327,24.673-55,54.999-55c30.327,0,55,24.673,55,55v29.029
|
|
18
|
+
C203.652,105.088,184.91,100,165,100c-19.909,0-38.651,5.088-54.999,14.028V85z"/>
|
|
19
|
+
</g>
|
|
20
|
+
</svg>
|
|
21
|
+
|
|
22
|
+
export default LockIcon;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
const initSize = 16;
|
|
5
|
+
|
|
6
|
+
const ReviewIcon = ({ width, height, size = 16 }) =>
|
|
7
|
+
<svg
|
|
8
|
+
width={width || size}
|
|
9
|
+
height={height || size}
|
|
10
|
+
viewBox={`0 0 ${width || size} ${height || size}`}
|
|
11
|
+
style={{ width: `${width || size}px`, height: `${height || size}px`, }}
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
13
|
+
<g style={ {transform: `scale(${(width || size)/initSize})` } }>
|
|
14
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 4a4 4 0 014-4h8a4 4 0 014 4v8a4 4 0 01-4 4H4a4 4 0 01-4-4V4zm6.996.165a1.017 1.017 0 112.012 0L8 11 6.996 4.165zM8 11a1 1 0 110 2 1 1 0 010-2z" />
|
|
15
|
+
</g>
|
|
16
|
+
</svg>
|
|
17
|
+
|
|
18
|
+
export default ReviewIcon;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
const initSize = 330;
|
|
5
|
+
|
|
6
|
+
const UnlockIcon = ({ width, height, size = 16 }) =>
|
|
7
|
+
<svg
|
|
8
|
+
width={width || size}
|
|
9
|
+
height={height || size}
|
|
10
|
+
viewBox={`0 0 ${width || size} ${height || size}`}
|
|
11
|
+
style={{ width: `${width || size}px`, height: `${height || size}px`, }}
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
13
|
+
<g style={ { transform: `scale(${(width || size)/initSize})` } }>
|
|
14
|
+
<path d="M15,160c8.283,0,15-6.716,15-15V85c0-30.327,24.672-55,55-55c30.326,0,55,24.673,55,55v42.893
|
|
15
|
+
c-24.479,21.105-40,52.327-40,87.107c0,63.411,51.588,115,115,115c63.41,0,115-51.589,115-115s-51.59-115-115-115
|
|
16
|
+
c-15.961,0-31.172,3.271-45,9.174V85c0-46.869-38.131-85-85-85S0,38.131,0,85v60C0,153.284,6.715,160,15,160z"/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
|
19
|
+
|
|
20
|
+
export default UnlockIcon;
|
package/admin/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import pluginId from './pluginId';
|
|
|
4
4
|
import Initializer from './components/Initializer';
|
|
5
5
|
import PluginIcon from './components/PluginIcon';
|
|
6
6
|
import pluginPermissions from './permissions';
|
|
7
|
+
import reducers from './reducers';
|
|
7
8
|
|
|
8
9
|
const name = pluginPkg.strapi.name;
|
|
9
10
|
|
|
@@ -24,6 +25,7 @@ export default {
|
|
|
24
25
|
},
|
|
25
26
|
permissions: pluginPermissions,
|
|
26
27
|
});
|
|
28
|
+
app.addReducers(reducers);
|
|
27
29
|
app.registerPlugin({
|
|
28
30
|
id: pluginId,
|
|
29
31
|
initializer: Initializer,
|
|
@@ -33,10 +35,10 @@ export default {
|
|
|
33
35
|
},
|
|
34
36
|
|
|
35
37
|
bootstrap(app) {
|
|
36
|
-
app.injectContentManagerComponent('editView', 'informations', {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
38
|
+
// app.injectContentManagerComponent('editView', 'informations', {
|
|
39
|
+
// name: 'comments-link',
|
|
40
|
+
// Component: () => 'TODO: Comments count',
|
|
41
|
+
// });
|
|
40
42
|
},
|
|
41
43
|
|
|
42
44
|
async registerTrads({ locales }) {
|
|
@@ -6,15 +6,41 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
|
+
import { connect } from 'react-redux';
|
|
10
|
+
import { bindActionCreators, compose } from 'redux';
|
|
9
11
|
import { Switch, Route } from 'react-router-dom';
|
|
10
|
-
import {
|
|
12
|
+
import { useQuery } from 'react-query';
|
|
13
|
+
import {
|
|
14
|
+
NotFound,
|
|
15
|
+
LoadingIndicatorPage,
|
|
16
|
+
useNotification } from '@strapi/helper-plugin';
|
|
17
|
+
|
|
11
18
|
import ComingSoonPage from '../ComingSoonPage';
|
|
12
19
|
import getUrl from '../../utils/getUrl';
|
|
13
20
|
import Discover from '../Discover';
|
|
14
21
|
import Details from '../Details';
|
|
22
|
+
import { fetchConfig } from './utils/api';
|
|
23
|
+
import { setConfig } from './reducer/actions';
|
|
24
|
+
import makeAppView from './reducer/selectors';
|
|
25
|
+
|
|
26
|
+
const App = ({ setConfig }) => {
|
|
27
|
+
|
|
28
|
+
const toggleNotification = useNotification();
|
|
29
|
+
|
|
30
|
+
const { isLoading, isFetching } = useQuery(
|
|
31
|
+
'get-config', () => fetchConfig(toggleNotification),
|
|
32
|
+
{
|
|
33
|
+
initialData: { },
|
|
34
|
+
onSuccess: (response) => {
|
|
35
|
+
setConfig(response);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
);
|
|
15
39
|
|
|
40
|
+
if (isLoading || isFetching) {
|
|
41
|
+
return (<LoadingIndicatorPage />)
|
|
42
|
+
}
|
|
16
43
|
|
|
17
|
-
const App = () => {
|
|
18
44
|
return (
|
|
19
45
|
<div>
|
|
20
46
|
<Switch>
|
|
@@ -29,4 +55,17 @@ const App = () => {
|
|
|
29
55
|
);
|
|
30
56
|
};
|
|
31
57
|
|
|
32
|
-
|
|
58
|
+
const mapStateToProps = makeAppView();
|
|
59
|
+
|
|
60
|
+
export function mapDispatchToProps(dispatch) {
|
|
61
|
+
return bindActionCreators(
|
|
62
|
+
{ setConfig },
|
|
63
|
+
dispatch
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const withConnect = connect(
|
|
67
|
+
mapStateToProps,
|
|
68
|
+
mapDispatchToProps
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
export default compose(withConnect)(App);
|