strapi-plugin-comments 2.0.0-beta.7 → 2.0.1
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 +186 -134
- package/__mocks__/initSetup.js +53 -0
- package/admin/src/components/ActionButton/styles.js +10 -0
- package/admin/src/components/DiscussionThread/index.js +3 -7
- package/admin/src/components/DiscussionThreadItem/index.js +25 -70
- 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 +42 -24
- package/admin/src/pages/Details/components/DetailsFilters/index.js +8 -10
- package/admin/src/pages/Details/index.js +6 -15
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +30 -67
- package/admin/src/translations/en.json +13 -15
- package/admin/src/utils/resolveCommentStatus.js +4 -4
- package/content-types/comment.js +0 -4
- package/package.json +8 -5
- package/public/assets/logo.png +0 -0
- package/public/assets/preview.png +0 -0
- package/server/controllers/client.js +7 -2
- package/server/controllers/utils/__tests__/functions.test.js +2 -2
- package/server/controllers/utils/parsers.js +3 -1
- package/server/graphql/mutations/createAbuseReport.js +8 -3
- package/server/graphql/mutations/createComment.js +8 -3
- package/server/graphql/mutations/removeComment.js +8 -3
- package/server/graphql/mutations/updateComment.js +8 -3
- package/server/graphql/resolvers-config.js +13 -8
- package/server/graphql/types/comment-nested.js +1 -1
- package/server/graphql/types/comment.js +0 -1
- package/server/graphql/types/create-comment-author.js +1 -1
- package/server/graphql/types/identify-comment-author.js +1 -1
- package/server/graphql/types/index.js +0 -1
- package/server/services/__tests__/common.test.js +165 -0
- package/server/services/admin.js +10 -7
- package/server/services/client.js +45 -31
- package/server/services/common.js +38 -16
- package/server/services/utils/__tests__/functions.test.js +9 -73
- package/server/services/utils/functions.js +23 -34
- 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/server/graphql/types/comment-author-user.js +0 -9
|
@@ -8,8 +8,6 @@ const {
|
|
|
8
8
|
isEqualEntity,
|
|
9
9
|
getModelUid,
|
|
10
10
|
getRelatedGroups,
|
|
11
|
-
checkBadWords,
|
|
12
|
-
isValidUserContext,
|
|
13
11
|
resolveUserContextError,
|
|
14
12
|
} = require('./utils/functions');
|
|
15
13
|
const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
|
|
@@ -49,7 +47,7 @@ module.exports = ({ strapi }) => ({
|
|
|
49
47
|
id: threadOf,
|
|
50
48
|
related: relation,
|
|
51
49
|
}) : true;
|
|
52
|
-
const validContext = isValidUserContext(user);
|
|
50
|
+
const validContext = this.getCommonService().isValidUserContext(user);
|
|
53
51
|
|
|
54
52
|
if (!linkToThread) {
|
|
55
53
|
throw new PluginError(400, 'Thread does not exist');
|
|
@@ -59,16 +57,12 @@ module.exports = ({ strapi }) => ({
|
|
|
59
57
|
throw resolveUserContextError(user);
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
if (checkBadWords(content) && singleRelationFulfilled) {
|
|
63
|
-
const { author
|
|
60
|
+
if (this.getCommonService().checkBadWords(content) && singleRelationFulfilled) {
|
|
61
|
+
const { author = {}, ...rest } = data;
|
|
64
62
|
let authorData = {};
|
|
65
63
|
if (validContext && user) {
|
|
66
64
|
authorData = {
|
|
67
|
-
authorUser: user?.id
|
|
68
|
-
};
|
|
69
|
-
} else if (authorUser) {
|
|
70
|
-
authorData = {
|
|
71
|
-
authorUser,
|
|
65
|
+
authorUser: user?.id,
|
|
72
66
|
};
|
|
73
67
|
} else {
|
|
74
68
|
if (author.email && !REGEX.email.test(author.email)) {
|
|
@@ -83,8 +77,8 @@ module.exports = ({ strapi }) => ({
|
|
|
83
77
|
};
|
|
84
78
|
}
|
|
85
79
|
|
|
86
|
-
if (!isEmpty(authorData) && !authorData.authorId) {
|
|
87
|
-
throw new PluginError(400, '
|
|
80
|
+
if (!isEmpty(authorData) && !(authorData.authorId || authorData.authorUser)) {
|
|
81
|
+
throw new PluginError(400, `Not able to recognise author of a comment. Make sure you've provided "author" property in a payload or authenticated your request properly.`);
|
|
88
82
|
}
|
|
89
83
|
|
|
90
84
|
if (isApprovalFlowEnabled &&
|
|
@@ -98,7 +92,10 @@ module.exports = ({ strapi }) => ({
|
|
|
98
92
|
...authorData,
|
|
99
93
|
related: relation,
|
|
100
94
|
approvalStatus: isApprovalFlowEnabled ? APPROVAL_STATUS.PENDING : null,
|
|
101
|
-
}
|
|
95
|
+
},
|
|
96
|
+
populate: {
|
|
97
|
+
authorUser: true,
|
|
98
|
+
},
|
|
102
99
|
});
|
|
103
100
|
return this.getCommonService().sanitizeCommentEntity({
|
|
104
101
|
...entity,
|
|
@@ -109,41 +106,42 @@ module.exports = ({ strapi }) => ({
|
|
|
109
106
|
},
|
|
110
107
|
|
|
111
108
|
// Update a comment
|
|
112
|
-
async update(id, relation, data, user) {
|
|
109
|
+
async update(id, relation, data, user = undefined) {
|
|
113
110
|
const { content } = data;
|
|
114
111
|
|
|
115
112
|
const singleRelationFulfilled = relation && REGEX.relatedUid.test(relation)
|
|
116
113
|
|
|
117
114
|
if(!singleRelationFulfilled) {
|
|
118
|
-
throw new PluginError(400, `
|
|
115
|
+
throw new PluginError(400, `Request property "relation" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
const existingEntity = await this.getCommonService().findOne({
|
|
122
119
|
id,
|
|
123
120
|
related: relation,
|
|
124
121
|
});
|
|
125
|
-
|
|
122
|
+
|
|
123
|
+
const validContext = this.getCommonService().isValidUserContext(user);
|
|
126
124
|
|
|
127
125
|
if (!validContext) {
|
|
128
126
|
throw resolveUserContextError(user);
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
if (isEqualEntity(existingEntity, data, user) && content) {
|
|
132
|
-
if (checkBadWords(content)) {
|
|
130
|
+
if (this.getCommonService().checkBadWords(content)) {
|
|
133
131
|
const entity = await strapi.db.query(getModelUid('comment')).update({
|
|
134
132
|
where: { id },
|
|
135
133
|
data: { content },
|
|
136
|
-
populate: { threadOf: true },
|
|
134
|
+
populate: { threadOf: true, authorUser: true },
|
|
137
135
|
});
|
|
138
136
|
return this.getCommonService().sanitizeCommentEntity(entity);
|
|
139
137
|
}
|
|
140
138
|
}
|
|
141
|
-
throw new PluginError(
|
|
139
|
+
throw new PluginError(403, `You're not allowed to take an action on that entity. Make sure you've provided "author" property in a payload or authenticated your request properly.`);
|
|
142
140
|
},
|
|
143
141
|
|
|
144
142
|
// Report abuse in comment
|
|
145
|
-
async reportAbuse(id, relation, payload, user) {
|
|
146
|
-
if (!isValidUserContext(user)) {
|
|
143
|
+
async reportAbuse(id, relation, payload, user = undefined) {
|
|
144
|
+
if (!this.getCommonService().isValidUserContext(user)) {
|
|
147
145
|
throw resolveUserContextError(user);
|
|
148
146
|
}
|
|
149
147
|
const reportAgainstEntity = await this.getCommonService().findOne({
|
|
@@ -173,18 +171,34 @@ module.exports = ({ strapi }) => ({
|
|
|
173
171
|
throw new PluginError(500, 'Report cannot be created');
|
|
174
172
|
}
|
|
175
173
|
}
|
|
176
|
-
throw new PluginError(
|
|
174
|
+
throw new PluginError(403, `You're not allowed to take an action on that entity. Make sure that comment exist or you've authenticated your request properly.`);
|
|
177
175
|
},
|
|
178
176
|
|
|
179
|
-
async markAsRemoved(id, relation, authorId){
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
async markAsRemoved(id, relation, authorId, user = undefined){
|
|
178
|
+
if (!this.getCommonService().isValidUserContext(user)) {
|
|
179
|
+
throw resolveUserContextError(user);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const author = user?.id || authorId;
|
|
185
183
|
|
|
186
|
-
if (!
|
|
187
|
-
throw new PluginError(
|
|
184
|
+
if (!author) {
|
|
185
|
+
throw new PluginError(403, `You're not allowed to take an action on that entity. Make sure that you've provided proper "authorId" or authenticated your request properly.`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let entity;
|
|
189
|
+
try {
|
|
190
|
+
const byAuthor = user?.id ? {
|
|
191
|
+
authorUser: author
|
|
192
|
+
} : {
|
|
193
|
+
authorId: author
|
|
194
|
+
}
|
|
195
|
+
entity = await this.getCommonService().findOne({
|
|
196
|
+
id,
|
|
197
|
+
related: relation,
|
|
198
|
+
...byAuthor,
|
|
199
|
+
});
|
|
200
|
+
} catch(e) {
|
|
201
|
+
throw new PluginError(404, `Entity does not exist or you're not allowed to take an action on it`);
|
|
188
202
|
}
|
|
189
203
|
|
|
190
204
|
const removedEntity = await strapi.db.query(getModelUid('comment'))
|
|
@@ -194,7 +208,7 @@ module.exports = ({ strapi }) => ({
|
|
|
194
208
|
related: relation
|
|
195
209
|
},
|
|
196
210
|
data: { removed: true },
|
|
197
|
-
populate: { threadOf: true },
|
|
211
|
+
populate: { threadOf: true, authorUser: true },
|
|
198
212
|
})
|
|
199
213
|
await this.markAsRemovedNested(id, true);
|
|
200
214
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const BadWordsFilter = require('bad-words');
|
|
4
|
+
const { isArray, isNumber, isObject, isNil, first, parseInt } = require('lodash');
|
|
5
5
|
const PluginError = require('./../utils/error');
|
|
6
6
|
const {
|
|
7
7
|
getModelUid,
|
|
@@ -22,7 +22,8 @@ module.exports = ({ strapi }) => ({
|
|
|
22
22
|
if (prop && isArray(prop)) {
|
|
23
23
|
queryProp = prop.join('.');
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
const result = strapi.config.get(`plugin.comments${ queryProp ? '.' + queryProp : ''}`);
|
|
26
|
+
return isNil(result) ? defaultValue : result;
|
|
26
27
|
},
|
|
27
28
|
|
|
28
29
|
// Find comments in the flat structure
|
|
@@ -86,8 +87,12 @@ module.exports = ({ strapi }) => ({
|
|
|
86
87
|
async findOne(criteria) {
|
|
87
88
|
const entity = await strapi.db.query(getModelUid('comment'))
|
|
88
89
|
.findOne({
|
|
89
|
-
where: criteria
|
|
90
|
-
|
|
90
|
+
where: criteria,
|
|
91
|
+
populate: {
|
|
92
|
+
reports: true,
|
|
93
|
+
authorUser: true,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
91
96
|
if (!entity){
|
|
92
97
|
throw new PluginError(404, 'Not found');
|
|
93
98
|
}
|
|
@@ -154,15 +159,32 @@ module.exports = ({ strapi }) => ({
|
|
|
154
159
|
}
|
|
155
160
|
},
|
|
156
161
|
|
|
157
|
-
sanitizeCommentEntity
|
|
158
|
-
|
|
159
|
-
...
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
sanitizeCommentEntity(entity) {
|
|
163
|
+
return {
|
|
164
|
+
...buildAuthorModel({
|
|
165
|
+
...entity,
|
|
166
|
+
threadOf: isObject(entity.threadOf) ? buildAuthorModel(entity.threadOf) : entity.threadOf,
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
isValidUserContext(user) {
|
|
172
|
+
return user ? !isNil(user?.id) : true;
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
checkBadWords(content) {
|
|
176
|
+
const config = this.getConfig('badWords', true);
|
|
177
|
+
if (config) {
|
|
178
|
+
const filter = new BadWordsFilter(isObject(config) ? config : undefined);
|
|
179
|
+
if (content && filter.isProfane(content)) {
|
|
180
|
+
throw new PluginError(400, 'Bad language used! Please polite your comment...', {
|
|
181
|
+
content: {
|
|
182
|
+
original: content,
|
|
183
|
+
filtered: content && filter.clean(content),
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return content;
|
|
189
|
+
},
|
|
168
190
|
});
|
|
@@ -1,86 +1,22 @@
|
|
|
1
|
-
const PluginError = require('
|
|
2
|
-
const {
|
|
1
|
+
const PluginError = require('../../../utils/error');
|
|
2
|
+
const { resolveUserContextError, getModelUid } = require('../functions');
|
|
3
3
|
|
|
4
4
|
beforeEach(() => {
|
|
5
5
|
Object.defineProperty(global, 'strapi', {
|
|
6
|
-
value:
|
|
7
|
-
plugins: {
|
|
8
|
-
comments: {
|
|
9
|
-
package: require('../../../package.json'),
|
|
10
|
-
services: {
|
|
11
|
-
comments: require('../../comments'),
|
|
12
|
-
},
|
|
13
|
-
models: {
|
|
14
|
-
comment: require('../../../models/comment.settings.json'),
|
|
15
|
-
report: require('../../../models/report.settings.json'),
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
config: {
|
|
20
|
-
plugins: {
|
|
21
|
-
comments: {
|
|
22
|
-
enableUsers: false,
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
},
|
|
6
|
+
value: require('../../../../__mocks__/initSetup')(),
|
|
27
7
|
writable: true,
|
|
28
8
|
});
|
|
29
9
|
})
|
|
30
10
|
|
|
31
|
-
describe('Test
|
|
32
|
-
|
|
33
|
-
describe('Bad Words filtering', () => {
|
|
34
|
-
const text = 'Lorem ipsum dolor sit fuck amet';
|
|
35
|
-
|
|
36
|
-
test('Should find bad words usage and throw error PluginError', () => {
|
|
37
|
-
expect.assertions(6);
|
|
38
|
-
try {
|
|
39
|
-
checkBadWords(text);
|
|
40
|
-
} catch (e) {
|
|
41
|
-
expect(e).toBeInstanceOf(PluginError);
|
|
42
|
-
expect(e).toHaveProperty('status', 400);
|
|
43
|
-
expect(e).toHaveProperty('name', 'Strapi:Plugin:Comments');
|
|
44
|
-
expect(e).toHaveProperty('message', 'Bad language used! Please polite your comment...');
|
|
45
|
-
expect(e).toHaveProperty('payload');
|
|
46
|
-
expect(e.payload).toEqual(expect.objectContaining({
|
|
47
|
-
content: {
|
|
48
|
-
original: text,
|
|
49
|
-
filtered: text.replace('fuck', '****'),
|
|
50
|
-
}
|
|
51
|
-
}));
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('Should skip bad words filtering because of configuration change', () => {
|
|
56
|
-
global.strapi.config.plugins.comments.badWords = false;
|
|
57
|
-
|
|
58
|
-
expect(checkBadWords(text)).toEqual(text);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('Extracting metadata', () => {
|
|
63
|
-
test('Should extract plugin metadata properly', () => {
|
|
64
|
-
const { model, service, plugin, pluginName } = extractMeta(strapi.plugins);
|
|
65
|
-
|
|
66
|
-
expect(model).toHaveProperty('info.name', 'comment');
|
|
67
|
-
expect(service).toHaveProperty('findAll');
|
|
68
|
-
expect(Object.keys(plugin)).toEqual(expect.arrayContaining(['package', 'services', 'models']));
|
|
69
|
-
expect(pluginName).toBe('comments');
|
|
70
|
-
});
|
|
71
|
-
});
|
|
11
|
+
describe('Test service functions utils', () => {
|
|
72
12
|
|
|
73
|
-
describe('
|
|
74
|
-
test('
|
|
75
|
-
|
|
76
|
-
expect(isValidUserContext(undefined)).toEqual(true);
|
|
77
|
-
expect(isValidUserContext({ id: 1 })).toEqual(true);
|
|
13
|
+
describe('Getting models uid\'s ', () => {
|
|
14
|
+
test('Should get comments uid', () => {
|
|
15
|
+
expect(getModelUid('comment')).toBe('plugins::comments.comment');
|
|
78
16
|
});
|
|
79
17
|
|
|
80
|
-
test('
|
|
81
|
-
|
|
82
|
-
expect(isValidUserContext(undefined)).toEqual(false);
|
|
83
|
-
expect(isValidUserContext({ id: 1 })).toEqual(true);
|
|
18
|
+
test('Should get report uid', () => {
|
|
19
|
+
expect(getModelUid('comment-report')).toBe('plugins::comments.comment-report');
|
|
84
20
|
});
|
|
85
21
|
});
|
|
86
22
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
const BadWordsFilter = require('bad-words');
|
|
2
1
|
const PluginError = require('./../../utils/error');
|
|
3
2
|
const { REGEX } = require('./../../utils/constants')
|
|
4
|
-
const { first,
|
|
3
|
+
const { first, isObject, isEmpty } = require('lodash');
|
|
5
4
|
|
|
6
5
|
const buildNestedStructure = (
|
|
7
6
|
entities,
|
|
@@ -33,7 +32,7 @@ const buildNestedStructure = (
|
|
|
33
32
|
|
|
34
33
|
module.exports = {
|
|
35
34
|
isEqualEntity: (existing, data, user) => {
|
|
36
|
-
const {
|
|
35
|
+
const { author: existingAuthor } = existing;
|
|
37
36
|
const { author } = data;
|
|
38
37
|
|
|
39
38
|
// Disallow approval status change by Client
|
|
@@ -42,10 +41,10 @@ module.exports = {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
// Make sure that author is exact the same
|
|
45
|
-
if (
|
|
46
|
-
const existingUserId =
|
|
47
|
-
const receivedUserId = user?.id || author;
|
|
48
|
-
return existingUserId === receivedUserId;
|
|
44
|
+
if (user) {
|
|
45
|
+
const existingUserId = existingAuthor?.id || existingAuthor;
|
|
46
|
+
const receivedUserId = user?.id || author?.id;
|
|
47
|
+
return receivedUserId && (existingUserId === receivedUserId);
|
|
49
48
|
}
|
|
50
49
|
return existingAuthor.id === author?.id;
|
|
51
50
|
},
|
|
@@ -63,21 +62,6 @@ module.exports = {
|
|
|
63
62
|
reports: (item.reports || []).filter(report => !report.resolved),
|
|
64
63
|
} : item),
|
|
65
64
|
|
|
66
|
-
checkBadWords: content => {
|
|
67
|
-
const config = get(strapi.config, 'plugins.comments.badWords', true);
|
|
68
|
-
if (config) {
|
|
69
|
-
const filter = new BadWordsFilter(isObject(config) ? config : undefined);
|
|
70
|
-
if (content && filter.isProfane(content)) {
|
|
71
|
-
throw new PluginError(400, 'Bad language used! Please polite your comment...', {
|
|
72
|
-
content: {
|
|
73
|
-
original: content,
|
|
74
|
-
filtered: content && filter.clean(content),
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return content;
|
|
80
|
-
},
|
|
81
65
|
convertContentTypeNameToSlug: str => {
|
|
82
66
|
const plainConversion = str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
83
67
|
return first(plainConversion) === '-' ? plainConversion.slice(1, plainConversion.length) : plainConversion;
|
|
@@ -85,22 +69,27 @@ module.exports = {
|
|
|
85
69
|
|
|
86
70
|
buildNestedStructure,
|
|
87
71
|
|
|
88
|
-
isValidUserContext: (user = {}) => {
|
|
89
|
-
const builtInContextEnabled = get(strapi.config, 'plugins.comments.enableUsers', false);
|
|
90
|
-
return builtInContextEnabled ? !isNil(user.id) : true;
|
|
91
|
-
},
|
|
92
|
-
|
|
93
72
|
buildAuthorModel: (item) => {
|
|
94
73
|
const { authorUser, authorId, authorName, authorEmail, authorAvatar, ...rest } = item;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
74
|
+
let author = {};
|
|
75
|
+
if (authorUser) {
|
|
76
|
+
author = {
|
|
77
|
+
id: authorUser.id,
|
|
78
|
+
name: authorUser.username,
|
|
79
|
+
email: authorUser.email,
|
|
80
|
+
avatar: authorUser.avatar,
|
|
81
|
+
};
|
|
82
|
+
} else if(authorId) {
|
|
83
|
+
author = {
|
|
84
|
+
id: authorId,
|
|
85
|
+
name: authorName,
|
|
86
|
+
email: authorEmail,
|
|
87
|
+
avatar: authorAvatar,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
101
90
|
return {
|
|
102
91
|
...rest,
|
|
103
|
-
author,
|
|
92
|
+
author: isEmpty(author) ? undefined : author,
|
|
104
93
|
};
|
|
105
94
|
},
|
|
106
95
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { getPluginService } = require('../functions');
|
|
2
|
+
|
|
3
|
+
beforeEach(() => {
|
|
4
|
+
Object.defineProperty(global, 'strapi', {
|
|
5
|
+
value: require('../../../__mocks__/initSetup')(),
|
|
6
|
+
writable: true,
|
|
7
|
+
});
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('Test plugin functions utils', () => {
|
|
11
|
+
|
|
12
|
+
describe('Get plugin service', () => {
|
|
13
|
+
test('Should get common service', () => {
|
|
14
|
+
expect(getPluginService('common')).toHaveProperty('findAllFlat');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* Entity Details
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React from 'react';
|
|
8
|
-
import PropTypes from 'prop-types';
|
|
9
|
-
import { useIntl } from 'react-intl';
|
|
10
|
-
import { Box } from '@strapi/design-system/Box';
|
|
11
|
-
import { Flex } from '@strapi/design-system/Flex';
|
|
12
|
-
import { Stack } from '@strapi/design-system/Stack';
|
|
13
|
-
import { Typography } from '@strapi/design-system/Typography';
|
|
14
|
-
|
|
15
|
-
const DiscussionThreadItemMeta = ({ author, createdAt, updatedAt }) => {
|
|
16
|
-
|
|
17
|
-
const { formatDate } = useIntl();
|
|
18
|
-
|
|
19
|
-
const dateTime = formatDate(updatedAt || createdAt, { dateStyle: 'short', timeStyle: 'short' });
|
|
20
|
-
const { name } = author;
|
|
21
|
-
|
|
22
|
-
return (<Flex as={Box} paddingTop={2} direction="row">
|
|
23
|
-
<Stack size={2} horizontal style={{ flexGrow: 1, justifyContent: 'space-between'}}>
|
|
24
|
-
<Typography variant="pi" fontWeight="bold">{ name }</Typography>
|
|
25
|
-
<Typography variant="pi">{ dateTime }</Typography>
|
|
26
|
-
</Stack>
|
|
27
|
-
</Flex>);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
DiscussionThreadItemMeta.propTypes = {
|
|
31
|
-
author: PropTypes.shape({
|
|
32
|
-
id: PropTypes.oneOfType(PropTypes.string, PropTypes.number).isRequired,
|
|
33
|
-
name: PropTypes.string.isRequired,
|
|
34
|
-
email: PropTypes.string,
|
|
35
|
-
avatar: PropTypes.string,
|
|
36
|
-
}).isRequired,
|
|
37
|
-
createdAt: PropTypes.string.isRequired,
|
|
38
|
-
updatedAt: PropTypes.string,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export default DiscussionThreadItemMeta;
|
|
42
|
-
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* Entity Details
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React, { useState } from 'react';
|
|
8
|
-
import PropTypes from 'prop-types';
|
|
9
|
-
import { useMutation, useQueryClient } from 'react-query';
|
|
10
|
-
import { isEmpty, orderBy } from 'lodash';
|
|
11
|
-
import { Badge } from '@strapi/design-system/Badge';
|
|
12
|
-
import { Box } from '@strapi/design-system/Box';
|
|
13
|
-
import { Button } from '@strapi/design-system/Button';
|
|
14
|
-
import { Divider } from '@strapi/design-system/Divider';
|
|
15
|
-
import { Flex } from '@strapi/design-system/Flex';
|
|
16
|
-
import { Typography } from '@strapi/design-system/Typography';
|
|
17
|
-
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
18
|
-
import { getMessage, handleAPIError } from '../../utils';
|
|
19
|
-
import { blockItem, blockItemThread, resolveReport } from '../../pages/utils/api';
|
|
20
|
-
import { DiscussionThreadItemWarningsWrapper } from './styles';
|
|
21
|
-
import ReportsReviewModal from '../ReportsReviewModal';
|
|
22
|
-
import ReportsReviewTable from '../ReportsReviewTable';
|
|
23
|
-
import pluginId from '../../pluginId';
|
|
24
|
-
import { LockIcon, ReviewIcon } from '../icons';
|
|
25
|
-
|
|
26
|
-
const DiscussionThreadItemWarnings = (item) => {
|
|
27
|
-
const { id, reports, gotThread, allowedActions: { canModerate, canAccessReports, canReviewReports }} = item;
|
|
28
|
-
|
|
29
|
-
const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
|
|
30
|
-
const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
|
|
31
|
-
|
|
32
|
-
const toggleNotification = useNotification();
|
|
33
|
-
const queryClient = useQueryClient();
|
|
34
|
-
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
35
|
-
|
|
36
|
-
const onSuccess = (message, stateAction = () => {}, indalidate = true) => async () => {
|
|
37
|
-
if (indalidate) {
|
|
38
|
-
await queryClient.invalidateQueries('get-details-data');
|
|
39
|
-
}
|
|
40
|
-
toggleNotification({
|
|
41
|
-
type: 'success',
|
|
42
|
-
message: `${pluginId}.${message}`,
|
|
43
|
-
});
|
|
44
|
-
stateAction(false);
|
|
45
|
-
unlockApp();
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
49
|
-
|
|
50
|
-
const resolveReportMutation = useMutation(resolveReport, {
|
|
51
|
-
onSuccess: onSuccess('page.details.panel.discussion.warnings.reports.dialog.confirmation.success', () => {}, false),
|
|
52
|
-
onError,
|
|
53
|
-
refetchActive: false,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const blockItemMutation = useMutation(blockItem, {
|
|
57
|
-
onSuccess: onSuccess(
|
|
58
|
-
'page.details.actions.comment.block.confirmation.success',
|
|
59
|
-
setReportsReviewVisible
|
|
60
|
-
),
|
|
61
|
-
onError,
|
|
62
|
-
refetchActive: false,
|
|
63
|
-
});
|
|
64
|
-
const blockItemThreadMutation = useMutation(blockItemThread, {
|
|
65
|
-
onSuccess: onSuccess(
|
|
66
|
-
'page.details.actions.thread.block.confirmation.success',
|
|
67
|
-
setReportsReviewVisible
|
|
68
|
-
),
|
|
69
|
-
onError,
|
|
70
|
-
refetchActive: false,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const handleReportsReviewClick = () => setReportsReviewVisible(true);
|
|
74
|
-
const handleBlockItemClick = async () => {
|
|
75
|
-
lockApp();
|
|
76
|
-
blockItemMutation.mutate(id);
|
|
77
|
-
};
|
|
78
|
-
const handleBlockItemThreadClick = async () => {
|
|
79
|
-
lockApp();
|
|
80
|
-
blockItemThreadMutation.mutate(id);
|
|
81
|
-
};
|
|
82
|
-
const handleReportsReviewClose = async () => {
|
|
83
|
-
await queryClient.invalidateQueries('get-details-data')
|
|
84
|
-
setReportsReviewVisible(false);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
|
|
88
|
-
|
|
89
|
-
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
|
|
90
|
-
const openReports = reports?.filter(_ => !_.resolved);
|
|
91
|
-
|
|
92
|
-
if (isEmpty(reports) || isEmpty(openReports)) {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return (<>
|
|
97
|
-
{ canAccessReports && (<DiscussionThreadItemWarningsWrapper as={Flex} direction="row" padding={2} background="danger100" borderColor="danger200" border={1} hasRadius>
|
|
98
|
-
<Box as={Flex} paddingRight={2} direction="row" grow={1}>
|
|
99
|
-
<Badge textColor="neutral0" backgroundColor="warning600">{ openReports.length }</Badge>
|
|
100
|
-
<Box paddingLeft={2}>
|
|
101
|
-
<Typography textColor="warning600" fontWeight="bold">{getMessage(`page.details.panel.discussion.warnings.reports.description`, 'abuse reports')}</Typography>
|
|
102
|
-
</Box>
|
|
103
|
-
</Box>
|
|
104
|
-
<Button onClick={handleReportsReviewClick} variant="ghost" endIcon={<ReviewIcon />}>
|
|
105
|
-
{ getMessage(`page.details.actions.comment.report.review`, 'review') }
|
|
106
|
-
</Button>
|
|
107
|
-
</DiscussionThreadItemWarningsWrapper>) }
|
|
108
|
-
{ (canAccessReports || canReviewReports) && (<ReportsReviewModal
|
|
109
|
-
isVisible={reportsReviewVisible}
|
|
110
|
-
isActionAsync={isLoading}
|
|
111
|
-
onClose={handleReportsReviewClose}
|
|
112
|
-
startActions={canModerate && (<>
|
|
113
|
-
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
114
|
-
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
115
|
-
</Button>
|
|
116
|
-
{ gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
117
|
-
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
118
|
-
</Button>) }
|
|
119
|
-
</>)}
|
|
120
|
-
item={item}>
|
|
121
|
-
<Typography variant="sigma" textColor="neutral600" id="reports-list">
|
|
122
|
-
{ getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
|
|
123
|
-
</Typography>
|
|
124
|
-
<Box paddingTop={2} paddingBottom={6}>
|
|
125
|
-
<Divider />
|
|
126
|
-
</Box>
|
|
127
|
-
<ReportsReviewTable
|
|
128
|
-
commentId={id}
|
|
129
|
-
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
130
|
-
mutation={resolveReportMutation}
|
|
131
|
-
allowedActions={{ canAccessReports, canReviewReports }}
|
|
132
|
-
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
133
|
-
/>
|
|
134
|
-
</ReportsReviewModal>) }
|
|
135
|
-
</>);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
DiscussionThreadItemWarnings.propTypes = {
|
|
139
|
-
allowedActions: PropTypes.shape({
|
|
140
|
-
canModerate: PropTypes.bool,
|
|
141
|
-
canAccessReports: PropTypes.bool,
|
|
142
|
-
canReviewReports: PropTypes.bool,
|
|
143
|
-
}),
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export default DiscussionThreadItemWarnings;
|
|
147
|
-
|