strapi-plugin-comments 2.0.0-rc.2 → 2.0.0

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 (36) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/README.md +20 -4
  3. package/__mocks__/initSetup.js +53 -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/pages/Details/components/DetailsEntity/index.js +40 -24
  20. package/admin/src/pages/Details/components/DetailsFilters/index.js +8 -10
  21. package/admin/src/pages/Details/index.js +9 -15
  22. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +28 -65
  23. package/admin/src/translations/en.json +13 -15
  24. package/admin/src/utils/resolveCommentStatus.js +4 -4
  25. package/package.json +7 -5
  26. package/server/controllers/utils/__tests__/functions.test.js +2 -2
  27. package/server/graphql/types/comment-nested.js +1 -0
  28. package/server/services/__tests__/common.test.js +165 -0
  29. package/server/services/client.js +2 -3
  30. package/server/services/common.js +19 -2
  31. package/server/services/utils/__tests__/functions.test.js +9 -73
  32. package/server/services/utils/functions.js +1 -17
  33. package/server/utils/__tests__/functions.test.js +17 -0
  34. package/admin/src/components/DiscussionThreadItemMeta/index.js +0 -42
  35. package/admin/src/components/DiscussionThreadItemWarnings/index.js +0 -147
  36. package/admin/src/components/DiscussionThreadItemWarnings/styles.js +0 -7
@@ -6,7 +6,7 @@ jobs:
6
6
  environment:
7
7
  CODECOV_TOKEN: 02e2d4e8-88c4-4e5d-99da-a9a2888634c6
8
8
  docker:
9
- - image: circleci/node:12.13.0
9
+ - image: circleci/node:14.17.3
10
10
  working_directory: ~/repo
11
11
  steps:
12
12
  - checkout
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </div>
4
4
  <div align="center">
5
5
  <h1>Strapi v4 - Comments plugin</h1>
6
- <p>Powerfull Strapi based comments moderation tool for you and your users</p>
6
+ <p>Powerful Strapi based comments moderation tool for you and your users</p>
7
7
  <a href="https://www.npmjs.org/package/strapi-plugin-comments">
8
8
  <img alt="GitHub package.json version" src="https://img.shields.io/github/package-json/v/VirtusLab-Open-Source/strapi-plugin-comments?label=npm&logo=npm">
9
9
  </a>
@@ -16,6 +16,12 @@
16
16
  <a href="https://codecov.io/gh/VirtusLab-Open-Source/strapi-plugin-comments">
17
17
  <img src="https://codecov.io/gh/VirtusLab-Open-Source/strapi-plugin-comments/coverage.svg?branch=master" alt="codecov.io" />
18
18
  </a>
19
+ <a href="https://sharing.clickup.com/tl/xhcmx-83/strapiv-4-comments-roadmap">
20
+ <img src="https://img.shields.io/website?down_message=roadmap&label=product&up_message=roadmap&url=https%3A%2F%2Fsharing.clickup.com%2Ftl%2Fxhcmx-83%2Fstrapiv-4-comments-roadmap" />
21
+ </a>
22
+ <a href="https://sharing.clickup.com/b/xhcmx-63/strapiv-4-comments-board">
23
+ <img src="https://img.shields.io/website?down_message=board&label=product&up_color=blue&up_message=board&url=https%3A%2F%2Fsharing.clickup.com%2Fb%2Fxhcmx-63%2Fstrapiv-4-comments-board" />
24
+ </a>
19
25
  </div>
20
26
 
21
27
  ---
@@ -62,7 +68,7 @@ or just run Strapi in the development mode with `--watch-admin` option:
62
68
  yarn develop --watch-admin
