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.
- package/.circleci/config.yml +1 -1
- package/README.md +20 -4
- package/__mocks__/initSetup.js +53 -0
- package/admin/src/components/ActionButton/styles.js +10 -0
- package/admin/src/components/DiscussionThread/index.js +2 -6
- package/admin/src/components/DiscussionThreadItem/index.js +25 -68
- package/admin/src/components/DiscussionThreadItem/styles.js +4 -79
- package/admin/src/components/DiscussionThreadItemActions/index.js +82 -40
- package/admin/src/components/DiscussionThreadItemActions/styles.js +1 -12
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +2 -3
- package/admin/src/components/DiscussionThreadItemFooter/index.js +47 -0
- package/admin/src/components/DiscussionThreadItemFooter/styles.js +13 -0
- package/admin/src/components/DiscussionThreadItemReviewAction/index.js +131 -0
- package/admin/src/components/IconButton/styles.js +14 -0
- package/admin/src/components/ReportsReviewModal/index.js +1 -1
- package/admin/src/components/ReportsReviewTable/index.js +12 -6
- package/admin/src/components/StatusBadge/index.js +17 -0
- package/admin/src/components/StatusBadge/styles.js +17 -0
- package/admin/src/pages/Details/components/DetailsEntity/index.js +40 -24
- package/admin/src/pages/Details/components/DetailsFilters/index.js +8 -10
- package/admin/src/pages/Details/index.js +9 -15
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +28 -65
- package/admin/src/translations/en.json +13 -15
- package/admin/src/utils/resolveCommentStatus.js +4 -4
- package/package.json +7 -5
- package/server/controllers/utils/__tests__/functions.test.js +2 -2
- package/server/graphql/types/comment-nested.js +1 -0
- package/server/services/__tests__/common.test.js +165 -0
- package/server/services/client.js +2 -3
- package/server/services/common.js +19 -2
- package/server/services/utils/__tests__/functions.test.js +9 -73
- package/server/services/utils/functions.js +1 -17
- package/server/utils/__tests__/functions.test.js +17 -0
- package/admin/src/components/DiscussionThreadItemMeta/index.js +0 -42
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +0 -147
- package/admin/src/components/DiscussionThreadItemWarnings/styles.js +0 -7
package/.circleci/config.yml
CHANGED
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>
|
|
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
|
|
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.
|
|
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.
|
|
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
|
+
};
|
|
@@ -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="
|
|
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
|
-
*
|
|
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
|
|
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 {
|
|
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 (<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
</
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
</
|
|
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
|
|
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
|
|
47
|
-
align-items: flex-
|
|
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 {
|
|
16
|
-
import {
|
|
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
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
{
|
|
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 (
|
|
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
|
-
|
|
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
|
+
`;
|