strapi-plugin-comments 2.0.0-rc.2 → 2.0.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.
Files changed (65) hide show
  1. package/.husky/pre-commit +4 -0
  2. package/README.md +53 -9
  3. package/__mocks__/initSetup.js +82 -0
  4. package/admin/src/components/ActionButton/styles.js +10 -0
  5. package/admin/src/components/DiscussionThread/index.js +2 -6
  6. package/admin/src/components/DiscussionThreadItem/index.js +25 -68
  7. package/admin/src/components/DiscussionThreadItem/styles.js +4 -79
  8. package/admin/src/components/DiscussionThreadItemActions/index.js +82 -40
  9. package/admin/src/components/DiscussionThreadItemActions/styles.js +1 -12
  10. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +2 -3
  11. package/admin/src/components/DiscussionThreadItemFooter/index.js +47 -0
  12. package/admin/src/components/DiscussionThreadItemFooter/styles.js +13 -0
  13. package/admin/src/components/DiscussionThreadItemReviewAction/index.js +131 -0
  14. package/admin/src/components/IconButton/styles.js +14 -0
  15. package/admin/src/components/ReportsReviewModal/index.js +1 -1
  16. package/admin/src/components/ReportsReviewTable/index.js +12 -6
  17. package/admin/src/components/StatusBadge/index.js +17 -0
  18. package/admin/src/components/StatusBadge/styles.js +17 -0
  19. package/admin/src/hooks/useConfig.js +43 -0
  20. package/admin/src/index.js +31 -2
  21. package/admin/src/pages/Details/components/DetailsEntity/index.js +42 -24
  22. package/admin/src/pages/Details/components/DetailsFilters/index.js +8 -10
  23. package/admin/src/pages/Details/index.js +11 -17
  24. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +30 -67
  25. package/admin/src/pages/Settings/index.js +342 -0
  26. package/admin/src/pages/Settings/utils/api.js +50 -0
  27. package/admin/src/permissions.js +3 -0
  28. package/admin/src/translations/en.json +50 -16
  29. package/admin/src/utils/index.js +2 -1
  30. package/admin/src/utils/parseRegExp.js +10 -0
  31. package/admin/src/utils/resolveCommentStatus.js +4 -4
  32. package/package.json +12 -7
  33. package/server/bootstrap.js +24 -0
  34. package/server/config/index.js +1 -0
  35. package/server/controllers/admin.js +33 -8
  36. package/server/controllers/client.js +4 -4
  37. package/server/controllers/utils/__tests__/functions.test.js +2 -2
  38. package/server/controllers/utils/parsers.js +3 -1
  39. package/server/graphql/index.js +7 -8
  40. package/server/graphql/queries/findAllFlat.js +4 -2
  41. package/server/graphql/queries/findAllInHierarchy.js +3 -3
  42. package/server/graphql/resolvers-config.js +2 -4
  43. package/server/graphql/types/comment-nested.js +1 -0
  44. package/server/graphql/types/report-reason.js +10 -10
  45. package/server/register.js +1 -3
  46. package/server/routes/admin.js +24 -0
  47. package/server/services/__tests__/common.test.js +246 -0
  48. package/server/services/admin.js +60 -6
  49. package/server/services/client.js +13 -9
  50. package/server/services/common.js +86 -11
  51. package/server/services/utils/__tests__/functions.test.js +20 -73
  52. package/server/services/utils/functions.js +9 -17
  53. package/server/utils/__tests__/functions.test.js +17 -0
  54. package/server/utils/constants.js +10 -0
  55. package/strapi-server.js +12 -5
  56. package/.circleci/config.yml +0 -48
  57. package/.eslintrc +0 -35
  58. package/.github/pull_request_template.md +0 -13
  59. package/.nvmrc +0 -1
  60. package/admin/src/components/DiscussionThreadItemMeta/index.js +0 -42
  61. package/admin/src/components/DiscussionThreadItemWarnings/index.js +0 -147
  62. package/admin/src/components/DiscussionThreadItemWarnings/styles.js +0 -7
  63. package/codecov.yml +0 -4
  64. package/public/assets/logo.png +0 -0
  65. package/public/assets/preview.png +0 -0