63
69
  ```
64
70
 
65
- The **Comments** plugin should appear in the navigation menu of Strapi after you run app again.
71
+ The **Comments** plugin should appear in the **Plugins** section of Strapi sidebar after you run app again.
66
72
 
67
73
  Enjoy 🎉
68
74
 
@@ -78,7 +84,7 @@ In our minimum support we're following [official Node.js releases timelines](htt
78
84
 
79
85
  **Supported Strapi versions**:
80
86
 
81
- - Strapi v4.0.6 (recently tested)
87
+ - Strapi v4.0.7 (recently tested)
82
88
  - Strapi v4.x
83
89
 
84
90
  > This plugin is designed for **Strapi v4** and is not working with v3.x. To get version for **Strapi v3** install version [v1.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-comments/tree/strapi-v3).
@@ -705,15 +711,25 @@ Live example of plugin usage can be found in the [VirtusLab Strapi Examples](htt
705
711
 
706
712
  ## 🤝 Contributing
707
713
 
714
+ <div>
715
+ <a href="https://sharing.clickup.com/tl/xhcmx-83/strapiv-4-comments-roadmap">
716
+ <img src="https://img.shields.io/website?down_message=roadmap&label=product&up_message=roadmap&url=https%3A%2F%2Fsharing.clickup.com%2Ftl%2Fxhcmx-83%2Fstrapiv-4-comments-roadmap" />
717
+ </a>
718
+ <a href="https://sharing.clickup.com/b/xhcmx-63/strapiv-4-comments-board">
719
+ <img src="https://img.shields.io/website?down_message=board&label=product&up_color=blue&up_message=board&url=https%3A%2F%2Fsharing.clickup.com%2Fb%2Fxhcmx-63%2Fstrapiv-4-comments-board" />
720
+ </a>
721
+ </div>
722
+
708
723
  Feel free to fork and make a Pull Request to this plugin project. All the input is warmly welcome!
709
724
 
710
725
  ## 👨‍💻 Community support
711
726
 
712
727
  For general help using Strapi, please refer to [the official Strapi documentation](https://strapi.io/documentation/). For additional help, you can use one of these channels to ask a question:
713
728
 
714
- - [Discord](https://discord.strapi.io/) We're present on official Strapi Discord workspace. Look for @cyp3r and DM.
729
+ - [Discord](https://discord.strapi.io/) We're present on official Strapi Discord workspace. Find us by `[VirtusLab]` prefix and DM.
715
730
  - [Slack - VirtusLab Open Source](https://virtuslab-oss.slack.com) We're present on a public channel #strapi-molecules
716
731
  - [GitHub](https://github.com/VirtusLab-Open-Source/strapi-plugin-comments/issues) (Bug reports, Contributions, Questions and Discussions)
732
+ - [E-mail](mailto:strapi@virtuslab.com) - we will respond back as soon as possible
717
733
 
718
734
  ## 📝 License
719
735
 
@@ -0,0 +1,53 @@
1
+ const { get, set } = require('lodash');
2
+
3
+ module.exports = (config = {}) => {
4
+ const mock = {
5
+ getRef: function() {
6
+ return this;
7
+ },
8
+ plugin: function(name) {
9
+ return this.plugins[name];
10
+ },
11
+ plugins: {
12
+ comments: {
13
+ service: function(name) {
14
+ return this.services[name]({ strapi: mock.getRef() });
15
+ },
16
+ package: require('../package.json'),
17
+ services: {
18
+ common: require('../server/services/common'),
19
+ client: require('../server/services/client'),
20
+ admin: require('../server/services/admin'),
21
+ },
22
+ contentTypes: {
23
+ comment: {
24
+ ...require('../content-types/comment'),
25
+ uid: 'plugins::comments.comment',
26
+ },
27
+ 'comment-report': {
28
+ ...require('../content-types/report'),
29
+ uid: 'plugins::comments.comment-report'
30
+ },
31
+ }
32
+ }
33
+ },
34
+ config: {
35
+ get: function(prop = '') {
36
+ return get(this.plugins, prop.replace('plugin.', ''));
37
+ },
38
+ set: function(prop = '', value) {
39
+ return set(this.plugins, prop.replace('plugin.', ''), value);
40
+ },
41
+ plugins: {
42
+ comments: {
43
+ approvalFlow: ['api::blog-post.blog-post'],
44
+ entryLabel: {
45
+ 'api::blog-post.blog-post': ['alternative_subject'],
46
+ },
47
+ ...config
48
+ }
49
+ }
50
+ }
51
+ };
52
+ return mock;
53
+ };
@@ -0,0 +1,10 @@
1
+
2
+ import styled from 'styled-components';
3
+ import { Button } from '@strapi/design-system/Button';
4
+
5
+ export const ActionButton = styled(Button)`
6
+ padding: 7px 16px;
7
+ margin-left: ${({ isSingle }) => isSingle ? '0' : '.5rem'};
8
+
9
+ white-space: nowrap;
10
+ `;
@@ -8,7 +8,6 @@ import React from 'react';
8
8
  import PropTypes from 'prop-types';
9
9
  import { isNil } from 'lodash';
10
10
  import { Box } from '@strapi/design-system/Box';
11
- import { Divider } from '@strapi/design-system/Divider';
12
11
  import { Flex } from '@strapi/design-system/Flex';
13
12
  import { Link } from '@strapi/design-system/Link';
14
13
  import { Typography } from '@strapi/design-system/Typography';
@@ -22,17 +21,14 @@ const DiscussionThread = ({ level = [], selected = {}, allowedActions, isReloadi
22
21
  const rootThread = selected?.threadOf;
23
22
  return (<LoadingIndicatorContainer as={Box} padding={4}>
24
23
  { isReloading && <LoadingIndicatorOverlay />}
25
- <Flex as={Box} direction="row" justifyContent="space-between">
26
- <Typography variant="sigma" textColor="neutral600" id="entity-details">
24
+ <Flex as={Box} direction="row" justifyContent="space-between" marginBottom={2}>
25
+ <Typography variant="delta" textColor="neutral800" id="discussion-thread">
27
26
  { getMessage('page.details.panel.discussion', 'Discussion') }
28
27
  </Typography>
29
28
  { rootThread && (<Link to={getUrl(`discover/${rootThread.id}`)} startIcon={<ArrowUp />}>
30
29
  { getMessage('page.details.panel.discussion.nav.back') }
31
30
  </Link>) }
32
31
  </Flex>
33
- <Box paddingTop={2} paddingBottom={6}>
34
- <Divider />
35
- </Box>
36
32
  <Flex as="ul" direction="column" alignItems="flex-start">
37
33
  { rootThread && (<DiscussionThreadItem {...rootThread} allowedActions={allowedActions} isThreadAuthor root pinned />) }
38
34
  { level.map(item => {
@@ -1,90 +1,47 @@
1
1
  /**
2
2
  *
3
- * Entity Details
3
+ * Discussion thread - Item
4
4
  *
5
5
  */
6
6
 
7
7
  import React from 'react';
8
8
  import PropTypes from 'prop-types';
9
- import { Avatar, Initials } from '@strapi/design-system/Avatar';
10
9
  import { Box } from '@strapi/design-system/Box';
11
- import { Divider } from '@strapi/design-system/Divider';
12
10
  import { Flex } from '@strapi/design-system/Flex';
13
- import { Link } from '@strapi/design-system/Link';
14
11
  import { Typography } from '@strapi/design-system/Typography';
15
- import { ArrowRight } from '@strapi/icons';
16
- import DiscussionThreadItemMeta from '../DiscussionThreadItemMeta';
17
- import { AvatarWrapper, DiscussionThreadItemBox, DiscussionThreadItemBoxContent, DiscussionThreadItemContent, DiscussionThreadItemLeft, DiscussionThreadItemLinkBox, DiscussionThreadItemRight, DiscussionThreadItemRoot } from './styles';
12
+ import DiscussionThreadItemFooter from '../DiscussionThreadItemFooter';
18
13
  import DiscussionThreadItemActions from '../DiscussionThreadItemActions';
19
- import { getMessage, getUrl, renderInitials } from '../../utils';
20
- import DiscussionThreadItemWarnings from '../DiscussionThreadItemWarnings';
14
+ import { DiscussionThreadItemContainer, DiscussionThreadFullsize, DiscussionThreadItemContent } from './styles';
21
15
 
22
16
  const DiscussionThreadItem = (props) => {
23
17
  const {
18
+ as = "li",
24
19
  isSelected,
25
- isThreadAuthor,
26
20
  content,
27
21
  root,
28
22
  preview,
29
- pinned,
30
- blocked,
31
- removed,
32
- blockedThread,
33
- gotThread,
34
- threadFirstItemId,
35
- author = {} } = props;
36
-
37
- let background = 'neutral100';
38
- let borderColor = 'neutral200';
39
- let dividerColor = 'neutral200';
40
- let StyledComponent = DiscussionThreadItemRight;
41
-
42
- if(isThreadAuthor || root){
43
- StyledComponent = DiscussionThreadItemLeft;
44
- borderColor = 'neutral100';
45
- dividerColor = 'neutral100';
46
- background = null;
47
- }
48
-
49
- if (isSelected) {
50
- background = 'secondary100'
51
- borderColor = 'secondary500';
52
- dividerColor = 'secondary200';
53
- }
54
-
55
- if (root) {
56
- StyledComponent = DiscussionThreadItemRoot;
57
- }
58
-
59
- const { name, avatar } = author;
60
- const isBlocked = blocked || blockedThread;
23
+ pinned } = props;
61
24
 
62
- return (<StyledComponent as="li">
63
- <DiscussionThreadItemContent as={Flex} direction="row">
64
- <AvatarWrapper>
65
- { avatar ? <Avatar src={avatar} alt={name} /> : <Initials>{ renderInitials(name) }</Initials> }
66
- </AvatarWrapper>
67
- <DiscussionThreadItemBox hasRadius shadow="filterShadow" borderColor={borderColor} background={background} padding={2}>
68
- <Flex as={Box} paddingBottom={2} direction="column">
69
- <DiscussionThreadItemBoxContent as={Flex} direction="row">
70
- <Box as={Flex} grow={1} alignItems="center">
71
- <Typography variant="omega">{ content }</Typography>
72
- </Box>
73
- { !preview && (<DiscussionThreadItemActions { ...props } root={root || pinned} />) }
74
- </DiscussionThreadItemBoxContent>
75
- { !(preview || isBlocked || removed) && (<DiscussionThreadItemWarnings { ...props } gotThread={gotThread || root} />) }
76
- </Flex>
77
- <Divider background={dividerColor} />
78
- <DiscussionThreadItemMeta { ...props } />
79
- </DiscussionThreadItemBox>
80
- </DiscussionThreadItemContent>
81
-
82
- { (gotThread && !(removed || preview || pinned)) && (<DiscussionThreadItemLinkBox direction="column" alignItems="flex-end">
83
- <Link to={getUrl(`discover/${threadFirstItemId}`)} endIcon={<ArrowRight />}>
84
- { getMessage('page.details.panel.discussion.nav.drilldown') }
85
- </Link>
86
- </DiscussionThreadItemLinkBox>) }
87
- </StyledComponent>);
25
+ return (<DiscussionThreadFullsize as={as} marginBottom={preview ? 4 : 0}>
26
+ <DiscussionThreadItemContainer
27
+ as={Flex}
28
+ direction="column"
29
+ alignItems="flex-start"
30
+ hasRadius
31
+ background={isSelected ? 'neutral100' : null}
32
+ paddingLeft={2} paddingRight={2} paddingTop={4} paddingBottom={4}>
33
+ <DiscussionThreadItemContent
34
+ as={Flex}
35
+ paddingBottom={2}
36
+ direction="row">
37
+ <Box as={Flex} grow={1} alignItems="center" marginTop="6px">
38
+ <Typography variant="omega" textColor="neutral800">{ content }</Typography>
39
+ </Box>
40
+ { !preview && (<DiscussionThreadItemActions { ...props } root={root || pinned} preview={preview} />) }
41
+ </DiscussionThreadItemContent>
42
+ <DiscussionThreadItemFooter { ...props } />
43
+ </DiscussionThreadItemContainer>
44
+ </DiscussionThreadFullsize>);
88
45
  };
89
46
 
90
47
  DiscussionThreadItem.propTypes = {
@@ -1,88 +1,13 @@
1
1
  import styled from 'styled-components';
2
2
  import { Box } from '@strapi/design-system/Box';
3
- import { Flex } from '@strapi/design-system/Flex';
4
3
 
5
- export const AvatarWrapper = styled(Box)``;
6
- export const InitialsWrapper = styled(Box)``;
7
-
8
- export const DiscussionThreadItemLinkBox = styled(Flex)`
4
+ export const DiscussionThreadFullsize = styled(Box)`
9
5
  width: 100%;
10
- margin-top: .5rem;
11
- margin-bottom: .5rem;
12
6
  `
