strapi-plugin-comments 2.0.0 → 2.0.4
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/.husky/pre-commit +4 -0
- package/README.md +33 -5
- package/__mocks__/initSetup.js +31 -2
- package/admin/src/hooks/useConfig.js +49 -0
- package/admin/src/index.js +31 -2
- package/admin/src/pages/Details/components/DetailsEntity/index.js +3 -1
- package/admin/src/pages/Details/index.js +7 -7
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +2 -2
- package/admin/src/pages/Settings/components/RestartAlert/styles.js +8 -0
- package/admin/src/pages/Settings/index.js +376 -0
- package/admin/src/pages/Settings/utils/api.js +60 -0
- package/admin/src/permissions.js +3 -0
- package/admin/src/translations/en.json +44 -1
- package/admin/src/utils/handleAPIError.js +3 -2
- package/admin/src/utils/index.js +2 -1
- package/admin/src/utils/parseRegExp.js +10 -0
- package/package.json +8 -5
- package/server/bootstrap.js +24 -0
- package/server/config/index.js +1 -0
- package/server/controllers/admin.js +42 -8
- package/server/controllers/client.js +4 -4
- package/server/controllers/utils/parsers.js +3 -1
- package/server/graphql/index.js +7 -8
- package/server/graphql/queries/findAllFlat.js +4 -2
- package/server/graphql/queries/findAllInHierarchy.js +3 -3
- package/server/graphql/resolvers-config.js +2 -4
- package/server/graphql/types/report-reason.js +10 -10
- package/server/register.js +1 -3
- package/server/routes/admin.js +32 -0
- package/server/services/__tests__/common.test.js +95 -14
- package/server/services/admin.js +57 -6
- package/server/services/client.js +14 -8
- package/server/services/common.js +69 -11
- package/server/services/utils/__tests__/functions.test.js +12 -1
- package/server/services/utils/functions.js +9 -1
- package/server/utils/constants.js +10 -0
- package/strapi-server.js +12 -5
- package/.circleci/config.yml +0 -48
- package/.eslintrc +0 -35
- package/.github/pull_request_template.md +0 -13
- package/.nvmrc +0 -1
- package/codecov.yml +0 -4
- package/public/assets/logo.png +0 -0
- package/public/assets/preview.png +0 -0
package/README.md
CHANGED
|
@@ -37,10 +37,10 @@ A plugin for [Strapi Headless CMS](https://github.com/strapi/strapi) that provid
|
|
|
37
37
|
|
|
38
38
|
- **Comments Public REST + GraphQL API:** Elegant, entirely customizable and a fully extensible admin panel.
|
|
39
39
|
- **Strapi & generic users:** Support for built-in & also generic non-Strapi users that might be the comments authors.
|
|
40
|
-
- **Any Content Type relation:** Comments can
|
|
40
|
+
- **Any Content Type relation:** Comments can be linked to any of your Content Types by default. Simply, you're controlling it.
|
|
41
41
|
- **Moderation Panel:** Search & Filter through the bucket with your auditory comments. Manage them by blocking single ones or full threads. All in combined list & hierarchical tree view of threads.
|
|
42
|
-
- **Automated Bad Words filtering:** By
|
|
43
|
-
- **Abuse Reporting & Reviewing:** Don't allow inferior language, react to reports from your community, send email
|
|
42
|
+
- **Automated Bad Words filtering:** By default end users are not allowed to post abusing comments where bad words have been used.
|
|
43
|
+
- **Abuse Reporting & Reviewing:** Don't allow inferior language, react to reports from your community, send email notifications about issued reports
|
|
44
44
|
|
|
45
45
|
## ⚙️ Versions
|
|
46
46
|
|
|
@@ -92,6 +92,16 @@ In our minimum support we're following [official Node.js releases timelines](htt
|
|
|
92
92
|
**We recommend always using the latest version of Strapi to start your new projects**.
|
|
93
93
|
|
|
94
94
|
## 🔧 Configuration
|
|
95
|
+
|
|
96
|
+
### In `v2.0.3` and newest
|
|
97
|
+
|
|
98
|
+
Version `2.0.3` introduce the intuitive **Settings** page which you can easly access via `Strapi Settings -> Section: Comments Plugin -> Configuration`. On dedicated page you will be able to setup all crucial properties which drives the plugin and customize each individual collection for which **Comments plugin** should be enabled.
|
|
99
|
+
|
|
100
|
+
> *Note*
|
|
101
|
+
> Default configuration for your plugin is fetched from `config/plugins.js` or directly from the plugin itself. If you would like to customize the default state to which you might revert, please follow the next section.
|
|
102
|
+
|
|
103
|
+
### In `v2.0.2` and older + default configuration state for `v2.0.3` and newest
|
|
104
|
+
|
|
95
105
|
To setup amend default plugin configuration we recommend to put following snippet as part of `config/plugins.js` or `config/<env>/plugins.js` file. If the file does not exist yet, you have to create it manually. If you've got already configurations for other plugins stores by this way, use just the `comments` part within exising `plugins` item.
|
|
96
106
|
|
|
97
107
|
|
|
@@ -128,6 +138,9 @@ To setup amend default plugin configuration we recommend to put following snippe
|
|
|
128
138
|
## Additional GQL Configuration
|
|
129
139
|
All you need to do is to install and enable `@strapi/plugin-graphql` for you instance based on the **[official Strapi v4 docs](https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#configurations)** and decide if you would like to call it by anyone (open for world) or only by authenticated users (Strapi users).
|
|
130
140
|
|
|
141
|
+
> **Important!**
|
|
142
|
+
> If you're using `config/plugins.js` to configure your plugins , please put `comments` property before `graphql`. Otherwise types are not going to be properly added to GraphQL Schema. That's because of dynamic types which base on plugin configuration which are added on `boostrap` stage, not `register`. This is not valid if you're using `graphql` plugin without any custom configuration, so most of cases in real.
|
|
143
|
+
|
|
131
144
|
```json
|
|
132
145
|
{
|
|
133
146
|
// ...
|
|
@@ -203,7 +216,7 @@ Feature / Capability focused permissions:
|
|
|
203
216
|
|
|
204
217
|
`GET <host>/api/comments/api::<collection name>.<content type name>:<entity id>`
|
|
205
218
|
|
|
206
|
-
Return a hierarchical tree structure of comments for specified instance of Content Type like for example `Page` with `ID: 1
|
|
219
|
+
Return a hierarchical tree structure of comments for specified instance of Content Type like for example `Page` with `ID: 1`.
|
|
207
220
|
|
|
208
221
|
**Example URL**: `https://localhost:1337/comments/api::page.page:1`
|
|
209
222
|
|
|
@@ -226,6 +239,8 @@ Return a hierarchical tree structure of comments for specified instance of Conte
|
|
|
226
239
|
// ...
|
|
227
240
|
]
|
|
228
241
|
```
|
|
242
|
+
#### Strapi REST API properties support:
|
|
243
|
+
- [sorting](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/sort-pagination.html#sorting)
|
|
229
244
|
|
|
230
245
|
### Get Comments (flat structure)
|
|
231
246
|
|
|
@@ -255,6 +270,11 @@ Return a flat structure of comments for specified instance of Content Type like
|
|
|
255
270
|
- `200` - Successful. Response with list of comments (can be empty)
|
|
256
271
|
- `400` - Bad Request. Requested list for not valid / not existing Content Type
|
|
257
272
|
|
|
273
|
+
#### Strapi REST API properties support:
|
|
274
|
+
- [filtering](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/filtering-locale-publication.html#filtering)
|
|
275
|
+
- [sorting](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/sort-pagination.html#sorting)
|
|
276
|
+
- [pagination](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/sort-pagination.html#pagination)
|
|
277
|
+
|
|
258
278
|
### Post a Comment
|
|
259
279
|
|
|
260
280
|
*GraphQL equivalent: [Public GraphQL API -> Post a Comments](#post-a-comment-1)*
|
|
@@ -353,7 +373,7 @@ Updates a specified Comment content based on it `commentId` and related to speci
|
|
|
353
373
|
|
|
354
374
|
*GraphQL equivalent: [Public GraphQL API -> Delete Comment](#delete-comment-1)*
|
|
355
375
|
|
|
356
|
-
`DELETE <host>/api/comments/api::<collection name>.<content type name>:<entity id
|
|
376
|
+
`DELETE <host>/api/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>?authorId=<authorId>`
|
|
357
377
|
|
|
358
378
|
Deletes a specified Comment based on it `commentId` and related to specified instance of Content Type like for example `Page` with `ID: 1`.
|
|
359
379
|
|
|
@@ -460,6 +480,9 @@ query {
|
|
|
460
480
|
}
|
|
461
481
|
```
|
|
462
482
|
|
|
483
|
+
#### Strapi GraphQL API properties support:
|
|
484
|
+
- [sorting](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.html#sorting)
|
|
485
|
+
|
|
463
486
|
### Get Comments (flat structure)
|
|
464
487
|
|
|
465
488
|
*REST API equivalent: [Public REST API -> Get Comments (flat structure)](#get-comments-flat-structure)*
|
|
@@ -516,6 +539,11 @@ query {
|
|
|
516
539
|
}
|
|
517
540
|
```
|
|
518
541
|
|
|
542
|
+
#### Strapi GraphQL API properties support:
|
|
543
|
+
- [filtering](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.html#filters)
|
|
544
|
+
- [sorting](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.html#sorting)
|
|
545
|
+
- [pagination](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/graphql-api.html#pagination)
|
|
546
|
+
|
|
519
547
|
### Post a Comment
|
|
520
548
|
|
|
521
549
|
*REST API equivalent: [Public REST API -> Post a Comment](#post-a-comment)*
|
package/__mocks__/initSetup.js
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
const { get, set } = require('lodash');
|
|
2
2
|
|
|
3
|
-
module.exports = (config = {}) => {
|
|
3
|
+
module.exports = (config = {}, toStore = false) => {
|
|
4
4
|
const mock = {
|
|
5
|
+
db: toStore ? {
|
|
6
|
+
plugin: {
|
|
7
|
+
comments: {
|
|
8
|
+
config: { ...config },
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
}: {},
|
|
5
12
|
getRef: function() {
|
|
6
13
|
return this;
|
|
7
14
|
},
|
|
8
15
|
plugin: function(name) {
|
|
9
16
|
return this.plugins[name];
|
|
10
17
|
},
|
|
18
|
+
store: async function(storeProps) {
|
|
19
|
+
const { type, name } = storeProps; // { type: 'plugin', name: 'comments' }
|
|
20
|
+
|
|
21
|
+
const mockedStore = {
|
|
22
|
+
get: async function(props) { // { key: 'config' }
|
|
23
|
+
const { key } = props;
|
|
24
|
+
return new Promise(resolve => resolve(get(mock.db, `${type}.${name}.${key}`, undefined)));
|
|
25
|
+
},
|
|
26
|
+
set: async function(props) { // { key: 'config', value: {...} }
|
|
27
|
+
const { key, value } = props;
|
|
28
|
+
set(mock.db, `${type}.${name}.${key}`, value);
|
|
29
|
+
return this.get(key);
|
|
30
|
+
},
|
|
31
|
+
delete: async function(props) { // { key: 'config' }
|
|
32
|
+
const { key } = props;
|
|
33
|
+
set(mock.db, `${type}.${name}.${key}`, undefined);
|
|
34
|
+
return new Promise(resolve => resolve(true));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return new Promise(resolve => resolve(mockedStore));
|
|
39
|
+
},
|
|
11
40
|
plugins: {
|
|
12
41
|
comments: {
|
|
13
42
|
service: function(name) {
|
|
@@ -44,7 +73,7 @@ module.exports = (config = {}) => {
|
|
|
44
73
|
entryLabel: {
|
|
45
74
|
'api::blog-post.blog-post': ['alternative_subject'],
|
|
46
75
|
},
|
|
47
|
-
...config
|
|
76
|
+
...(toStore ? {} : config)
|
|
48
77
|
}
|
|
49
78
|
}
|
|
50
79
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
|
2
|
+
import { fetchConfig, restartStrapi, restoreConfig, updateConfig } from '../pages/Settings/utils/api';
|
|
3
|
+
import pluginId from '../pluginId';
|
|
4
|
+
|
|
5
|
+
const useConfig = (toggleNotification) => {
|
|
6
|
+
const queryClient = useQueryClient();
|
|
7
|
+
|
|
8
|
+
const fetch = useQuery('get-config', () =>
|
|
9
|
+
fetchConfig(toggleNotification)
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const handleError = (type, callback = () => {}) => {
|
|
13
|
+
toggleNotification({
|
|
14
|
+
type: 'warning',
|
|
15
|
+
message: `${pluginId}.page.settings.notification.${type}.error`,
|
|
16
|
+
});
|
|
17
|
+
callback();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const handleSuccess = (type, callback = () => {}, invalidateQueries = true) => {
|
|
21
|
+
if (invalidateQueries) {
|
|
22
|
+
queryClient.invalidateQueries('get-config');
|
|
23
|
+
};
|
|
24
|
+
toggleNotification({
|
|
25
|
+
type: 'success',
|
|
26
|
+
message: `${pluginId}.page.settings.notification.${type}.success`,
|
|
27
|
+
});
|
|
28
|
+
callback();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const submitMutation = useMutation(updateConfig, {
|
|
32
|
+
onSuccess: () => handleSuccess('submit'),
|
|
33
|
+
onError: () => handleError('submit'),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const restoreMutation = useMutation(restoreConfig, {
|
|
37
|
+
onSuccess: () => handleSuccess('restore'),
|
|
38
|
+
onError: () => handleError('restore'),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const restartMutation = useMutation(restartStrapi, {
|
|
42
|
+
onSuccess: () => handleSuccess('restart', () => {}, false),
|
|
43
|
+
onError: () => handleError('restart'),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return { fetch, restartMutation, submitMutation, restoreMutation };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default useConfig;
|
package/admin/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import PluginIcon from './components/PluginIcon';
|
|
|
6
6
|
import pluginPermissions from './permissions';
|
|
7
7
|
import reducers from './reducers';
|
|
8
8
|
|
|
9
|
-
const name = pluginPkg.strapi
|
|
9
|
+
const { name, displayName } = pluginPkg.strapi;
|
|
10
10
|
|
|
11
11
|
export default {
|
|
12
12
|
register(app) {
|
|
@@ -16,7 +16,7 @@ export default {
|
|
|
16
16
|
icon: PluginIcon,
|
|
17
17
|
intlLabel: {
|
|
18
18
|
id: `${pluginId}.plugin.name`,
|
|
19
|
-
defaultMessage:
|
|
19
|
+
defaultMessage: displayName,
|
|
20
20
|
},
|
|
21
21
|
Component: async () => {
|
|
22
22
|
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
|
|
@@ -25,6 +25,35 @@ export default {
|
|
|
25
25
|
},
|
|
26
26
|
permissions: pluginPermissions.access,
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
app.createSettingSection(
|
|
30
|
+
{
|
|
31
|
+
id: pluginId,
|
|
32
|
+
intlLabel: {
|
|
33
|
+
id: `${pluginId}.plugin.section`,
|
|
34
|
+
defaultMessage: `${displayName} plugin`,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
[
|
|
38
|
+
{
|
|
39
|
+
intlLabel: {
|
|
40
|
+
id: `${pluginId}.plugin.section.item`,
|
|
41
|
+
defaultMessage: 'Configuration',
|
|
42
|
+
},
|
|
43
|
+
id: 'comments',
|
|
44
|
+
to: `/settings/${pluginId}`,
|
|
45
|
+
Component: async () => {
|
|
46
|
+
const component = await import(
|
|
47
|
+
/* webpackChunkName: "documentation-settings" */ './pages/Settings'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return component;
|
|
51
|
+
},
|
|
52
|
+
permissions: pluginPermissions.settings,
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
);
|
|
56
|
+
|
|
28
57
|
app.addReducers(reducers);
|
|
29
58
|
app.registerPlugin({
|
|
30
59
|
id: pluginId,
|
|
@@ -37,8 +37,10 @@ const DetailsEntity = ({
|
|
|
37
37
|
.map(_ => capitalize(_))
|
|
38
38
|
.join(' ');
|
|
39
39
|
|
|
40
|
+
const entityIsRenderable = data && !isEmpty(data) && !isEmpty(itemKeys);
|
|
41
|
+
|
|
40
42
|
return (<Box padding={4}>
|
|
41
|
-
{
|
|
43
|
+
{ entityIsRenderable && (<Box marginBottom={4}>
|
|
42
44
|
<Typography variant="sigma" textColor="neutral600" id="entity-details">
|
|
43
45
|
{ getMessage('page.details.panel.entity', 'Details') }
|
|
44
46
|
</Typography>
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import getMessage from '../../utils/getMessage';
|
|
29
29
|
import { fetchDetailsData, fetchContentTypeData } from '../utils/api';
|
|
30
30
|
import pluginPermissions from '../../permissions';
|
|
31
|
-
import { getUrl } from '../../utils';
|
|
31
|
+
import { getUrl, parseRegExp } from '../../utils';
|
|
32
32
|
import Nav from '../../components/Nav';
|
|
33
33
|
import DetailsEntity from './components/DetailsEntity';
|
|
34
34
|
import DiscussionThread from '../../components/DiscussionThread';
|
|
@@ -60,7 +60,10 @@ const Details = ({ config }) => {
|
|
|
60
60
|
|
|
61
61
|
const [filters, setFilters] = useState({});
|
|
62
62
|
|
|
63
|
-
const regexUID = new RegExp(
|
|
63
|
+
const regexUID = new RegExp(
|
|
64
|
+
parseRegExp(config.regex.uid).value,
|
|
65
|
+
parseRegExp(config.regex.uid).flags
|
|
66
|
+
);
|
|
64
67
|
|
|
65
68
|
const { isLoading: isLoadingForData, data, isFetching } = useQuery(
|
|
66
69
|
['get-details-data', id, filters, canAccess],
|
|
@@ -102,15 +105,12 @@ const Details = ({ config }) => {
|
|
|
102
105
|
<ContentLayout>
|
|
103
106
|
<TwoColsLayout
|
|
104
107
|
startCol={<DiscussionThread level={level} selected={selected} isReloading={isLoading} allowedActions={{ canModerate, canAccessReports, canReviewReports }} />}
|
|
105
|
-
endCol={
|
|
106
|
-
<Loader small>{ getMessage('page.details.panel.loading', 'Loading...') }</Loader>
|
|
107
|
-
</Box>) :
|
|
108
|
-
(<DetailsEntity
|
|
108
|
+
endCol={<DetailsEntity
|
|
109
109
|
data={entity}
|
|
110
110
|
schema={contentTypeData?.schema}
|
|
111
111
|
config={config}
|
|
112
112
|
filters={filters}
|
|
113
|
-
onFiltersChange={handleChangeFilters} />
|
|
113
|
+
onFiltersChange={handleChangeFilters} />}
|
|
114
114
|
/>
|
|
115
115
|
</ContentLayout>
|
|
116
116
|
</>) }
|
|
@@ -98,7 +98,7 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
|
|
|
98
98
|
}, status) }</StatusBadge>);
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
const renderEntryUrl = entry => `/content-manager/collectionType/${entry.uid}/${entry.id}
|
|
101
|
+
const renderEntryUrl = entry => entry ? `/content-manager/collectionType/${entry.uid}/${entry.id}` : null;
|
|
102
102
|
const renderDetailsUrl = entry => getUrl(`discover/${entry.id}`);
|
|
103
103
|
|
|
104
104
|
const gotApprovalFlow = !isNil(item.approvalStatus);
|
|
@@ -130,7 +130,7 @@ const DiscoverTableRow = ({ config, item, allowedActions: { canModerate, canAcce
|
|
|
130
130
|
}, '#' + item.threadOf.id) }</Link>) : '-' }
|
|
131
131
|
</Td>
|
|
132
132
|
<Td style={{ maxWidth: '15vw' }}>
|
|
133
|
-
<TableLink to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related, config) }</TableLink>
|
|
133
|
+
{ item.related ? (<TableLink to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related, config) }</TableLink>) : '-' }
|
|
134
134
|
</Td>
|
|
135
135
|
<Td>
|
|
136
136
|
<Typography textColor="neutral800">{ formatDate(item.updatedAt || item.createdAt, { dateStyle: 'long', timeStyle: 'short' }) }</Typography>
|