strapi-plugin-comments 2.0.0-beta.1 → 2.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/admin/src/components/DiscussionThread/index.js +3 -1
- package/admin/src/components/DiscussionThreadItem/index.js +4 -4
- package/admin/src/components/DiscussionThreadItem/styles.js +1 -0
- package/admin/src/components/DiscussionThreadItemActions/index.js +30 -44
- package/admin/src/components/DiscussionThreadItemActions/styles.js +0 -4
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +72 -0
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/styles.js +6 -0
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +13 -9
- package/admin/src/components/Nav/index.js +5 -1
- package/admin/src/components/ReportsReviewModal/index.js +0 -2
- package/admin/src/components/ReportsReviewTable/index.js +9 -5
- package/admin/src/components/icons/index.js +6 -0
- package/admin/src/components/icons/lock.js +22 -0
- package/admin/src/components/icons/review.js +18 -0
- package/admin/src/components/icons/unlock.js +20 -0
- package/admin/src/index.js +6 -4
- package/admin/src/pages/App/index.js +42 -3
- package/admin/src/pages/App/reducer/actions.js +8 -0
- package/admin/src/pages/App/reducer/constants.js +4 -0
- package/admin/src/pages/App/reducer/index.js +26 -0
- package/admin/src/pages/App/reducer/init.js +7 -0
- package/admin/src/pages/App/reducer/selectors.js +35 -0
- package/admin/src/pages/App/utils/api.js +12 -0
- package/admin/src/pages/Details/components/DetailsEntity/index.js +66 -0
- package/admin/src/pages/Details/components/DetailsFilters/index.js +36 -0
- package/admin/src/pages/Details/components/DetailsFilters/styles.js +8 -0
- package/admin/src/pages/Details/index.js +41 -14
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +190 -0
- package/admin/src/pages/Discover/components/DiscoverTableRow/styles.js +8 -0
- package/admin/src/pages/Discover/index.js +25 -50
- package/admin/src/pages/Discover/utils/api.js +2 -11
- package/admin/src/pages/{Details/utils → utils}/api.js +15 -3
- package/admin/src/reducers.js +8 -0
- package/admin/src/translations/en.json +16 -4
- package/admin/src/utils/constants.js +3 -6
- package/admin/src/utils/getMessage.js +10 -3
- package/admin/src/utils/getRelatedGroups.js +1 -3
- package/admin/src/utils/handleAPIError.js +1 -1
- package/admin/src/utils/index.js +4 -2
- package/admin/src/utils/renderEntryTitle.js +14 -0
- package/admin/src/utils/resolveCommentStatus.js +30 -0
- package/admin/src/utils/resolveCommentStatusColor.js +22 -0
- package/package.json +7 -3
- package/server/config/index.js +6 -1
- package/server/controllers/admin.js +8 -24
- package/server/controllers/client.js +11 -2
- package/server/services/admin.js +75 -14
- package/server/services/client.js +22 -14
- package/server/services/common.js +41 -70
- package/server/services/utils/functions.js +7 -14
- package/server/utils/constants.js +3 -3
- package/admin/src/components/EntityDetails/index.js +0 -53
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import produce from 'immer'; // current
|
|
2
|
+
import { set } from 'lodash';
|
|
3
|
+
import { GET_CONFIG, SET_CONFIG } from './constants';
|
|
4
|
+
|
|
5
|
+
const initialState = {
|
|
6
|
+
config: {},
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const reducer = (state = initialState, action) =>
|
|
10
|
+
// eslint-disable-next-line consistent-return
|
|
11
|
+
produce(state, draftState => {
|
|
12
|
+
switch (action.type) {
|
|
13
|
+
case SET_CONFIG: {
|
|
14
|
+
set(draftState, 'config', action.data);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
case GET_CONFIG: {
|
|
18
|
+
return draftState?.config;
|
|
19
|
+
}
|
|
20
|
+
default:
|
|
21
|
+
return draftState;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default reducer;
|
|
26
|
+
export { initialState };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { createSelector } from 'reselect';
|
|
4
|
+
import { initialState } from './';
|
|
5
|
+
import { REDUCER_NAME } from './constants';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Direct selector to the listView state domain
|
|
9
|
+
*/
|
|
10
|
+
const appView = () => state => state[REDUCER_NAME] || initialState;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Other specific selectors
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default selector used by listView
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const makeAppView = () =>
|
|
21
|
+
createSelector(
|
|
22
|
+
appView(),
|
|
23
|
+
substate => {
|
|
24
|
+
return substate;
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const selectConfig = state => {
|
|
29
|
+
const { config } = state;
|
|
30
|
+
|
|
31
|
+
return config;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default makeAppView;
|
|
35
|
+
export { appView, selectConfig };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
4
|
+
export const fetchConfig = async (toggleNotification) => {
|
|
5
|
+
try {
|
|
6
|
+
const { data } = await axiosInstance.get(getApiURL(`moderate/config`));
|
|
7
|
+
|
|
8
|
+
return data;
|
|
9
|
+
} catch (err) {
|
|
10
|
+
handleAPIError(err, toggleNotification);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -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 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,36 @@
|
|
|
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
|
+
onLabel={getMessage('page.details.filters.removed.show', 'Show')}
|
|
24
|
+
offLabel={getMessage('page.details.filters.removed.hide', 'Hide')}
|
|
25
|
+
selected={removed} onChange={() => handleOnChange('removed', !removed)}
|
|
26
|
+
visibleLabels />
|
|
27
|
+
</Stack>
|
|
28
|
+
</Box>);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
DetailsFilters.propTypes = {
|
|
32
|
+
data: PropTypes.object.isRequired,
|
|
33
|
+
onChange: PropTypes.func.isRequired,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
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 {
|
|
@@ -54,16 +59,20 @@ const Details = () => {
|
|
|
54
59
|
allowedActions: { canView },
|
|
55
60
|
} = useRBAC(viewPermissions);
|
|
56
61
|
|
|
62
|
+
const [filters, setFilters] = useState({});
|
|
63
|
+
|
|
64
|
+
const regexUID = new RegExp(config.regex.uid);
|
|
65
|
+
|
|
57
66
|
const { isLoading: isLoadingForData, data, isFetching } = useQuery(
|
|
58
|
-
['get-details-data', id],
|
|
59
|
-
() => fetchDetailsData(id, toggleNotification),
|
|
67
|
+
['get-details-data', id, filters],
|
|
68
|
+
() => fetchDetailsData(id, filters, toggleNotification),
|
|
60
69
|
{
|
|
61
70
|
initialData: { },
|
|
62
71
|
}
|
|
63
72
|
);
|
|
64
73
|
|
|
65
74
|
const { entity, level, selected } = data;
|
|
66
|
-
const entityUidValid = entity?.uid &&
|
|
75
|
+
const entityUidValid = entity?.uid && regexUID.test(entity.uid);
|
|
67
76
|
|
|
68
77
|
const { data: contentTypeData } = useQuery(
|
|
69
78
|
['get-additional-data', entity?.uid],
|
|
@@ -74,28 +83,33 @@ const Details = () => {
|
|
|
74
83
|
}
|
|
75
84
|
)
|
|
76
85
|
|
|
86
|
+
const handleChangeFilters = props => setFilters(props);
|
|
87
|
+
|
|
77
88
|
const isLoading = isLoadingForData || isFetching;
|
|
78
89
|
|
|
79
90
|
return <Box background="neutral100">
|
|
80
|
-
<Layout
|
|
91
|
+
<Layout>
|
|
81
92
|
{(isLoading || isLoadingForPermissions) && isEmpty(data) ? (<LoadingIndicatorPage />) : (
|
|
82
93
|
<>
|
|
83
94
|
<HeaderLayout
|
|
84
95
|
navigationAction={
|
|
85
96
|
<Link startIcon={<ArrowLeft />} to={getUrl(`discover`)}>
|
|
86
|
-
{ getMessage('
|
|
97
|
+
{ getMessage('HeaderLayout.link.go-back', 'Back', false) }
|
|
87
98
|
</Link>}
|
|
88
99
|
title={ getMessage('page.details.header') }
|
|
89
|
-
subtitle={ getMessage('page.details.header.description') }
|
|
100
|
+
subtitle={ getMessage('page.details.header.description') }
|
|
101
|
+
primaryAction={<DetailsFilters data={filters} onChange={handleChangeFilters} />}
|
|
102
|
+
secondaryAction={<Box as={Flex}> </Box>}
|
|
103
|
+
as="h2" />
|
|
90
104
|
<ContentLayout>
|
|
91
105
|
<TwoColsLayout
|
|
92
106
|
startCol={<DiscussionThread level={level} selected={selected} isReloading={isLoading} />}
|
|
93
107
|
endCol={ <Stack size={2}>
|
|
94
108
|
<DiscussionStatus item={selected} />
|
|
95
109
|
{ !contentTypeData?.schema ? (<Box padding={4}>
|
|
96
|
-
<Loader small>
|
|
110
|
+
<Loader small>{ getMessage('page.details.panel.loading', 'Loading...') }</Loader>
|
|
97
111
|
</Box>) :
|
|
98
|
-
(<
|
|
112
|
+
(<DetailsEntity data={entity} schema={contentTypeData?.schema} config={config} />)
|
|
99
113
|
}
|
|
100
114
|
</Stack>
|
|
101
115
|
}
|
|
@@ -106,4 +120,17 @@ const Details = () => {
|
|
|
106
120
|
</Box>;
|
|
107
121
|
}
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
const mapStateToProps = makeAppView();
|
|
124
|
+
|
|
125
|
+
export function mapDispatchToProps(dispatch) {
|
|
126
|
+
return bindActionCreators(
|
|
127
|
+
{ },
|
|
128
|
+
dispatch
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const withConnect = connect(
|
|
132
|
+
mapStateToProps,
|
|
133
|
+
mapDispatchToProps
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
export default compose(withConnect)(memo(Details, isEqual));
|
|
@@ -0,0 +1,190 @@
|
|
|
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 { isNil, 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 } from '@strapi/icons';
|
|
17
|
+
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
18
|
+
import { getMessage, getUrl, handleAPIError, resolveCommentStatus, resolveCommentStatusColor } from '../../../../utils';
|
|
19
|
+
import ReportsReviewModal from '../../../../components/ReportsReviewModal';
|
|
20
|
+
import ReportsReviewTable from '../../../../components/ReportsReviewTable';
|
|
21
|
+
import { blockItem, blockItemThread, resolveReport } from '../../../utils/api';
|
|
22
|
+
import pluginId from '../../../../pluginId';
|
|
23
|
+
import { ReviewIcon, LockIcon } from '../../../../components/icons';
|
|
24
|
+
import { TableLink } from './styles';
|
|
25
|
+
import renderEntryTitle from '../../../../utils/renderEntryTitle';
|
|
26
|
+
import DiscussionThreadItemApprovalFlowActions from '../../../../components/DiscussionThreadItemApprovalFlowActions';
|
|
27
|
+
|
|
28
|
+
const DiscoverTableRow = ({ config, item, onClick }) => {
|
|
29
|
+
|
|
30
|
+
const { id, reports } = item;
|
|
31
|
+
|
|
32
|
+
const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
|
|
33
|
+
const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
|
|
34
|
+
|
|
35
|
+
const { formatDate } = useIntl();
|
|
36
|
+
const toggleNotification = useNotification();
|
|
37
|
+
const queryClient = useQueryClient();
|
|
38
|
+
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
39
|
+
|
|
40
|
+
const onSuccess = (message, stateAction = () => {}, indalidate = true) => async () => {
|
|
41
|
+
if (indalidate) {
|
|
42
|
+
await queryClient.invalidateQueries('get-data');
|
|
43
|
+
}
|
|
44
|
+
toggleNotification({
|
|
45
|
+
type: 'success',
|
|
46
|
+
message: `${pluginId}.${message}`,
|
|
47
|
+
});
|
|
48
|
+
stateAction(false);
|
|
49
|
+
unlockApp();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
53
|
+
|
|
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
|
+
const blockItemMutation = useMutation(blockItem, {
|
|
61
|
+
onSuccess: onSuccess(
|
|
62
|
+
'page.details.actions.comment.block.confirmation.success',
|
|
63
|
+
setReportsReviewVisible
|
|
64
|
+
),
|
|
65
|
+
onError,
|
|
66
|
+
refetchActive: false,
|
|
67
|
+
});
|
|
68
|
+
const blockItemThreadMutation = useMutation(blockItemThread, {
|
|
69
|
+
onSuccess: onSuccess(
|
|
70
|
+
'page.details.actions.thread.block.confirmation.success',
|
|
71
|
+
setReportsReviewVisible
|
|
72
|
+
),
|
|
73
|
+
onError,
|
|
74
|
+
refetchActive: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const handleReportsReviewClick = () => setReportsReviewVisible(true);
|
|
78
|
+
const handleBlockItemClick = async () => {
|
|
79
|
+
lockApp();
|
|
80
|
+
blockItemMutation.mutate(id);
|
|
81
|
+
};
|
|
82
|
+
const handleBlockItemThreadClick = async () => {
|
|
83
|
+
lockApp();
|
|
84
|
+
blockItemThreadMutation.mutate(id);
|
|
85
|
+
};
|
|
86
|
+
const handleReportsReviewClose = async () => {
|
|
87
|
+
await queryClient.invalidateQueries('get-data');
|
|
88
|
+
setReportsReviewVisible(false);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
|
|
92
|
+
const openReports = reports?.filter(_ => !_.resolved);
|
|
93
|
+
const hasReports = !isEmpty(openReports);
|
|
94
|
+
const reviewFlowEnabled = hasReports && !(item.blocked || item.blockedThread);
|
|
95
|
+
|
|
96
|
+
const handleClick = () => onClick();
|
|
97
|
+
const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
|
|
98
|
+
|
|
99
|
+
const renderStatus = (props) => {
|
|
100
|
+
const status = resolveCommentStatus({ ...props, reviewFlowEnabled });
|
|
101
|
+
const color = resolveCommentStatusColor(status);
|
|
102
|
+
|
|
103
|
+
return (<Badge backgroundColor={`${color}100`} textColor={`${color}600`}>{ getMessage({
|
|
104
|
+
id: `page.common.item.status.${status}`,
|
|
105
|
+
props: {
|
|
106
|
+
count: openReports.length
|
|
107
|
+
}
|
|
108
|
+
}, status) }</Badge>);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const renderEntryUrl = entry => `/content-manager/collectionType/${entry.uid}/${entry.id}`;
|
|
112
|
+
const renderDetailsUrl = entry => getUrl(`discover/${entry.id}`);
|
|
113
|
+
|
|
114
|
+
const gotApprovalFlow = !isNil(item.approvalStatus);
|
|
115
|
+
const needsApproval = gotApprovalFlow && (item.approvalStatus === 'PENDING');
|
|
116
|
+
|
|
117
|
+
let actionItemsCount = 1;
|
|
118
|
+
if(reviewFlowEnabled || needsApproval) {
|
|
119
|
+
actionItemsCount = 2;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Tr key={item.id}>
|
|
124
|
+
<Td>
|
|
125
|
+
<Typography textColor="neutral800" fontWeight="bold">#{ item.id }</Typography>
|
|
126
|
+
</Td>
|
|
127
|
+
<Td style={{ maxWidth: '30vw' }}>
|
|
128
|
+
<Typography textColor="neutral800" ellipsis>{item.content}</Typography>
|
|
129
|
+
</Td>
|
|
130
|
+
<Td>
|
|
131
|
+
{ item.threadOf?.id ? (<Link to={ renderDetailsUrl(item.threadOf) }>{ getMessage({
|
|
132
|
+
id: 'page.discover.table.cell.thread',
|
|
133
|
+
props: { id: item.threadOf.id },
|
|
134
|
+
}, '#' + item.threadOf.id) }</Link>) : '-' }
|
|
135
|
+
</Td>
|
|
136
|
+
<Td style={{ maxWidth: '15vw' }}>
|
|
137
|
+
<TableLink to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related, config) }</TableLink>
|
|
138
|
+
</Td>
|
|
139
|
+
<Td>
|
|
140
|
+
<Typography textColor="neutral800">{ formatDate(item.updatedAt || item.createdAt, { dateStyle: 'long', timeStyle: 'short' }) }</Typography>
|
|
141
|
+
</Td>
|
|
142
|
+
<Td>
|
|
143
|
+
<Typography textColor="neutral800">{ renderStatus(item) }</Typography>
|
|
144
|
+
</Td>
|
|
145
|
+
<Td>
|
|
146
|
+
<Flex direction="column" alignItems="flex-end">
|
|
147
|
+
<Stack size={actionItemsCount} horizontal>
|
|
148
|
+
{ reviewFlowEnabled && (<IconButton onClick={handleReportsReviewClick} label={ getMessage('page.discover.table.reports.review') } icon={<ReviewIcon />} />)}
|
|
149
|
+
{ needsApproval && (<DiscussionThreadItemApprovalFlowActions id={item.id} queryToInvalidate="get-data" />)}
|
|
150
|
+
<IconButton onClick={handleClick} label={ getMessage('page.discover.table.action.display') } icon={<Eye />} />
|
|
151
|
+
</Stack>
|
|
152
|
+
</Flex>
|
|
153
|
+
{ reviewFlowEnabled && (<ReportsReviewModal
|
|
154
|
+
isVisible={reportsReviewVisible}
|
|
155
|
+
isActionAsync={isLoading}
|
|
156
|
+
onClose={handleReportsReviewClose}
|
|
157
|
+
startActions={<>
|
|
158
|
+
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
159
|
+
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
160
|
+
</Button>
|
|
161
|
+
{ item.gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
162
|
+
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
163
|
+
</Button>) }
|
|
164
|
+
</>}
|
|
165
|
+
item={item}>
|
|
166
|
+
<Typography variant="sigma" textColor="neutral600" id="reports-list">
|
|
167
|
+
{ getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
|
|
168
|
+
</Typography>
|
|
169
|
+
<Box paddingTop={2} paddingBottom={6}>
|
|
170
|
+
<Divider />
|
|
171
|
+
</Box>
|
|
172
|
+
<ReportsReviewTable
|
|
173
|
+
commentId={item.id}
|
|
174
|
+
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
175
|
+
mutation={resolveReportMutation}
|
|
176
|
+
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
177
|
+
/>
|
|
178
|
+
</ReportsReviewModal>) }
|
|
179
|
+
</Td>
|
|
180
|
+
</Tr>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
DiscoverTableRow.propTypes = {
|
|
185
|
+
config: PropTypes.object.isRequired,
|
|
186
|
+
item: PropTypes.object.isRequired,
|
|
187
|
+
onClick: PropTypes.func.isRequired,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export default DiscoverTableRow;
|