13
-
14
- const DiscussionThreadItemStyled = styled(Box)`
15
- display: flex;
16
- flex-direction: column;
17
- width: 100%;
18
- margin-bottom: 1rem;
19
-
20
- ${AvatarWrapper},
21
- ${InitialsWrapper} {
22
- align-self: flex-start;
23
- margin-top: .4rem;
24
- }
25
- `;
26
-
27
- export const DiscussionThreadItemContent = styled(Box)`
28
- max-width: 45%;
29
- `;
30
-
31
- export const DiscussionThreadItemLeft = styled(DiscussionThreadItemStyled)`
7
+ export const DiscussionThreadItemContent = styled(DiscussionThreadFullsize)`
32
8
  align-items: flex-start;
33
- padding-left: 4rem;
34
- text-align: left;
35
-
36
- ${DiscussionThreadItemContent} {
37
- align-self: flex-start;
38
- }
39
-
40
- ${AvatarWrapper},
41
- ${InitialsWrapper} {
42
- margin-right: 1rem;
43
- }
44
9
  `;
45
10
 
46
- export const DiscussionThreadItemRight = styled(DiscussionThreadItemStyled)`
47
- align-items: flex-end;
48
- padding-right: 4rem;
49
- text-align: left;
50
-
51
- ${DiscussionThreadItemContent} {
52
- align-self: flex-end;
53
- flex-direction: row-reverse;
54
- }
55
-
56
- ${AvatarWrapper},
57
- ${InitialsWrapper} {
58
- margin-left: 1rem;
59
- }
60
-
61
- ${DiscussionThreadItemLinkBox} {
62
- margin-right: 3rem;
63
- }
64
- `;
65
-
66
- export const DiscussionThreadItemBox = styled(Box)`
67
-
11
+ export const DiscussionThreadItemContainer = styled(DiscussionThreadFullsize)`
12
+ align-items: flex-start;
68
13
  `;
