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.
Files changed (44) hide show
  1. package/.husky/pre-commit +4 -0
  2. package/README.md +33 -5
  3. package/__mocks__/initSetup.js +31 -2
  4. package/admin/src/hooks/useConfig.js +49 -0
  5. package/admin/src/index.js +31 -2
  6. package/admin/src/pages/Details/components/DetailsEntity/index.js +3 -1
  7. package/admin/src/pages/Details/index.js +7 -7
  8. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +2 -2
  9. package/admin/src/pages/Settings/components/RestartAlert/styles.js +8 -0
  10. package/admin/src/pages/Settings/index.js +376 -0
  11. package/admin/src/pages/Settings/utils/api.js +60 -0
  12. package/admin/src/permissions.js +3 -0
  13. package/admin/src/translations/en.json +44 -1
  14. package/admin/src/utils/handleAPIError.js +3 -2
  15. package/admin/src/utils/index.js +2 -1
  16. package/admin/src/utils/parseRegExp.js +10 -0
  17. package/package.json +8 -5
  18. package/server/bootstrap.js +24 -0
  19. package/server/config/index.js +1 -0
  20. package/server/controllers/admin.js +42 -8
  21. package/server/controllers/client.js +4 -4
  22. package/server/controllers/utils/parsers.js +3 -1
  23. package/server/graphql/index.js +7 -8
  24. package/server/graphql/queries/findAllFlat.js +4 -2
  25. package/server/graphql/queries/findAllInHierarchy.js +3 -3
  26. package/server/graphql/resolvers-config.js +2 -4
  27. package/server/graphql/types/report-reason.js +10 -10
  28. package/server/register.js +1 -3
  29. package/server/routes/admin.js +32 -0
  30. package/server/services/__tests__/common.test.js +95 -14
  31. package/server/services/admin.js +57 -6
  32. package/server/services/client.js +14 -8
  33. package/server/services/common.js +69 -11
  34. package/server/services/utils/__tests__/functions.test.js +12 -1
  35. package/server/services/utils/functions.js +9 -1
  36. package/server/utils/constants.js +10 -0
  37. package/strapi-server.js +12 -5
  38. package/.circleci/config.yml +0 -48
  39. package/.eslintrc +0 -35
  40. package/.github/pull_request_template.md +0 -13
  41. package/.nvmrc +0 -1
  42. package/codecov.yml +0 -4
  43. package/public/assets/logo.png +0 -0
  44. package/public/assets/preview.png +0 -0
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ yarn test:unit
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 by linked to any of your Content Types by default. Simply, you're controlling it.
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 detault 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 notifiactions about abuse reports
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>/<commentId>?authorId=<authorId>`
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)*
@@ -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;
@@ -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.name;
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: name,
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
- { !isEmpty(keys) && (<Box marginBottom={4}>
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(config.regex.uid);
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={!contentTypeData?.schema ? (<Box padding={4}>
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>
@@ -0,0 +1,8 @@
1
+ import styled from 'styled-components';
2
+ import { Alert } from '@strapi/design-system/Alert';
3
+
4
+ export const RestartAlert = styled(Alert)`
5
+ [role="status"] {
6
+ flex-direction: column;
7
+ }
8
+ `