strapi-plugin-comments 1.0.2 → 2.0.0-beta.3
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 +2 -2
- package/README.md +70 -109
- package/admin/src/components/ConfirmationDialog/index.js +56 -0
- package/admin/src/components/DiscussionStatus/index.js +33 -0
- package/admin/src/components/DiscussionThread/index.js +59 -0
- package/admin/src/components/DiscussionThreadItem/index.js +87 -0
- package/admin/src/components/DiscussionThreadItem/styles.js +88 -0
- package/admin/src/components/DiscussionThreadItemActions/index.js +178 -0
- package/admin/src/components/DiscussionThreadItemActions/styles.js +22 -0
- package/admin/src/components/DiscussionThreadItemMeta/index.js +31 -0
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +141 -0
- package/admin/src/components/DiscussionThreadItemWarnings/styles.js +7 -0
- package/admin/src/components/EntityDetails/index.js +53 -0
- package/admin/src/{containers → components}/Initializer/index.js +4 -4
- package/admin/src/components/LoadingIndicatorOverlay/index.js +15 -0
- package/admin/src/components/LoadingIndicatorOverlay/styles.js +20 -0
- package/admin/src/components/Nav/index.js +45 -0
- package/admin/src/components/PluginIcon/index.js +12 -0
- package/admin/src/components/ReportsReviewModal/index.js +43 -0
- package/admin/src/components/ReportsReviewTable/index.js +146 -0
- package/admin/src/components/TableFilters/index.js +53 -0
- package/admin/src/components/TablePagination/index.js +35 -0
- package/admin/src/components/icons/comments.js +9 -0
- 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 +58 -42
- package/admin/src/pages/App/index.js +71 -0
- 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/ComingSoonPage/index.js +25 -0
- package/admin/src/pages/Details/index.js +109 -0
- package/admin/src/pages/Details/utils/api.js +41 -0
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +196 -0
- package/admin/src/pages/Discover/index.js +157 -0
- package/admin/src/pages/Discover/utils/api.js +14 -0
- package/admin/src/pages/Discover/utils/filtersSchema.js +9 -0
- package/admin/src/permissions.js +13 -33
- package/admin/src/pluginId.js +2 -4
- package/admin/src/reducers.js +8 -0
- package/admin/src/translations/en.json +73 -33
- package/admin/src/translations/fr.json +1 -0
- package/admin/src/utils/axiosInstance.js +35 -0
- package/admin/src/utils/constants.js +28 -0
- package/admin/src/utils/getApiUrl.js +5 -0
- package/admin/src/utils/getMessage.js +21 -0
- package/admin/src/utils/getRelatedGroups.js +5 -0
- package/admin/src/utils/getUrl.js +5 -0
- package/admin/src/utils/handleAPIError.js +15 -0
- package/admin/src/utils/index.js +6 -0
- package/codecov.yml +2 -1
- package/content-types/comment.js +97 -0
- package/content-types/index.js +11 -0
- package/content-types/report.js +54 -0
- package/package.json +11 -25
- package/server/bootstrap.js +5 -0
- package/server/config/index.js +10 -0
- package/server/controllers/admin.js +134 -0
- package/server/controllers/client.js +117 -0
- package/server/controllers/index.js +9 -0
- package/{controllers → server/controllers}/utils/__tests__/functions.test.js +0 -0
- package/{controllers → server/controllers}/utils/functions.js +1 -1
- package/server/destroy.js +5 -0
- package/server/index.js +22 -0
- package/server/register.js +5 -0
- package/server/routes/admin.js +92 -0
- package/server/routes/client.js +52 -0
- package/server/routes/index.js +15 -0
- package/server/services/admin.js +215 -0
- package/server/services/client.js +227 -0
- package/server/services/common.js +197 -0
- package/server/services/index.js +11 -0
- package/{services → server/services}/utils/__tests__/functions.test.js +7 -9
- package/{services → server/services}/utils/functions.js +19 -8
- package/server/utils/constants.js +16 -0
- package/{services → server}/utils/error.js +0 -0
- package/server/utils/functions.js +19 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +9 -0
- package/admin/src/assets/images/icon-cross-blue.svg +0 -1
- package/admin/src/assets/images/icon_remove.svg +0 -19
- package/admin/src/assets/images/logo.svg +0 -5
- package/admin/src/components/AbuseReportsPopUp/ItemPreviewContainer.js +0 -8
- package/admin/src/components/AbuseReportsPopUp/ModalFooter.js +0 -31
- package/admin/src/components/AbuseReportsPopUp/TableContainer.js +0 -63
- package/admin/src/components/AbuseReportsPopUp/index.js +0 -148
- package/admin/src/components/Container/index.js +0 -7
- package/admin/src/components/EmptyView/index.js +0 -25
- package/admin/src/components/Filters/index.js +0 -151
- package/admin/src/components/Filters/index.styled.js +0 -82
- package/admin/src/components/Filters/utils.js +0 -21
- package/admin/src/components/Item/CardItem.js +0 -30
- package/admin/src/components/Item/CardWrapper.js +0 -10
- package/admin/src/components/Item/index.js +0 -92
- package/admin/src/components/ItemDetails/CardItem.js +0 -33
- package/admin/src/components/ItemDetails/CardLevelCounter.js +0 -46
- package/admin/src/components/ItemDetails/CardLevelCounterLink.js +0 -24
- package/admin/src/components/ItemDetails/CardLevelWrapper.js +0 -22
- package/admin/src/components/ItemDetails/CardWrapper.js +0 -52
- package/admin/src/components/ItemDetails/index.js +0 -151
- package/admin/src/components/ItemFooter/CardItemAuthor.js +0 -10
- package/admin/src/components/ItemFooter/CardItemRelation.js +0 -18
- package/admin/src/components/ItemFooter/Wrapper.js +0 -20
- package/admin/src/components/ItemFooter/index.js +0 -87
- package/admin/src/components/ItemHeader/CardHeaderBlocked.js +0 -24
- package/admin/src/components/ItemHeader/CardHeaderIndicator.js +0 -30
- package/admin/src/components/ItemHeader/CardHeaderIndicatorBlue.js +0 -11
- package/admin/src/components/ItemHeader/CardHeaderIndicatorRed.js +0 -11
- package/admin/src/components/ItemHeader/CardHeaderIndicatorsContainer.js +0 -18
- package/admin/src/components/ItemHeader/CardHeaderReports.js +0 -40
- package/admin/src/components/ItemHeader/Wrapper.js +0 -16
- package/admin/src/components/ItemHeader/index.js +0 -80
- package/admin/src/components/ItemModeration/Wrapper.js +0 -19
- package/admin/src/components/ItemModeration/index.js +0 -47
- package/admin/src/components/List/Container.js +0 -20
- package/admin/src/components/List/index.js +0 -21
- package/admin/src/components/Search/index.js +0 -83
- package/admin/src/containers/App/Wrapper.js +0 -14
- package/admin/src/containers/App/index.js +0 -47
- package/admin/src/containers/DataManagerProvider/actions.js +0 -11
- package/admin/src/containers/DataManagerProvider/index.js +0 -280
- package/admin/src/containers/DataManagerProvider/init.js +0 -5
- package/admin/src/containers/DataManagerProvider/reducer.js +0 -59
- package/admin/src/containers/DetailsView/Wrapper.js +0 -18
- package/admin/src/containers/DetailsView/index.js +0 -100
- package/admin/src/containers/ListView/FadedWrapper.js +0 -42
- package/admin/src/containers/ListView/Footer.js +0 -53
- package/admin/src/containers/ListView/Wrapper.js +0 -17
- package/admin/src/containers/ListView/components.js +0 -138
- package/admin/src/containers/ListView/index.js +0 -45
- package/admin/src/containers/Panel/actions.js +0 -5
- package/admin/src/containers/Panel/index.js +0 -191
- package/admin/src/containers/Panel/index.styled.js +0 -6
- package/admin/src/containers/Panel/reducer.js +0 -44
- package/admin/src/contexts/DataManagerContext.js +0 -5
- package/admin/src/hooks/useDataManager.js +0 -6
- package/admin/src/lifecycles.js +0 -3
- package/admin/src/translations/index.js +0 -7
- package/admin/src/utils/getTrad.js +0 -5
- package/admin/src/utils/getTradId.js +0 -5
- package/config/functions/bootstrap.js +0 -48
- package/config/routes.json +0 -116
- package/config/schema.graphql.js +0 -270
- package/controllers/comments.js +0 -211
- package/models/comment.js +0 -8
- package/models/comment.settings.json +0 -98
- package/models/report.js +0 -8
- package/models/report.settings.json +0 -49
- package/services/comments.js +0 -486
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* Details
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useRef, useMemo } from 'react';
|
|
8
|
+
import { useQuery } from 'react-query';
|
|
9
|
+
import { useRouteMatch } from 'react-router-dom';
|
|
10
|
+
import { Layout, HeaderLayout, TwoColsLayout, ContentLayout } from '@strapi/design-system/Layout';
|
|
11
|
+
import { Box } from '@strapi/design-system/Box';
|
|
12
|
+
import { Link } from '@strapi/design-system/Link';
|
|
13
|
+
import { Loader } from '@strapi/design-system/Loader';
|
|
14
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
15
|
+
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
|
16
|
+
import { ArrowLeft } from '@strapi/icons';
|
|
17
|
+
|
|
18
|
+
import { isEmpty } from 'lodash';
|
|
19
|
+
import {
|
|
20
|
+
LoadingIndicatorPage,
|
|
21
|
+
useTracking,
|
|
22
|
+
useNotification,
|
|
23
|
+
useRBAC,
|
|
24
|
+
useFocusWhenNavigate,
|
|
25
|
+
} from '@strapi/helper-plugin';
|
|
26
|
+
import getMessage from '../../utils/getMessage';
|
|
27
|
+
import { fetchDetailsData, fetchContentTypeData } from './utils/api';
|
|
28
|
+
import pluginPermissions from '../../permissions';
|
|
29
|
+
import { getUrl } from '../../utils';
|
|
30
|
+
import Nav from '../../components/Nav';
|
|
31
|
+
import { REGEX } from '../../utils/constants';
|
|
32
|
+
import EntityDetails from '../../components/EntityDetails';
|
|
33
|
+
import DiscussionThread from '../../components/DiscussionThread';
|
|
34
|
+
import DiscussionStatus from '../../components/DiscussionStatus';
|
|
35
|
+
|
|
36
|
+
const Details = () => {
|
|
37
|
+
useFocusWhenNavigate();
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
params: { id },
|
|
41
|
+
} = useRouteMatch(getUrl(`discover/:id`));
|
|
42
|
+
|
|
43
|
+
const { notifyStatus } = useNotifyAT();
|
|
44
|
+
const { trackUsage } = useTracking();
|
|
45
|
+
const trackUsageRef = useRef(trackUsage);
|
|
46
|
+
const toggleNotification = useNotification();
|
|
47
|
+
|
|
48
|
+
const viewPermissions = useMemo(() => {
|
|
49
|
+
return { view: pluginPermissions.view };
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
isLoading: isLoadingForPermissions,
|
|
54
|
+
allowedActions: { canView },
|
|
55
|
+
} = useRBAC(viewPermissions);
|
|
56
|
+
|
|
57
|
+
const { isLoading: isLoadingForData, data, isFetching } = useQuery(
|
|
58
|
+
['get-details-data', id],
|
|
59
|
+
() => fetchDetailsData(id, toggleNotification),
|
|
60
|
+
{
|
|
61
|
+
initialData: { },
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const { entity, level, selected } = data;
|
|
66
|
+
const entityUidValid = entity?.uid && REGEX.uid.test(entity?.uid);
|
|
67
|
+
|
|
68
|
+
const { data: contentTypeData } = useQuery(
|
|
69
|
+
['get-additional-data', entity?.uid],
|
|
70
|
+
() => fetchContentTypeData(entity?.uid, toggleNotification),
|
|
71
|
+
{
|
|
72
|
+
enabled: !!entity?.uid,
|
|
73
|
+
suspense: !entityUidValid,
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const isLoading = isLoadingForData || isFetching;
|
|
78
|
+
|
|
79
|
+
return <Box background="neutral100">
|
|
80
|
+
<Layout sideNav={<Nav />}>
|
|
81
|
+
{(isLoading || isLoadingForPermissions) && isEmpty(data) ? (<LoadingIndicatorPage />) : (
|
|
82
|
+
<>
|
|
83
|
+
<HeaderLayout
|
|
84
|
+
navigationAction={
|
|
85
|
+
<Link startIcon={<ArrowLeft />} to={getUrl(`discover`)}>
|
|
86
|
+
{ getMessage('HeaderLayout.link.go-back', 'Back', false) }
|
|
87
|
+
</Link>}
|
|
88
|
+
title={ getMessage('page.details.header') }
|
|
89
|
+
subtitle={ getMessage('page.details.header.description') } as="h2" />
|
|
90
|
+
<ContentLayout>
|
|
91
|
+
<TwoColsLayout
|
|
92
|
+
startCol={<DiscussionThread level={level} selected={selected} isReloading={isLoading} />}
|
|
93
|
+
endCol={ <Stack size={2}>
|
|
94
|
+
<DiscussionStatus item={selected} />
|
|
95
|
+
{ !contentTypeData?.schema ? (<Box padding={4}>
|
|
96
|
+
<Loader small>Fetching entity...</Loader>
|
|
97
|
+
</Box>) :
|
|
98
|
+
(<EntityDetails data={entity} schema={contentTypeData?.schema} />)
|
|
99
|
+
}
|
|
100
|
+
</Stack>
|
|
101
|
+
}
|
|
102
|
+
/>
|
|
103
|
+
</ContentLayout>
|
|
104
|
+
</>) }
|
|
105
|
+
</Layout>
|
|
106
|
+
</Box>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default Details;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
4
|
+
export const fetchDetailsData = async (id, toggleNotification) => {
|
|
5
|
+
try {
|
|
6
|
+
const { data } = await axiosInstance.get(getApiURL(`moderate/single/${id}`));
|
|
7
|
+
|
|
8
|
+
return data;
|
|
9
|
+
} catch (err) {
|
|
10
|
+
handleAPIError(err, toggleNotification);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const fetchContentTypeData = async (uid, toggleNotification) => {
|
|
15
|
+
try {
|
|
16
|
+
const { data } = await axiosInstance.get(`/content-type-builder/content-types/${uid}`);
|
|
17
|
+
return data?.data;
|
|
18
|
+
} catch (err) {
|
|
19
|
+
handleAPIError(err, toggleNotification);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const blockItem = id => {
|
|
24
|
+
return axiosInstance.patch(getApiURL(`moderate/single/${id}/block`));
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const unblockItem = id => {
|
|
28
|
+
return axiosInstance.patch(getApiURL(`moderate/single/${id}/unblock`));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const blockItemThread = id => {
|
|
32
|
+
return axiosInstance.patch(getApiURL(`moderate/thread/${id}/block`));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const unblockItemThread = id => {
|
|
36
|
+
return axiosInstance.patch(getApiURL(`moderate/thread/${id}/unblock`));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const resolveReport = ({ id, reportId }) => {
|
|
40
|
+
return axiosInstance.patch(getApiURL(`moderate/single/${id}/report/${reportId}/resolve`));
|
|
41
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
import { useMutation, useQueryClient } from 'react-query';
|
|
5
|
+
import { first, isEmpty, orderBy } from 'lodash';
|
|
6
|
+
import { Badge } from '@strapi/design-system/Badge';
|
|
7
|
+
import { Box } from '@strapi/design-system/Box';
|
|
8
|
+
import { Button } from '@strapi/design-system/Button';
|
|
9
|
+
import { Divider } from '@strapi/design-system/Divider';
|
|
10
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
11
|
+
import { IconButton } from '@strapi/design-system/IconButton';
|
|
12
|
+
import { Link } from '@strapi/design-system/Link';
|
|
13
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
14
|
+
import { Tr, Td } from '@strapi/design-system/Table';
|
|
15
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
16
|
+
import { Eye, Lock } from '@strapi/icons';
|
|
17
|
+
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
18
|
+
import { getMessage, handleAPIError } from '../../../../utils';
|
|
19
|
+
import { COMMENT_STATUS } from '../../../../utils/constants';
|
|
20
|
+
import ReportsReviewModal from '../../../../components/ReportsReviewModal';
|
|
21
|
+
import ReportsReviewTable from '../../../../components/ReportsReviewTable';
|
|
22
|
+
import { blockItem, blockItemThread, resolveReport } from '../../../Details/utils/api';
|
|
23
|
+
import pluginId from '../../../../pluginId';
|
|
24
|
+
import { ReviewIcon, LockIcon } from '../../../../components/icons';
|
|
25
|
+
|
|
26
|
+
const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
27
|
+
|
|
28
|
+
const { id, reports } = item;
|
|
29
|
+
|
|
30
|
+
const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
|
|
31
|
+
const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
|
|
32
|
+
|
|
33
|
+
const { formatDate } = useIntl();
|
|
34
|
+
const toggleNotification = useNotification();
|
|
35
|
+
const queryClient = useQueryClient();
|
|
36
|
+
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
37
|
+
|
|
38
|
+
const onSuccess = (message, stateAction = () => {}, indalidate = true) => async () => {
|
|
39
|
+
if (indalidate) {
|
|
40
|
+
await queryClient.invalidateQueries('get-data');
|
|
41
|
+
}
|
|
42
|
+
toggleNotification({
|
|
43
|
+
type: 'success',
|
|
44
|
+
message: `${pluginId}.${message}`,
|
|
45
|
+
});
|
|
46
|
+
stateAction(false);
|
|
47
|
+
unlockApp();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
51
|
+
|
|
52
|
+
const resolveReportMutation = useMutation(resolveReport, {
|
|
53
|
+
onSuccess: onSuccess('page.details.panel.discussion.warnings.reports.dialog.confirmation.success', () => {}, false),
|
|
54
|
+
onError,
|
|
55
|
+
refetchActive: false,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const blockItemMutation = useMutation(blockItem, {
|
|
59
|
+
onSuccess: onSuccess(
|
|
60
|
+
'page.details.actions.comment.block.confirmation.success',
|
|
61
|
+
setReportsReviewVisible
|
|
62
|
+
),
|
|
63
|
+
onError,
|
|
64
|
+
refetchActive: false,
|
|
65
|
+
});
|
|
66
|
+
const blockItemThreadMutation = useMutation(blockItemThread, {
|
|
67
|
+
onSuccess: onSuccess(
|
|
68
|
+
'page.details.actions.thread.block.confirmation.success',
|
|
69
|
+
setReportsReviewVisible
|
|
70
|
+
),
|
|
71
|
+
onError,
|
|
72
|
+
refetchActive: false,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const handleReportsReviewClick = () => setReportsReviewVisible(true);
|
|
76
|
+
const handleBlockItemClick = async () => {
|
|
77
|
+
lockApp();
|
|
78
|
+
blockItemMutation.mutate(id);
|
|
79
|
+
};
|
|
80
|
+
const handleBlockItemThreadClick = async () => {
|
|
81
|
+
lockApp();
|
|
82
|
+
blockItemThreadMutation.mutate(id);
|
|
83
|
+
};
|
|
84
|
+
const handleReportsReviewClose = async () => {
|
|
85
|
+
await queryClient.invalidateQueries('get-data');
|
|
86
|
+
setReportsReviewVisible(false);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
|
|
90
|
+
const openReports = reports?.filter(_ => !_.resolved);
|
|
91
|
+
const hasReports = !isEmpty(openReports);
|
|
92
|
+
const reviewFlowEnabled = hasReports && !(item.blocked || item.blockedThread);
|
|
93
|
+
|
|
94
|
+
const handleClick = () => onClick();
|
|
95
|
+
const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
|
|
96
|
+
|
|
97
|
+
const renderStatus = ({ blocked, blockedThread }) => {
|
|
98
|
+
let status = blocked || blockedThread ? COMMENT_STATUS.BLOCKED : COMMENT_STATUS.OPEN;
|
|
99
|
+
if (reviewFlowEnabled) {
|
|
100
|
+
status = COMMENT_STATUS.TO_REVIEW;
|
|
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
|
+
};
|
|
114
|
+
return (<Badge backgroundColor={`${color}100`} textColor={`${color}600`}>{ getMessage({
|
|
115
|
+
id: `page.discover.table.header.status.${status}`,
|
|
116
|
+
props: {
|
|
117
|
+
count: openReports.length
|
|
118
|
+
}
|
|
119
|
+
}, status) }</Badge>);
|
|
120
|
+
};
|
|
121
|
+
|
|
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
|
+
const renderEntryUrl = entry => `/content-manager/collectionType/${entry.uid}/${entry.id}`;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Tr key={item.id}>
|
|
137
|
+
<Td style={{ maxWidth: '40vw' }}>
|
|
138
|
+
<Typography textColor="neutral800" ellipsis>{item.content}</Typography>
|
|
139
|
+
</Td>
|
|
140
|
+
<Td>
|
|
141
|
+
<Typography textColor="neutral800">{item.threadOf?.id || '-'}</Typography>
|
|
142
|
+
</Td>
|
|
143
|
+
<Td>
|
|
144
|
+
<Link to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related) }</Link>
|
|
145
|
+
</Td>
|
|
146
|
+
<Td>
|
|
147
|
+
<Typography textColor="neutral800">{ formatDate(item.updatedAt || item.createdAt, { dateStyle: 'long', timeStyle: 'short' }) }</Typography>
|
|
148
|
+
</Td>
|
|
149
|
+
<Td>
|
|
150
|
+
<Typography textColor="neutral800">{ renderStatus(item) }</Typography>
|
|
151
|
+
</Td>
|
|
152
|
+
<Td>
|
|
153
|
+
<Flex direction="column" alignItems="flex-end">
|
|
154
|
+
<Stack size={reviewFlowEnabled ? 2 : 1} horizontal>
|
|
155
|
+
{ reviewFlowEnabled && (<IconButton onClick={handleReportsReviewClick} label={ getMessage('page.discover.table.reports.review') } icon={<ReviewIcon />} />)}
|
|
156
|
+
<IconButton onClick={handleClick} label={ getMessage('page.discover.table.action.display') } icon={<Eye />} />
|
|
157
|
+
</Stack>
|
|
158
|
+
</Flex>
|
|
159
|
+
{ reviewFlowEnabled && (<ReportsReviewModal
|
|
160
|
+
isVisible={reportsReviewVisible}
|
|
161
|
+
isActionAsync={isLoading}
|
|
162
|
+
onClose={handleReportsReviewClose}
|
|
163
|
+
startActions={<>
|
|
164
|
+
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
165
|
+
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
166
|
+
</Button>
|
|
167
|
+
{ item.gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
168
|
+
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
169
|
+
</Button>) }
|
|
170
|
+
</>}
|
|
171
|
+
item={item}>
|
|
172
|
+
<Typography variant="sigma" textColor="neutral600" id="reports-list">
|
|
173
|
+
{ getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
|
|
174
|
+
</Typography>
|
|
175
|
+
<Box paddingTop={2} paddingBottom={6}>
|
|
176
|
+
<Divider />
|
|
177
|
+
</Box>
|
|
178
|
+
<ReportsReviewTable
|
|
179
|
+
commentId={item.id}
|
|
180
|
+
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
181
|
+
mutation={resolveReportMutation}
|
|
182
|
+
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
183
|
+
/>
|
|
184
|
+
</ReportsReviewModal>) }
|
|
185
|
+
</Td>
|
|
186
|
+
</Tr>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
DiscoverTableRow.propTypes = {
|
|
191
|
+
config: PropTypes.object.isRequired,
|
|
192
|
+
item: PropTypes.object.isRequired,
|
|
193
|
+
onClick: PropTypes.func.isRequired,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export default DiscoverTableRow;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* Discover
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo, useRef, useMemo } from 'react';
|
|
8
|
+
import { connect } from 'react-redux';
|
|
9
|
+
import { bindActionCreators, compose } from 'redux';
|
|
10
|
+
import isEqual from 'react-fast-compare';
|
|
11
|
+
import { useQuery } from 'react-query';
|
|
12
|
+
import { useHistory } from 'react-router-dom';
|
|
13
|
+
import { isEmpty } from 'lodash';
|
|
14
|
+
import { Box } from '@strapi/design-system/Box';
|
|
15
|
+
import { Layout, HeaderLayout, ActionLayout, ContentLayout } from '@strapi/design-system/Layout';
|
|
16
|
+
import { Table, Thead, Tbody, Tr, Th, Td } from '@strapi/design-system/Table';
|
|
17
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
18
|
+
import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
|
|
19
|
+
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
EmptyStateLayout,
|
|
23
|
+
SearchURLQuery,
|
|
24
|
+
LoadingIndicatorPage,
|
|
25
|
+
useTracking,
|
|
26
|
+
useNotification,
|
|
27
|
+
useRBAC,
|
|
28
|
+
useFocusWhenNavigate,
|
|
29
|
+
useQueryParams,
|
|
30
|
+
} from '@strapi/helper-plugin';
|
|
31
|
+
import getMessage from '../../utils/getMessage';
|
|
32
|
+
import { fetchData } from './utils/api';
|
|
33
|
+
import pluginPermissions from '../../permissions';
|
|
34
|
+
|
|
35
|
+
import getUrl from '../../utils/getUrl';
|
|
36
|
+
import Nav from '../../components/Nav';
|
|
37
|
+
import TablePagination from '../../components/TablePagination';
|
|
38
|
+
import filtersSchema from './utils/filtersSchema';
|
|
39
|
+
import TableFilters from '../../components/TableFilters';
|
|
40
|
+
import makeAppView from '../App/reducer/selectors';
|
|
41
|
+
import DiscoverTableRow from './components/DiscoverTableRow';
|
|
42
|
+
|
|
43
|
+
const Discover = ({ config }) => {
|
|
44
|
+
useFocusWhenNavigate();
|
|
45
|
+
|
|
46
|
+
const { push } = useHistory();
|
|
47
|
+
const { notifyStatus } = useNotifyAT();
|
|
48
|
+
const { trackUsage } = useTracking();
|
|
49
|
+
const trackUsageRef = useRef(trackUsage);
|
|
50
|
+
const toggleNotification = useNotification();
|
|
51
|
+
const [{ query: queryParams }] = useQueryParams();
|
|
52
|
+
const _q = queryParams?._q || '';
|
|
53
|
+
|
|
54
|
+
const viewPermissions = useMemo(() => {
|
|
55
|
+
return { view: pluginPermissions.view };
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
isLoading: isLoadingForPermissions,
|
|
60
|
+
allowedActions: { canView },
|
|
61
|
+
} = useRBAC(viewPermissions);
|
|
62
|
+
|
|
63
|
+
const { isLoading: isLoadingForData, data: { result, pagination = {} }, isFetching } = useQuery(
|
|
64
|
+
['get-data', queryParams],
|
|
65
|
+
() => fetchData(queryParams, toggleNotification),
|
|
66
|
+
{
|
|
67
|
+
initialData: { },
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const handleClickDisplay = id => {
|
|
72
|
+
push(getUrl(`discover/${id}`));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isLoading = isLoadingForData || isFetching;
|
|
76
|
+
const { total } = pagination;
|
|
77
|
+
|
|
78
|
+
const emptyLayout = {
|
|
79
|
+
comments: {
|
|
80
|
+
id: getMessage('page.discover.table.empty'),
|
|
81
|
+
defaultMessage: "You don't have any comments yet.",
|
|
82
|
+
},
|
|
83
|
+
search: {
|
|
84
|
+
id: getMessage('page.discover.table.empty.search'),
|
|
85
|
+
defaultMessage: 'No comments match the search.',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
const emptyContent = _q ? 'search' : 'comments';
|
|
92
|
+
|
|
93
|
+
const COL_COUNT = 6;
|
|
94
|
+
|
|
95
|
+
return <Box background="neutral100">
|
|
96
|
+
<Layout sideNav={<Nav />}>
|
|
97
|
+
{isLoading || isLoadingForPermissions ? (<LoadingIndicatorPage />) : (
|
|
98
|
+
<>
|
|
99
|
+
<HeaderLayout title={ getMessage('page.discover.header') } subtitle={ `${total} ${ getMessage('page.discover.header.count')}` } as="h2" />
|
|
100
|
+
<ActionLayout
|
|
101
|
+
startActions={<>
|
|
102
|
+
<SearchURLQuery
|
|
103
|
+
label={ getMessage('search.label', 'Search', false) }
|
|
104
|
+
/>
|
|
105
|
+
<TableFilters displayedFilters={filtersSchema} />
|
|
106
|
+
</>} />
|
|
107
|
+
<ContentLayout>
|
|
108
|
+
{ !isEmpty(result) ? (<>
|
|
109
|
+
<Table colCount={COL_COUNT} rowCount={result.length}>
|
|
110
|
+
<Thead>
|
|
111
|
+
<Tr>
|
|
112
|
+
<Th>
|
|
113
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.message') }</Typography>
|
|
114
|
+
</Th>
|
|
115
|
+
<Th>
|
|
116
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.thread') }</Typography>
|
|
117
|
+
</Th>
|
|
118
|
+
<Th>
|
|
119
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.entry') }</Typography>
|
|
120
|
+
</Th>
|
|
121
|
+
<Th>
|
|
122
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.lastUpdate') }</Typography>
|
|
123
|
+
</Th>
|
|
124
|
+
<Th>
|
|
125
|
+
<Typography variant="sigma">{ getMessage('page.discover.table.header.status') }</Typography>
|
|
126
|
+
</Th>
|
|
127
|
+
<Th>
|
|
128
|
+
<VisuallyHidden>{ getMessage('page.discover.table.header.actions') }</VisuallyHidden>
|
|
129
|
+
</Th>
|
|
130
|
+
</Tr>
|
|
131
|
+
</Thead>
|
|
132
|
+
<Tbody>
|
|
133
|
+
{result.map(entry => (<DiscoverTableRow config={config} item={entry} onClick={() => handleClickDisplay(entry.id)}/>))}
|
|
134
|
+
</Tbody>
|
|
135
|
+
</Table>
|
|
136
|
+
<TablePagination pagination={{ pageCount: pagination?.pageCount || 1 }} />
|
|
137
|
+
</>) : (<EmptyStateLayout content={emptyLayout[emptyContent]} />) }
|
|
138
|
+
</ContentLayout>
|
|
139
|
+
</>) }
|
|
140
|
+
</Layout>
|
|
141
|
+
</Box>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const mapStateToProps = makeAppView();
|
|
145
|
+
|
|
146
|
+
export function mapDispatchToProps(dispatch) {
|
|
147
|
+
return bindActionCreators(
|
|
148
|
+
{ },
|
|
149
|
+
dispatch
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const withConnect = connect(
|
|
153
|
+
mapStateToProps,
|
|
154
|
+
mapDispatchToProps
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
export default compose(withConnect)(memo(Discover, isEqual));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { stringify } from 'qs';
|
|
2
|
+
|
|
3
|
+
import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
6
|
+
export const fetchData = async (queryParams, toggleNotification) => {
|
|
7
|
+
try {
|
|
8
|
+
const { data } = await axiosInstance.get(getApiURL(`moderate/all${queryParams ? `?${stringify(queryParams, { encode: false })}` : ''}`));
|
|
9
|
+
|
|
10
|
+
return data;
|
|
11
|
+
} catch (err) {
|
|
12
|
+
handleAPIError(err, toggleNotification);
|
|
13
|
+
}
|
|
14
|
+
};
|
package/admin/src/permissions.js
CHANGED
|
@@ -1,34 +1,14 @@
|
|
|
1
1
|
const pluginPermissions = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ action: '
|
|
9
|
-
{ action: '
|
|
10
|
-
{ action: '
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
moderate: [
|
|
16
|
-
{ action: 'plugins::comments.read', subject: null },
|
|
17
|
-
{ action: 'plugins::comments.moderate.block.comment', subject: null },
|
|
18
|
-
{ action: 'plugins::comments.moderate.block.thread', subject: null },
|
|
19
|
-
],
|
|
20
|
-
moderateComments: [
|
|
21
|
-
{ action: 'plugins::comments.read', subject: null },
|
|
22
|
-
{ action: 'plugins::comments.moderate.block.comment', subject: null },
|
|
23
|
-
],
|
|
24
|
-
moderateThreads: [
|
|
25
|
-
{ action: 'plugins::comments.read', subject: null },
|
|
26
|
-
{ action: 'plugins::comments.moderate.block.thread', subject: null },
|
|
27
|
-
],
|
|
28
|
-
moderateReports: [
|
|
29
|
-
{ action: 'plugins::comments.read', subject: null },
|
|
30
|
-
{ action: 'plugins::comments.moderate.reports', subject: null },
|
|
31
|
-
],
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export default pluginPermissions;
|
|
2
|
+
access: [
|
|
3
|
+
{ action: 'plugin::comments.read', subject: null },
|
|
4
|
+
{ action: 'plugin::comments.reports.read', subject: null },
|
|
5
|
+
{ action: 'plugin::comments.discovery.read', subject: null },
|
|
6
|
+
{ action: 'plugin::comments.settings.read', subject: null },
|
|
7
|
+
],
|
|
8
|
+
discover: [{ action: 'plugin::comments.read', subject: null }],
|
|
9
|
+
moderate: [{ action: 'plugin::comments.moderate', subject: null }],
|
|
10
|
+
reports: [{ action: 'plugin::comments.reports.read', subject: null }],
|
|
11
|
+
reportsReview: [{ action: 'plugin::comments.reports.action', subject: null }],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default pluginPermissions;
|
package/admin/src/pluginId.js
CHANGED