69
-
70
- export const DiscussionThreadItemBoxContent = styled(Box)`
71
- width: 100%;
72
- align-items: stretch;
73
- `
74
-
75
- export const DiscussionThreadItemRoot = styled(DiscussionThreadItemLeft)`
76
- padding-left: 0;
77
- padding-right: 0;
78
-
79
-
80
- ${DiscussionThreadItemContent} {
81
- width: 100%;
82
- max-width: 100%;
83
- }
84
-
85
- ${DiscussionThreadItemBox} {
86
- flex-grow: 1;
87
- }
88
- `;
@@ -7,24 +7,42 @@
7
7
  import React, { useState } from 'react';
8
8
  import PropTypes from 'prop-types';
9
9
  import { useMutation, useQueryClient } from 'react-query';
10
+ import { useHistory } from 'react-router-dom';
10
11
  import { isNil, isEmpty } from 'lodash';
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
14
  import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
15
- import { getMessage, handleAPIError, resolveCommentStatus, resolveCommentStatusColor } from '../../utils';
16
- import { DiscussionThreadItemActionsBadge, DiscussionThreadItemActionsWrapper } from './styles';
15
+ import { Eye } from '@strapi/icons';
16
+ import { getMessage, getUrl, handleAPIError, resolveCommentStatus, resolveCommentStatusColor } from '../../utils';
17
+ import { DiscussionThreadItemActionsWrapper } from './styles';
17
18
  import ConfirmationDialog from '../ConfirmationDialog';
