diff --git a/README.md b/README.md index be687bae9..dcffbbcb0 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,7 @@ To test out the email sending functionality, you can run [inbucket](https://www. which provides a test SMTP server that can visualize emails in the browser: ```bash -docker run -d --name inbucket -p 2500:2500 -p 9000:9000 inbucket/inbucket +docker run -d --name inbucket --restart always -p 2500:2500 -p 9000:9000 inbucket/inbucket ``` You can then configure the email server on Coral by updating the Tenant with: diff --git a/src/core/client/admin/routes/Configure/OnOffField.tsx b/src/core/client/admin/routes/Configure/OnOffField.tsx index c609ec9e6..c0a284011 100644 --- a/src/core/client/admin/routes/Configure/OnOffField.tsx +++ b/src/core/client/admin/routes/Configure/OnOffField.tsx @@ -13,6 +13,8 @@ interface Props { invert?: boolean; onLabel?: React.ReactNode; offLabel?: React.ReactNode; + format?: ((value: any, name: string) => any) | null; + parse?: ((value: any, name: string) => any) | null; } const OnOffField: FunctionComponent = ({ @@ -21,9 +23,17 @@ const OnOffField: FunctionComponent = ({ onLabel, offLabel, invert = false, + parse = parseStringBool, + format, }) => (
- + {({ input }) => ( = ({ )} - + {({ input }) => ( ["settings"] & - PropTypesOf["settings"]; + PropTypesOf["settings"] & + PropTypesOf["settings"]; onInitValues: (values: any) => void; } @@ -19,6 +21,11 @@ const ModerationConfig: FunctionComponent = ({ onInitValues, }) => ( + ({ fragment ModerationConfigContainer_settings on Settings { ...AkismetConfigContainer_settings ...PerspectiveConfigContainer_settings + ...PreModerationConfigContainer_settings } `, })(ModerationConfigContainer); diff --git a/src/core/client/admin/routes/Configure/sections/Moderation/PreModerationConfig.tsx b/src/core/client/admin/routes/Configure/sections/Moderation/PreModerationConfig.tsx new file mode 100644 index 000000000..839796c92 --- /dev/null +++ b/src/core/client/admin/routes/Configure/sections/Moderation/PreModerationConfig.tsx @@ -0,0 +1,65 @@ +import { Localized } from "fluent-react/compat"; +import React, { FunctionComponent } from "react"; + +import { parseStringBool } from "coral-framework/lib/form"; +import { + FieldSet, + FormField, + HorizontalGutter, + InputLabel, + Typography, +} from "coral-ui/components"; + +import Header from "../../Header"; +import OnOffField from "../../OnOffField"; + +interface Props { + disabled: boolean; +} + +const parse = (v: string) => { + return parseStringBool(v) ? "PRE" : "POST"; +}; + +const format = (v: "PRE" | "POST") => { + return v === "PRE"; +}; + +const PreModerationConfig: FunctionComponent = ({ disabled }) => { + return ( + }> + +
Pre-moderation
+
+ + + When pre-moderation is turned on, comments will not be published + unless approved by a moderator. + + + }> + + + Pre-moderate all comments sitewide + + + + + }> + + + Pre-moderate comments containing links sitewide + + + + +
+ ); +}; + +export default PreModerationConfig; diff --git a/src/core/client/admin/routes/Configure/sections/Moderation/PreModerationConfigContainer.tsx b/src/core/client/admin/routes/Configure/sections/Moderation/PreModerationConfigContainer.tsx new file mode 100644 index 000000000..95e5198a5 --- /dev/null +++ b/src/core/client/admin/routes/Configure/sections/Moderation/PreModerationConfigContainer.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { graphql } from "react-relay"; + +import { PreModerationConfigContainer_settings as SettingsData } from "coral-admin/__generated__/PreModerationConfigContainer_settings.graphql"; +import { withFragmentContainer } from "coral-framework/lib/relay"; + +import PreModerationConfig from "./PreModerationConfig"; + +interface Props { + settings: SettingsData; + onInitValues: (values: SettingsData) => void; + disabled: boolean; +} + +class PreModerationConfigContainer extends React.Component { + constructor(props: Props) { + super(props); + props.onInitValues(props.settings); + } + + public render() { + const { disabled } = this.props; + return ; + } +} + +const enhanced = withFragmentContainer({ + settings: graphql` + fragment PreModerationConfigContainer_settings on Settings { + moderation + premodLinksEnable + } + `, +})(PreModerationConfigContainer); + +export default enhanced; diff --git a/src/core/client/admin/routes/Configure/sections/Moderation/__snapshots__/ModerationConfig.spec.tsx.snap b/src/core/client/admin/routes/Configure/sections/Moderation/__snapshots__/ModerationConfig.spec.tsx.snap index ab24a8aae..7bd009582 100644 --- a/src/core/client/admin/routes/Configure/sections/Moderation/__snapshots__/ModerationConfig.spec.tsx.snap +++ b/src/core/client/admin/routes/Configure/sections/Moderation/__snapshots__/ModerationConfig.spec.tsx.snap @@ -5,6 +5,11 @@ exports[`renders correctly 1`] = ` data-testid="configure-moderationContainer" size="double" > + +
+ + Pre-moderation + +

+ When pre-moderation is turned on, comments will not be published unless +approved by a moderator. +

+
+ + Pre-moderate all comments sitewide + +
+
+ + +
+
+ + +
+
+
+
+ + Pre-moderate comments containing links sitewide + +
+
+ + +
+
+ + +
+
+
+
diff --git a/src/core/client/admin/test/configure/moderation.spec.tsx b/src/core/client/admin/test/configure/moderation.spec.tsx index b0883acb6..784b8da42 100644 --- a/src/core/client/admin/test/configure/moderation.spec.tsx +++ b/src/core/client/admin/test/configure/moderation.spec.tsx @@ -1,5 +1,5 @@ import { pureMerge } from "coral-common/utils"; -import { GQLResolver } from "coral-framework/schema"; +import { GQLMODERATION_MODE, GQLResolver } from "coral-framework/schema"; import { createResolversStub, CreateTestRendererParams, @@ -60,6 +60,102 @@ it("renders configure moderation", async () => { expect(within(configureContainer).toJSON()).toMatchSnapshot(); }); +it("change site wide pre-moderation", async () => { + const resolvers = createResolversStub({ + Mutation: { + updateSettings: ({ variables }) => { + expectAndFail(variables.settings.moderation).toEqual( + GQLMODERATION_MODE.PRE + ); + return { + settings: pureMerge(settings, variables.settings), + }; + }, + }, + }); + const { + configureContainer, + moderationContainer, + saveChangesButton, + } = await createTestRenderer({ + resolvers, + }); + + const preModerationContainer = within(moderationContainer).getAllByText( + "Pre-moderate all comments sitewide", + { selector: "fieldset" } + )[0]; + + const onField = within(preModerationContainer).getByLabelText("On"); + + // Let's enable it. + onField.props.onChange(onField.props.value.toString()); + + // Send form + within(configureContainer) + .getByType("form") + .props.onSubmit(); + + // Submit button and text field should be disabled. + expect(saveChangesButton.props.disabled).toBe(true); + expect(onField.props.disabled).toBe(true); + + // Wait for submission to be finished + await wait(() => { + expect(onField.props.disabled).toBe(false); + }); + + // Should have successfully sent with server. + expect(resolvers.Mutation!.updateSettings!.called).toBe(true); +}); + +it("change site wide link pre-moderation", async () => { + const resolvers = createResolversStub({ + Mutation: { + updateSettings: ({ variables }) => { + expectAndFail(variables.settings.premodLinksEnable).toEqual(true); + return { + settings: pureMerge(settings, variables.settings), + }; + }, + }, + }); + const { + configureContainer, + moderationContainer, + saveChangesButton, + } = await createTestRenderer({ + resolvers, + }); + + const preModerationContainer = within(moderationContainer).getAllByText( + "Pre-moderate comments containing links sitewide", + { selector: "fieldset" } + )[0]; + + const onField = within(preModerationContainer).getByLabelText("On"); + + // Let's enable it. + onField.props.onChange(onField.props.value.toString()); + + // Send form + within(configureContainer) + .getByType("form") + .props.onSubmit(); + + // Submit button and text field should be disabled. + expect(saveChangesButton.props.disabled).toBe(true); + expect(onField.props.disabled).toBe(true); + + // Wait for submission to be finished + await wait(() => { + expect(onField.props.disabled).toBe(false); + }); + + // Should have successfully sent with server. + expect(resolvers.Mutation!.updateSettings!.called).toBe(true); +}); + it("change akismet settings", async () => { const resolvers = createResolversStub({ Mutation: { diff --git a/src/locales/en-US/admin.ftl b/src/locales/en-US/admin.ftl index 97a4ab582..2b10007bf 100644 --- a/src/locales/en-US/admin.ftl +++ b/src/locales/en-US/admin.ftl @@ -215,6 +215,16 @@ configure-auth-oidc-jwksURI = JWKS URI configure-auth-oidc-useLoginOn = Use OpenID Connect login on ### Moderation +#### Pre-Moderation +configure-moderation-preModeration-title = Pre-moderation +configure-moderation-preModeration-explanation = + When pre-moderation is turned on, comments will not be published unless + approved by a moderator. +configure-moderation-preModeration-moderation = + Pre-moderate all comments sitewide +configure-moderation-preModeration-premodLinksEnable = + Pre-moderate comments containing links sitewide + configure-moderation-apiKey = API Key configure-moderation-akismet-title = Akismet Spam Detection Filter