mirror of
https://github.com/wassname/talk.git
synced 2026-08-09 12:30:42 +08:00
[CORL-116] Configure sitewide commenting (#2193)
* feat: configure sitewide commenting * fix: repaired snapshots * fix: updated snapshots * test: update snapshots
This commit is contained in:
@@ -11,48 +11,56 @@ interface Props {
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
invert?: boolean;
|
||||
onLabel?: React.ReactNode;
|
||||
offLabel?: React.ReactNode;
|
||||
}
|
||||
|
||||
const OnOffField: StatelessComponent<Props> = ({
|
||||
name,
|
||||
disabled,
|
||||
onLabel,
|
||||
offLabel,
|
||||
invert = false,
|
||||
}) => (
|
||||
<div>
|
||||
<Field name={name} type="radio" parse={parseStringBool} value={!invert}>
|
||||
{({ input }) => (
|
||||
<Localized id="configure-onOffField-on">
|
||||
<RadioButton
|
||||
id={`${input.name}-true`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
value={input.value}
|
||||
>
|
||||
On
|
||||
</RadioButton>
|
||||
</Localized>
|
||||
<RadioButton
|
||||
id={`${input.name}-true`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
value={input.value}
|
||||
>
|
||||
{onLabel || (
|
||||
<Localized id="configure-onOffField-on">
|
||||
<span>On</span>
|
||||
</Localized>
|
||||
)}
|
||||
</RadioButton>
|
||||
)}
|
||||
</Field>
|
||||
<Field name={name} type="radio" parse={parseStringBool} value={invert}>
|
||||
{({ input }) => (
|
||||
<Localized id="configure-onOffField-off">
|
||||
<RadioButton
|
||||
id={`${input.name}-fase`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
value={input.value}
|
||||
>
|
||||
Off
|
||||
</RadioButton>
|
||||
</Localized>
|
||||
<RadioButton
|
||||
id={`${input.name}-false`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
value={input.value}
|
||||
>
|
||||
{offLabel || (
|
||||
<Localized id="configure-onOffField-off">
|
||||
<span>Off</span>
|
||||
</Localized>
|
||||
)}
|
||||
</RadioButton>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
+8
-1
@@ -8,6 +8,7 @@ import ClosingCommentStreamsConfigContainer from "../containers/ClosingCommentSt
|
||||
import CommentEditingConfigContainer from "../containers/CommentEditingConfigContainer";
|
||||
import CommentLengthConfigContainer from "../containers/CommentLengthConfigContainer";
|
||||
import GuidelinesConfigContainer from "../containers/GuidelinesConfigContainer";
|
||||
import SitewideCommentingConfigContainer from "../containers/SitewideCommentingConfigContainer";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
@@ -15,7 +16,8 @@ interface Props {
|
||||
PropTypesOf<typeof CommentLengthConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof CommentEditingConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosedStreamMessageConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"];
|
||||
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof SitewideCommentingConfigContainer>["settings"];
|
||||
onInitValues: (values: any) => void;
|
||||
}
|
||||
|
||||
@@ -25,6 +27,11 @@ const General: StatelessComponent<Props> = ({
|
||||
onInitValues,
|
||||
}) => (
|
||||
<HorizontalGutter size="double" data-testid="configure-generalContainer">
|
||||
<SitewideCommentingConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<GuidelinesConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent, Suspense } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
InputLabel,
|
||||
Spinner,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import OnOffField from "talk-admin/routes/configure/components/OnOffField";
|
||||
import Header from "../../../components/Header";
|
||||
import LazyMarkdown from "./LazyMarkdown";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const SitewideCommentingConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<Localized id="configure-general-sitewideCommenting-title">
|
||||
<Header container="legend">Sitewide Commenting</Header>
|
||||
</Localized>
|
||||
<Localized id="configure-general-sitewideCommenting-explanation">
|
||||
<Typography variant="detail">
|
||||
Open or close comment streams for new comments sitewide. When new
|
||||
comments are turned off sitewide, new comments cannot be submitted, but
|
||||
existing comments can continue to receive “Respect” reactions, be
|
||||
reported, and be shared.
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-general-sitewideCommenting-enableNewCommentsSitewide">
|
||||
<InputLabel container="legend">Enable New Comments Sitewide</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField
|
||||
name="disableCommenting.enabled"
|
||||
disabled={disabled}
|
||||
invert
|
||||
onLabel={
|
||||
<Localized id="configure-general-sitewideCommenting-onCommentStreamsOpened">
|
||||
<span>On - Comment streams opened for new comments</span>
|
||||
</Localized>
|
||||
}
|
||||
offLabel={
|
||||
<Localized id="configure-general-sitewideCommenting-offCommentStreamsClosed">
|
||||
<span>Off - Comment streams closed for new comments</span>
|
||||
</Localized>
|
||||
}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-general-sitewideCommenting-message">
|
||||
<InputLabel htmlFor="configure-general-sitewideCommenting-message">
|
||||
Sitewide Closed Comments Message
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-general-sitewideCommenting-messageExplanation">
|
||||
<InputDescription>
|
||||
Write a message that will be displayed when comment streams are closed
|
||||
sitewide
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
</FormField>
|
||||
|
||||
<Field name="disableCommenting.message">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyMarkdown
|
||||
id="configure-general-sitewideCommenting-message"
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
/>
|
||||
</Suspense>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default SitewideCommentingConfig;
|
||||
+1
@@ -50,6 +50,7 @@ const enhanced = withFragmentContainer<Props>({
|
||||
...CommentEditingConfigContainer_settings
|
||||
...ClosedStreamMessageConfigContainer_settings
|
||||
...ClosingCommentStreamsConfigContainer_settings
|
||||
...SitewideCommentingConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(GeneralConfigContainer);
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { SitewideCommentingConfigContainer_settings as SettingsData } from "talk-admin/__generated__/SitewideCommentingConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import SitewideCommentingConfig from "../components/SitewideCommentingConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class SitewideCommentingConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <SitewideCommentingConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment SitewideCommentingConfigContainer_settings on Settings {
|
||||
disableCommenting {
|
||||
enabled
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(SitewideCommentingConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -107,6 +107,107 @@ exports[`renders configure general 1`] = `
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
data-testid="configure-generalContainer"
|
||||
>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
>
|
||||
Sitewide Commenting
|
||||
</legend>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextPrimary"
|
||||
>
|
||||
Open or close comment streams for new comments sitewide. When new comments
|
||||
are turned off sitewide, new comments cannot be submitted, but existing
|
||||
comments can continue to receive “Respect” reactions, be reported, and be
|
||||
shared.
|
||||
</p>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
>
|
||||
Enable New Comments Sitewide
|
||||
</legend>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="disableCommenting.enabled-true"
|
||||
name="disableCommenting.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="radio"
|
||||
value={false}
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="disableCommenting.enabled-true"
|
||||
>
|
||||
<span>
|
||||
On - Comment streams opened for new comments
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="disableCommenting.enabled-false"
|
||||
name="disableCommenting.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="radio"
|
||||
value={true}
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="disableCommenting.enabled-false"
|
||||
>
|
||||
<span>
|
||||
Off - Comment streams closed for new comments
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="configure-general-sitewideCommenting-message"
|
||||
>
|
||||
Sitewide Closed Comments Message
|
||||
</label>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary"
|
||||
>
|
||||
Write a message that will be displayed when comment streams are closed sitewide
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="MarkdownEditor-wrapper"
|
||||
>
|
||||
<textarea
|
||||
id="configure-general-sitewideCommenting-message"
|
||||
name="disableCommenting.message"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset
|
||||
className="FieldSet-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
@@ -143,7 +244,9 @@ exports[`renders configure general 1`] = `
|
||||
className="RadioButton-label"
|
||||
htmlFor="communityGuidelines.enabled-true"
|
||||
>
|
||||
On
|
||||
<span>
|
||||
On
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -153,7 +256,7 @@ exports[`renders configure general 1`] = `
|
||||
checked={true}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="communityGuidelines.enabled-fase"
|
||||
id="communityGuidelines.enabled-false"
|
||||
name="communityGuidelines.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
@@ -163,9 +266,11 @@ exports[`renders configure general 1`] = `
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="communityGuidelines.enabled-fase"
|
||||
htmlFor="communityGuidelines.enabled-false"
|
||||
>
|
||||
Off
|
||||
<span>
|
||||
Off
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,7 +353,9 @@ Markdown can be found
|
||||
className="RadioButton-label"
|
||||
htmlFor="charCount.enabled-true"
|
||||
>
|
||||
On
|
||||
<span>
|
||||
On
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -258,7 +365,7 @@ Markdown can be found
|
||||
checked={true}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="charCount.enabled-fase"
|
||||
id="charCount.enabled-false"
|
||||
name="charCount.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
@@ -268,9 +375,11 @@ Markdown can be found
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="charCount.enabled-fase"
|
||||
htmlFor="charCount.enabled-false"
|
||||
>
|
||||
Off
|
||||
<span>
|
||||
Off
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -471,7 +580,9 @@ moderation panel.
|
||||
className="RadioButton-label"
|
||||
htmlFor="autoCloseStream-true"
|
||||
>
|
||||
On
|
||||
<span>
|
||||
On
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -481,7 +592,7 @@ moderation panel.
|
||||
checked={true}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="autoCloseStream-fase"
|
||||
id="autoCloseStream-false"
|
||||
name="autoCloseStream"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
@@ -491,9 +602,11 @@ moderation panel.
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="autoCloseStream-fase"
|
||||
htmlFor="autoCloseStream-false"
|
||||
>
|
||||
Off
|
||||
<span>
|
||||
Off
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,9 @@ the
|
||||
className="RadioButton-label"
|
||||
htmlFor="integrations.perspective.enabled-true"
|
||||
>
|
||||
On
|
||||
<span>
|
||||
On
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -168,7 +170,7 @@ the
|
||||
checked={true}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="integrations.perspective.enabled-fase"
|
||||
id="integrations.perspective.enabled-false"
|
||||
name="integrations.perspective.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
@@ -178,9 +180,11 @@ the
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="integrations.perspective.enabled-fase"
|
||||
htmlFor="integrations.perspective.enabled-false"
|
||||
>
|
||||
Off
|
||||
<span>
|
||||
Off
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -424,7 +428,9 @@ the comment will be published.
|
||||
className="RadioButton-label"
|
||||
htmlFor="integrations.akismet.enabled-true"
|
||||
>
|
||||
On
|
||||
<span>
|
||||
On
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -434,7 +440,7 @@ the comment will be published.
|
||||
checked={true}
|
||||
className="RadioButton-input"
|
||||
disabled={false}
|
||||
id="integrations.akismet.enabled-fase"
|
||||
id="integrations.akismet.enabled-false"
|
||||
name="integrations.akismet.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
@@ -444,9 +450,11 @@ the comment will be published.
|
||||
/>
|
||||
<label
|
||||
className="RadioButton-label"
|
||||
htmlFor="integrations.akismet.enabled-fase"
|
||||
htmlFor="integrations.akismet.enabled-false"
|
||||
>
|
||||
Off
|
||||
<span>
|
||||
Off
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,67 @@ it("renders configure general", async () => {
|
||||
expect(within(configureContainer).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("change site wide commenting", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
s.onFirstCall().callsFake((_: any, data: any) => {
|
||||
expect(data.input.settings.disableCommenting).toEqual({
|
||||
enabled: true,
|
||||
message: "Closing message",
|
||||
});
|
||||
settingsRecord = merge(settingsRecord, data.input.settings);
|
||||
return {
|
||||
settings: settingsRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
})
|
||||
);
|
||||
const {
|
||||
configureContainer,
|
||||
generalContainer,
|
||||
saveChangesButton,
|
||||
} = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateSettings: updateSettingsStub,
|
||||
},
|
||||
});
|
||||
|
||||
const sitewideCommentingContainer = within(generalContainer).getAllByText(
|
||||
"Sitewide Commenting",
|
||||
{ selector: "fieldset" }
|
||||
)[0];
|
||||
|
||||
const offField = within(sitewideCommentingContainer).getByLabelText(
|
||||
"Off - Comment streams closed for new comments"
|
||||
);
|
||||
const contentField = within(sitewideCommentingContainer).getByLabelText(
|
||||
"Sitewide Closed Comments Message"
|
||||
);
|
||||
|
||||
// Let's enable it.
|
||||
offField.props.onChange(offField.props.value.toString());
|
||||
|
||||
// Let's change the content.
|
||||
contentField.props.onChange("Closing message");
|
||||
|
||||
// Send form
|
||||
within(configureContainer)
|
||||
.getByType("form")
|
||||
.props.onSubmit();
|
||||
|
||||
// Submit button and text field should be disabled.
|
||||
expect(saveChangesButton.props.disabled).toBe(true);
|
||||
expect(offField.props.disabled).toBe(true);
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(offField.props.disabled).toBe(false);
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateSettingsStub.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change community guidlines", async () => {
|
||||
let settingsRecord = cloneDeep(settings);
|
||||
const updateSettingsStub = createSinonStub(s =>
|
||||
|
||||
@@ -11,6 +11,9 @@ export const settings = {
|
||||
max: 1000,
|
||||
min: 3,
|
||||
},
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
},
|
||||
closedTimeout: 604800,
|
||||
autoCloseStream: false,
|
||||
closedMessage: null,
|
||||
|
||||
Reference in New Issue
Block a user