18
19
  import { blockItem, blockItemThread, unblockItem, unblockItemThread } from '../../pages/utils/api';
19
20
  import pluginId from '../../pluginId';
20
21
  import { LockIcon, UnlockIcon } from '../icons';
21
22
  import DiscussionThreadItemApprovalFlowActions from '../DiscussionThreadItemApprovalFlowActions';
22
-
23
- const DiscussionThreadItemActions = ({ id, blocked, removed, blockedThread, gotThread, pinned, reports = [], approvalStatus, root, allowedActions: { canModerate, canAccessReports, canReviewReports } }) => {
23
+ import StatusBadge from '../StatusBadge';
24
+ import { IconButtonGroupStyled } from '../IconButton/styles';
25
+ import { ActionButton } from '../ActionButton/styles';
26
+ import DiscussionThreadItemReviewAction from '../DiscussionThreadItemReviewAction';
27
+
28
+ const DiscussionThreadItemActions = ({ allowedActions: { canModerate, canAccessReports, canReviewReports }, ...item }) => {
29
+ const { id,
30
+ blocked,
31
+ removed,
32
+ blockedThread,
33
+ gotThread,
34
+ threadItemsCount,
35
+ threadFirstItemId,
36
+ pinned,
37
+ preview,
38
+ reports = [],
39
+ approvalStatus } = item;
24
40
 
25
41
  const [blockConfirmationVisible, setBlockConfirmationVisible] = useState(false);
26
42
  const [blockThreadConfirmationVisible, setBlockThreadConfirmationVisible] = useState(false);
43
+ const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(blockedThread);
27
44
 
45
+ const { push } = useHistory();
28
46
  const toggleNotification = useNotification();
29
47
  const queryClient = useQueryClient();
30
48
  const { lockApp, unlockApp } = useOverlayBlocker();
@@ -75,17 +93,20 @@ const DiscussionThreadItemActions = ({ id, blocked, removed, blockedThread, gotT
75
93
  const openReports = reports?.filter(_ => !_.resolved);
76
94
  const hasReports = !isEmpty(openReports);
77
95
  const reviewFlowEnabled = (canAccessReports || canReviewReports) && hasReports;
78
-
79
- const resolveStatusBadge = () => {
80
- const status = resolveCommentStatus({ removed, blocked, blockedThread, approvalStatus, reviewFlowEnabled: !isBlocked && reviewFlowEnabled });
81
- return {
82
- badgeVisible: isBlocked || gotApprovalFlow || removed,
83
- badgeColor: resolveCommentStatusColor(status),
84
- badgeLabel: status,
85
- };
86
- };
87
-
88
- const {badgeVisible, badgeColor, badgeLabel } = resolveStatusBadge();
96
+ const hasActiveThread = gotThread && !(removed || preview || pinned || blockedThread);
97
+ const isStatusBadgeVisible = isBlocked || reviewFlowEnabled;
98
+
99
+ const renderStatus = (props) => {
100
+ const status = resolveCommentStatus({ ...props, reviewFlowEnabled });
101
+ const color = resolveCommentStatusColor(status);
102
+
103
+ return (<StatusBadge backgroundColor={`${color}100`} textColor={`${color}700`} color={color}>{ getMessage({
104
+ id: `page.common.item.status.${status}`,
105
+ props: {
106
+ count: openReports.length
107
+ }
108
+ }, status) }</StatusBadge>);
109
+ };
89
110
 
90
111
  const handleBlockClick = () => setBlockConfirmationVisible(true);
91
112
  const handleBlockConfirm = async () => {
@@ -121,36 +142,57 @@ const DiscussionThreadItemActions = ({ id, blocked, removed, blockedThread, gotT
121
142
  }
122
143
  };
123
144
 
145
+ const handleDrilldownClick = () => {
146
+ if (hasActiveThread) {
147
+ push(getUrl(`discover/${threadFirstItemId}`));
148
+ }
149
+ };
150
+
151
+ const handleBlockActionClick = async (mutation, onCallback) => {
152
+ lockApp();
153
+ await mutation.mutateAsync(id);
154
+ onCallback();
155
+ };
156
+
157
+ const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
158
+
159
+ const anyGroupButtonsVisible = needsApproval || reviewFlowEnabled || !blockedThread;
160
+ const isLoading = unblockItemMutation.isLoading || blockItemMutation.isLoading || blockItemThreadMutation.isLoading || unblockItemThreadMutation.isLoading;
161
+
124
162
  if (removed || isRejected || !canModerate) {
125
163
  return (<DiscussionThreadItemActionsWrapper as={Flex} direction="row">
126
- <DiscussionThreadItemActionsBadge
127
- color={badgeColor}
128
- backgroundColor={`${badgeColor}100`}
129
- textColor={`${badgeColor}600`}>
130
- { getMessage({
131
- id: `page.common.item.status.${badgeLabel}`,
132
- props: { count: openReports.length },
133
- }, badgeLabel)}
134
- </DiscussionThreadItemActionsBadge>
164
+ { renderStatus(item) }
135
165
  </DiscussionThreadItemActionsWrapper>);
136
166
  }
137
167
 
138
168
  return (<>
139
169
  <DiscussionThreadItemActionsWrapper as={Flex} direction="row">
140
- { badgeVisible && (<DiscussionThreadItemActionsBadge
141
- color={badgeColor}
142
- backgroundColor={`${badgeColor}100`}
143
- textColor={`${badgeColor}600`}>
144
- { getMessage({
145
- id: `page.common.item.status.${badgeLabel}`,
146
- props: { count: openReports.length },
147
- }, badgeLabel)}
148
- </DiscussionThreadItemActionsBadge>)}
149
- { !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' } : {}} />)}
150
- { !blockedThread && blocked && (<IconButton onClick={handleUnblockClick} loading={unblockItemMutation.isLoading} icon={<UnlockIcon />} label={getMessage('page.details.actions.comment.unblock', 'Unblock')} style={(blocked && !blockedThread) ? { marginTop: '1px', marginRight: '.5rem' } : {}} />)}
151
- { (!blockedThread && (gotThread || pinned)) && (<Button onClick={handleBlockThreadClick} startIcon={<LockIcon />} loading={blockItemThreadMutation.isLoading} variant="danger">{ getMessage('page.details.actions.thread.block', 'Block thread') }</Button>) }
152
- { (blockedThread && (gotThread || pinned)) && (<Button onClick={handleUnblockThreadClick} startIcon={<UnlockIcon />} loading={unblockItemThreadMutation.isLoading} variant="success">{ getMessage('page.details.actions.thread.unblock', 'Unblock thread') }</Button>) }
153
- { needsApproval && (<DiscussionThreadItemApprovalFlowActions id={id} allowedActions={{ canModerate }} queryToInvalidate="get-details-data" />) }
170
+ { isStatusBadgeVisible && (renderStatus(item))}
171
+ { (!blockedThread && (gotThread || pinned)) && (<ActionButton onClick={handleBlockThreadClick} startIcon={<LockIcon />} loading={blockItemThreadMutation.isLoading} variant="danger">{ getMessage('page.details.actions.thread.block', 'Block thread') }</ActionButton>) }
172
+ { (blockedThread && (gotThread || pinned)) && (<ActionButton onClick={handleUnblockThreadClick} startIcon={<UnlockIcon />} loading={unblockItemThreadMutation.isLoading} variant="success">{ getMessage('page.details.actions.thread.unblock', 'Unblock thread') }</ActionButton>) }
173
+ { anyGroupButtonsVisible && (<IconButtonGroupStyled isSingle withMargin>
174
+ { !blockedThread && !(blocked || needsApproval) && (<IconButton onClick={handleBlockClick} loading={blockItemMutation.isLoading} icon={<LockIcon />} label={getMessage('page.details.actions.comment.block', 'Block')} />)}
175
+ { !blockedThread && blocked && (<IconButton onClick={handleUnblockClick} loading={unblockItemMutation.isLoading} icon={<UnlockIcon />} label={getMessage('page.details.actions.comment.unblock', 'Unblock')} />)}
176
+ { needsApproval && (<DiscussionThreadItemApprovalFlowActions id={id} allowedActions={{ canModerate }} queryToInvalidate="get-details-data" />) }
177
+ <DiscussionThreadItemReviewAction
178
+ item={item}
179
+ queryToInvalidate="get-details-data"
180
+ areBlockButtonsDisabled={blockButtonsDisabled}
181
+ isLoading={isLoading}
182
+ allowedActions={{ canModerate, canAccessReports, canReviewReports }}
183
+ blockItemMutation={blockItemMutation}
184
+ blockItemThreadMutation={blockItemThreadMutation}
185
+ onBlockButtonsStateChange={handleBlockButtonsStateChange}
186
+ onBlockActionClick={handleBlockActionClick}
187
+ />
188
+ </IconButtonGroupStyled>)}
189
+ { hasActiveThread && (<IconButtonGroupStyled isSingle withMargin>
190
+ <IconButton
191
+ onClick={handleDrilldownClick}
192
+ icon={<Eye />}
193
+ label={getMessage('page.details.panel.discussion.nav.drilldown', 'Drilldown thread')}
194
+ style={(blocked && !blockedThread) ? { marginTop: '1px', marginRight: '.5rem' } : {}} />
195
+ </IconButtonGroupStyled>) }
154
196
  </DiscussionThreadItemActionsWrapper>
155
197
  { !blocked && (<ConfirmationDialog
156
198
  isVisible={blockConfirmationVisible}
@@ -162,7 +204,7 @@ const DiscussionThreadItemActions = ({ id, blocked, removed, blockedThread, gotT
162
204
  onCancel={handleBlockCancel}>
163
205
  { getMessage('page.details.actions.comment.block.confirmation.description') }
164
206
  </ConfirmationDialog>) }
165
- { (!blockedThread && root) && (<ConfirmationDialog
207
+ { !blockedThread && (<ConfirmationDialog
166
208
  isVisible={blockThreadConfirmationVisible}
167
209
  isActionAsync={blockItemThreadMutation.isLoading}
168
210
  header={getMessage('page.details.actions.thread.block.confirmation.header')}
@@ -1,18 +1,7 @@
1
1
  import styled from 'styled-components';
2
2
  import { Box } from '@strapi/design-system/Box';
3
- import { Badge } from '@strapi/design-system/Badge';
4
- import { IconButtonGroup } from '@strapi/design-system/IconButton';
5
-
6
- export const DiscussionThreadItemActionsBadge = styled(Badge)`
7
- border: 1px ${ ({ theme, color }) => theme.colors[`${color}200`] } solid;
8
- `;
9
3
 
10
4
  export const DiscussionThreadItemActionsWrapper = styled(Box)`
11
5
  margin-left: 1rem;
12
- align-items: start;
13
-
14
- ${DiscussionThreadItemActionsBadge} {
15
- margin-top: .175rem;
16
- margin-right: .5rem;
17
- }
6
+ align-items: flex-start;
18
7
  `;
@@ -11,7 +11,6 @@ import { IconButton } from '@strapi/design-system/IconButton';
11
11
  import { Check, Cross } from '@strapi/icons';
12
12
  import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
13
13
  import { getMessage, handleAPIError } from '../../utils';
14
- import { DiscussionThreadItemActionsGroupWrapper } from './styles';
15
14
  import { approveItem, rejectItem } from '../../pages/utils/api';
16
15
  import pluginId from '../../pluginId';
17
16
 
@@ -60,10 +59,10 @@ const DiscussionThreadItemApprovalFlowActions = ({ id, allowedActions: { canMode
60
59
  };
61
60
 
62
61
  if (canModerate) {
63
- return (<DiscussionThreadItemActionsGroupWrapper>
62
+ return (<>
64
63
  <IconButton icon={<Check />} label={getMessage('page.details.actions.comment.approve', 'Approve')} onClick={handleApproveClick} />
65
64
  <IconButton icon={<Cross />} label={getMessage('page.details.actions.comment.reject', 'Reject')} onClick={handleRejectClick} />
66
- </DiscussionThreadItemActionsGroupWrapper>);
65
+ </>);
67
66
  }
68
67
  return null;
69
68
  };
@@ -0,0 +1,47 @@
1
+ /**
2
+ *
3
+ * Discussion thread - Item footer
4
+ *
5
+ */
6
+
7
+ import React from 'react';
8
+ import PropTypes from 'prop-types';
9
+ import { useIntl } from 'react-intl';
10
+ import { Avatar, Initials } from '@strapi/design-system/Avatar';
11
+ import { Box } from '@strapi/design-system/Box';
12
+ import { Typography } from '@strapi/design-system/Typography';
13
+ import { renderInitials } from '../../utils';
14
+ import { DiscussionThreadItemFooterMeta, DiscussionThreadItemFooterStyled } from './styles';
15
+
16
+ const DiscussionThreadItemFooter = ({ children, author, createdAt, updatedAt }) => {
17
+
18
+ const { formatDate } = useIntl();
19
+
20
+ const dateTime = formatDate(updatedAt || createdAt, { dateStyle: 'medium', timeStyle: 'short' });
21
+ const { name, avatar } = author;
22
+
23
+
24
+ return (<DiscussionThreadItemFooterStyled as={Box} paddingTop={2} direction="row">
25
+ <DiscussionThreadItemFooterMeta size={3} horizontal>
26
+ { avatar ? <Avatar src={avatar} alt={name} /> : <Initials>{ renderInitials(name) }</Initials> }
27
+ <Typography variant="pi" fontWeight="bold" textColor="neutral800">{ name }</Typography>
28
+ <Typography variant="pi" textColor="neutral600">{ dateTime }</Typography>
29
+ </DiscussionThreadItemFooterMeta>
30
+ { children }
31
+ </DiscussionThreadItemFooterStyled>);
32
+ };
33
+
34
+ DiscussionThreadItemFooter.propTypes = {
35
+ author: PropTypes.shape({
36
+ id: PropTypes.oneOfType(PropTypes.string, PropTypes.number).isRequired,
37
+ name: PropTypes.string.isRequired,
38
+ email: PropTypes.string,
39
+ avatar: PropTypes.string,
40
+ }).isRequired,
41
+ createdAt: PropTypes.string.isRequired,
42
+ updatedAt: PropTypes.string,
43
+ children: PropTypes.array,
44
+ };
45
+
46
+ export default DiscussionThreadItemFooter;
47
+
@@ -0,0 +1,13 @@
1
+ import styled from 'styled-components';
2
+ import { Flex } from '@strapi/design-system/Flex';
3
+ import { Stack } from '@strapi/design-system/Stack';
4
+
5
+ export const DiscussionThreadItemFooterStyled = styled(Flex)`
6
+ width: 100%;
7
+ `;
8
+
9
+ export const DiscussionThreadItemFooterMeta = styled(Stack)`
10
+ * + * {
11
+ margin-left: 8px;
12
+ }
13
+ `;