strapi-plugin-comments 2.0.0-beta.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/README.md CHANGED
@@ -74,8 +74,12 @@ To setup amend default plugin configuration we recommend to put following snippe
74
74
  ```js
75
75
  ...
76
76
  comments: {
77
- badWords: false,
78
- moderatorRoles: ["Authenticated"]
77
+ badWords: false,
78
+ moderatorRoles: ["Authenticated"],
79
+ entryLabel: {
80
+ '*': ['Title', 'title', 'Name', 'name', 'Subject', 'subject'],
81
+ 'api::page.page': ['MyField'],
82
+ },
79
83
  },
80
84
  ...
81
85
  ```
@@ -83,6 +87,7 @@ To setup amend default plugin configuration we recommend to put following snippe
83
87
  ### Properties
84
88
  - `badWords` - Enabled support for [bad words filtering](https://www.npmjs.com/package/bad-words). Can be turned off or overwritten using [options reference](https://www.npmjs.com/package/bad-words#constructor). Default value: `true`.
85
89
  - `moderatorRoles` - Optional list of names of roles. Users with those roles will be notified by email when a new abuse report is created. This feature requires a built-in [Strapi email plugin](https://docs.strapi.io/developer-docs/latest/plugins/email.html) configured.
90
+ - `entryLabel` - ordered list of property names per Content Type to generate related entity label. Default set as `*`.
86
91
 
87
92
  ## Additional GQL Configuration
88
93
 
@@ -34,12 +34,13 @@ const DiscussionThread = ({ level = [], selected = {}, isReloading }) => {
34
34
  <Divider />
35
35
  </Box>
36
36
  <Flex as="ul" direction="column" alignItems="flex-start">
37
- { rootThread && (<DiscussionThreadItem {...rootThread} isThreadAuthor root />) }
37
+ { rootThread && (<DiscussionThreadItem {...rootThread} isThreadAuthor root pinned />) }
38
38
  { level.map(item => {
39
39
  const isSelected = selected.id === item.id;
40
40
  const isThreadAuthor = !isNil(selected?.threadOf?.authorId) && (selected?.threadOf?.authorId === item.authorId);
41
41
  return (<DiscussionThreadItem
42
42
  {...item}
43
+ root={isNil(rootThread)}
43
44
  blockedThread={rootThread?.blockedThread || item.blockedThread}
44
45
  isSelected={isSelected}
45
46
  isThreadAuthor={isThreadAuthor}
@@ -21,7 +21,7 @@ import { getMessage, getUrl } from '../../utils';
21
21
  import DiscussionThreadItemWarnings from '../DiscussionThreadItemWarnings';
22
22
 
23
23
  const DiscussionThreadItem = (item) => {
24
- const { id, isSelected, isThreadAuthor, content, root, preview, blocked, blockedThread, gotThread, threadFirstItemId, authorUser, authorAvatar, authorName } = item;
24
+ const { id, isSelected, isThreadAuthor, content, root, preview, pinned, blocked, blockedThread, gotThread, threadFirstItemId, authorUser, authorAvatar, authorName } = item;
25
25
 
26
26
  let background = 'neutral100';
27
27
  let borderColor = 'neutral200';
@@ -62,16 +62,16 @@ const DiscussionThreadItem = (item) => {
62
62
  <Box as={Flex} grow={1} alignItems="center">
63
63
  <Typography variant="omega">{ content }</Typography>
64
64
  </Box>
65
- { !preview && (<DiscussionThreadItemActions { ...item } root={root} />) }
65
+ { !preview && (<DiscussionThreadItemActions { ...item } root={root || pinned} />) }
66
66
  </DiscussionThreadItemBoxContent>
67
- { !(preview || isBlocked) && (<DiscussionThreadItemWarnings { ...item } />) }
67
+ { !(preview || isBlocked) && (<DiscussionThreadItemWarnings { ...item } gotThread={gotThread || root} />) }
68
68
  </Flex>
69
69
  <Divider background={dividerColor} />
70
70
  <DiscussionThreadItemMeta { ...item } />
71
71
  </DiscussionThreadItemBox>
72
72
  </DiscussionThreadItemContent>
73
73
 
74
- { (gotThread && !preview) && (<DiscussionThreadItemLinkBox direction="row" alignItems="flex-end">
74
+ { (gotThread && !(preview || pinned)) && (<DiscussionThreadItemLinkBox direction="column" alignItems="flex-end">
75
75
  <Link to={getUrl(`discover/${threadFirstItemId}`)} endIcon={<ArrowRight />}>
76
76
  { getMessage('page.details.panel.discussion.nav.drilldown') }
77
77
  </Link>
@@ -6,6 +6,7 @@ export const AvatarWrapper = styled(Box)``;
6
6
  export const InitialsWrapper = styled(Box)``;
7
7
 
8
8
  export const DiscussionThreadItemLinkBox = styled(Flex)`
9
+ width: 100%;
9
10
  margin-top: .5rem;
10
11
  margin-bottom: .5rem;
11
12
  `
@@ -11,14 +11,16 @@ import { isNil } from 'lodash';
11
11
  import { Button } from '@strapi/design-system/Button';
12
12
  import { Flex } from '@strapi/design-system/Flex';
13
13
  import { IconButton } from '@strapi/design-system/IconButton';
14
- import { Lock, Check, Cross, Key } from '@strapi/icons';
14
+ import { Check, Cross } from '@strapi/icons';
15
15
  import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
16
16
  import { getMessage, handleAPIError } from '../../utils';
17
17
  import { DiscussionThreadItemActionsBadge, DiscussionThreadItemActionsGroupWrapper, DiscussionThreadItemActionsWrapper } from './styles';
18
18
  import ConfirmationDialog from '../ConfirmationDialog';
19
19
  import { blockItem, blockItemThread, unblockItem, unblockItemThread } from '../../pages/Details/utils/api';
20
+ import pluginId from '../../pluginId';
21
+ import { LockIcon, UnlockIcon } from '../icons';
20
22
 
21
- const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatus, root }) => {
23
+ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, gotThread, pinned, approvalStatus, root }) => {
22
24
 
23
25
  const [blockConfirmationVisible, setBlockConfirmationVisible] = useState(false);
24
26
  const [blockThreadConfirmationVisible, setBlockThreadConfirmationVisible] = useState(false);
@@ -31,7 +33,7 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
31
33
  await queryClient.invalidateQueries('get-details-data');
32
34
  toggleNotification({
33
35
  type: 'success',
34
- message,
36
+ message: `${pluginId}.${message}`,
35
37
  });
36
38
  stateAction(false);
37
39
  unlockApp();
@@ -130,10 +132,10 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
130
132
  return (<>
131
133
  <DiscussionThreadItemActionsWrapper as={Flex} direction="row">
132
134
  { badgeVisible && (<DiscussionThreadItemActionsBadge color={badgeColor} backgroundColor={`${badgeColor}100`} textColor={`${badgeColor}600`}>{getMessage(`page.details.panel.discussion.status.${badgeLabel}`, badgeLabel)}</DiscussionThreadItemActionsBadge>)}
133
- { !blockedThread && !(blocked || needsApproval) && (<IconButton onClick={handleBlockClick} loading={blockItemMutation.isLoading} icon={<Lock />} label={getMessage('page.details.actions.comment.block', 'Block')} />)}
134
- { !blockedThread && blocked && (<IconButton onClick={handleUnblockClick} loading={unblockItemMutation.isLoading} icon={<Key />} label={getMessage('page.details.actions.comment.unblock', 'Unblock')} />)}
135
- { (!blockedThread && root) && (<Button onClick={handleBlockThreadClick} startIcon={<Lock />} loading={blockItemThreadMutation.isLoading} variant="danger">{ getMessage('page.details.actions.thread.block', 'Block thread') }</Button>) }
136
- { (blockedThread && root) && (<Button onClick={handleUnblockThreadClick} startIcon={<Key />} loading={unblockItemThreadMutation.isLoading} variant="success">{ getMessage('page.details.actions.thread.unblock', 'Unblock thread') }</Button>) }
135
+ { !blockedThread && !(blocked || needsApproval) && (<IconButton onClick={handleBlockClick} loading={blockItemMutation.isLoading} icon={<LockIcon />} label={getMessage('page.details.actions.comment.block', 'Block')} style={(!blockedThread && root) ? { marginTop: '1px', marginRight: '.5rem' } : {}} />)}
136
+ { !blockedThread && blocked && (<IconButton onClick={handleUnblockClick} loading={unblockItemMutation.isLoading} icon={<UnlockIcon />} label={getMessage('page.details.actions.comment.unblock', 'Unblock')} />)}
137
+ { (!blockedThread && (gotThread || pinned)) && (<Button onClick={handleBlockThreadClick} startIcon={<LockIcon />} loading={blockItemThreadMutation.isLoading} variant="danger">{ getMessage('page.details.actions.thread.block', 'Block thread') }</Button>) }
138
+ { (blockedThread && (gotThread || pinned)) && (<Button onClick={handleUnblockThreadClick} startIcon={<UnlockIcon />} loading={unblockItemThreadMutation.isLoading} variant="success">{ getMessage('page.details.actions.thread.unblock', 'Unblock thread') }</Button>) }
137
139
  { needsApproval && (<DiscussionThreadItemActionsGroupWrapper>
138
140
  <IconButton icon={<Check />} label={getMessage('page.details.actions.comment.approve', 'Approve')} />
139
141
  <IconButton icon={<Cross />} label={getMessage('page.details.actions.comment.reject', 'Reject')} />
@@ -144,7 +146,7 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
144
146
  isActionAsync={blockItemMutation.isLoading}
145
147
  header={getMessage('page.details.actions.comment.block.confirmation.header')}
146
148
  labelConfirm={getMessage('page.details.actions.comment.block.confirmation.button.confirm')}
147
- iconConfirm={<Lock />}
149
+ iconConfirm={<LockIcon />}
148
150
  onConfirm={handleBlockConfirm}
149
151
  onCancel={handleBlockCancel}>
150
152
  { getMessage('page.details.actions.comment.block.confirmation.description') }
@@ -154,7 +156,7 @@ const DiscussionThreadItemActions = ({ id, blocked, blockedThread, approvalStatu
154
156
  isActionAsync={blockItemThreadMutation.isLoading}
155
157
  header={getMessage('page.details.actions.thread.block.confirmation.header')}
156
158
  labelConfirm={getMessage('page.details.actions.thread.block.confirmation.button.confirm')}
157
- iconConfirm={<Lock />}
159
+ iconConfirm={<LockIcon />}
158
160
  onConfirm={handleBlockThreadConfirm}
159
161
  onCancel={handleBlockThreadCancel}>
160
162
  { getMessage('page.details.actions.thread.block.confirmation.description') }
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  import React, { useState } from 'react';
8
- import PropTypes from 'prop-types';
9
8
  import { useMutation, useQueryClient } from 'react-query';
10
9
  import { isEmpty, orderBy } from 'lodash';
11
10
  import { Badge } from '@strapi/design-system/Badge';
@@ -14,18 +13,20 @@ import { Button } from '@strapi/design-system/Button';
14
13
  import { Divider } from '@strapi/design-system/Divider';
15
14
  import { Flex } from '@strapi/design-system/Flex';
16
15
  import { Typography } from '@strapi/design-system/Typography';
17
- import { ArrowRight, Lock } from '@strapi/icons';
18
16
  import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
19
17
  import { getMessage, handleAPIError } from '../../utils';
20
18
  import { blockItem, blockItemThread, resolveReport } from '../../pages/Details/utils/api';
21
19
  import { DiscussionThreadItemWarningsWrapper } from './styles';
22
20
  import ReportsReviewModal from '../ReportsReviewModal';
23
21
  import ReportsReviewTable from '../ReportsReviewTable';
22
+ import pluginId from '../../pluginId';
23
+ import { LockIcon, ReviewIcon } from '../icons';
24
24
 
25
25
  const DiscussionThreadItemWarnings = (item) => {
26
- const { id, reports } = item;
26
+ const { id, reports, gotThread } = item;
27
27
 
28
28
  const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
29
+ const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
29
30
 
30
31
  const toggleNotification = useNotification();
31
32
  const queryClient = useQueryClient();
@@ -37,7 +38,7 @@ const DiscussionThreadItemWarnings = (item) => {
37
38
  }
38
39
  toggleNotification({
39
40
  type: 'success',
40
- message,
41
+ message: `${pluginId}.${message}`,
41
42
  });
42
43
  stateAction(false);
43
44
  unlockApp();
@@ -82,6 +83,8 @@ const DiscussionThreadItemWarnings = (item) => {
82
83
  setReportsReviewVisible(false);
83
84
  };
84
85
 
86
+ const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
87
+
85
88
  const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
86
89
  const openReports = reports?.filter(_ => !_.resolved);
87
90
 
@@ -97,7 +100,7 @@ const DiscussionThreadItemWarnings = (item) => {
97
100
  <Typography textColor="warning600" fontWeight="bold">{getMessage(`page.details.panel.discussion.warnings.reports.description`, 'abuse reports')}</Typography>
98
101
  </Box>
99
102
  </Box>
100
- <Button onClick={handleReportsReviewClick} variant="ghost" endIcon={<ArrowRight />}>
103
+ <Button onClick={handleReportsReviewClick} variant="ghost" endIcon={<ReviewIcon />}>
101
104
  { getMessage(`page.details.actions.comment.report.review`, 'review') }
102
105
  </Button>
103
106
  </DiscussionThreadItemWarningsWrapper>
@@ -106,12 +109,12 @@ const DiscussionThreadItemWarnings = (item) => {
106
109
  isActionAsync={isLoading}
107
110
  onClose={handleReportsReviewClose}
108
111
  startActions={<>
109
- <Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<Lock />}>
112
+ <Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
110
113
  { getMessage(`page.details.actions.comment.block`, 'Block comment') }
111
114
  </Button>
112
- <Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<Lock />}>
115
+ { gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
113
116
  { getMessage(`page.details.actions.thread.block`, 'Block thread') }
114
- </Button>
117
+ </Button>) }
115
118
  </>}
116
119
  item={item}>
117
120
  <Typography variant="sigma" textColor="neutral600" id="reports-list">
@@ -124,6 +127,7 @@ const DiscussionThreadItemWarnings = (item) => {
124
127
  commentId={item.id}
125
128
  items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
126
129
  mutation={resolveReportMutation}
130
+ onBlockButtonsStateChange={handleBlockButtonsStateChange}
127
131
  />
128
132
  </ReportsReviewModal>
129
133
  </>);
@@ -13,7 +13,6 @@ import DiscussionThreadItem from '../DiscussionThreadItem';
13
13
 
14
14
  const ReportsReviewModal = ({
15
15
  isVisible = false,
16
- isActionAsync = false,
17
16
  item,
18
17
  children,
19
18
  onClose,
@@ -34,7 +33,6 @@ const ReportsReviewModal = ({
34
33
 
35
34
  ReportsReviewModal.propTypes = {
36
35
  isVisible: PropTypes.bool,
37
- isActionAsync: PropTypes.bool,
38
36
  startActions: PropTypes.array,
39
37
  endActions: PropTypes.array,
40
38
  item: PropTypes.object.isRequired,
@@ -23,7 +23,8 @@ import { REPORT_REASON, REPORT_STATUS } from '../../utils/constants';
23
23
  const ReportsReviewTable = ({
24
24
  commentId,
25
25
  items,
26
- mutation
26
+ mutation,
27
+ onBlockButtonsStateChange,
27
28
  }) => {
28
29
 
29
30
  const [storedItems, setStoredItems] = useState([]);
@@ -65,10 +66,12 @@ const ReportsReviewTable = ({
65
66
  });
66
67
  console.log(item);
67
68
  if (item) {
68
- setStoredItems(storedItems.map(_ => ({
69
- ..._,
70
- resolved: reportId === _.id ? true : _.resolved,
71
- })));
69
+ const updatedItems = storedItems.map(_ => ({
70
+ ..._,
71
+ resolved: reportId === _.id ? true : _.resolved,
72
+ }));
73
+ setStoredItems(updatedItems);
74
+ onBlockButtonsStateChange(updatedItems.filter(_ => !_.resolved).length === 0);
72
75
  }
73
76
  }
74
77
 
@@ -137,6 +140,7 @@ ReportsReviewTable.propTypes = {
137
140
  commentId: PropTypes.string.isRequired,
138
141
  items: PropTypes.array.isRequired,
139
142
  mutation: PropTypes.func.isRequired,
143
+ onBlockButtonsStateChange: PropTypes.func.isRequired,
140
144
  };
141
145
 
142
146
  export default ReportsReviewTable;
@@ -0,0 +1,6 @@
1
+ import CommentsIcon from './comments';
2
+ import ReviewIcon from './review';
3
+ import LockIcon from './lock';
4
+ import UnlockIcon from './unlock';
5
+
6
+ export { CommentsIcon, ReviewIcon, LockIcon, UnlockIcon };
@@ -0,0 +1,22 @@
1
+
2
+ import React from 'react';
3
+
4
+ const initSize = 330;
5
+
6
+ const LockIcon = ({ width, height, size = 16 }) =>
7
+ <svg
8
+ width={width || size}
9
+ height={height || size}
10
+ viewBox={`0 0 ${width || size} ${height || size}`}
11
+ style={{ width: `${width || size}px`, height: `${height || size}px`, }}
12
+ xmlns="http://www.w3.org/2000/svg">
13
+ <g style={ { transform: `scale(${(width || size)/initSize})` } }>
14
+ <path d="M165,330c63.411,0,115-51.589,115-115c0-29.771-11.373-56.936-30-77.379V85c0-46.869-38.131-85-85-85
15
+ S80.001,38.131,80.001,85v52.619C61.373,158.064,50,185.229,50,215C50,278.411,101.589,330,165,330z M180,219.986V240
16
+ c0,8.284-6.716,15-15,15s-15-6.716-15-15v-20.014c-6.068-4.565-10-11.824-10-19.986c0-13.785,11.215-25,25-25s25,11.215,25,25
17
+ C190,208.162,186.068,215.421,180,219.986z M110.001,85c0-30.327,24.673-55,54.999-55c30.327,0,55,24.673,55,55v29.029
18
+ C203.652,105.088,184.91,100,165,100c-19.909,0-38.651,5.088-54.999,14.028V85z"/>
19
+ </g>
20
+ </svg>
21
+
22
+ export default LockIcon;
@@ -0,0 +1,18 @@
1
+
2
+ import React from 'react';
3
+
4
+ const initSize = 24;
5
+
6
+ const ReviewIcon = ({ width, height, size = 16 }) =>
7
+ <svg
8
+ width={width || size}
9
+ height={height || size}
10
+ viewBox={`0 0 ${width || size} ${height || size}`}
11
+ style={{ width: `${width || size}px`, height: `${height || size}px`, }}
12
+ xmlns="http://www.w3.org/2000/svg">
13
+ <g style={ {transform: `scale(${(width || size)/initSize})` } }>
14
+ <path strokeWidth="1" d="M5.5,21 C7.98528137,21 10,18.9852814 10,16.5 C10,14.0147186 7.98528137,12 5.5,12 C3.01471863,12 1,14.0147186 1,16.5 C1,18.9852814 3.01471863,21 5.5,21 Z M1,16 L1,7 L1,6.5 C1,4.01471863 3.01471863,2 5.5,2 L6,2 M23,16 L23,7 L23,6.5 C23,4.01471863 20.9852814,2 18.5,2 L18,2 M18.5,21 C20.9852814,21 23,18.9852814 23,16.5 C23,14.0147186 20.9852814,12 18.5,12 C16.0147186,12 14,14.0147186 14,16.5 C14,18.9852814 16.0147186,21 18.5,21 Z M10,17 C10,17 10,15 12,15 C14,15 14,17 14,17"/>
15
+ </g>
16
+ </svg>
17
+
18
+ export default ReviewIcon;
@@ -0,0 +1,20 @@
1
+
2
+ import React from 'react';
3
+
4
+ const initSize = 330;
5
+
6
+ const UnlockIcon = ({ width, height, size = 16 }) =>
7
+ <svg
8
+ width={width || size}
9
+ height={height || size}
10
+ viewBox={`0 0 ${width || size} ${height || size}`}
11
+ style={{ width: `${width || size}px`, height: `${height || size}px`, }}
12
+ xmlns="http://www.w3.org/2000/svg">
13
+ <g style={ { transform: `scale(${(width || size)/initSize})` } }>
14
+ <path d="M15,160c8.283,0,15-6.716,15-15V85c0-30.327,24.672-55,55-55c30.326,0,55,24.673,55,55v42.893
15
+ c-24.479,21.105-40,52.327-40,87.107c0,63.411,51.588,115,115,115c63.41,0,115-51.589,115-115s-51.59-115-115-115
16
+ c-15.961,0-31.172,3.271-45,9.174V85c0-46.869-38.131-85-85-85S0,38.131,0,85v60C0,153.284,6.715,160,15,160z"/>
17
+ </g>
18
+ </svg>
19
+
20
+ export default UnlockIcon;
@@ -5,7 +5,9 @@
5
5
  *
6
6
  */
7
7
 
8
- import React, { useReducer } from 'react';
8
+ import React from 'react';
9
+ import { connect } from 'react-redux';
10
+ import { bindActionCreators, compose } from 'redux';
9
11
  import { Switch, Route } from 'react-router-dom';
10
12
  import { useQuery } from 'react-query';
11
13
  import {
@@ -18,23 +20,19 @@ import getUrl from '../../utils/getUrl';
18
20
  import Discover from '../Discover';
19
21
  import Details from '../Details';
20
22
  import { fetchConfig } from './utils/api';
21
- import initReducer from './reducer/init';
22
- import reducer, { initialState } from './reducer';
23
23
  import { setConfig } from './reducer/actions';
24
+ import makeAppView from './reducer/selectors';
24
25
 
25
- const App = () => {
26
+ const App = ({ setConfig }) => {
26
27
 
27
28
  const toggleNotification = useNotification();
28
- const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
29
- initReducer(initialState)
30
- );
31
29
 
32
30
  const { isLoading, isFetching } = useQuery(
33
31
  'get-config', () => fetchConfig(toggleNotification),
34
32
  {
35
33
  initialData: { },
36
34
  onSuccess: (response) => {
37
- dispatch(setConfig(response));
35
+ setConfig(response);
38
36
  }
39
37
  }
40
38
  );
@@ -57,4 +55,17 @@ const App = () => {
57
55
  );
58
56
  };
59
57
 
60
- export default App;
58
+ const mapStateToProps = makeAppView();
59
+
60
+ export function mapDispatchToProps(dispatch) {
61
+ return bindActionCreators(
62
+ { setConfig },
63
+ dispatch
64
+ );
65
+ }
66
+ const withConnect = connect(
67
+ mapStateToProps,
68
+ mapDispatchToProps
69
+ );
70
+
71
+ export default compose(withConnect)(App);
@@ -18,7 +18,6 @@ const reducer = (state = initialState, action) =>
18
18
  return draftState?.config;
19
19
  }
20
20
  default:
21
- console.log('draftState', draftState);
22
21
  return draftState;
23
22
  }
24
23
  });
@@ -26,7 +26,7 @@ const makeAppView = () =>
26
26
  );
27
27
 
28
28
  const selectConfig = state => {
29
- const { config } = state[REDUCER_NAME];
29
+ const { config } = state;
30
30
 
31
31
  return config;
32
32
  };
@@ -1,7 +1,4 @@
1
- import { stringify } from 'qs';
2
- import { isEmpty } from 'lodash';
3
-
4
- import { getApiURL, axiosInstance } from '../../../utils';
1
+ import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
5
2
 
6
3
  // eslint-disable-next-line import/prefer-default-export
7
4
  export const fetchConfig = async (toggleNotification) => {
@@ -10,11 +7,6 @@ export const fetchConfig = async (toggleNotification) => {
10
7
 
11
8
  return data;
12
9
  } catch (err) {
13
- toggleNotification({
14
- type: 'warning',
15
- message: { id: 'notification.error' },
16
- });
17
-
18
- throw new Error('error');
10
+ handleAPIError(err, toggleNotification);
19
11
  }
20
12
  };
@@ -83,7 +83,7 @@ const Details = () => {
83
83
  <HeaderLayout
84
84
  navigationAction={
85
85
  <Link startIcon={<ArrowLeft />} to={getUrl(`discover`)}>
86
- { getMessage('button.back.label', '', false) }
86
+ { getMessage('HeaderLayout.link.go-back', 'Back', false) }
87
87
  </Link>}
88
88
  title={ getMessage('page.details.header') }
89
89
  subtitle={ getMessage('page.details.header.description') } as="h2" />
@@ -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;
@@ -8,21 +8,15 @@ import React, { memo, useRef, useMemo } from 'react';
8
8
  import { connect } from 'react-redux';
9
9
  import { bindActionCreators, compose } from 'redux';
10
10
  import isEqual from 'react-fast-compare';
11
- import { useIntl } from 'react-intl';
12
11
  import { useQuery } from 'react-query';
13
12
  import { useHistory } from 'react-router-dom';
14
13
  import { isEmpty } from 'lodash';
15
- import { Badge } from '@strapi/design-system/Badge';
16
- import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
17
14
  import { Box } from '@strapi/design-system/Box';
18
- import { Flex } from '@strapi/design-system/Flex';
19
- import { IconButton } from '@strapi/design-system/IconButton';
20
15
  import { Layout, HeaderLayout, ActionLayout, ContentLayout } from '@strapi/design-system/Layout';
21
16
  import { Table, Thead, Tbody, Tr, Th, Td } from '@strapi/design-system/Table';
22
17
  import { Typography } from '@strapi/design-system/Typography';
23
18
  import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
24
19
  import { useNotifyAT } from '@strapi/design-system/LiveRegions';
25
- import { Eye } from '@strapi/icons';
26
20
 
27
21
  import {
28
22
  EmptyStateLayout,
@@ -40,12 +34,11 @@ import pluginPermissions from '../../permissions';
40
34
 
41
35
  import getUrl from '../../utils/getUrl';
42
36
  import Nav from '../../components/Nav';
43
- import { COMMENT_STATUS } from '../../utils/constants';
44
37
  import TablePagination from '../../components/TablePagination';
45
38
  import filtersSchema from './utils/filtersSchema';
46
39
  import TableFilters from '../../components/TableFilters';
47
- import appReducer from '../App/reducer';
48
- import makeAppView, { selectConfig } from '../App/reducer/selectors';
40
+ import makeAppView from '../App/reducer/selectors';
41
+ import DiscoverTableRow from './components/DiscoverTableRow';
49
42
 
50
43
  const Discover = ({ config }) => {
51
44
  useFocusWhenNavigate();
@@ -55,7 +48,6 @@ const Discover = ({ config }) => {
55
48
  const { trackUsage } = useTracking();
56
49
  const trackUsageRef = useRef(trackUsage);
57
50
  const toggleNotification = useNotification();
58
- const { formatDate } = useIntl();
59
51
  const [{ query: queryParams }] = useQueryParams();
60
52
  const _q = queryParams?._q || '';
61
53
 
@@ -69,7 +61,7 @@ const Discover = ({ config }) => {
69
61
  } = useRBAC(viewPermissions);
70
62
 
71
63
  const { isLoading: isLoadingForData, data: { result, pagination = {} }, isFetching } = useQuery(
72
- ['get-providers', queryParams],
64
+ ['get-data', queryParams],
73
65
  () => fetchData(queryParams, toggleNotification),
74
66
  {
75
67
  initialData: { },
@@ -94,22 +86,11 @@ const Discover = ({ config }) => {
94
86
  },
95
87
  };
96
88
 
97
- const renderStatus = ({ blocked, blockedThread }) => {
98
- const status = blocked || blockedThread ? COMMENT_STATUS.BLOCKED : COMMENT_STATUS.OPEN;
99
- let color = 'secondary';
100
- switch (status) {
101
- case COMMENT_STATUS.OPEN:
102
- color = 'success';
103
- break;
104
- case COMMENT_STATUS.BLOCKED:
105
- color = 'danger';
106
- };
107
- return (<Badge backgroundColor={`${color}100`} textColor={`${color}600`}>{ getMessage(`page.discover.table.header.status.${status}`, status) }</Badge>);
108
- };
89
+
109
90
 
110
91
  const emptyContent = _q ? 'search' : 'comments';
111
92
 
112
- const COL_COUNT = 7;
93
+ const COL_COUNT = 6;
113
94
 
114
95
  return <Box background="neutral100">
115
96
  <Layout sideNav={<Nav />}>
@@ -128,9 +109,6 @@ const Discover = ({ config }) => {
128
109
  <Table colCount={COL_COUNT} rowCount={result.length}>
129
110
  <Thead>
130
111
  <Tr>
131
- <Th>
132
- <BaseCheckbox aria-label="Select all entries" />
133
- </Th>
134
112
  <Th>
135
113
  <Typography variant="sigma">{ getMessage('page.discover.table.header.message') }</Typography>
136
114
  </Th>
@@ -152,31 +130,7 @@ const Discover = ({ config }) => {
152
130
  </Tr>
153
131
  </Thead>
154
132
  <Tbody>
155
- {result.map(entry => <Tr key={entry.id}>
156
- <Td>
157
- <BaseCheckbox aria-label={`Select ${entry.content}`} />
158
- </Td>
159
- <Td style={{ maxWidth: '40vw' }}>
160
- <Typography textColor="neutral800" ellipsis>{entry.content}</Typography>
161
- </Td>
162
- <Td>
163
- <Typography textColor="neutral800">{entry.threadOf?.id || '-'}</Typography>
164
- </Td>
165
- <Td>
166
- <Typography textColor="neutral800">{entry.related?.id}</Typography>
167
- </Td>
168
- <Td>
169
- <Typography textColor="neutral800">{ formatDate(entry.updatedAt || entry.createdAt, { dateStyle: 'long', timeStyle: 'short' }) }</Typography>
170
- </Td>
171
- <Td>
172
- <Typography textColor="neutral800">{ renderStatus(entry) }</Typography>
173
- </Td>
174
- <Td>
175
- <Flex direction="column" alignItems="flex-end">
176
- <IconButton onClick={() => handleClickDisplay(entry.id)} label={ getMessage('page.discover.table.action.display') } icon={<Eye />} />
177
- </Flex>
178
- </Td>
179
- </Tr>)}
133
+ {result.map(entry => (<DiscoverTableRow config={config} item={entry} onClick={() => handleClickDisplay(entry.id)}/>))}
180
134
  </Tbody>
181
135
  </Table>
182
136
  <TablePagination pagination={{ pageCount: pagination?.pageCount || 1 }} />
@@ -1,7 +1,6 @@
1
1
  import { stringify } from 'qs';
2
- import { isEmpty } from 'lodash';
3
2
 
4
- import { getApiURL, axiosInstance } from '../../../utils';
3
+ import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
5
4
 
6
5
  // eslint-disable-next-line import/prefer-default-export
7
6
  export const fetchData = async (queryParams, toggleNotification) => {
@@ -10,11 +9,6 @@ export const fetchData = async (queryParams, toggleNotification) => {
10
9
 
11
10
  return data;
12
11
  } catch (err) {
13
- toggleNotification({
14
- type: 'warning',
15
- message: { id: 'notification.error' },
16
- });
17
-
18
- throw new Error('error');
12
+ handleAPIError(err, toggleNotification);
19
13
  }
20
14
  };
@@ -17,13 +17,14 @@
17
17
  "page.discover.table.header.status": "Status",
18
18
  "page.discover.table.header.status.OPEN": "OPEN",
19
19
  "page.discover.table.header.status.BLOCKED": "BLOCKED",
20
- "page.discover.table.header.status.TO_REVIEW": "TO REVIEW",
20
+ "page.discover.table.header.status.TO_REVIEW": "TO REVIEW ({count, number})",
21
21
  "page.discover.table.header.actions": "Status",
22
22
  "page.discover.table.action.display": "Display",
23
23
  "page.discover.table.action.reports": "Reports",
24
24
  "page.discover.table.empty": "You don't have any comments yet.",
25
25
  "page.discover.table.empty.search": "No comments match the search.",
26
26
  "page.discover.table.filters": "Filtering",
27
+ "page.discover.table.reports.review": "Review reports",
27
28
  "page.details.header": "Discussion panel",
28
29
  "page.details.header.description": "Drilldown and manage discussion related to the entity",
29
30
  "page.details.panel.discussion": "Discussion",
@@ -1,14 +1,21 @@
1
1
 
2
2
  import { useIntl } from 'react-intl';
3
+ import { isString } from 'lodash';
3
4
 
4
5
  import pluginId from '../pluginId';
5
6
 
6
- const getMessage = (id, defaultMessage, inPluginScope = true) => {
7
+ const getMessage = (input, defaultMessage, inPluginScope = true) => {
7
8
  const { formatMessage } = useIntl();
9
+ let formattedId = ''
10
+ if (isString(input)) {
11
+ formattedId = input;
12
+ } else {
13
+ formattedId = input?.id;
14
+ }
8
15
  return formatMessage({
9
- id: `${inPluginScope ? pluginId : 'app.component'}.${id}`,
16
+ id: `${inPluginScope ? pluginId : 'app.components'}.${formattedId}`,
10
17
  defaultMessage,
11
- })
18
+ }, input?.props || undefined)
12
19
  };
13
20
 
14
21
  export default getMessage;
@@ -2,7 +2,7 @@
2
2
  const handleAPIError = (err = null, toggleNotification = null) => {
3
3
  toggleNotification({
4
4
  type: 'warning',
5
- message: { id: 'notification.error' },
5
+ message: 'app.components.notification.error',
6
6
  });
7
7
 
8
8
  if (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-comments",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.3",
4
4
  "description": "Strapi - Comments plugin",
5
5
  "strapi": {
6
6
  "name": "comments",
@@ -16,8 +16,13 @@ module.exports = {
16
16
  try {
17
17
  return this.getService('common')
18
18
  .findAllFlat({
19
- ...query,
20
- related: relation,
19
+ query: {
20
+ ...query,
21
+ related: relation,
22
+ },
23
+ populate: {
24
+ threadOf: true,
25
+ },
21
26
  });
22
27
  } catch (e) {
23
28
  throwError(ctx, e);
@@ -34,6 +39,9 @@ module.exports = {
34
39
  ...query,
35
40
  related: relation,
36
41
  },
42
+ populate: {
43
+ threadOf: true,
44
+ },
37
45
  dropBlockedThreads: true,
38
46
  });
39
47
  } catch (e) {
@@ -55,7 +55,17 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
55
55
  }
56
56
 
57
57
  const entities = await strapi.db.query(getModelUid('comment'))
58
- .findMany(params, ['authorUser', 'reports']);
58
+ .findMany({
59
+ ...params,
60
+ populate: {
61
+ authorUser: true,
62
+ reports: {
63
+ where: {
64
+ resolved: false
65
+ },
66
+ },
67
+ },
68
+ });
59
69
  const total = await strapi.db.query(getModelUid('comment'))
60
70
  .count({
61
71
  where: params.where,
@@ -78,11 +88,27 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
78
88
 
79
89
  // Find single comment
80
90
  async findOneAndThread(id) {
91
+ const reportsPopulation = {
92
+ reports: {
93
+ where: {
94
+ resolved: false,
95
+ },
96
+ }
97
+ };
98
+
81
99
  const entity = await strapi.db.query(getModelUid('comment')).findOne({
82
100
  where: {
83
101
  id
84
102
  },
85
- populate: ['threadOf', 'threadOf.reports', 'authorUser', 'reports']
103
+ populate: {
104
+ authorUser: true,
105
+ threadOf: {
106
+ populate: {
107
+ ...reportsPopulation
108
+ },
109
+ },
110
+ ...reportsPopulation,
111
+ },
86
112
  });
87
113
 
88
114
  if (!entity){
@@ -109,19 +135,22 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
109
135
  threadOf: levelThreadId,
110
136
  related: entity.related,
111
137
  },
138
+ populate: {
139
+ ...reportsPopulation,
140
+ },
112
141
  startingFromId: levelThreadId,
113
142
  isAdmin: true
114
143
  }, false);
115
144
 
116
- const selectedEntity = filterOurResolvedReports(this.getCommonService().sanitizeCommentEntity(entity));
145
+ const selectedEntity = this.getCommonService().sanitizeCommentEntity(entity);
117
146
 
118
147
  return {
119
148
  entity: relatedEntity,
120
149
  selected: {
121
150
  ...selectedEntity,
122
- threadOf: selectedEntity.threadOf ? filterOurResolvedReports(selectedEntity.threadOf) : null,
151
+ threadOf: selectedEntity.threadOf ? selectedEntity.threadOf : null,
123
152
  },
124
- level: entitiesOnSameLevel.map(_ => filterOurResolvedReports(_))
153
+ level: entitiesOnSameLevel,
125
154
  };
126
155
  },
127
156
 
@@ -160,7 +160,7 @@ module.exports = ({ strapi }) => ({
160
160
  try {
161
161
  await this.sendAbuseReportEmail(entity.reason, entity.content); // Could also add some info about relation
162
162
  } catch (err) {
163
- throw err;
163
+ return entity;
164
164
  }
165
165
  } else {
166
166
  throw new PluginError(500, 'Report cannot be created');
@@ -63,7 +63,7 @@ module.exports = ({ strapi }) => ({
63
63
  },
64
64
 
65
65
  // Find comments in the flat structure
66
- async findAllFlat(query, isAdmin = false, relatedEntity = null) {
66
+ async findAllFlat({ query, populate = {} }, isAdmin = false, relatedEntity = null) {
67
67
  const criteria = {
68
68
  ...query,
69
69
  $or: [{ removed: { $null: true } }, { removed: false }],
@@ -73,14 +73,19 @@ module.exports = ({ strapi }) => ({
73
73
  delete criteria.$or;
74
74
  }
75
75
 
76
- const populate = { authorUser: true, reports: true };
76
+ const defaultPopulate = {
77
+ authorUser: true,
78
+ };
77
79
 
78
80
  const entries = await strapi.db.query(getModelUid('comment'))
79
81
  .findMany({
80
82
  where: {
81
83
  ...criteria,
82
84
  },
83
- populate,
85
+ populate: {
86
+ ...defaultPopulate,
87
+ ...populate,
88
+ },
84
89
  });
85
90
 
86
91
  const entriesWithThreads = await Promise.all(entries.map(async _ => {
@@ -97,12 +102,12 @@ module.exports = ({ strapi }) => ({
97
102
  const result = entries
98
103
  .map(_ => {
99
104
  const threadedItem = entriesWithThreads.find(item => item.id === _.id);
100
- return filterOurResolvedReports(this.sanitizeCommentEntity({
105
+ return this.sanitizeCommentEntity({
101
106
  ..._,
102
107
  threadOf: query.threadOf || _.threadOf || null,
103
108
  gotThread: (threadedItem?.itemsInTread || 0) > 0,
104
109
  threadFirstItemId: threadedItem?.firstThreadItemId,
105
- }))
110
+ });
106
111
  });
107
112
 
108
113
  if (relatedEntities.filter(_ => _).length > 0) {
@@ -114,11 +119,12 @@ module.exports = ({ strapi }) => ({
114
119
  // Find comments and create relations tree structure
115
120
  async findAllInHierarchy ({
116
121
  query,
122
+ populate = {},
117
123
  startingFromId = null,
118
124
  dropBlockedThreads = false,
119
125
  isAdmin = false,
120
126
  }, relatedEntity) {
121
- const entities = await this.findAllFlat(query, isAdmin, relatedEntity);
127
+ const entities = await this.findAllFlat({ query, populate }, isAdmin, relatedEntity);
122
128
  return buildNestedStructure(entities, startingFromId, 'threadOf', dropBlockedThreads, false);
123
129
  },
124
130