strapi-plugin-comments 2.0.2 → 2.0.6
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 +31 -8
- package/__mocks__/initSetup.js +31 -2
- package/admin/src/components/FormSwitch/index.js +18 -0
- package/admin/src/components/FormSwitch/styles.js +8 -0
- package/admin/src/hooks/useConfig.js +49 -0
- package/admin/src/index.js +31 -2
- package/admin/src/pages/Details/index.js +5 -2
- package/admin/src/pages/Settings/components/RestartAlert/styles.js +8 -0
- package/admin/src/pages/Settings/index.js +392 -0
- package/admin/src/pages/Settings/utils/api.js +60 -0
- package/admin/src/permissions.js +3 -0
- package/admin/src/translations/en.json +46 -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 +10 -7
- 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 +11 -2
- package/server/controllers/utils/__tests__/parsers.test.js +94 -0
- package/server/controllers/utils/parsers.js +15 -13
- package/server/graphql/index.js +7 -8
- package/server/graphql/queries/findAllFlat.js +1 -1
- package/server/graphql/resolvers-config.js +2 -4
- package/server/graphql/types/index.js +3 -0
- package/server/graphql/types/report-reason.js +10 -10
- package/server/graphql/types/response-find-all.js +7 -0
- package/server/graphql/types/response-meta.js +6 -0
- package/server/graphql/types/response-pagination.js +11 -0
- 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 +92 -15
- package/server/services/utils/__tests__/functions.test.js +12 -1
- package/server/services/utils/functions.js +9 -1
- package/server/utils/constants.js +9 -0
- package/strapi-server.js +12 -5
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import React, { useRef, useMemo, useState } from 'react';
|
|
2
|
+
import { useQuery } from 'react-query';
|
|
3
|
+
import { Formik } from 'formik';
|
|
4
|
+
import { capitalize, first, orderBy, isEmpty, isEqual } from 'lodash';
|
|
5
|
+
import {
|
|
6
|
+
CheckPermissions,
|
|
7
|
+
LoadingIndicatorPage,
|
|
8
|
+
Form,
|
|
9
|
+
useTracking,
|
|
10
|
+
useNotification,
|
|
11
|
+
useRBAC,
|
|
12
|
+
useFocusWhenNavigate,
|
|
13
|
+
useOverlayBlocker,
|
|
14
|
+
} from '@strapi/helper-plugin';
|
|
15
|
+
import { Accordion, AccordionToggle, AccordionContent, AccordionGroup } from '@strapi/design-system/Accordion';
|
|
16
|
+
import { Main } from '@strapi/design-system/Main';
|
|
17
|
+
import { ContentLayout, HeaderLayout } from '@strapi/design-system/Layout';
|
|
18
|
+
import { Button } from '@strapi/design-system/Button';
|
|
19
|
+
import { Box } from '@strapi/design-system/Box';
|
|
20
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
21
|
+
import { Switch } from '@strapi/design-system/Switch';
|
|
22
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
23
|
+
import { Grid, GridItem } from '@strapi/design-system/Grid';
|
|
24
|
+
import { ToggleInput } from '@strapi/design-system/ToggleInput';
|
|
25
|
+
import { Select, Option } from '@strapi/design-system/Select';
|
|
26
|
+
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
|
27
|
+
import { Tooltip } from '@strapi/design-system/Tooltip';
|
|
28
|
+
import { Check, Refresh, Play, Information } from '@strapi/icons';
|
|
29
|
+
|
|
30
|
+
import pluginPermissions from '../../permissions';
|
|
31
|
+
import useConfig from '../../hooks/useConfig';
|
|
32
|
+
import { fetchAllContentTypes, fetchRoles } from './utils/api';
|
|
33
|
+
import { getMessage, parseRegExp } from '../../utils';
|
|
34
|
+
import ConfirmationDialog from '../../components/ConfirmationDialog';
|
|
35
|
+
import { RestartAlert } from './components/RestartAlert/styles';
|
|
36
|
+
import FormSwitch from '../../components/FormSwitch';
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const Settings = () => {
|
|
40
|
+
useFocusWhenNavigate();
|
|
41
|
+
|
|
42
|
+
const { notifyStatus } = useNotifyAT();
|
|
43
|
+
const { trackUsage } = useTracking();
|
|
44
|
+
const trackUsageRef = useRef(trackUsage);
|
|
45
|
+
const toggleNotification = useNotification();
|
|
46
|
+
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
47
|
+
|
|
48
|
+
const viewPermissions = useMemo(() => ({
|
|
49
|
+
access: pluginPermissions.settings.read,
|
|
50
|
+
change: pluginPermissions.settings.change,
|
|
51
|
+
}), []);
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
isLoading: isLoadingForPermissions,
|
|
55
|
+
allowedActions: { canAccess, canChange },
|
|
56
|
+
} = useRBAC(viewPermissions);
|
|
57
|
+
|
|
58
|
+
const [restoreConfigmationVisible, setRestoreConfigmationVisible] = useState(false);
|
|
59
|
+
const [restartRequired, setRestartRequired] = useState(false);
|
|
60
|
+
const [contentTypeExpanded, setContentTypeExpanded] = useState(undefined);
|
|
61
|
+
|
|
62
|
+
const { fetch, restartMutation, submitMutation, restoreMutation } = useConfig(toggleNotification);
|
|
63
|
+
const { data: configData, isLoading: isConfigLoading, err: configErr } = fetch;
|
|
64
|
+
|
|
65
|
+
const { data: allCollectionsData, isLoading: areCollectionsLoading, err: collectionsErr } = useQuery(
|
|
66
|
+
['get-all-content-types', canAccess],
|
|
67
|
+
() => fetchAllContentTypes(toggleNotification)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const { data: allRolesData, isLoading: areRolesLoading, err: rolesErr } = useQuery(
|
|
71
|
+
['get-all-roles', canAccess],
|
|
72
|
+
() => fetchRoles(toggleNotification)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const isLoading = isLoadingForPermissions || isConfigLoading || areCollectionsLoading || areRolesLoading;
|
|
76
|
+
const isError = configErr || collectionsErr || rolesErr;
|
|
77
|
+
|
|
78
|
+
const preparePayload = ({ enabledCollections, gqlAuthEnabled, approvalFlow, entryLabel, ...rest }) => ({
|
|
79
|
+
...rest,
|
|
80
|
+
enabledCollections,
|
|
81
|
+
approvalFlow: approvalFlow.filter(_ => enabledCollections.includes(_)),
|
|
82
|
+
entryLabel: {
|
|
83
|
+
...Object.keys(entryLabel).reduce((prev, curr) => ({
|
|
84
|
+
...prev,
|
|
85
|
+
[curr]: enabledCollections.includes(curr) ? entryLabel[curr] : undefined,
|
|
86
|
+
}), {}),
|
|
87
|
+
'*': entryLabel['*'],
|
|
88
|
+
},
|
|
89
|
+
reportReasons: configData?.reportReasons,
|
|
90
|
+
gql: gqlAuthEnabled ? { auth: true } : undefined,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (isLoading || isError) {
|
|
94
|
+
return (<LoadingIndicatorPage>
|
|
95
|
+
{ getMessage('page.settings.loading')}
|
|
96
|
+
</LoadingIndicatorPage>);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const regexUID = !isLoading ? new RegExp(
|
|
100
|
+
parseRegExp(fetch.data.regex?.uid).value,
|
|
101
|
+
parseRegExp(fetch.data.regex?.uid).flags
|
|
102
|
+
) : null;
|
|
103
|
+
|
|
104
|
+
const allRoles = allRolesData?.data || [];
|
|
105
|
+
const allCollections = !isLoading && allCollectionsData
|
|
106
|
+
.filter(({ uid }) => first(uid.split(regexUID).filter(s => s && s.length > 0)) === 'api');
|
|
107
|
+
const enabledCollections = configData?.enabledCollections
|
|
108
|
+
?.map(uid => allCollections.find(_ => _.uid === uid) ? uid : undefined)
|
|
109
|
+
.filter(_ => _) || [];
|
|
110
|
+
const entryLabel = configData?.entryLabel || {};
|
|
111
|
+
const approvalFlow = configData?.approvalFlow || [];
|
|
112
|
+
const badWords = configData?.badWords || undefined;
|
|
113
|
+
const isGQLPluginEnabled = configData?.isGQLPluginEnabled;
|
|
114
|
+
const gqlAuthEnabled = configData?.gql?.auth || undefined;
|
|
115
|
+
const moderatorRoles = configData?.moderatorRoles
|
|
116
|
+
?.filter(code => allRoles.find(_ => _.code === code)) || [];
|
|
117
|
+
|
|
118
|
+
const changeApprovalFlowFor = (uid, current, value) => {
|
|
119
|
+
const currentSet = new Set(current);
|
|
120
|
+
if (value) {
|
|
121
|
+
currentSet.add(uid);
|
|
122
|
+
} else {
|
|
123
|
+
currentSet.delete(uid);
|
|
124
|
+
}
|
|
125
|
+
return Array.from(currentSet);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const changeEntryLabelFor = (uid, current, value) => ({
|
|
129
|
+
...current,
|
|
130
|
+
[uid]: value && !isEmpty(value) ? [...value] : undefined,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const handleUpdateConfiguration = async (form) => {
|
|
134
|
+
if (canChange) {
|
|
135
|
+
lockApp();
|
|
136
|
+
const payload = preparePayload(form);
|
|
137
|
+
await submitMutation.mutateAsync(payload);
|
|
138
|
+
const enabledCollectionsChanged = !isEqual(payload.enabledCollections, configData?.enabledCollections);
|
|
139
|
+
const gqlAuthChanged = !isEqual(payload.gql?.auth, configData?.gql?.auth);
|
|
140
|
+
if (enabledCollectionsChanged || gqlAuthChanged) {
|
|
141
|
+
setRestartRequired(true);
|
|
142
|
+
}
|
|
143
|
+
unlockApp();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const handleRestoreConfirmation = () => setRestoreConfigmationVisible(true);
|
|
148
|
+
const handleRestoreConfiguration = async () => {
|
|
149
|
+
if (canChange) {
|
|
150
|
+
lockApp();
|
|
151
|
+
await restoreMutation.mutateAsync();
|
|
152
|
+
unlockApp();
|
|
153
|
+
setRestartRequired(true);
|
|
154
|
+
setRestoreConfigmationVisible(false);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const handleRestoreCancel = () => setRestoreConfigmationVisible(false);
|
|
158
|
+
|
|
159
|
+
const handleRestart = async () => {
|
|
160
|
+
if (canChange) {
|
|
161
|
+
lockApp();
|
|
162
|
+
await restartMutation.mutateAsync();
|
|
163
|
+
setRestartRequired(false);
|
|
164
|
+
unlockApp();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const handleRestartDiscard = () => setRestartRequired(false);
|
|
168
|
+
|
|
169
|
+
const handleSetContentTypeExpanded = key => setContentTypeExpanded(key === contentTypeExpanded ? undefined : key);
|
|
170
|
+
|
|
171
|
+
const boxDefaultProps = {
|
|
172
|
+
background: "neutral0",
|
|
173
|
+
hasRadius: true,
|
|
174
|
+
shadow: "filterShadow",
|
|
175
|
+
padding: 6,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<Main>
|
|
180
|
+
<Formik
|
|
181
|
+
initialValues={{
|
|
182
|
+
enabledCollections,
|
|
183
|
+
moderatorRoles,
|
|
184
|
+
badWords,
|
|
185
|
+
approvalFlow,
|
|
186
|
+
entryLabel,
|
|
187
|
+
gqlAuthEnabled,
|
|
188
|
+
}}
|
|
189
|
+
enableReinitialize={true}
|
|
190
|
+
onSubmit={handleUpdateConfiguration}
|
|
191
|
+
>
|
|
192
|
+
{({ handleSubmit, setFieldValue, values }) => (
|
|
193
|
+
<Form noValidate onSubmit={handleSubmit}>
|
|
194
|
+
<HeaderLayout
|
|
195
|
+
title={getMessage('page.settings.header.title')}
|
|
196
|
+
subtitle={getMessage('page.settings.header.description')}
|
|
197
|
+
primaryAction={
|
|
198
|
+
<CheckPermissions permissions={pluginPermissions.settingsChange}>
|
|
199
|
+
<Button type="submit" startIcon={<Check />} disabled={restartRequired} >
|
|
200
|
+
{getMessage('page.settings.actions.submit')}
|
|
201
|
+
</Button>
|
|
202
|
+
</CheckPermissions>
|
|
203
|
+
}
|
|
204
|
+
/>
|
|
205
|
+
<ContentLayout>
|
|
206
|
+
<Stack size={4}>
|
|
207
|
+
{ restartRequired && (
|
|
208
|
+
<RestartAlert
|
|
209
|
+
closeLabel={getMessage('page.settings.actions.restart.alert.cancel')}
|
|
210
|
+
title={getMessage('page.settings.actions.restart.alert.title')}
|
|
211
|
+
action={<Box><Button onClick={handleRestart} startIcon={<Play />}>{getMessage('page.settings.actions.restart')}</Button></Box>}
|
|
212
|
+
onClose={handleRestartDiscard}>
|
|
213
|
+
{ getMessage('page.settings.actions.restart.alert.description')}
|
|
214
|
+
</RestartAlert>)}
|
|
215
|
+
|
|
216
|
+
<Box {...boxDefaultProps}>
|
|
217
|
+
<Stack size={4}>
|
|
218
|
+
<Typography variant="delta" as="h2">
|
|
219
|
+
{getMessage('page.settings.section.general')}
|
|
220
|
+
</Typography>
|
|
221
|
+
<Grid gap={4}>
|
|
222
|
+
<GridItem col={12}>
|
|
223
|
+
<Select
|
|
224
|
+
name="enabledCollections"
|
|
225
|
+
label={getMessage('page.settings.form.enabledCollections.label')}
|
|
226
|
+
placeholder={getMessage('page.settings.form.enabledCollections.placeholder')}
|
|
227
|
+
hint={getMessage('page.settings.form.enabledCollections.hint')}
|
|
228
|
+
onClear={() => setFieldValue('enabledCollections', [], false)}
|
|
229
|
+
value={values.enabledCollections}
|
|
230
|
+
onChange={(value) => setFieldValue('enabledCollections', value, false)}
|
|
231
|
+
disabled={restartRequired}
|
|
232
|
+
multi
|
|
233
|
+
withTags
|
|
234
|
+
>
|
|
235
|
+
{ allCollections.map(({ uid, schema: { displayName } }) =>
|
|
236
|
+
(<Option key={uid} value={uid}>{displayName}</Option>))}
|
|
237
|
+
</Select>
|
|
238
|
+
</GridItem>
|
|
239
|
+
{ !isEmpty(values.enabledCollections) && (
|
|
240
|
+
<GridItem col={12}>
|
|
241
|
+
<AccordionGroup
|
|
242
|
+
label={getMessage('page.settings.form.contentTypesSettings.label')}
|
|
243
|
+
labelAction={<Tooltip description={getMessage('page.settings.form.contentTypesSettings.tooltip')}>
|
|
244
|
+
<Information aria-hidden={true} />
|
|
245
|
+
</Tooltip>}>
|
|
246
|
+
{ orderBy(values.enabledCollections).map(uid => {
|
|
247
|
+
const { schema: { displayName, attributes = {} } } = allCollections.find(_ => _.uid === uid);
|
|
248
|
+
const stringAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'string');
|
|
249
|
+
const key = `collectionSettings-${uid}`;
|
|
250
|
+
return (<Accordion
|
|
251
|
+
expanded={contentTypeExpanded === key}
|
|
252
|
+
toggle={() => handleSetContentTypeExpanded(key)}
|
|
253
|
+
key={key}
|
|
254
|
+
id={key}
|
|
255
|
+
size="S">
|
|
256
|
+
<AccordionToggle title={displayName} togglePosition="left" />
|
|
257
|
+
<AccordionContent>
|
|
258
|
+
<Box padding={6}>
|
|
259
|
+
<Stack size={4}>
|
|
260
|
+
<FormSwitch
|
|
261
|
+
name={`collectionSettings-${uid}-approvalFlow`}
|
|
262
|
+
label={getMessage('page.settings.form.approvalFlow.label')}
|
|
263
|
+
hint={getMessage({
|
|
264
|
+
id: 'page.settings.form.approvalFlow.hint',
|
|
265
|
+
props: { name: displayName },
|
|
266
|
+
})}
|
|
267
|
+
selected={values.approvalFlow.includes(uid)}
|
|
268
|
+
onChange={() => setFieldValue('approvalFlow', changeApprovalFlowFor(uid, values.approvalFlow, !values.approvalFlow.includes(uid)), [])}
|
|
269
|
+
onLabel={getMessage('compontents.toogle.enabled')}
|
|
270
|
+
offLabel={getMessage('compontents.toogle.disabled')}
|
|
271
|
+
disabled={restartRequired}
|
|
272
|
+
visibleLabels />
|
|
273
|
+
{ !isEmpty(stringAttributes) && (<Select
|
|
274
|
+
name={`collectionSettings-${uid}-entryLabel`}
|
|
275
|
+
label={getMessage('page.settings.form.entryLabel.label')}
|
|
276
|
+
placeholder={getMessage('page.settings.form.entryLabel.placeholder')}
|
|
277
|
+
hint={getMessage('page.settings.form.entryLabel.hint')}
|
|
278
|
+
onClear={() => setFieldValue('entryLabel', changeEntryLabelFor(uid, values.entryLabel))}
|
|
279
|
+
value={values.entryLabel[uid] || []}
|
|
280
|
+
onChange={(value) => setFieldValue('entryLabel', changeEntryLabelFor(uid, values.entryLabel, value))}
|
|
281
|
+
multi
|
|
282
|
+
withTags
|
|
283
|
+
disabled={restartRequired}
|
|
284
|
+
>
|
|
285
|
+
{ stringAttributes.map(key =>
|
|
286
|
+
(<Option key={`collectionSettings-${uid}-entryLabel-${key}`} value={key}>{ capitalize(key.split('_').join(' ')) }</Option>))}
|
|
287
|
+
</Select>) }
|
|
288
|
+
</Stack>
|
|
289
|
+
</Box>
|
|
290
|
+
</AccordionContent>
|
|
291
|
+
</Accordion>);
|
|
292
|
+
})}
|
|
293
|
+
</AccordionGroup>
|
|
294
|
+
</GridItem>)}
|
|
295
|
+
</Grid>
|
|
296
|
+
</Stack>
|
|
297
|
+
</Box>
|
|
298
|
+
|
|
299
|
+
<Box {...boxDefaultProps}>
|
|
300
|
+
<Stack size={4}>
|
|
301
|
+
<Typography variant="delta" as="h2">
|
|
302
|
+
{getMessage('page.settings.section.additional')}
|
|
303
|
+
</Typography>
|
|
304
|
+
<Grid gap={4}>
|
|
305
|
+
<GridItem col={6} xs={12}>
|
|
306
|
+
<Select
|
|
307
|
+
name="moderatorRoles"
|
|
308
|
+
label={getMessage('page.settings.form.moderatorRoles.label')}
|
|
309
|
+
placeholder={getMessage('page.settings.form.moderatorRoles.placeholder')}
|
|
310
|
+
hint={getMessage('page.settings.form.moderatorRoles.hint')}
|
|
311
|
+
onClear={() => setFieldValue('moderatorRoles', [], false)}
|
|
312
|
+
value={values.moderatorRoles}
|
|
313
|
+
onChange={(value) => setFieldValue('moderatorRoles', value, false)}
|
|
314
|
+
disabled={restartRequired}
|
|
315
|
+
multi
|
|
316
|
+
withTags
|
|
317
|
+
>
|
|
318
|
+
{ allRoles.map(({ code, name }) =>
|
|
319
|
+
(<Option key={code} value={code}>{name}</Option>))}
|
|
320
|
+
</Select>
|
|
321
|
+
</GridItem>
|
|
322
|
+
<GridItem col={6} xs={12}>
|
|
323
|
+
<ToggleInput
|
|
324
|
+
name="badWords"
|
|
325
|
+
label={getMessage('page.settings.form.badWords.label')}
|
|
326
|
+
hint={getMessage('page.settings.form.badWords.hint')}
|
|
327
|
+
checked={values.badWords}
|
|
328
|
+
onChange={({ target: { checked } }) => setFieldValue('badWords', checked, false)}
|
|
329
|
+
onLabel={getMessage('compontents.toogle.enabled')}
|
|
330
|
+
offLabel={getMessage('compontents.toogle.disabled')}
|
|
331
|
+
disabled={restartRequired}
|
|
332
|
+
/>
|
|
333
|
+
</GridItem>
|
|
334
|
+
{ isGQLPluginEnabled && (<GridItem col={6} xs={12}>
|
|
335
|
+
<ToggleInput
|
|
336
|
+
name="badWords"
|
|
337
|
+
label={getMessage('page.settings.form.gqlAuth.label')}
|
|
338
|
+
hint={getMessage('page.settings.form.gqlAuth.hint')}
|
|
339
|
+
checked={values.gqlAuthEnabled}
|
|
340
|
+
onChange={({ target: { checked } }) => setFieldValue('gqlAuthEnabled', checked, false)}
|
|
341
|
+
onLabel={getMessage('compontents.toogle.enabled')}
|
|
342
|
+
offLabel={getMessage('compontents.toogle.disabled')}
|
|
343
|
+
disabled={restartRequired}
|
|
344
|
+
/>
|
|
345
|
+
</GridItem>) }
|
|
346
|
+
</Grid>
|
|
347
|
+
</Stack>
|
|
348
|
+
</Box>
|
|
349
|
+
|
|
350
|
+
<CheckPermissions permissions={pluginPermissions.settingsChange}>
|
|
351
|
+
<Box {...boxDefaultProps}>
|
|
352
|
+
<Stack size={4}>
|
|
353
|
+
<Stack size={2}>
|
|
354
|
+
<Typography variant="delta" as="h2">
|
|
355
|
+
{getMessage('page.settings.section.restore')}
|
|
356
|
+
</Typography>
|
|
357
|
+
<Typography variant="pi"as="h4">
|
|
358
|
+
{getMessage('page.settings.section.restore.subtitle')}
|
|
359
|
+
</Typography>
|
|
360
|
+
</Stack>
|
|
361
|
+
<Grid gap={4}>
|
|
362
|
+
<GridItem col={6}>
|
|
363
|
+
<Button variant="danger-light" startIcon={<Refresh />} onClick={handleRestoreConfirmation}>
|
|
364
|
+
{getMessage('page.settings.actions.restore')}
|
|
365
|
+
</Button>
|
|
366
|
+
|
|
367
|
+
<ConfirmationDialog
|
|
368
|
+
isVisible={restoreConfigmationVisible}
|
|
369
|
+
isActionAsync={restoreMutation.isLoading}
|
|
370
|
+
header={getMessage('page.settings.actions.restore.confirmation.header')}
|
|
371
|
+
labelConfirm={getMessage('page.settings.actions.restore.confirmation.button.confirm')}
|
|
372
|
+
iconConfirm={<Refresh />}
|
|
373
|
+
onConfirm={handleRestoreConfiguration}
|
|
374
|
+
onCancel={handleRestoreCancel}>
|
|
375
|
+
{ getMessage('page.settings.actions.restore.confirmation.description') }
|
|
376
|
+
</ConfirmationDialog>
|
|
377
|
+
</GridItem>
|
|
378
|
+
</Grid>
|
|
379
|
+
</Stack>
|
|
380
|
+
</Box>
|
|
381
|
+
</CheckPermissions>
|
|
382
|
+
</Stack>
|
|
383
|
+
</ContentLayout>
|
|
384
|
+
</Form>
|
|
385
|
+
)}
|
|
386
|
+
</Formik>
|
|
387
|
+
</Main>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
export default Settings;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
4
|
+
export const fetchConfig = async (toggleNotification) => {
|
|
5
|
+
try {
|
|
6
|
+
const { data } = await axiosInstance.get(getApiURL(`settings/config`));
|
|
7
|
+
|
|
8
|
+
return data;
|
|
9
|
+
} catch (err) {
|
|
10
|
+
handleAPIError(err, toggleNotification, 'page.settings.notification.fetch.error');
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const updateConfig = async (body, toggleNotification) => {
|
|
15
|
+
try {
|
|
16
|
+
const { data } = await axiosInstance.put(getApiURL(`settings/config`), body);
|
|
17
|
+
|
|
18
|
+
return data;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
handleAPIError(err, toggleNotification);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const restoreConfig = async (toggleNotification) => {
|
|
25
|
+
try {
|
|
26
|
+
const { data } = await axiosInstance.delete(getApiURL(`settings/config`));
|
|
27
|
+
|
|
28
|
+
return data;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
handleAPIError(err, toggleNotification);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const fetchAllContentTypes = async (toggleNotification) => {
|
|
35
|
+
try {
|
|
36
|
+
const { data } = await axiosInstance.get(`/content-type-builder/content-types`);
|
|
37
|
+
return data?.data;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
handleAPIError(err, toggleNotification);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const fetchRoles = async (toggleNotification) => {
|
|
44
|
+
try {
|
|
45
|
+
const { data } = await axiosInstance.get(`/admin/roles`);
|
|
46
|
+
return data;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
handleAPIError(err, toggleNotification);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const restartStrapi = async (toggleNotification) => {
|
|
53
|
+
try {
|
|
54
|
+
const { data } = await axiosInstance.get(getApiURL(`settings/restart`));
|
|
55
|
+
|
|
56
|
+
return data;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
handleAPIError(err, toggleNotification);
|
|
59
|
+
}
|
|
60
|
+
};
|
package/admin/src/permissions.js
CHANGED
|
@@ -6,11 +6,14 @@ const pluginPermissions = {
|
|
|
6
6
|
{ action: permissions.render(permissions.reports.read), subject: null },
|
|
7
7
|
// { action: 'plugin::comments.discover.read', subject: null },
|
|
8
8
|
{ action: permissions.render(permissions.settings.read), subject: null },
|
|
9
|
+
{ action: permissions.render(permissions.settings.read), subject: null },
|
|
9
10
|
],
|
|
10
11
|
// discover: [{ action: 'plugin::comments.discover.read', subject: null }],
|
|
11
12
|
moderate: [{ action: permissions.render(permissions.comments.moderate), subject: null }],
|
|
12
13
|
reports: [{ action: permissions.render(permissions.reports.read), subject: null }],
|
|
13
14
|
reportsReview: [{ action: permissions.render(permissions.reports.action), subject: null }],
|
|
15
|
+
settings: [{ action: permissions.render(permissions.settings.read), subject: null }],
|
|
16
|
+
settingsChange: [{ action: permissions.render(permissions.settings.change), subject: null }],
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
export default pluginPermissions;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"plugin.name": "Comments",
|
|
3
|
+
"plugin.section": "Comments plugin",
|
|
4
|
+
"plugin.section.item": "Configuration",
|
|
3
5
|
"common.button.back": "Go back",
|
|
4
6
|
"nav.header.moderation": "Moderation",
|
|
5
7
|
"nav.header.settings": "Settings",
|
|
@@ -79,8 +81,51 @@
|
|
|
79
81
|
"page.details.filters.label": "View",
|
|
80
82
|
"page.details.filters.removed.visibility": "Show removed comments",
|
|
81
83
|
"page.coming.soon": "Wait for what is coming soon...",
|
|
84
|
+
"page.settings": "Settings",
|
|
85
|
+
"page.settings.header.title": "Comments",
|
|
86
|
+
"page.settings.header.description": "Configure your comments moderation capabilities",
|
|
87
|
+
"page.settings.actions.submit": "Save",
|
|
88
|
+
"page.settings.actions.restore": "Restore default settings",
|
|
89
|
+
"page.settings.actions.restart": "Restart Strapi",
|
|
90
|
+
"page.settings.section.general": "General configuration",
|
|
91
|
+
"page.settings.section.additional": "Additional configuration",
|
|
92
|
+
"page.settings.section.restore": "Restore default settings",
|
|
93
|
+
"page.settings.section.restore.subtitle": "Discarding all of applied settings and getting back to plugin default ones. Use reasonable.",
|
|
94
|
+
"page.settings.form.enabledCollections.label": "Enable comments only for",
|
|
95
|
+
"page.settings.form.enabledCollections.placeholder": "Select one or more collection",
|
|
96
|
+
"page.settings.form.enabledCollections.hint": "If none is selected, all the content types are enabled",
|
|
97
|
+
"page.settings.form.contentTypesSettings.label": "Content types",
|
|
98
|
+
"page.settings.form.contentTypesSettings.tooltip": "Custom configuration per content type",
|
|
99
|
+
"page.settings.form.moderatorRoles.label": "Send significant notifications to",
|
|
100
|
+
"page.settings.form.moderatorRoles.placeholder": "Select one or more roles",
|
|
101
|
+
"page.settings.form.moderatorRoles.hint": "Roles which are going to be notified by the plugin about significant actions to perform",
|
|
102
|
+
"page.settings.form.badWords.label": "Bad words filtering",
|
|
103
|
+
"page.settings.form.badWords.hint": "If enabled, every post / update of comment is going to be checked against bad wording",
|
|
104
|
+
"page.settings.form.gqlAuth.label": "GraphQL queries authorization",
|
|
105
|
+
"page.settings.form.gqlAuth.hint": "If enabled, GraphQL API queries & mutations can be triggered only by Authenticated Strapi users. Otherwise API remains open.",
|
|
106
|
+
"page.settings.form.approvalFlow.label": "Approval flow",
|
|
107
|
+
"page.settings.form.approvalFlow.hint": "Comments associated with content type \"{name}\" are going to be taken through manual approval flow",
|
|
108
|
+
"page.settings.form.entryLabel.label": "Title fields",
|
|
109
|
+
"page.settings.form.entryLabel.placeholder": "Select at least one or leave empty to apply defaults",
|
|
110
|
+
"page.settings.form.entryLabel.hint": "If left empty title rendering is going to take following ordered fields: \"Title\", \"Subject\" & \"Name\"",
|
|
111
|
+
"page.settings.actions.restore.confirmation.header": "Restore default configuration",
|
|
112
|
+
"page.settings.actions.restore.confirmation.description": "You're about to restore plugin configuration to it default values. It might have destructive impact on already collected content. Do you really want to proceed?",
|
|
113
|
+
"page.settings.actions.restore.confirmation.button.confirm": "Yes, I want to restore",
|
|
114
|
+
"page.settings.actions.restart.alert.title": "Strapi requires restart",
|
|
115
|
+
"page.settings.actions.restart.alert.description": "You've made a configuration changes which requires your Strapi application to be restarted to take an effect in GraphQL schema. Do it manually or by using below trigger.",
|
|
116
|
+
"page.settings.actions.restart.alert.close": "Discard",
|
|
117
|
+
"page.settings.notification.fetch.error": "Failed to fetch configuration. Retrying...",
|
|
118
|
+
"page.settings.notification.submit.success": "Configuration has been saved successfully",
|
|
119
|
+
"page.settings.notification.submit.error": "Failed to save configuration. Try again.",
|
|
120
|
+
"page.settings.notification.restore.success": "Configuration has been restored successfully",
|
|
121
|
+
"page.settings.notification.restore.error": "Failed to restore configuration. Try again.",
|
|
122
|
+
"page.settings.notification.restart.success": "Application has been restarted successfully",
|
|
123
|
+
"page.settings.notification.restart.error": "Failed to restart your application. Try to do it manually.",
|
|
124
|
+
"page.settings.loading": "Fetching configuration...",
|
|
82
125
|
"compontents.confirmation.dialog.header": "Confirmation",
|
|
83
126
|
"compontents.confirmation.dialog.description": "Do you really want to perform this action?",
|
|
84
127
|
"compontents.confirmation.dialog.button.confirm": "Yes, I do",
|
|
85
|
-
"compontents.confirmation.dialog.button.cancel": "Cancel"
|
|
128
|
+
"compontents.confirmation.dialog.button.cancel": "Cancel",
|
|
129
|
+
"compontents.toogle.enabled": "Enabled",
|
|
130
|
+
"compontents.toogle.disabled": "Disabled"
|
|
86
131
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import pluginId from "../pluginId";
|
|
1
2
|
|
|
2
|
-
const handleAPIError = (err = null, toggleNotification = null) => {
|
|
3
|
+
const handleAPIError = (err = null, toggleNotification = null, message = 'app.components.notification.error') => {
|
|
3
4
|
toggleNotification({
|
|
4
5
|
type: 'warning',
|
|
5
|
-
message:
|
|
6
|
+
message: `${pluginId}.${message}`,
|
|
6
7
|
});
|
|
7
8
|
|
|
8
9
|
if (err) {
|
package/admin/src/utils/index.js
CHANGED
|
@@ -7,4 +7,5 @@ export { default as getRelatedGroups } from './getRelatedGroups';
|
|
|
7
7
|
export { default as renderEntryTitle } from './renderEntryTitle';
|
|
8
8
|
export { default as renderInitials } from './renderInitials';
|
|
9
9
|
export { default as resolveCommentStatus } from './resolveCommentStatus';
|
|
10
|
-
export { default as resolveCommentStatusColor } from './resolveCommentStatusColor';
|
|
10
|
+
export { default as resolveCommentStatusColor } from './resolveCommentStatusColor';
|
|
11
|
+
export { default as parseRegExp } from './parseRegExp';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-comments",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Strapi - Comments plugin",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"name": "comments",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"kind": "plugin"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"
|
|
12
|
+
"prepare": "husky install",
|
|
13
|
+
"publish:latest": "npm publish --tag latest",
|
|
13
14
|
"test:unit": "jest --verbose --coverage"
|
|
14
15
|
},
|
|
15
16
|
"repository": {
|
|
@@ -17,8 +18,8 @@
|
|
|
17
18
|
"url": "https://github.com/VirtusLab-Open-Source/strapi-plugin-comments"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@strapi/utils": "^4.0
|
|
21
|
-
"@strapi/plugin-graphql": "^4.0
|
|
21
|
+
"@strapi/utils": "^4.1.0",
|
|
22
|
+
"@strapi/plugin-graphql": "^4.1.0",
|
|
22
23
|
"bad-words": "^3.0.4"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
@@ -26,14 +27,16 @@
|
|
|
26
27
|
"jest": "^26.0.1",
|
|
27
28
|
"jest-cli": "^26.0.1",
|
|
28
29
|
"jest-styled-components": "^7.0.2",
|
|
29
|
-
"koa": "2.13.3"
|
|
30
|
+
"koa": "2.13.3",
|
|
31
|
+
"husky": "7.0.4",
|
|
32
|
+
"prettier": "2.5.1"
|
|
30
33
|
},
|
|
31
34
|
"peerDependencies": {
|
|
32
35
|
"@strapi/strapi": "4.x"
|
|
33
36
|
},
|
|
34
37
|
"author": {
|
|
35
|
-
"name": "VirtusLab
|
|
36
|
-
"email": "
|
|
38
|
+
"name": "VirtusLab",
|
|
39
|
+
"email": "strapi@virtuslab.com",
|
|
37
40
|
"url": "https://virtuslab.com"
|
|
38
41
|
},
|
|
39
42
|
"maintainers": [
|
package/server/bootstrap.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { isEmpty } = require('lodash');
|
|
3
4
|
const permissions = require('./../permissions');
|
|
5
|
+
const { getPluginService } = require('./utils/functions');
|
|
4
6
|
|
|
5
7
|
module.exports = async ({ strapi }) => {
|
|
8
|
+
|
|
9
|
+
// Provide GQL support
|
|
10
|
+
if (strapi.plugin('graphql')) {
|
|
11
|
+
const config = await getPluginService('common').getConfig();
|
|
12
|
+
const { enabledCollections } = config;
|
|
13
|
+
if (!isEmpty(enabledCollections)) {
|
|
14
|
+
await require('./graphql')({ strapi, config });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
// Check if the plugin users-permissions is installed because the navigation needs it
|
|
7
19
|
if (Object.keys(strapi.plugins).indexOf("users-permissions") === -1) {
|
|
8
20
|
throw new Error(
|
|
@@ -35,6 +47,18 @@ module.exports = async ({ strapi }) => {
|
|
|
35
47
|
uid: permissions.reports.action,
|
|
36
48
|
pluginName: "comments",
|
|
37
49
|
},
|
|
50
|
+
{
|
|
51
|
+
section: "plugins",
|
|
52
|
+
displayName: "Settings: Read",
|
|
53
|
+
uid: permissions.settings.read,
|
|
54
|
+
pluginName: "comments",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
section: "plugins",
|
|
58
|
+
displayName: "Settings: Change",
|
|
59
|
+
uid: permissions.settings.change,
|
|
60
|
+
pluginName: "comments",
|
|
61
|
+
},
|
|
38
62
|
];
|
|
39
63
|
|
|
40
64
|
await strapi.admin.services.permission.actionProvider.registerMany(actions);
|