@@ -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 = ({ data = {}, schema = {}, config = {} }) => {
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)),
@@ -34,25 +37,38 @@ const DetailsEntity = ({ data = {}, schema = {}, config = {} }) => {
34
37
  .map(_ => capitalize(_))
35
38
  .join(' ');
36
39
 
40
+ const entityIsRenderable = data && !isEmpty(data) && !isEmpty(itemKeys);
41
+
37
42
  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
+ { entityIsRenderable && (<Box marginBottom={4}>
44
+ <Typography variant="sigma" textColor="neutral600" id="entity-details">
45
+ { getMessage('page.details.panel.entity', 'Details') }
46
+ </Typography>
47
+ <Box paddingTop={2} paddingBottom={4}>
48
+ <Divider />
49
+ </Box>
50
+ <Stack size={itemKeys.length}>
51
+ <Flex direction="column" alignItems="flex-start">
52
+ <Typography fontWeight="bold">{ formatLabel(entityLabelKey) }</Typography>
53
+ <Typography>{ data[entityLabelKey] }</Typography>
54
+ </Flex>
55
+ { itemKeys.map(_ =>
56
+ (<Flex key={`prop_${_}`} direction="column" alignItems="flex-start">
57
+ <Typography fontWeight="bold">{ formatLabel(_) }</Typography>
58
+ <Typography>{ data[_] }</Typography>
59
+ </Flex>))
60
+ }
61
+ </Stack>
62
+ </Box>) }
63
+ <Box>
64
+ <Typography variant="sigma" textColor="neutral600" id="view-filters">
65
+ { getMessage('page.details.filters.label', 'View') }
66
+ </Typography>
67
+ <Box paddingTop={2} paddingBottom={4}>
68
+ <Divider />
69
+ </Box>
70
+ <DetailsFilters data={filters} onChange={onFiltersChange} />
43
71
  </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
72
  </Box>);
57
73
  };
58
74
 
@@ -60,6 +76,8 @@ DetailsEntity.propTypes = {
60
76
  data: PropTypes.object.isRequired,
61
77
  schema: PropTypes.object.isRequired,
62
78
  config: PropTypes.object.isRequired,
79
+ filters: PropTypes.object.isRequired,
80
+ onFiltersChange: PropTypes.func.isRequired,
63
81
  };
64
82
 
65
83
  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 { Switch } from '@strapi/design-system/Switch';
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} 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 />
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
 
