strapi-plugin-comments 2.0.0-beta.6 → 2.0.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/.circleci/config.yml +1 -1
- package/README.md +474 -119
- package/__mocks__/initSetup.js +53 -0
- package/admin/src/components/ActionButton/styles.js +10 -0
- package/admin/src/components/DiscussionThread/index.js +3 -7
- package/admin/src/components/DiscussionThreadItem/index.js +25 -74
- package/admin/src/components/DiscussionThreadItem/styles.js +4 -79
- package/admin/src/components/DiscussionThreadItemActions/index.js +82 -40
- package/admin/src/components/DiscussionThreadItemActions/styles.js +1 -12
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +2 -3
- package/admin/src/components/DiscussionThreadItemFooter/index.js +47 -0
- package/admin/src/components/DiscussionThreadItemFooter/styles.js +13 -0
- package/admin/src/components/DiscussionThreadItemReviewAction/index.js +131 -0
- package/admin/src/components/IconButton/styles.js +14 -0
- package/admin/src/components/ReportsReviewModal/index.js +1 -1
- package/admin/src/components/ReportsReviewTable/index.js +12 -6
- package/admin/src/components/StatusBadge/index.js +17 -0
- package/admin/src/components/StatusBadge/styles.js +17 -0
- package/admin/src/pages/Details/components/DetailsEntity/index.js +40 -24
- package/admin/src/pages/Details/components/DetailsFilters/index.js +8 -10
- package/admin/src/pages/Details/index.js +9 -15
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +36 -66
- package/admin/src/pages/Discover/index.js +4 -1
- package/admin/src/translations/en.json +14 -15
- package/admin/src/utils/index.js +2 -0
- package/admin/src/utils/renderInitials.js +5 -0
- package/admin/src/utils/resolveCommentStatus.js +4 -4
- package/content-types/comment.js +0 -4
- package/package.json +8 -4
- package/public/assets/logo.png +0 -0
- package/public/assets/preview.png +0 -0
- package/server/config/index.js +5 -0
- package/server/controllers/client.js +11 -21
- package/server/controllers/utils/__tests__/functions.test.js +2 -2
- package/server/controllers/utils/parsers.js +19 -0
- package/server/graphql/index.js +26 -0
- package/server/graphql/mutations/createAbuseReport.js +25 -0
- package/server/graphql/mutations/createComment.js +25 -0
- package/server/graphql/mutations/index.js +21 -0
- package/server/graphql/mutations/removeComment.js +25 -0
- package/server/graphql/mutations/updateComment.js +25 -0
- package/server/graphql/queries/findAllFlat.js +29 -0
- package/server/graphql/queries/findAllInHierarchy.js +31 -0
- package/server/graphql/queries/index.js +19 -0
- package/server/graphql/resolvers-config.js +15 -0
- package/server/graphql/types/comment-author.js +9 -0
- package/server/graphql/types/comment-nested.js +16 -0
- package/server/graphql/types/comment.js +15 -0
- package/server/graphql/types/create-comment-author.js +9 -0
- package/server/graphql/types/create-comment.js +9 -0
- package/server/graphql/types/create-report.js +9 -0
- package/server/graphql/types/id.js +24 -0
- package/server/graphql/types/identify-comment-author.js +6 -0
- package/server/graphql/types/index.js +16 -0
- package/server/graphql/types/remove-comment.js +8 -0
- package/server/graphql/types/report-reason.js +10 -0
- package/server/graphql/types/report.js +11 -0
- package/server/graphql/types/update-comment.js +9 -0
- package/server/register.js +3 -1
- package/server/services/__tests__/common.test.js +165 -0
- package/server/services/admin.js +18 -15
- package/server/services/client.js +73 -51
- package/server/services/common.js +42 -14
- package/server/services/graphql.js +190 -0
- package/server/services/index.js +2 -0
- package/server/services/utils/__tests__/functions.test.js +9 -73
- package/server/services/utils/functions.js +30 -27
- package/server/utils/__tests__/functions.test.js +17 -0
- package/admin/src/components/DiscussionThreadItemMeta/index.js +0 -31
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +0 -147
- package/admin/src/components/DiscussionThreadItemWarnings/styles.js +0 -7
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useMutation, useQueryClient } from 'react-query';
|
|
4
|
+
import { isNil, isEmpty, orderBy } from 'lodash';
|
|
5
|
+
import { Button } from '@strapi/design-system/Button';
|
|
6
|
+
import { IconButton } from '@strapi/design-system/IconButton';
|
|
7
|
+
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
8
|
+
import { ReviewIcon, LockIcon } from '../icons';
|
|
9
|
+
import { resolveReport } from '../../pages/utils/api';
|
|
10
|
+
import { getMessage, handleAPIError } from '../../utils';
|
|
11
|
+
import ReportsReviewTable from '../ReportsReviewTable';
|
|
12
|
+
import ReportsReviewModal from '../ReportsReviewModal';
|
|
13
|
+
import pluginId from '../../pluginId';
|
|
14
|
+
|
|
15
|
+
const DiscussionThreadItemReviewAction = ({
|
|
16
|
+
item,
|
|
17
|
+
isAnyActionLoading: isAnyActionLoading,
|
|
18
|
+
queryToInvalidate,
|
|
19
|
+
areBlockButtonsDisabled,
|
|
20
|
+
allowedActions: { canModerate, canAccessReports, canReviewReports },
|
|
21
|
+
blockItemMutation,
|
|
22
|
+
blockItemThreadMutation,
|
|
23
|
+
onBlockButtonsStateChange,
|
|
24
|
+
onBlockActionClick }) => {
|
|
25
|
+
|
|
26
|
+
const { reports } = item;
|
|
27
|
+
|
|
28
|
+
const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
|
|
29
|
+
|
|
30
|
+
const toggleNotification = useNotification();
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
const { unlockApp } = useOverlayBlocker();
|
|
33
|
+
|
|
34
|
+
const onSuccess = (message, stateAction = () => {}, indalidate = true) => async () => {
|
|
35
|
+
if (indalidate) {
|
|
36
|
+
await queryClient.invalidateQueries(queryToInvalidate);
|
|
37
|
+
}
|
|
38
|
+
toggleNotification({
|
|
39
|
+
type: 'success',
|
|
40
|
+
message: `${pluginId}.${message}`,
|
|
41
|
+
});
|
|
42
|
+
stateAction(false);
|
|
43
|
+
unlockApp();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
47
|
+
|
|
48
|
+
const resolveReportMutation = useMutation(resolveReport, {
|
|
49
|
+
onSuccess: onSuccess('page.details.panel.discussion.warnings.reports.dialog.confirmation.success', () => {}, false),
|
|
50
|
+
onError,
|
|
51
|
+
refetchActive: false,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const handleBlockActionClick = () => setReportsReviewVisible(false);
|
|
55
|
+
|
|
56
|
+
const handleReportsReviewClick = () => {
|
|
57
|
+
if (canAccessReports) {
|
|
58
|
+
setReportsReviewVisible(true);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const handleBlockItemClick = async () => {
|
|
62
|
+
if (canModerate) {
|
|
63
|
+
await onBlockActionClick(blockItemMutation, handleBlockActionClick);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const handleBlockItemThreadClick = async () => {
|
|
67
|
+
if (canModerate) {
|
|
68
|
+
await onBlockActionClick(blockItemThreadMutation, handleBlockActionClick);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleReportsReviewClose = async () => {
|
|
73
|
+
await queryClient.invalidateQueries(queryToInvalidate);
|
|
74
|
+
setReportsReviewVisible(false);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const isLoading = isAnyActionLoading && resolveReportMutation.isLoading;
|
|
78
|
+
const openReports = reports?.filter(_ => !_.resolved);
|
|
79
|
+
const hasReports = !isEmpty(openReports);
|
|
80
|
+
const reviewFlowEnabled = (canAccessReports && hasReports) && !(item.blocked || item.blockedThread);
|
|
81
|
+
|
|
82
|
+
if (reviewFlowEnabled) {
|
|
83
|
+
return (<>
|
|
84
|
+
<IconButton onClick={handleReportsReviewClick} label={ getMessage('page.discover.table.reports.review') } icon={<ReviewIcon />} />
|
|
85
|
+
<ReportsReviewModal
|
|
86
|
+
isVisible={reportsReviewVisible}
|
|
87
|
+
isActionAsync={isLoading}
|
|
88
|
+
allowedActions={{ canModerate, canAccessReports, canReviewReports }}
|
|
89
|
+
onClose={handleReportsReviewClose}
|
|
90
|
+
startActions={<Button onClick={handleReportsReviewClose} variant="tertiary">
|
|
91
|
+
{ getMessage('compontents.confirmation.dialog.button.cancel', 'Cancel') }
|
|
92
|
+
</Button>}
|
|
93
|
+
endActions={canModerate && (<>
|
|
94
|
+
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={areBlockButtonsDisabled}>
|
|
95
|
+
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
96
|
+
</Button>
|
|
97
|
+
{ item.gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={areBlockButtonsDisabled}>
|
|
98
|
+
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
99
|
+
</Button>) }
|
|
100
|
+
</>)}
|
|
101
|
+
item={item}>
|
|
102
|
+
<ReportsReviewTable
|
|
103
|
+
commentId={item.id}
|
|
104
|
+
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
105
|
+
mutation={resolveReportMutation}
|
|
106
|
+
allowedActions={{ canAccessReports, canReviewReports }}
|
|
107
|
+
onBlockButtonsStateChange={onBlockButtonsStateChange}
|
|
108
|
+
/>
|
|
109
|
+
</ReportsReviewModal>
|
|
110
|
+
</>);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
DiscussionThreadItemReviewAction.propTypes = {
|
|
116
|
+
item: PropTypes.object.isRequired,
|
|
117
|
+
isLoading: PropTypes.bool,
|
|
118
|
+
queryToInvalidate: PropTypes.string.isRequired,
|
|
119
|
+
areBlockButtonsDisabled: PropTypes.bool,
|
|
120
|
+
allowedActions: PropTypes.shape({
|
|
121
|
+
canModerate: PropTypes.bool,
|
|
122
|
+
canAccessReports: PropTypes.bool,
|
|
123
|
+
canReviewReports: PropTypes.bool,
|
|
124
|
+
}),
|
|
125
|
+
blockItemMutation: PropTypes.func.isRequired,
|
|
126
|
+
blockItemThreadMutation: PropTypes.func.isRequired,
|
|
127
|
+
onBlockButtonsStateChange: PropTypes.func.isRequired,
|
|
128
|
+
onBlockActionClick: PropTypes.func.isRequired,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default DiscussionThreadItemReviewAction;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { IconButtonGroup } from '@strapi/design-system/IconButton';
|
|
4
|
+
|
|
5
|
+
export const IconButtonGroupStyled = styled(IconButtonGroup)`
|
|
6
|
+
${ ({ isSingle }) => isSingle && (`
|
|
7
|
+
span:first-child button {
|
|
8
|
+
border-radius: 4px;
|
|
9
|
+
}
|
|
10
|
+
`)}
|
|
11
|
+
${ ({ withMargin }) => withMargin && (`
|
|
12
|
+
margin-left: .5rem;
|
|
13
|
+
`)}
|
|
14
|
+
`;
|
|
@@ -24,7 +24,7 @@ const ReportsReviewModal = ({
|
|
|
24
24
|
<Typography fontWeight="bold" textColor="neutral800" as="h2" id="title">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.header', 'Open reports') }</Typography>
|
|
25
25
|
</ModalHeader>
|
|
26
26
|
<ModalBody>
|
|
27
|
-
<DiscussionThreadItem {...item} root preview isSelected={
|
|
27
|
+
<DiscussionThreadItem as="div" {...item} root preview isSelected={true} />
|
|
28
28
|
{ children }
|
|
29
29
|
</ModalBody>
|
|
30
30
|
<ModalFooter variant="tertiary" startActions={startActions} endActions={endActions} />
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
import { useIntl } from 'react-intl';
|
|
9
9
|
import PropTypes from 'prop-types';
|
|
10
10
|
import { isEmpty } from 'lodash';
|
|
11
|
-
import { Badge } from '@strapi/design-system/Badge';
|
|
12
|
-
import { Button } from '@strapi/design-system/Button';
|
|
13
11
|
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
|
|
14
12
|
import { Flex } from '@strapi/design-system/Flex';
|
|
15
13
|
import { Table, Thead, Tbody, TFooter, Tr, Th, Td } from '@strapi/design-system/Table';
|
|
@@ -19,6 +17,8 @@
|
|
|
19
17
|
import { useOverlayBlocker } from '@strapi/helper-plugin';
|
|
20
18
|
import { getMessage } from '../../utils';
|
|
21
19
|
import { REPORT_REASON, REPORT_STATUS } from '../../utils/constants';
|
|
20
|
+
import StatusBadge from '../StatusBadge';
|
|
21
|
+
import { ActionButton } from '../ActionButton/styles';
|
|
22
22
|
|
|
23
23
|
const ReportsReviewTable = ({
|
|
24
24
|
commentId,
|
|
@@ -43,7 +43,7 @@ const ReportsReviewTable = ({
|
|
|
43
43
|
case REPORT_STATUS.OPEN:
|
|
44
44
|
color = 'danger';
|
|
45
45
|
};
|
|
46
|
-
return (<
|
|
46
|
+
return (<StatusBadge backgroundColor={`${color}100`} textColor={`${color}600`} color={color}>{ getMessage(`page.details.panel.discussion.warnings.reports.dialog.status.${status}`, status) }</StatusBadge>);
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
const renderReason = reason => {
|
|
@@ -55,7 +55,7 @@ const ReportsReviewTable = ({
|
|
|
55
55
|
case REPORT_REASON.BAD_LANGUAGE:
|
|
56
56
|
color = 'warning';
|
|
57
57
|
};
|
|
58
|
-
return (<
|
|
58
|
+
return (<StatusBadge backgroundColor={`${color}100`} textColor={`${color}600`} color={color}>{ getMessage(`page.details.panel.discussion.warnings.reports.dialog.reason.${reason}`, reason) }</StatusBadge>);
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
const handleClickResolve = async (reportId) => {
|
|
@@ -125,11 +125,17 @@ const ReportsReviewTable = ({
|
|
|
125
125
|
<Typography textColor="neutral800">{ formatDate(entry.createdAt, { dateStyle: 'short', timeStyle: 'short' }) }</Typography>
|
|
126
126
|
</Td>
|
|
127
127
|
<Td>
|
|
128
|
-
|
|
128
|
+
{ renderStatus(entry.resolved) }
|
|
129
129
|
</Td>
|
|
130
130
|
{ canReviewReports && (<Td>
|
|
131
131
|
{ !entry.resolved && (<Flex direction="column" alignItems="flex-end">
|
|
132
|
-
<
|
|
132
|
+
<ActionButton
|
|
133
|
+
isSingle
|
|
134
|
+
onClick={() => handleClickResolve(entry.id)}
|
|
135
|
+
startIcon={<Check />}
|
|
136
|
+
variant="success">
|
|
137
|
+
{ getMessage('page.details.panel.discussion.warnings.reports.dialog.actions.resolve', 'resolve') }
|
|
138
|
+
</ActionButton>
|
|
133
139
|
</Flex>) }
|
|
134
140
|
</Td>)}
|
|
135
141
|
</Tr>)}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Status badge
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
9
|
+
import { StatusBadge as StatusBadgeStyled } from './styles';
|
|
10
|
+
|
|
11
|
+
const StatusBadge = ({ children, textColor, ...rest }) => (<StatusBadgeStyled {...rest} >
|
|
12
|
+
<Typography variant="omega" fontWeight="semibold" textColor={textColor}>
|
|
13
|
+
{ children }
|
|
14
|
+
</Typography>
|
|
15
|
+
</StatusBadgeStyled>);
|
|
16
|
+
|
|
17
|
+
export default StatusBadge;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Badge } from '@strapi/design-system/Badge';
|
|
3
|
+
|
|
4
|
+
export const StatusBadge = styled(Badge)`
|
|
5
|
+
padding: 5px 8px;
|
|
6
|
+
border: 1px ${ ({ theme, color }) => theme.colors[`${color}200`] } solid;
|
|
7
|
+
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
|
|
10
|
+
text-overflow: ellipsis;
|
|
11
|
+
|
|
12
|
+
cursor: default;
|
|
13
|
+
|
|
14
|
+
span {
|
|
15
|
+
text-transform: none;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
@@ -6,24 +6,27 @@
|
|
|
6
6
|
|
|
7
7
|
import React from 'react';
|
|
8
8
|
import PropTypes from 'prop-types';
|
|
9
|
-
import { capitalize, isNil, first, take } from 'lodash';
|
|
9
|
+
import { capitalize, isNil, isEmpty, first, take } from 'lodash';
|
|
10
10
|
import { Box } from '@strapi/design-system/Box';
|
|
11
11
|
import { Divider } from '@strapi/design-system/Divider';
|
|
12
12
|
import { Flex } from '@strapi/design-system/Flex';
|
|
13
13
|
import { Stack } from '@strapi/design-system/Stack';
|
|
14
14
|
import { Typography } from '@strapi/design-system/Typography';
|
|
15
15
|
import { getMessage } from '../../../../utils';
|
|
16
|
+
import DetailsFilters from '../DetailsFilters';
|
|
16
17
|
|
|
17
|
-
const DetailsEntity = ({
|
|
18
|
+
const DetailsEntity = ({
|
|
19
|
+
data = {},
|
|
20
|
+
schema = {},
|
|
21
|
+
config = {},
|
|
22
|
+
filters,
|
|
23
|
+
onFiltersChange
|
|
24
|
+
}) => {
|
|
18
25
|
const { entryLabel = {} } = config;
|
|
19
26
|
const { attributes = {} } = schema;
|
|
20
27
|
const keys = Object.keys(attributes);
|
|
21
28
|
const entityLabelKey = first(entryLabel[data?.uid]);
|
|
22
|
-
|
|
23
|
-
if (keys.length === 0) {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
|
|
29
|
+
|
|
27
30
|
const FIELDS_LIMIT = 5;
|
|
28
31
|
const itemKeys = take(
|
|
29
32
|
keys.filter(_ => (attributes[_].type === 'string') && !isNil(data[_]) && (_ !== entityLabelKey)),
|
|
@@ -35,24 +38,35 @@ const DetailsEntity = ({ data = {}, schema = {}, config = {} }) => {
|
|
|
35
38
|
.join(' ');
|
|
36
39
|
|
|
37
40
|
return (<Box padding={4}>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
41
|
+
{ !isEmpty(keys) && (<Box marginBottom={4}>
|
|
42
|
+
<Typography variant="sigma" textColor="neutral600" id="entity-details">
|
|
43
|
+
{ getMessage('page.details.panel.entity', 'Details') }
|
|
44
|
+
</Typography>
|
|
45
|
+
<Box paddingTop={2} paddingBottom={4}>
|
|
46
|
+
<Divider />
|
|
47
|
+
</Box>
|
|
48
|
+
<Stack size={itemKeys.length}>
|
|
49
|
+
<Flex direction="column" alignItems="flex-start">
|
|
50
|
+
<Typography fontWeight="bold">{ formatLabel(entityLabelKey) }</Typography>
|
|
51
|
+
<Typography>{ data[entityLabelKey] }</Typography>
|
|
52
|
+
</Flex>
|
|
53
|
+
{ itemKeys.map(_ =>
|
|
54
|
+
(<Flex key={`prop_${_}`} direction="column" alignItems="flex-start">
|
|
55
|
+
<Typography fontWeight="bold">{ formatLabel(_) }</Typography>
|
|
56
|
+
<Typography>{ data[_] }</Typography>
|
|
57
|
+
</Flex>))
|
|
58
|
+
}
|
|
59
|
+
</Stack>
|
|
60
|
+
</Box>) }
|
|
61
|
+
<Box>
|
|
62
|
+
<Typography variant="sigma" textColor="neutral600" id="view-filters">
|
|
63
|
+
{ getMessage('page.details.filters.label', 'View') }
|
|
64
|
+
</Typography>
|
|
65
|
+
<Box paddingTop={2} paddingBottom={4}>
|
|
66
|
+
<Divider />
|
|
67
|
+
</Box>
|
|
68
|
+
<DetailsFilters data={filters} onChange={onFiltersChange} />
|
|
43
69
|
</Box>
|
|
44
|
-
<Stack size={itemKeys.length}>
|
|
45
|
-
<Flex direction="column" alignItems="flex-start">
|
|
46
|
-
<Typography fontWeight="bold">{ formatLabel(entityLabelKey) }</Typography>
|
|
47
|
-
<Typography>{ data[entityLabelKey] }</Typography>
|
|
48
|
-
</Flex>
|
|
49
|
-
{ itemKeys.map(_ =>
|
|
50
|
-
(<Flex key={`prop_${_}`} direction="column" alignItems="flex-start">
|
|
51
|
-
<Typography fontWeight="bold">{ formatLabel(_) }</Typography>
|
|
52
|
-
<Typography>{ data[_] }</Typography>
|
|
53
|
-
</Flex>))
|
|
54
|
-
}
|
|
55
|
-
</Stack>
|
|
56
70
|
</Box>);
|
|
57
71
|
};
|
|
58
72
|
|
|
@@ -60,6 +74,8 @@ DetailsEntity.propTypes = {
|
|
|
60
74
|
data: PropTypes.object.isRequired,
|
|
61
75
|
schema: PropTypes.object.isRequired,
|
|
62
76
|
config: PropTypes.object.isRequired,
|
|
77
|
+
filters: PropTypes.object.isRequired,
|
|
78
|
+
onFiltersChange: PropTypes.func.isRequired,
|
|
63
79
|
};
|
|
64
80
|
|
|
65
81
|
export default DetailsEntity;
|
|
@@ -3,8 +3,7 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import { Box } from '@strapi/design-system/Box';
|
|
4
4
|
import { Flex } from '@strapi/design-system/Flex';
|
|
5
5
|
import { Stack } from '@strapi/design-system/Stack';
|
|
6
|
-
import {
|
|
7
|
-
import { Typography } from '@strapi/design-system/Typography';
|
|
6
|
+
import { Checkbox } from '@strapi/design-system/Checkbox';
|
|
8
7
|
import { getMessage } from '../../../../utils';
|
|
9
8
|
|
|
10
9
|
const DetailsFilters = ({ data, onChange }) => {
|
|
@@ -17,14 +16,13 @@ const DetailsFilters = ({ data, onChange }) => {
|
|
|
17
16
|
});
|
|
18
17
|
|
|
19
18
|
return (<Box as={Flex}>
|
|
20
|
-
<Stack size={2}
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
visibleLabels />
|
|
19
|
+
<Stack size={2} vertical>
|
|
20
|
+
<Checkbox
|
|
21
|
+
checked={removed}
|
|
22
|
+
onChange={() => handleOnChange('removed', !removed)}
|
|
23
|
+
visibleLabels>
|
|
24
|
+
{ getMessage('page.details.filters.removed.label', 'Show removed comments') }
|
|
25
|
+
</Checkbox>
|
|
28
26
|
</Stack>
|
|
29
27
|
</Box>);
|
|
30
28
|
};
|
|
@@ -12,10 +12,8 @@ import { useQuery } from 'react-query';
|
|
|
12
12
|
import { useRouteMatch } from 'react-router-dom';
|
|
13
13
|
import { Layout, HeaderLayout, TwoColsLayout, ContentLayout } from '@strapi/design-system/Layout';
|
|
14
14
|
import { Box } from '@strapi/design-system/Box';
|
|
15
|
-
import { Flex } from '@strapi/design-system/Flex';
|
|
16
15
|
import { Link } from '@strapi/design-system/Link';
|
|
17
16
|
import { Loader } from '@strapi/design-system/Loader';
|
|
18
|
-
import { Stack } from '@strapi/design-system/Stack';
|
|
19
17
|
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
|
20
18
|
import { ArrowLeft } from '@strapi/icons';
|
|
21
19
|
|
|
@@ -34,9 +32,7 @@ import { getUrl } from '../../utils';
|
|
|
34
32
|
import Nav from '../../components/Nav';
|
|
35
33
|
import DetailsEntity from './components/DetailsEntity';
|
|
36
34
|
import DiscussionThread from '../../components/DiscussionThread';
|
|
37
|
-
import DiscussionStatus from '../../components/DiscussionStatus';
|
|
38
35
|
import makeAppView from '../App/reducer/selectors';
|
|
39
|
-
import DetailsFilters from './components/DetailsFilters';
|
|
40
36
|
|
|
41
37
|
const Details = ({ config }) => {
|
|
42
38
|
useFocusWhenNavigate();
|
|
@@ -102,21 +98,19 @@ const Details = ({ config }) => {
|
|
|
102
98
|
</Link>}
|
|
103
99
|
title={ getMessage('page.details.header') }
|
|
104
100
|
subtitle={ getMessage('page.details.header.description') }
|
|
105
|
-
primaryAction={<DetailsFilters data={filters} onChange={handleChangeFilters} />}
|
|
106
|
-
secondaryAction={<Box as={Flex}> </Box>}
|
|
107
101
|
as="h2" />
|
|
108
102
|
<ContentLayout>
|
|
109
103
|
<TwoColsLayout
|
|
110
104
|
startCol={<DiscussionThread level={level} selected={selected} isReloading={isLoading} allowedActions={{ canModerate, canAccessReports, canReviewReports }} />}
|
|
111
|
-
endCol={ <
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
105
|
+
endCol={!contentTypeData?.schema ? (<Box padding={4}>
|
|
106
|
+
<Loader small>{ getMessage('page.details.panel.loading', 'Loading...') }</Loader>
|
|
107
|
+
</Box>) :
|
|
108
|
+
(<DetailsEntity
|
|
109
|
+
data={entity}
|
|
110
|
+
schema={contentTypeData?.schema}
|
|
111
|
+
config={config}
|
|
112
|
+
filters={filters}
|
|
113
|
+
onFiltersChange={handleChangeFilters} />)}
|
|
120
114
|
/>
|
|
121
115
|
</ContentLayout>
|
|
122
116
|
</>) }
|
|
@@ -2,11 +2,8 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
4
|
import { useMutation, useQueryClient } from 'react-query';
|
|
5
|
-
import { isNil, isEmpty
|
|
6
|
-
import {
|
|
7
|
-
import { Box } from '@strapi/design-system/Box';
|
|
8
|
-
import { Button } from '@strapi/design-system/Button';
|
|
9
|
-
import { Divider } from '@strapi/design-system/Divider';
|
|
5
|
+
import { isNil, isEmpty } from 'lodash';
|
|
6
|
+
import { Avatar, Initials } from '@strapi/design-system/Avatar';
|
|
10
7
|
import { Flex } from '@strapi/design-system/Flex';
|
|
11
8
|
import { IconButton } from '@strapi/design-system/IconButton';
|
|
12
9
|
import { Link } from '@strapi/design-system/Link';
|
|
@@ -15,21 +12,21 @@ import { Tr, Td } from '@strapi/design-system/Table';
|
|
|
15
12
|
import { Typography } from '@strapi/design-system/Typography';
|
|
16
13
|
import { Eye } from '@strapi/icons';
|
|
17
14
|
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
18
|
-
import { getMessage, getUrl, handleAPIError, resolveCommentStatus, resolveCommentStatusColor } from '../../../../utils';
|
|
19
|
-
import
|
|
20
|
-
import ReportsReviewTable from '../../../../components/ReportsReviewTable';
|
|
21
|
-
import { blockItem, blockItemThread, resolveReport } from '../../../utils/api';
|
|
15
|
+
import { getMessage, getUrl, handleAPIError, renderInitials, resolveCommentStatus, resolveCommentStatusColor } from '../../../../utils';
|
|
16
|
+
import { blockItem, blockItemThread } from '../../../utils/api';
|
|
22
17
|
import pluginId from '../../../../pluginId';
|
|
23
18
|
import { ReviewIcon, LockIcon } from '../../../../components/icons';
|
|
24
19
|
import { TableLink } from './styles';
|
|
25
20
|
import renderEntryTitle from '../../../../utils/renderEntryTitle';
|
|
26
21
|
import DiscussionThreadItemApprovalFlowActions from '../../../../components/DiscussionThreadItemApprovalFlowActions';
|
|
22
|
+
import StatusBadge from '../../../../components/StatusBadge';
|
|
23
|
+
import { IconButtonGroupStyled } from '../../../../components/IconButton/styles';
|
|
24
|
+
import DiscussionThreadItemReviewAction from '../../../../components/DiscussionThreadItemReviewAction';
|
|
27
25
|
|
|
28
26
|
const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAccessReports, canReviewReports }, onClick }) => {
|
|
29
27
|
|
|
30
28
|
const { id, reports } = item;
|
|
31
29
|
|
|
32
|
-
const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
|
|
33
30
|
const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
|
|
34
31
|
|
|
35
32
|
const { formatDate } = useIntl();
|
|
@@ -51,34 +48,17 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
|
|
|
51
48
|
|
|
52
49
|
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
53
50
|
|
|
54
|
-
const resolveReportMutation = useMutation(resolveReport, {
|
|
55
|
-
onSuccess: onSuccess('page.details.panel.discussion.warnings.reports.dialog.confirmation.success', () => {}, false),
|
|
56
|
-
onError,
|
|
57
|
-
refetchActive: false,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
51
|
const blockItemMutation = useMutation(blockItem, {
|
|
61
|
-
onSuccess: onSuccess(
|
|
62
|
-
'page.details.actions.comment.block.confirmation.success',
|
|
63
|
-
setReportsReviewVisible
|
|
64
|
-
),
|
|
52
|
+
onSuccess: onSuccess('page.details.actions.comment.block.confirmation.success'),
|
|
65
53
|
onError,
|
|
66
54
|
refetchActive: false,
|
|
67
55
|
});
|
|
68
56
|
const blockItemThreadMutation = useMutation(blockItemThread, {
|
|
69
|
-
onSuccess: onSuccess(
|
|
70
|
-
'page.details.actions.thread.block.confirmation.success',
|
|
71
|
-
setReportsReviewVisible
|
|
72
|
-
),
|
|
57
|
+
onSuccess: onSuccess('page.details.actions.thread.block.confirmation.success'),
|
|
73
58
|
onError,
|
|
74
59
|
refetchActive: false,
|
|
75
60
|
});
|
|
76
61
|
|
|
77
|
-
const handleReportsReviewClick = () => {
|
|
78
|
-
if (canAccessReports) {
|
|
79
|
-
setReportsReviewVisible(true);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
62
|
const handleBlockItemClick = async () => {
|
|
83
63
|
if (canModerate) {
|
|
84
64
|
lockApp();
|
|
@@ -91,12 +71,14 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
|
|
|
91
71
|
blockItemThreadMutation.mutate(id);
|
|
92
72
|
}
|
|
93
73
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
74
|
+
|
|
75
|
+
const handleBlockActionClick = async (mutation, onCallback) => {
|
|
76
|
+
lockApp();
|
|
77
|
+
await mutation.mutateAsync(id);
|
|
78
|
+
onCallback();
|
|
97
79
|
};
|
|
98
80
|
|
|
99
|
-
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading
|
|
81
|
+
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading;
|
|
100
82
|
const openReports = reports?.filter(_ => !_.resolved);
|
|
101
83
|
const hasReports = !isEmpty(openReports);
|
|
102
84
|
const reviewFlowEnabled = (canAccessReports && hasReports) && !(item.blocked || item.blockedThread);
|
|
@@ -108,12 +90,12 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
|
|
|
108
90
|
const status = resolveCommentStatus({ ...props, reviewFlowEnabled });
|
|
109
91
|
const color = resolveCommentStatusColor(status);
|
|
110
92
|
|
|
111
|
-
return (<
|
|
93
|
+
return (<StatusBadge backgroundColor={`${color}100`} textColor={`${color}700`} color={color}>{ getMessage({
|
|
112
94
|
id: `page.common.item.status.${status}`,
|
|
113
95
|
props: {
|
|
114
96
|
count: openReports.length
|
|
115
97
|
}
|
|
116
|
-
}, status) }</
|
|
98
|
+
}, status) }</StatusBadge>);
|
|
117
99
|
};
|
|
118
100
|
|
|
119
101
|
const renderEntryUrl = entry => `/content-manager/collectionType/${entry.uid}/${entry.id}`;
|
|
@@ -132,6 +114,12 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
|
|
|
132
114
|
<Td>
|
|
133
115
|
<Typography textColor="neutral800" fontWeight="bold">#{ item.id }</Typography>
|
|
134
116
|
</Td>
|
|
117
|
+
<Td>
|
|
118
|
+
<Stack size={2} horizontal>
|
|
119
|
+
{ item.author?.avatar ? <Avatar src={item.author?.avatar} alt={item.author.name} /> : <Initials>{ renderInitials(item.author.name) }</Initials> }
|
|
120
|
+
<Typography textColor="neutral800" variant="pi">{ item.author.name }</Typography>
|
|
121
|
+
</Stack>
|
|
122
|
+
</Td>
|
|
135
123
|
<Td style={{ maxWidth: '30vw' }}>
|
|
136
124
|
<Typography textColor="neutral800" ellipsis>{item.content}</Typography>
|
|
137
125
|
</Td>
|
|
@@ -152,45 +140,27 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
|
|
|
152
140
|
</Td>
|
|
153
141
|
<Td>
|
|
154
142
|
<Flex direction="column" alignItems="flex-end">
|
|
155
|
-
<
|
|
156
|
-
{ reviewFlowEnabled && (<IconButton onClick={handleReportsReviewClick} label={ getMessage('page.discover.table.reports.review') } icon={<ReviewIcon />} />)}
|
|
143
|
+
<IconButtonGroupStyled isSingle={!(reviewFlowEnabled || (canModerate && needsApproval))}>
|
|
157
144
|
{ (canModerate && needsApproval) && (
|
|
158
145
|
<DiscussionThreadItemApprovalFlowActions
|
|
159
146
|
id={item.id}
|
|
160
147
|
allowedActions={{ canModerate }}
|
|
161
148
|
queryToInvalidate="get-data" />
|
|
162
149
|
)}
|
|
150
|
+
<DiscussionThreadItemReviewAction
|
|
151
|
+
item={item}
|
|
152
|
+
queryToInvalidate="get-data"
|
|
153
|
+
areBlockButtonsDisabled={blockButtonsDisabled}
|
|
154
|
+
isLoading={isLoading}
|
|
155
|
+
allowedActions={{ canModerate, canAccessReports, canReviewReports }}
|
|
156
|
+
blockItemMutation={blockItemMutation}
|
|
157
|
+
blockItemThreadMutation={blockItemThreadMutation}
|
|
158
|
+
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
159
|
+
onBlockActionClick={handleBlockActionClick}
|
|
160
|
+
/>
|
|
163
161
|
<IconButton onClick={handleClick} label={ getMessage('page.discover.table.action.display') } icon={<Eye />} />
|
|
164
|
-
</
|
|
162
|
+
</IconButtonGroupStyled>
|
|
165
163
|
</Flex>
|
|
166
|
-
{ reviewFlowEnabled && (<ReportsReviewModal
|
|
167
|
-
isVisible={reportsReviewVisible}
|
|
168
|
-
isActionAsync={isLoading}
|
|
169
|
-
allowedActions={{ canModerate, canAccessReports, canReviewReports }}
|
|
170
|
-
onClose={handleReportsReviewClose}
|
|
171
|
-
startActions={canModerate && (<>
|
|
172
|
-
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
173
|
-
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
174
|
-
</Button>
|
|
175
|
-
{ item.gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
176
|
-
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
177
|
-
</Button>) }
|
|
178
|
-
</>)}
|
|
179
|
-
item={item}>
|
|
180
|
-
<Typography variant="sigma" textColor="neutral600" id="reports-list">
|
|
181
|
-
{ getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
|
|
182
|
-
</Typography>
|
|
183
|
-
<Box paddingTop={2} paddingBottom={6}>
|
|
184
|
-
<Divider />
|
|
185
|
-
</Box>
|
|
186
|
-
<ReportsReviewTable
|
|
187
|
-
commentId={item.id}
|
|
188
|
-
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
189
|
-
mutation={resolveReportMutation}
|
|
190
|
-
allowedActions={{ canAccessReports, canReviewReports }}
|
|
191
|
-
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
192
|
-
/>
|
|
193
|
-
</ReportsReviewModal>) }
|
|
194
164
|
</Td>
|
|
195
165
|
</Tr>
|
|
196
166
|
);
|
|
@@ -91,7 +91,7 @@ const Discover = ({ config }) => {
|
|
|
91
91
|
|
|
92
92
|
const emptyContent = _q ? 'search' : 'comments';
|
|
93
93
|
|
|
94
|
-
const COL_COUNT =
|
|
94
|
+
const COL_COUNT = 8;
|
|
95
95
|
|
|
96
96
|
if (canAccess) {
|
|
97
97
|
return <Box background="neutral100">
|
|
@@ -114,6 +114,9 @@ const Discover = ({ config }) => {
|
|
|
114
114
|
<Th>
|
|
115
115
|
<Typography variant="sigma">{ getMessage('page.discover.table.header.id') }</Typography>
|
|
116
116
|
</Th>
|
|
117
|
+
<Th>
|
|
118
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.author') }</Typography>
|
|
119
|
+
</Th>
|
|
117
120
|
<Th>
|
|
118
121
|
<Typography variant="sigma">{ getMessage('page.discover.table.header.message') }</Typography>
|
|
119
122
|
</Th>
|