strapi-plugin-comments 2.0.0-beta.3 → 2.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +335 -11
- package/admin/src/components/DiscussionThread/index.js +9 -2
- package/admin/src/components/DiscussionThreadItem/index.js +27 -12
- package/admin/src/components/DiscussionThreadItemActions/index.js +61 -50
- package/admin/src/components/DiscussionThreadItemActions/styles.js +0 -4
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +81 -0
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/styles.js +6 -0
- package/admin/src/components/DiscussionThreadItemMeta/index.js +16 -5
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +16 -10
- package/admin/src/components/Nav/index.js +5 -1
- package/admin/src/components/ReportsReviewModal/index.js +5 -0
- package/admin/src/components/ReportsReviewTable/index.js +60 -52
- package/admin/src/components/icons/review.js +2 -2
- package/admin/src/index.js +1 -1
- package/admin/src/pages/Details/components/DetailsEntity/index.js +66 -0
- package/admin/src/pages/Details/components/DetailsFilters/index.js +37 -0
- package/admin/src/pages/Details/components/DetailsFilters/styles.js +8 -0
- package/admin/src/pages/Details/index.js +75 -42
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +69 -48
- package/admin/src/pages/Discover/components/DiscoverTableRow/styles.js +8 -0
- package/admin/src/pages/Discover/index.js +72 -56
- package/admin/src/pages/{Details/utils → utils}/api.js +15 -3
- package/admin/src/permissions.js +10 -8
- package/admin/src/translations/en.json +17 -4
- package/admin/src/utils/constants.js +3 -6
- package/admin/src/utils/getMessage.js +1 -1
- package/admin/src/utils/getRelatedGroups.js +1 -3
- package/admin/src/utils/index.js +6 -2
- package/admin/src/utils/renderEntryTitle.js +14 -0
- package/admin/src/utils/renderInitials.js +5 -0
- package/admin/src/utils/resolveCommentStatus.js +30 -0
- package/admin/src/utils/resolveCommentStatusColor.js +22 -0
- package/package.json +8 -3
- package/permissions.js +19 -0
- package/server/bootstrap.js +38 -2
- package/server/config/index.js +9 -3
- package/server/controllers/admin.js +3 -3
- package/server/controllers/client.js +4 -18
- package/server/controllers/utils/parsers.js +17 -0
- package/server/graphql/index.js +26 -0
- package/server/graphql/mutations/createAbuseReport.js +20 -0
- package/server/graphql/mutations/createComment.js +20 -0
- package/server/graphql/mutations/index.js +21 -0
- package/server/graphql/mutations/removeComment.js +20 -0
- package/server/graphql/mutations/updateComment.js +20 -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 +10 -0
- package/server/graphql/types/comment-author-user.js +9 -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 +16 -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 +17 -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/admin.js +42 -15
- package/server/services/client.js +54 -38
- package/server/services/common.js +38 -67
- package/server/services/graphql.js +190 -0
- package/server/services/index.js +2 -0
- package/server/services/utils/functions.js +24 -17
- package/server/utils/constants.js +3 -3
- package/strapi-server.js +33 -0
- package/admin/src/components/EntityDetails/index.js +0 -53
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Entity Details
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import { capitalize, isNil, first, take } from 'lodash';
|
|
10
|
+
import { Box } from '@strapi/design-system/Box';
|
|
11
|
+
import { Divider } from '@strapi/design-system/Divider';
|
|
12
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
13
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
14
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
15
|
+
import { getMessage } from '../../../../utils';
|
|
16
|
+
|
|
17
|
+
const DetailsEntity = ({ data = {}, schema = {}, config = {} }) => {
|
|
18
|
+
const { entryLabel = {} } = config;
|
|
19
|
+
const { attributes = {} } = schema;
|
|
20
|
+
const keys = Object.keys(attributes);
|
|
21
|
+
const entityLabelKey = first(entryLabel[data?.uid]);
|
|
22
|
+
|
|
23
|
+
if (keys.length === 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const FIELDS_LIMIT = 5;
|
|
28
|
+
const itemKeys = take(
|
|
29
|
+
keys.filter(_ => (attributes[_].type === 'string') && !isNil(data[_]) && (_ !== entityLabelKey)),
|
|
30
|
+
FIELDS_LIMIT
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const formatLabel = (label = '') => label.split('_')
|
|
34
|
+
.map(_ => capitalize(_))
|
|
35
|
+
.join(' ');
|
|
36
|
+
|
|
37
|
+
return (<Box padding={4}>
|
|
38
|
+
<Typography variant="sigma" textColor="neutral600" id="entity-details">
|
|
39
|
+
{ getMessage('page.details.panel.entity', 'Details') }
|
|
40
|
+
</Typography>
|
|
41
|
+
<Box paddingTop={2} paddingBottom={6}>
|
|
42
|
+
<Divider />
|
|
43
|
+
</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
|
+
</Box>);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
DetailsEntity.propTypes = {
|
|
60
|
+
data: PropTypes.object.isRequired,
|
|
61
|
+
schema: PropTypes.object.isRequired,
|
|
62
|
+
config: PropTypes.object.isRequired,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default DetailsEntity;
|
|
66
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Box } from '@strapi/design-system/Box';
|
|
4
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
5
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
6
|
+
import { Switch } from '@strapi/design-system/Switch';
|
|
7
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
8
|
+
import { getMessage } from '../../../../utils';
|
|
9
|
+
|
|
10
|
+
const DetailsFilters = ({ data, onChange }) => {
|
|
11
|
+
|
|
12
|
+
const { removed = false } = data;
|
|
13
|
+
|
|
14
|
+
const handleOnChange = (prop, value) => onChange({
|
|
15
|
+
...data,
|
|
16
|
+
[prop]: !value ? undefined : value,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return (<Box as={Flex}>
|
|
20
|
+
<Stack size={2} horizontal>
|
|
21
|
+
<Typography variant="epsilon">{getMessage('page.details.filters.label', '')}</Typography>
|
|
22
|
+
<Switch
|
|
23
|
+
label={getMessage('page.details.filters.removed.label', 'Comments visibility')}
|
|
24
|
+
onLabel={getMessage('page.details.filters.removed.show', 'Show')}
|
|
25
|
+
offLabel={getMessage('page.details.filters.removed.hide', 'Hide')}
|
|
26
|
+
selected={removed} onChange={() => handleOnChange('removed', !removed)}
|
|
27
|
+
visibleLabels />
|
|
28
|
+
</Stack>
|
|
29
|
+
</Box>);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
DetailsFilters.propTypes = {
|
|
33
|
+
data: PropTypes.object.isRequired,
|
|
34
|
+
onChange: PropTypes.func.isRequired,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default DetailsFilters;
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, { useRef, useMemo } from 'react';
|
|
7
|
+
import React, { memo, useRef, useMemo, useState } from 'react';
|
|
8
|
+
import { connect } from 'react-redux';
|
|
9
|
+
import { bindActionCreators, compose } from 'redux';
|
|
10
|
+
import isEqual from 'react-fast-compare';
|
|
8
11
|
import { useQuery } from 'react-query';
|
|
9
12
|
import { useRouteMatch } from 'react-router-dom';
|
|
10
13
|
import { Layout, HeaderLayout, TwoColsLayout, ContentLayout } from '@strapi/design-system/Layout';
|
|
11
14
|
import { Box } from '@strapi/design-system/Box';
|
|
15
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
12
16
|
import { Link } from '@strapi/design-system/Link';
|
|
13
17
|
import { Loader } from '@strapi/design-system/Loader';
|
|
14
18
|
import { Stack } from '@strapi/design-system/Stack';
|
|
@@ -24,16 +28,17 @@ import {
|
|
|
24
28
|
useFocusWhenNavigate,
|
|
25
29
|
} from '@strapi/helper-plugin';
|
|
26
30
|
import getMessage from '../../utils/getMessage';
|
|
27
|
-
import { fetchDetailsData, fetchContentTypeData } from '
|
|
31
|
+
import { fetchDetailsData, fetchContentTypeData } from '../utils/api';
|
|
28
32
|
import pluginPermissions from '../../permissions';
|
|
29
33
|
import { getUrl } from '../../utils';
|
|
30
34
|
import Nav from '../../components/Nav';
|
|
31
|
-
import
|
|
32
|
-
import EntityDetails from '../../components/EntityDetails';
|
|
35
|
+
import DetailsEntity from './components/DetailsEntity';
|
|
33
36
|
import DiscussionThread from '../../components/DiscussionThread';
|
|
34
37
|
import DiscussionStatus from '../../components/DiscussionStatus';
|
|
38
|
+
import makeAppView from '../App/reducer/selectors';
|
|
39
|
+
import DetailsFilters from './components/DetailsFilters';
|
|
35
40
|
|
|
36
|
-
const Details = () => {
|
|
41
|
+
const Details = ({ config }) => {
|
|
37
42
|
useFocusWhenNavigate();
|
|
38
43
|
|
|
39
44
|
const {
|
|
@@ -45,28 +50,35 @@ const Details = () => {
|
|
|
45
50
|
const trackUsageRef = useRef(trackUsage);
|
|
46
51
|
const toggleNotification = useNotification();
|
|
47
52
|
|
|
48
|
-
const viewPermissions = useMemo(() => {
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
const viewPermissions = useMemo(() => ({
|
|
54
|
+
access: pluginPermissions.access,
|
|
55
|
+
moderate: pluginPermissions.moderate,
|
|
56
|
+
accessReports: pluginPermissions.reports,
|
|
57
|
+
reviewReports: pluginPermissions.reportsReview,
|
|
58
|
+
}), []);
|
|
51
59
|
|
|
52
60
|
const {
|
|
53
61
|
isLoading: isLoadingForPermissions,
|
|
54
|
-
allowedActions: {
|
|
62
|
+
allowedActions: { canAccess, canModerate, canAccessReports, canReviewReports },
|
|
55
63
|
} = useRBAC(viewPermissions);
|
|
56
64
|
|
|
65
|
+
const [filters, setFilters] = useState({});
|
|
66
|
+
|
|
67
|
+
const regexUID = new RegExp(config.regex.uid);
|
|
68
|
+
|
|
57
69
|
const { isLoading: isLoadingForData, data, isFetching } = useQuery(
|
|
58
|
-
['get-details-data', id],
|
|
59
|
-
() => fetchDetailsData(id, toggleNotification),
|
|
70
|
+
['get-details-data', id, filters, canAccess],
|
|
71
|
+
() => fetchDetailsData(id, filters, toggleNotification),
|
|
60
72
|
{
|
|
61
73
|
initialData: { },
|
|
62
74
|
}
|
|
63
75
|
);
|
|
64
76
|
|
|
65
77
|
const { entity, level, selected } = data;
|
|
66
|
-
const entityUidValid = entity?.uid &&
|
|
78
|
+
const entityUidValid = entity?.uid && regexUID.test(entity.uid);
|
|
67
79
|
|
|
68
80
|
const { data: contentTypeData } = useQuery(
|
|
69
|
-
['get-additional-data', entity?.uid],
|
|
81
|
+
['get-additional-data', entity?.uid, canAccess],
|
|
70
82
|
() => fetchContentTypeData(entity?.uid, toggleNotification),
|
|
71
83
|
{
|
|
72
84
|
enabled: !!entity?.uid,
|
|
@@ -74,36 +86,57 @@ const Details = () => {
|
|
|
74
86
|
}
|
|
75
87
|
)
|
|
76
88
|
|
|
89
|
+
const handleChangeFilters = props => setFilters(props);
|
|
90
|
+
|
|
77
91
|
const isLoading = isLoadingForData || isFetching;
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
if (canAccess) {
|
|
94
|
+
return <Box background="neutral100">
|
|
95
|
+
<Layout>
|
|
96
|
+
{(isLoading || isLoadingForPermissions) && isEmpty(data) ? (<LoadingIndicatorPage />) : (
|
|
97
|
+
<>
|
|
98
|
+
<HeaderLayout
|
|
99
|
+
navigationAction={
|
|
100
|
+
<Link startIcon={<ArrowLeft />} to={getUrl(`discover`)}>
|
|
101
|
+
{ getMessage('HeaderLayout.link.go-back', 'Back', false) }
|
|
102
|
+
</Link>}
|
|
103
|
+
title={ getMessage('page.details.header') }
|
|
104
|
+
subtitle={ getMessage('page.details.header.description') }
|
|
105
|
+
primaryAction={<DetailsFilters data={filters} onChange={handleChangeFilters} />}
|
|
106
|
+
secondaryAction={<Box as={Flex}> </Box>}
|
|
107
|
+
as="h2" />
|
|
108
|
+
<ContentLayout>
|
|
109
|
+
<TwoColsLayout
|
|
110
|
+
startCol={<DiscussionThread level={level} selected={selected} isReloading={isLoading} allowedActions={{ canModerate, canAccessReports, canReviewReports }} />}
|
|
111
|
+
endCol={ <Stack size={2}>
|
|
112
|
+
<DiscussionStatus item={selected} />
|
|
113
|
+
{ !contentTypeData?.schema ? (<Box padding={4}>
|
|
114
|
+
<Loader small>{ getMessage('page.details.panel.loading', 'Loading...') }</Loader>
|
|
115
|
+
</Box>) :
|
|
116
|
+
(<DetailsEntity data={entity} schema={contentTypeData?.schema} config={config} />)
|
|
117
|
+
}
|
|
118
|
+
</Stack>
|
|
119
|
+
}
|
|
120
|
+
/>
|
|
121
|
+
</ContentLayout>
|
|
122
|
+
</>) }
|
|
123
|
+
</Layout>
|
|
124
|
+
</Box>;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const mapStateToProps = makeAppView();
|
|
130
|
+
|
|
131
|
+
export function mapDispatchToProps(dispatch) {
|
|
132
|
+
return bindActionCreators(
|
|
133
|
+
{ },
|
|
134
|
+
dispatch
|
|
135
|
+
);
|
|
107
136
|
}
|
|
137
|
+
const withConnect = connect(
|
|
138
|
+
mapStateToProps,
|
|
139
|
+
mapDispatchToProps
|
|
140
|
+
);
|
|
108
141
|
|
|
109
|
-
export default Details;
|
|
142
|
+
export default compose(withConnect)(memo(Details, isEqual));
|
|
@@ -2,7 +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 {
|
|
5
|
+
import { isNil, isEmpty, orderBy } from 'lodash';
|
|
6
|
+
import { Avatar, Initials } from '@strapi/design-system/Avatar';
|
|
6
7
|
import { Badge } from '@strapi/design-system/Badge';
|
|
7
8
|
import { Box } from '@strapi/design-system/Box';
|
|
8
9
|
import { Button } from '@strapi/design-system/Button';
|
|
@@ -13,17 +14,19 @@ import { Link } from '@strapi/design-system/Link';
|
|
|
13
14
|
import { Stack } from '@strapi/design-system/Stack';
|
|
14
15
|
import { Tr, Td } from '@strapi/design-system/Table';
|
|
15
16
|
import { Typography } from '@strapi/design-system/Typography';
|
|
16
|
-
import { Eye
|
|
17
|
+
import { Eye } from '@strapi/icons';
|
|
17
18
|
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
18
|
-
import { getMessage, handleAPIError } from '../../../../utils';
|
|
19
|
-
import { COMMENT_STATUS } from '../../../../utils/constants';
|
|
19
|
+
import { getMessage, getUrl, handleAPIError, renderInitials, resolveCommentStatus, resolveCommentStatusColor } from '../../../../utils';
|
|
20
20
|
import ReportsReviewModal from '../../../../components/ReportsReviewModal';
|
|
21
21
|
import ReportsReviewTable from '../../../../components/ReportsReviewTable';
|
|
22
|
-
import { blockItem, blockItemThread, resolveReport } from '../../../
|
|
22
|
+
import { blockItem, blockItemThread, resolveReport } from '../../../utils/api';
|
|
23
23
|
import pluginId from '../../../../pluginId';
|
|
24
24
|
import { ReviewIcon, LockIcon } from '../../../../components/icons';
|
|
25
|
+
import { TableLink } from './styles';
|
|
26
|
+
import renderEntryTitle from '../../../../utils/renderEntryTitle';
|
|
27
|
+
import DiscussionThreadItemApprovalFlowActions from '../../../../components/DiscussionThreadItemApprovalFlowActions';
|
|
25
28
|
|
|
26
|
-
const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
29
|
+
const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAccessReports, canReviewReports }, onClick }) => {
|
|
27
30
|
|
|
28
31
|
const { id, reports } = item;
|
|
29
32
|
|
|
@@ -72,14 +75,22 @@ const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
|
72
75
|
refetchActive: false,
|
|
73
76
|
});
|
|
74
77
|
|
|
75
|
-
const handleReportsReviewClick = () =>
|
|
78
|
+
const handleReportsReviewClick = () => {
|
|
79
|
+
if (canAccessReports) {
|
|
80
|
+
setReportsReviewVisible(true);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
76
83
|
const handleBlockItemClick = async () => {
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
if (canModerate) {
|
|
85
|
+
lockApp();
|
|
86
|
+
blockItemMutation.mutate(id);
|
|
87
|
+
}
|
|
79
88
|
};
|
|
80
89
|
const handleBlockItemThreadClick = async () => {
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
if (canModerate) {
|
|
91
|
+
lockApp();
|
|
92
|
+
blockItemThreadMutation.mutate(id);
|
|
93
|
+
}
|
|
83
94
|
};
|
|
84
95
|
const handleReportsReviewClose = async () => {
|
|
85
96
|
await queryClient.invalidateQueries('get-data');
|
|
@@ -89,59 +100,56 @@ const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
|
89
100
|
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
|
|
90
101
|
const openReports = reports?.filter(_ => !_.resolved);
|
|
91
102
|
const hasReports = !isEmpty(openReports);
|
|
92
|
-
const reviewFlowEnabled = hasReports && !(item.blocked || item.blockedThread);
|
|
103
|
+
const reviewFlowEnabled = (canAccessReports && hasReports) && !(item.blocked || item.blockedThread);
|
|
93
104
|
|
|
94
105
|
const handleClick = () => onClick();
|
|
95
106
|
const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
|
|
96
107
|
|
|
97
|
-
const renderStatus = (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
let color = 'secondary';
|
|
103
|
-
switch (status) {
|
|
104
|
-
case COMMENT_STATUS.OPEN:
|
|
105
|
-
color = 'success';
|
|
106
|
-
break;
|
|
107
|
-
case COMMENT_STATUS.TO_REVIEW:
|
|
108
|
-
color = 'warning';
|
|
109
|
-
break;
|
|
110
|
-
case COMMENT_STATUS.BLOCKED:
|
|
111
|
-
color = 'danger';
|
|
112
|
-
break;
|
|
113
|
-
};
|
|
108
|
+
const renderStatus = (props) => {
|
|
109
|
+
const status = resolveCommentStatus({ ...props, reviewFlowEnabled });
|
|
110
|
+
const color = resolveCommentStatusColor(status);
|
|
111
|
+
|
|
114
112
|
return (<Badge backgroundColor={`${color}100`} textColor={`${color}600`}>{ getMessage({
|
|
115
|
-
id: `page.
|
|
113
|
+
id: `page.common.item.status.${status}`,
|
|
116
114
|
props: {
|
|
117
115
|
count: openReports.length
|
|
118
116
|
}
|
|
119
117
|
}, status) }</Badge>);
|
|
120
118
|
};
|
|
121
119
|
|
|
122
|
-
const renderEntryTitle = entry => {
|
|
123
|
-
const { entryLabel } = config;
|
|
124
|
-
const rule = entry.uid in entryLabel ? entryLabel[entry.uid] : entryLabel['*'];
|
|
125
|
-
return first(
|
|
126
|
-
Object.keys(entry)
|
|
127
|
-
.filter(_ => (rule === _) || rule.includes(_))
|
|
128
|
-
.map(_ => entry[_])
|
|
129
|
-
.filter(_ => _)
|
|
130
|
-
);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
120
|
const renderEntryUrl = entry => `/content-manager/collectionType/${entry.uid}/${entry.id}`;
|
|
121
|
+
const renderDetailsUrl = entry => getUrl(`discover/${entry.id}`);
|
|
122
|
+
|
|
123
|
+
const gotApprovalFlow = !isNil(item.approvalStatus);
|
|
124
|
+
const needsApproval = gotApprovalFlow && (item.approvalStatus === 'PENDING');
|
|
125
|
+
|
|
126
|
+
let actionItemsCount = 1;
|
|
127
|
+
if(reviewFlowEnabled || needsApproval) {
|
|
128
|
+
actionItemsCount = 2;
|
|
129
|
+
}
|
|
134
130
|
|
|
135
131
|
return (
|
|
136
132
|
<Tr key={item.id}>
|
|
137
|
-
<Td
|
|
138
|
-
<Typography textColor="neutral800"
|
|
133
|
+
<Td>
|
|
134
|
+
<Typography textColor="neutral800" fontWeight="bold">#{ item.id }</Typography>
|
|
139
135
|
</Td>
|
|
140
136
|
<Td>
|
|
141
|
-
<
|
|
137
|
+
<Stack size={2} horizontal>
|
|
138
|
+
{ item.author?.avatar ? <Avatar src={item.author?.avatar} alt={item.author.name} /> : <Initials>{ renderInitials(item.author.name) }</Initials> }
|
|
139
|
+
<Typography textColor="neutral800" variant="pi">{ item.author.name }</Typography>
|
|
140
|
+
</Stack>
|
|
141
|
+
</Td>
|
|
142
|
+
<Td style={{ maxWidth: '30vw' }}>
|
|
143
|
+
<Typography textColor="neutral800" ellipsis>{item.content}</Typography>
|
|
142
144
|
</Td>
|
|
143
145
|
<Td>
|
|
144
|
-
<Link to={
|
|
146
|
+
{ item.threadOf?.id ? (<Link to={ renderDetailsUrl(item.threadOf) }>{ getMessage({
|
|
147
|
+
id: 'page.discover.table.cell.thread',
|
|
148
|
+
props: { id: item.threadOf.id },
|
|
149
|
+
}, '#' + item.threadOf.id) }</Link>) : '-' }
|
|
150
|
+
</Td>
|
|
151
|
+
<Td style={{ maxWidth: '15vw' }}>
|
|
152
|
+
<TableLink to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related, config) }</TableLink>
|
|
145
153
|
</Td>
|
|
146
154
|
<Td>
|
|
147
155
|
<Typography textColor="neutral800">{ formatDate(item.updatedAt || item.createdAt, { dateStyle: 'long', timeStyle: 'short' }) }</Typography>
|
|
@@ -151,23 +159,30 @@ const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
|
151
159
|
</Td>
|
|
152
160
|
<Td>
|
|
153
161
|
<Flex direction="column" alignItems="flex-end">
|
|
154
|
-
<Stack size={
|
|
162
|
+
<Stack size={actionItemsCount} horizontal>
|
|
155
163
|
{ reviewFlowEnabled && (<IconButton onClick={handleReportsReviewClick} label={ getMessage('page.discover.table.reports.review') } icon={<ReviewIcon />} />)}
|
|
164
|
+
{ (canModerate && needsApproval) && (
|
|
165
|
+
<DiscussionThreadItemApprovalFlowActions
|
|
166
|
+
id={item.id}
|
|
167
|
+
allowedActions={{ canModerate }}
|
|
168
|
+
queryToInvalidate="get-data" />
|
|
169
|
+
)}
|
|
156
170
|
<IconButton onClick={handleClick} label={ getMessage('page.discover.table.action.display') } icon={<Eye />} />
|
|
157
171
|
</Stack>
|
|
158
172
|
</Flex>
|
|
159
173
|
{ reviewFlowEnabled && (<ReportsReviewModal
|
|
160
174
|
isVisible={reportsReviewVisible}
|
|
161
175
|
isActionAsync={isLoading}
|
|
176
|
+
allowedActions={{ canModerate, canAccessReports, canReviewReports }}
|
|
162
177
|
onClose={handleReportsReviewClose}
|
|
163
|
-
startActions={<>
|
|
178
|
+
startActions={canModerate && (<>
|
|
164
179
|
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
165
180
|
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
166
181
|
</Button>
|
|
167
182
|
{ item.gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
168
183
|
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
169
184
|
</Button>) }
|
|
170
|
-
</>}
|
|
185
|
+
</>)}
|
|
171
186
|
item={item}>
|
|
172
187
|
<Typography variant="sigma" textColor="neutral600" id="reports-list">
|
|
173
188
|
{ getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
|
|
@@ -179,6 +194,7 @@ const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
|
179
194
|
commentId={item.id}
|
|
180
195
|
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
181
196
|
mutation={resolveReportMutation}
|
|
197
|
+
allowedActions={{ canAccessReports, canReviewReports }}
|
|
182
198
|
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
183
199
|
/>
|
|
184
200
|
</ReportsReviewModal>) }
|
|
@@ -190,6 +206,11 @@ const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
|
190
206
|
DiscoverTableRow.propTypes = {
|
|
191
207
|
config: PropTypes.object.isRequired,
|
|
192
208
|
item: PropTypes.object.isRequired,
|
|
209
|
+
allowedActions: PropTypes.shape({
|
|
210
|
+
canModerate: PropTypes.bool,
|
|
211
|
+
canAccessReports: PropTypes.bool,
|
|
212
|
+
canReviewReports: PropTypes.bool,
|
|
213
|
+
}),
|
|
193
214
|
onClick: PropTypes.func.isRequired,
|
|
194
215
|
};
|
|
195
216
|
|
|
@@ -51,17 +51,20 @@ const Discover = ({ config }) => {
|
|
|
51
51
|
const [{ query: queryParams }] = useQueryParams();
|
|
52
52
|
const _q = queryParams?._q || '';
|
|
53
53
|
|
|
54
|
-
const viewPermissions = useMemo(() => {
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const viewPermissions = useMemo(() => ({
|
|
55
|
+
access: pluginPermissions.access,
|
|
56
|
+
moderate: pluginPermissions.moderate,
|
|
57
|
+
accessReports: pluginPermissions.reports,
|
|
58
|
+
reviewReports: pluginPermissions.reportsReview,
|
|
59
|
+
}), []);
|
|
57
60
|
|
|
58
61
|
const {
|
|
59
62
|
isLoading: isLoadingForPermissions,
|
|
60
|
-
allowedActions: {
|
|
63
|
+
allowedActions: { canAccess, canModerate, canAccessReports, canReviewReports },
|
|
61
64
|
} = useRBAC(viewPermissions);
|
|
62
65
|
|
|
63
66
|
const { isLoading: isLoadingForData, data: { result, pagination = {} }, isFetching } = useQuery(
|
|
64
|
-
['get-data', queryParams],
|
|
67
|
+
['get-data', queryParams, canAccess],
|
|
65
68
|
() => fetchData(queryParams, toggleNotification),
|
|
66
69
|
{
|
|
67
70
|
initialData: { },
|
|
@@ -86,59 +89,72 @@ const Discover = ({ config }) => {
|
|
|
86
89
|
},
|
|
87
90
|
};
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
92
|
const emptyContent = _q ? 'search' : 'comments';
|
|
92
93
|
|
|
93
|
-
const COL_COUNT =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
94
|
+
const COL_COUNT = 8;
|
|
95
|
+
|
|
96
|
+
if (canAccess) {
|
|
97
|
+
return <Box background="neutral100">
|
|
98
|
+
<Layout>
|
|
99
|
+
{isLoading || isLoadingForPermissions ? (<LoadingIndicatorPage />) : (
|
|
100
|
+
<>
|
|
101
|
+
<HeaderLayout title={ getMessage('page.discover.header') } subtitle={ `${total} ${ getMessage('page.discover.header.count')}` } as="h2" />
|
|
102
|
+
<ActionLayout
|
|
103
|
+
startActions={<>
|
|
104
|
+
<SearchURLQuery
|
|
105
|
+
label={ getMessage('search.label', 'Search', false) }
|
|
106
|
+
/>
|
|
107
|
+
<TableFilters displayedFilters={filtersSchema} />
|
|
108
|
+
</>} />
|
|
109
|
+
<ContentLayout>
|
|
110
|
+
{ !isEmpty(result) ? (<>
|
|
111
|
+
<Table colCount={COL_COUNT} rowCount={result.length}>
|
|
112
|
+
<Thead>
|
|
113
|
+
<Tr>
|
|
114
|
+
<Th>
|
|
115
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.id') }</Typography>
|
|
116
|
+
</Th>
|
|
117
|
+
<Th>
|
|
118
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.author') }</Typography>
|
|
119
|
+
</Th>
|
|
120
|
+
<Th>
|
|
121
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.message') }</Typography>
|
|
122
|
+
</Th>
|
|
123
|
+
<Th>
|
|
124
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.thread') }</Typography>
|
|
125
|
+
</Th>
|
|
126
|
+
<Th>
|
|
127
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.entry') }</Typography>
|
|
128
|
+
</Th>
|
|
129
|
+
<Th>
|
|
130
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.lastUpdate') }</Typography>
|
|
131
|
+
</Th>
|
|
132
|
+
<Th>
|
|
133
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.status') }</Typography>
|
|
134
|
+
</Th>
|
|
135
|
+
<Th>
|
|
136
|
+
<VisuallyHidden>{ getMessage('page.discover.table.header.actions') }</VisuallyHidden>
|
|
137
|
+
</Th>
|
|
138
|
+
</Tr>
|
|
139
|
+
</Thead>
|
|
140
|
+
<Tbody>
|
|
141
|
+
{result.map(entry => (<DiscoverTableRow
|
|
142
|
+
key={`comment-${entry.id}`}
|
|
143
|
+
config={config}
|
|
144
|
+
item={entry}
|
|
145
|
+
allowedActions={{ canModerate, canAccessReports, canReviewReports }}
|
|
146
|
+
onClick={() => handleClickDisplay(entry.id)}
|
|
147
|
+
/>))}
|
|
148
|
+
</Tbody>
|
|
149
|
+
</Table>
|
|
150
|
+
<TablePagination pagination={{ pageCount: pagination?.pageCount || 1 }} />
|
|
151
|
+
</>) : (<EmptyStateLayout content={emptyLayout[emptyContent]} />) }
|
|
152
|
+
</ContentLayout>
|
|
153
|
+
</>) }
|
|
154
|
+
</Layout>
|
|
155
|
+
</Box>;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
142
158
|
}
|
|
143
159
|
|
|
144
160
|
const mapStateToProps = makeAppView();
|