@@ -30,13 +28,11 @@ import {
30
28
  import getMessage from '../../utils/getMessage';
31
29
  import { fetchDetailsData, fetchContentTypeData } from '../utils/api';
32
30
  import pluginPermissions from '../../permissions';
33
- import { getUrl } from '../../utils';
31
+ import { getUrl, parseRegExp } 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();
@@ -64,7 +60,10 @@ const Details = ({ config }) => {
64
60
 
65
61
  const [filters, setFilters] = useState({});
66
62
 
67
- const regexUID = new RegExp(config.regex.uid);
63
+ const regexUID = new RegExp(
64
+ parseRegExp(config.regex.uid).value,
65
+ parseRegExp(config.regex.uid).flags
66
+ );
68
67
 
69
68
  const { isLoading: isLoadingForData, data, isFetching } = useQuery(
70
69
  ['get-details-data', id, filters, canAccess],
@@ -102,21 +101,16 @@ const Details = ({ config }) => {
102
101
  </Link>}
103
102
  title={ getMessage('page.details.header') }
104
103
  subtitle={ getMessage('page.details.header.description') }
105
- primaryAction={<DetailsFilters data={filters} onChange={handleChangeFilters} />}
106
- secondaryAction={<Box as={Flex}>&nbsp;</Box>}
107
104
  as="h2" />
108
105
  <ContentLayout>
109
106
  <TwoColsLayout
110
107
  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
- }
108
+ endCol={<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,12 +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, orderBy } from 'lodash';
5
+ import { isNil, isEmpty } from 'lodash';
6
6
  import { Avatar, Initials } from '@strapi/design-system/Avatar';
7
- import { Badge } from '@strapi/design-system/Badge';
8
- import { Box } from '@strapi/design-system/Box';
9
- import { Button } from '@strapi/design-system/Button';
10
- import { Divider } from '@strapi/design-system/Divider';
11
7
  import { Flex } from '@strapi/design-system/Flex';
12
8
  import { IconButton } from '@strapi/design-system/IconButton';
13
9
  import { Link } from '@strapi/design-system/Link';
@@ -17,20 +13,20 @@ import { Typography } from '@strapi/design-system/Typography';
17
13
  import { Eye } from '@strapi/icons';
18
14
  import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
19
15
  import { getMessage, getUrl, handleAPIError, renderInitials, resolveCommentStatus, resolveCommentStatusColor } from '../../../../utils';
20
- import ReportsReviewModal from '../../../../components/ReportsReviewModal';
21
- import ReportsReviewTable from '../../../../components/ReportsReviewTable';
22
- import { blockItem, blockItemThread, resolveReport } from '../../../utils/api';
16
+ import { blockItem, blockItemThread } from '../../../utils/api';
23
17
  import pluginId from '../../../../pluginId';
24
18
  import { ReviewIcon, LockIcon } from '../../../../components/icons';
25
19
  import { TableLink } from './styles';
26
20
  import renderEntryTitle from '../../../../utils/renderEntryTitle';
27
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';
28
25
 
29
26
  const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAccessReports, canReviewReports }, onClick }) => {
30
27
 
31
28
  const { id, reports } = item;
32
29
 
33
- const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
34
30
  const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
35
31
 
36
32
  const { formatDate } = useIntl();
@@ -52,34 +48,17 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
52
48
 
53
49
  const onError = err => { handleAPIError(err, toggleNotification); };
54
50
 
55
- const resolveReportMutation = useMutation(resolveReport, {
56
- onSuccess: onSuccess('page.details.panel.discussion.warnings.reports.dialog.confirmation.success', () => {}, false),
57
- onError,
58
- refetchActive: false,
59
- });
60
-
61
51
  const blockItemMutation = useMutation(blockItem, {
62
- onSuccess: onSuccess(
63
- 'page.details.actions.comment.block.confirmation.success',
64
- setReportsReviewVisible
65
- ),
52
+ onSuccess: onSuccess('page.details.actions.comment.block.confirmation.success'),
66
53
  onError,
67
54
  refetchActive: false,
68
55
  });
69
56
  const blockItemThreadMutation = useMutation(blockItemThread, {
70
- onSuccess: onSuccess(
71
- 'page.details.actions.thread.block.confirmation.success',
72
- setReportsReviewVisible
73
- ),
57
+ onSuccess: onSuccess('page.details.actions.thread.block.confirmation.success'),
74
58
  onError,
75
59
  refetchActive: false,
76
60
  });
77
61
 
78
- const handleReportsReviewClick = () => {
79
- if (canAccessReports) {
80
- setReportsReviewVisible(true);
81
- }
82
- };
83
62
  const handleBlockItemClick = async () => {
84
63
  if (canModerate) {
85
64
  lockApp();
@@ -92,12 +71,14 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
92
71
  blockItemThreadMutation.mutate(id);
93
72
  }
94
73
  };
95
- const handleReportsReviewClose = async () => {
96
- await queryClient.invalidateQueries('get-data');
97
- setReportsReviewVisible(false);
74
+
75
+ const handleBlockActionClick = async (mutation, onCallback) => {
76
+ lockApp();
77
+ await mutation.mutateAsync(id);
78
+ onCallback();
98
79
  };
99
80
 
100
- const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
81
+ const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading;
101
82
  const openReports = reports?.filter(_ => !_.resolved);
102
83
  const hasReports = !isEmpty(openReports);
103
84
  const reviewFlowEnabled = (canAccessReports && hasReports) && !(item.blocked || item.blockedThread);
@@ -109,15 +90,15 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
109
90
  const status = resolveCommentStatus({ ...props, reviewFlowEnabled });
110
91
  const color = resolveCommentStatusColor(status);
111
92
 
112
- return (<Badge backgroundColor={`${color}100`} textColor={`${color}600`}>{ getMessage({
93
+ return (<StatusBadge backgroundColor={`${color}100`} textColor={`${color}700`} color={color}>{ getMessage({
113
94
  id: `page.common.item.status.${status}`,
114
95
  props: {
115
96
  count: openReports.length
116
97
  }
117
- }, status) }</Badge>);
98
+ }, status) }</StatusBadge>);
118
99
  };
119
100
 
120
- const renderEntryUrl = entry => `/content-manager/collectionType/${entry.uid}/${entry.id}`;
101
+ const renderEntryUrl = entry => entry ? `/content-manager/collectionType/${entry.uid}/${entry.id}` : null;
121
102
  const renderDetailsUrl = entry => getUrl(`discover/${entry.id}`);
122
103
 
123
104
  const gotApprovalFlow = !isNil(item.approvalStatus);
@@ -149,7 +130,7 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
149
130
  }, '#' + item.threadOf.id) }</Link>) : '-' }
150
131
  </Td>
151
132
  <Td style={{ maxWidth: '15vw' }}>
152
- <TableLink to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related, config) }</TableLink>
133
+ { item.related ? (<TableLink to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related, config) }</TableLink>) : '-' }
153
134
  </Td>
154
135
  <Td>
155
136
  <Typography textColor="neutral800">{ formatDate(item.updatedAt || item.createdAt, { dateStyle: 'long', timeStyle: 'short' }) }</Typography>
@@ -159,45 +140,27 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
159
140
  </Td>
160
141
  <Td>
161
142
  <Flex direction="column" alignItems="flex-end">
162
- <Stack size={actionItemsCount} horizontal>
163
- { reviewFlowEnabled && (<IconButton onClick={handleReportsReviewClick} label={ getMessage('page.discover.table.reports.review') } icon={<ReviewIcon />} />)}
143
+ <IconButtonGroupStyled isSingle={!(reviewFlowEnabled || (canModerate && needsApproval))}>
164
144
  { (canModerate && needsApproval) && (
165
145
  <DiscussionThreadItemApprovalFlowActions
166
146
  id={item.id}
167
147
  allowedActions={{ canModerate }}
168
148
  queryToInvalidate="get-data" />
169
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
+ />
170
161
  <IconButton onClick={handleClick} label={ getMessage('page.discover.table.action.display') } icon={<Eye />} />
171
- </Stack>
162
+ </IconButtonGroupStyled>
172
163
  </Flex>
173
- { reviewFlowEnabled && (<ReportsReviewModal
174
- isVisible={reportsReviewVisible}
175
- isActionAsync={isLoading}
176
- allowedActions={{ canModerate, canAccessReports, canReviewReports }}
177
- onClose={handleReportsReviewClose}
178
- startActions={canModerate && (<>
179
- <Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
180
- { getMessage(`page.details.actions.comment.block`, 'Block comment') }
181
- </Button>
182
- { item.gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
183
- { getMessage(`page.details.actions.thread.block`, 'Block thread') }
184
- </Button>) }
185
- </>)}
186
- item={item}>
187
- <Typography variant="sigma" textColor="neutral600" id="reports-list">
188
- { getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
189
- </Typography>
190
- <Box paddingTop={2} paddingBottom={6}>
191
- <Divider />
192
- </Box>
193
- <ReportsReviewTable
194
- commentId={item.id}
195
- items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
196
- mutation={resolveReportMutation}
197
- allowedActions={{ canAccessReports, canReviewReports }}
198
- onBlockButtonsStateChange={handleBlockButtonsStateChange}
199
- />
200
- </ReportsReviewModal>) }
201
164
  </Td>
202
165
  </Tr>
203
166
  );