mirror of
https://github.com/wassname/talk.git
synced 2026-07-18 12:40:13 +08:00
[CORL-158] Open/Close stream inside of configure tab (#2223)
* feat: Open or Close stream inside of configure tab * feat: default disable/close commenting message * fix: adjusted tests
This commit is contained in:
@@ -204,7 +204,7 @@ shared.
|
||||
id="configure-general-sitewideCommenting-message"
|
||||
name="disableCommenting.message"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
value="Comments are closed on this story."
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -705,7 +705,7 @@ moderation panel.
|
||||
id="configure-general-closedStreamMessage-content"
|
||||
name="closeCommenting.message"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
value="Comments are closed on this story."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,10 +15,12 @@ export const settings = {
|
||||
},
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "Comments are closed on this story.",
|
||||
},
|
||||
closeCommenting: {
|
||||
auto: false,
|
||||
timeout: 604800,
|
||||
message: "Comments are closed on this story.",
|
||||
},
|
||||
customCSSURL: null,
|
||||
domains: ["localhost:8080"],
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import { ReactTestInstance } from "react-test-renderer";
|
||||
|
||||
import findParentsWithType from "./findParentsWithType";
|
||||
import findParentWithType from "./findParentWithType";
|
||||
import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText";
|
||||
|
||||
const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => (
|
||||
@@ -39,14 +38,17 @@ export function getByText(
|
||||
pattern: TextMatchPattern,
|
||||
options?: TextMatchOptions & SelectorOptions
|
||||
) {
|
||||
const result = findParentWithType(
|
||||
container.find(matcher(pattern, options)),
|
||||
const results = findParentsWithType(
|
||||
container.findAll(matcher(pattern, options)),
|
||||
options && options.selector
|
||||
);
|
||||
if (!result) {
|
||||
throw new Error(`Couldn't find text ${pattern}`);
|
||||
if (results.length === 1) {
|
||||
return results[0];
|
||||
}
|
||||
return result;
|
||||
if (results.length === 0) {
|
||||
throw new Error(`Could't find element with text ${pattern}`);
|
||||
}
|
||||
throw new Error(`Found multiple elements with text ${pattern}`);
|
||||
}
|
||||
|
||||
export function getAllByText(
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { graphql } from "react-relay";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
createMutationContainer,
|
||||
MutationInput,
|
||||
MutationResponsePromise,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
import { CloseStoryMutation as MutationTypes } from "talk-stream/__generated__/CloseStoryMutation.graphql";
|
||||
|
||||
export type CloseStoryInput = MutationInput<MutationTypes>;
|
||||
|
||||
const mutation = graphql`
|
||||
mutation CloseStoryMutation($input: CloseStoryInput!) {
|
||||
closeStory(input: $input) {
|
||||
story {
|
||||
...ConfigureContainer_story
|
||||
}
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let clientMutationId = 0;
|
||||
|
||||
function commit(environment: Environment, input: CloseStoryInput) {
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
variables: {
|
||||
input: {
|
||||
...input,
|
||||
clientMutationId: (clientMutationId++).toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const withCloseStoryMutation = createMutationContainer(
|
||||
"closeStory",
|
||||
commit
|
||||
);
|
||||
|
||||
export type CloseStoryMutation = (
|
||||
input: CloseStoryInput
|
||||
) => MutationResponsePromise<MutationTypes, "closeStory">;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { graphql } from "react-relay";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
createMutationContainer,
|
||||
MutationInput,
|
||||
MutationResponsePromise,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
import { OpenStoryMutation as MutationTypes } from "talk-stream/__generated__/OpenStoryMutation.graphql";
|
||||
|
||||
export type OpenStoryInput = MutationInput<MutationTypes>;
|
||||
|
||||
const mutation = graphql`
|
||||
mutation OpenStoryMutation($input: OpenStoryInput!) {
|
||||
openStory(input: $input) {
|
||||
story {
|
||||
...ConfigureContainer_story
|
||||
}
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let clientMutationId = 0;
|
||||
|
||||
function commit(environment: Environment, input: OpenStoryInput) {
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
variables: {
|
||||
input: {
|
||||
...input,
|
||||
clientMutationId: (clientMutationId++).toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const withOpenStoryMutation = createMutationContainer(
|
||||
"openStory",
|
||||
commit
|
||||
);
|
||||
|
||||
export type OpenStoryMutation = (
|
||||
input: OpenStoryInput
|
||||
) => MutationResponsePromise<MutationTypes, "openStory">;
|
||||
@@ -60,3 +60,13 @@ export {
|
||||
withUpdateStorySettingsMutation,
|
||||
UpdateStorySettingsMutation,
|
||||
} from "./UpdateStorySettingsMutation";
|
||||
export {
|
||||
OpenStoryInput,
|
||||
withOpenStoryMutation,
|
||||
OpenStoryMutation,
|
||||
} from "./OpenStoryMutation";
|
||||
export {
|
||||
CloseStoryInput,
|
||||
withCloseStoryMutation,
|
||||
CloseStoryMutation,
|
||||
} from "./CloseStoryMutation";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.heading {
|
||||
padding-bottom: calc(1.5 * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Button, Flex, Typography } from "talk-ui/components";
|
||||
|
||||
import styles from "./CloseStream.css";
|
||||
|
||||
interface Props {
|
||||
onClick: () => void;
|
||||
disableButton?: boolean;
|
||||
}
|
||||
|
||||
const CloseStream: StatelessComponent<Props> = ({ onClick, disableButton }) => (
|
||||
<div>
|
||||
<Localized id="configure-closeStream-title">
|
||||
<Typography variant="heading2" className={styles.heading}>
|
||||
Close Comment Stream
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Flex alignItems="flex-start" itemGutter>
|
||||
<Localized id="configure-closeStream-description">
|
||||
<Typography>
|
||||
This comment stream is currently open. By closing this comment stream,
|
||||
no new comments may be submitted and all previously submitted comments
|
||||
will still be displayed.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="configure-closeStream-closeStream">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
className={styles.button}
|
||||
onClick={onClick}
|
||||
disabled={disableButton}
|
||||
>
|
||||
Close Stream
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CloseStream;
|
||||
@@ -4,20 +4,27 @@ import { PropTypesOf } from "talk-framework/types";
|
||||
import UserBoxContainer from "talk-stream/containers/UserBoxContainer";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import ConfigureCommentStreamContainer from "../containers/ConfigureCommentStreamContainer";
|
||||
import ConfigureStreamContainer from "../containers/ConfigureStreamContainer";
|
||||
import OpenOrCloseStreamContainer from "../containers/OpenOrCloseStreamContainer";
|
||||
import HorizontalRule from "./HorizontalRule";
|
||||
|
||||
export interface Props {
|
||||
me: PropTypesOf<typeof UserBoxContainer>["me"];
|
||||
settings: PropTypesOf<typeof UserBoxContainer>["settings"];
|
||||
story: PropTypesOf<typeof ConfigureCommentStreamContainer>["story"];
|
||||
story: PropTypesOf<typeof ConfigureStreamContainer>["story"] &
|
||||
PropTypesOf<typeof OpenOrCloseStreamContainer>["story"];
|
||||
}
|
||||
|
||||
const Configure: StatelessComponent<Props> = props => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<UserBoxContainer me={props.me} settings={props.settings} />
|
||||
<ConfigureCommentStreamContainer story={props.story} />
|
||||
</HorizontalGutter>
|
||||
<div>
|
||||
<HorizontalGutter size="double">
|
||||
<UserBoxContainer me={props.me} settings={props.settings} />
|
||||
<ConfigureStreamContainer story={props.story} />
|
||||
</HorizontalGutter>
|
||||
<HorizontalRule />
|
||||
<OpenOrCloseStreamContainer story={props.story} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+5
-5
@@ -17,7 +17,7 @@ import MessageBoxConfigContainer from "../containers/MessageBoxConfigContainer";
|
||||
import PremodConfigContainer from "../containers/PremodConfigContainer";
|
||||
import PremodLinksConfigContainer from "../containers/PremodLinksConfigContainer";
|
||||
|
||||
import styles from "./ConfigureCommentStream.css";
|
||||
import styles from "./ConfigureStream.css";
|
||||
|
||||
interface Props {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
@@ -26,7 +26,7 @@ interface Props {
|
||||
PropTypesOf<typeof MessageBoxConfigContainer>["storySettings"];
|
||||
}
|
||||
|
||||
const ConfigureCommentStream: StatelessComponent<Props> = ({
|
||||
const ConfigureStream: StatelessComponent<Props> = ({
|
||||
onSubmit,
|
||||
storySettings,
|
||||
}) => (
|
||||
@@ -40,12 +40,12 @@ const ConfigureCommentStream: StatelessComponent<Props> = ({
|
||||
alignItems="flex-start"
|
||||
itemGutter
|
||||
>
|
||||
<Localized id="configure-commentStream-title">
|
||||
<Localized id="configure-stream-title">
|
||||
<Typography variant="heading2" className={styles.heading}>
|
||||
Configure this Comment Stream
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="configure-commentStream-apply">
|
||||
<Localized id="configure-stream-apply">
|
||||
<Button
|
||||
color="success"
|
||||
variant="filled"
|
||||
@@ -81,4 +81,4 @@ const ConfigureCommentStream: StatelessComponent<Props> = ({
|
||||
</Form>
|
||||
);
|
||||
|
||||
export default ConfigureCommentStream;
|
||||
export default ConfigureStream;
|
||||
@@ -0,0 +1,7 @@
|
||||
.root {
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--palette-divider);
|
||||
padding-top: 1px;
|
||||
margin-top: calc(3 * var(--spacing-unit));
|
||||
margin-bottom: calc(2 * var(--spacing-unit));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const root: string;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
|
||||
import HorizontalRule from "./HorizontalRule";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<HorizontalRule />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import styles from "./HorizontalRule.css";
|
||||
|
||||
const HorizontalRule: StatelessComponent = ({ children }) => (
|
||||
<hr className={styles.root} />
|
||||
);
|
||||
|
||||
export default HorizontalRule;
|
||||
@@ -0,0 +1,7 @@
|
||||
.heading {
|
||||
padding-bottom: calc(1.5 * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Button, Flex, Typography } from "talk-ui/components";
|
||||
|
||||
import styles from "./OpenStream.css";
|
||||
|
||||
interface Props {
|
||||
onClick: () => void;
|
||||
disableButton?: boolean;
|
||||
}
|
||||
|
||||
const OpenStream: StatelessComponent<Props> = ({ onClick, disableButton }) => (
|
||||
<div>
|
||||
<Localized id="configure-openStream-title">
|
||||
<Typography variant="heading2" className={styles.heading}>
|
||||
Open Stream
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Flex alignItems="flex-start" itemGutter>
|
||||
<Localized id="configure-openStream-description">
|
||||
<Typography>
|
||||
This comment stream is currently closed. By opening this comment
|
||||
stream new comments may be submitted and displayed
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="configure-openStream-openStream">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
className={styles.button}
|
||||
onClick={onClick}
|
||||
disabled={disableButton}
|
||||
>
|
||||
Open Stream
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default OpenStream;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<hr
|
||||
className="HorizontalRule-root"
|
||||
/>
|
||||
`;
|
||||
@@ -28,7 +28,8 @@ export class StreamContainer extends React.Component<ConfigureContainerProps> {
|
||||
const enhanced = withFragmentContainer<ConfigureContainerProps>({
|
||||
story: graphql`
|
||||
fragment ConfigureContainer_story on Story {
|
||||
...ConfigureCommentStreamContainer_story
|
||||
...ConfigureStreamContainer_story
|
||||
...OpenOrCloseStreamContainer_story
|
||||
}
|
||||
`,
|
||||
me: graphql`
|
||||
|
||||
+6
-6
@@ -3,21 +3,21 @@ import React from "react";
|
||||
|
||||
import { SubmitHookHandler } from "talk-framework/lib/form";
|
||||
import { graphql, withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { ConfigureCommentStreamContainer_story as StoryData } from "talk-stream/__generated__/ConfigureCommentStreamContainer_story.graphql";
|
||||
import { ConfigureStreamContainer_story as StoryData } from "talk-stream/__generated__/ConfigureStreamContainer_story.graphql";
|
||||
import {
|
||||
UpdateStorySettingsInput,
|
||||
UpdateStorySettingsMutation,
|
||||
withUpdateStorySettingsMutation,
|
||||
} from "talk-stream/mutations";
|
||||
|
||||
import ConfigureCommentStream from "../components/ConfigureCommentStream";
|
||||
import ConfigureStream from "../components/ConfigureStream";
|
||||
|
||||
interface Props {
|
||||
story: StoryData;
|
||||
updateStorySettings: UpdateStorySettingsMutation;
|
||||
}
|
||||
|
||||
class ConfigureCommentStreamContainer extends React.Component<Props> {
|
||||
class ConfigureStreamContainer extends React.Component<Props> {
|
||||
private handleExecute = async (
|
||||
data: UpdateStorySettingsInput["settings"],
|
||||
form: FormApi
|
||||
@@ -33,7 +33,7 @@ class ConfigureCommentStreamContainer extends React.Component<Props> {
|
||||
return (
|
||||
<SubmitHookHandler onExecute={this.handleExecute}>
|
||||
{({ onSubmit }) => (
|
||||
<ConfigureCommentStream
|
||||
<ConfigureStream
|
||||
onSubmit={onSubmit}
|
||||
storySettings={this.props.story.settings}
|
||||
/>
|
||||
@@ -45,7 +45,7 @@ class ConfigureCommentStreamContainer extends React.Component<Props> {
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
story: graphql`
|
||||
fragment ConfigureCommentStreamContainer_story on Story {
|
||||
fragment ConfigureStreamContainer_story on Story {
|
||||
id
|
||||
settings {
|
||||
...PremodConfigContainer_storySettings
|
||||
@@ -54,5 +54,5 @@ const enhanced = withFragmentContainer<Props>({
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(withUpdateStorySettingsMutation(ConfigureCommentStreamContainer));
|
||||
})(withUpdateStorySettingsMutation(ConfigureStreamContainer));
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
|
||||
import { graphql, withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { OpenOrCloseStreamContainer_story as StoryData } from "talk-stream/__generated__/OpenOrCloseStreamContainer_story.graphql";
|
||||
import {
|
||||
CloseStoryMutation,
|
||||
OpenStoryMutation,
|
||||
withCloseStoryMutation,
|
||||
withOpenStoryMutation,
|
||||
} from "talk-stream/mutations";
|
||||
|
||||
import CloseStream from "../components/CloseStream";
|
||||
import OpenStream from "../components/OpenStream";
|
||||
|
||||
interface Props {
|
||||
story: StoryData;
|
||||
openStory: OpenStoryMutation;
|
||||
closeStory: CloseStoryMutation;
|
||||
}
|
||||
|
||||
interface State {
|
||||
waitingForResponse: boolean;
|
||||
}
|
||||
|
||||
class OpenOrCloseStreamContainer extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
waitingForResponse: false,
|
||||
};
|
||||
|
||||
private handleOnClick = async () => {
|
||||
if (!this.state.waitingForResponse) {
|
||||
this.setState({ waitingForResponse: true });
|
||||
if (this.props.story.isClosed) {
|
||||
await this.props.openStory({ id: this.props.story.id });
|
||||
} else {
|
||||
await this.props.closeStory({ id: this.props.story.id });
|
||||
}
|
||||
this.setState({ waitingForResponse: false });
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
return this.props.story.isClosed ? (
|
||||
<OpenStream
|
||||
onClick={this.handleOnClick}
|
||||
disableButton={this.state.waitingForResponse}
|
||||
/>
|
||||
) : (
|
||||
<CloseStream
|
||||
onClick={this.handleOnClick}
|
||||
disableButton={this.state.waitingForResponse}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
story: graphql`
|
||||
fragment OpenOrCloseStreamContainer_story on Story {
|
||||
id
|
||||
isClosed
|
||||
}
|
||||
`,
|
||||
})(withOpenStoryMutation(withCloseStoryMutation(OpenOrCloseStreamContainer)));
|
||||
export default enhanced;
|
||||
+209
-173
@@ -8,209 +8,245 @@ exports[`renders configure 1`] = `
|
||||
id="tabPane-CONFIGURE"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-flex Flex-halfItemGutter Flex-wrap"
|
||||
className="Flex-root"
|
||||
>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
className="Flex-flex Flex-halfItemGutter Flex-wrap"
|
||||
>
|
||||
Signed in as
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Moderator
|
||||
</span>
|
||||
.
|
||||
Signed in as
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
>
|
||||
Moderator
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Not you?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
autoComplete="off"
|
||||
id="configure-form"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-justifySpaceBetween Flex-alignFlexStart"
|
||||
>
|
||||
<span>
|
||||
Not you?
|
||||
</span>
|
||||
<h1
|
||||
className="Typography-root Typography-heading2 Typography-colorTextPrimary ConfigureStream-heading"
|
||||
>
|
||||
Configure this Comment Stream
|
||||
</h1>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorSuccess Button-variantFilled Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
type="submit"
|
||||
>
|
||||
Sign Out
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
autoComplete="off"
|
||||
id="configure-form"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-justifySpaceBetween Flex-alignFlexStart"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading2 Typography-colorTextPrimary ConfigureCommentStream-heading"
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
Configure this Comment Stream
|
||||
</h1>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorSuccess Button-variantFilled Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="CheckBox-root"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="CheckBox-input"
|
||||
disabled={false}
|
||||
id="moderation"
|
||||
name="moderation"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
className="CheckBox-label"
|
||||
htmlFor="moderation"
|
||||
>
|
||||
<span
|
||||
className="CheckBox-labelSpan"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
<span>
|
||||
Enable Pre-Moderation
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="ToggleConfig-details"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
|
||||
>
|
||||
Moderators must approve any comment before it is published to this stream.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="CheckBox-root"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="CheckBox-input"
|
||||
disabled={false}
|
||||
id="premodLinksEnable"
|
||||
name="premodLinksEnable"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
className="CheckBox-label"
|
||||
htmlFor="premodLinksEnable"
|
||||
>
|
||||
<span
|
||||
className="CheckBox-labelSpan"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
<span>
|
||||
Pre-Moderate Comments Containing Links
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="ToggleConfig-details"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
|
||||
>
|
||||
Moderators must approve any comment that contains a link before it is published to this stream.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="CheckBox-root"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="CheckBox-input"
|
||||
disabled={false}
|
||||
id="messageBox.enabled"
|
||||
name="messageBox.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
className="CheckBox-label"
|
||||
htmlFor="messageBox.enabled"
|
||||
>
|
||||
<span
|
||||
className="CheckBox-labelSpan"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
<span>
|
||||
Enable Message Box for this Stream
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="ToggleConfig-details"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
className="CheckBox-root"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="CheckBox-input"
|
||||
disabled={false}
|
||||
id="moderation"
|
||||
name="moderation"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
className="CheckBox-label"
|
||||
htmlFor="moderation"
|
||||
>
|
||||
<span
|
||||
className="CheckBox-labelSpan"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
<span>
|
||||
Enable Pre-Moderation
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="ToggleConfig-details"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
|
||||
>
|
||||
Add a message to the top of the comment box for your readers. Use this to pose a topic,
|
||||
ask a question or make announcements relating to this story.
|
||||
Moderators must approve any comment before it is published to this stream.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="CheckBox-root"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="CheckBox-input"
|
||||
disabled={false}
|
||||
id="premodLinksEnable"
|
||||
name="premodLinksEnable"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
className="CheckBox-label"
|
||||
htmlFor="premodLinksEnable"
|
||||
>
|
||||
<span
|
||||
className="CheckBox-labelSpan"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
<span>
|
||||
Pre-Moderate Comments Containing Links
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="ToggleConfig-details"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
|
||||
>
|
||||
Moderators must approve any comment that contains a link before it is published to this stream.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="CheckBox-root"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="CheckBox-input"
|
||||
disabled={false}
|
||||
id="messageBox.enabled"
|
||||
name="messageBox.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
className="CheckBox-label"
|
||||
htmlFor="messageBox.enabled"
|
||||
>
|
||||
<span
|
||||
className="CheckBox-labelSpan"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
<span>
|
||||
Enable Message Box for this Stream
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="ToggleConfig-details"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
|
||||
>
|
||||
Add a message to the top of the comment box for your readers. Use this to pose a topic,
|
||||
ask a question or make announcements relating to this story.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr
|
||||
className="HorizontalRule-root"
|
||||
/>
|
||||
<div>
|
||||
<h1
|
||||
className="Typography-root Typography-heading2 Typography-colorTextPrimary CloseStream-heading"
|
||||
>
|
||||
Close Comment Stream
|
||||
</h1>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignFlexStart"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
This comment stream is currently open. By closing this comment stream,
|
||||
no new comments may be submitted and all previously submitted comments
|
||||
will still be displayed.
|
||||
</p>
|
||||
<button
|
||||
className="BaseButton-root Button-root CloseStream-button Button-sizeRegular Button-colorError Button-variantOutlined"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close Stream
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { waitForElement, within } from "talk-framework/testHelpers";
|
||||
|
||||
import { meAsModerator, settings, stories } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
async function createTestRenderer(
|
||||
resolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean; status?: string } = {}
|
||||
) {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
story: sinon.stub().returns(stories[0]),
|
||||
me: sinon.stub().returns(meAsModerator),
|
||||
...resolver.Query,
|
||||
},
|
||||
};
|
||||
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(stories[0].id, "storyID");
|
||||
},
|
||||
});
|
||||
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
|
||||
return { testRenderer, tabPane };
|
||||
}
|
||||
|
||||
it("close stream", async () => {
|
||||
const closeStoryStub = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.id).toBe(stories[0].id);
|
||||
return {
|
||||
story: { ...stories[0], isClosed: true },
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
const { tabPane } = await createTestRenderer({
|
||||
Mutation: {
|
||||
closeStory: closeStoryStub,
|
||||
},
|
||||
});
|
||||
|
||||
const button = within(tabPane).getByText("Close Stream", {
|
||||
selector: "button",
|
||||
});
|
||||
button.props.onClick();
|
||||
|
||||
expect(button.props.disabled).toBe(true);
|
||||
|
||||
// Stream should then appear closed.
|
||||
await waitForElement(() =>
|
||||
within(tabPane).getByText("Open Stream", {
|
||||
selector: "button",
|
||||
})
|
||||
);
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(closeStoryStub.called).toBe(true);
|
||||
});
|
||||
|
||||
it("opens stream", async () => {
|
||||
const openStoryStub = sinon.stub().callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.id).toBe(stories[0].id);
|
||||
return {
|
||||
story: { ...stories[0], isClosed: false },
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
const { tabPane } = await createTestRenderer({
|
||||
Query: {
|
||||
story: sinon.stub().returns({ ...stories[0], isClosed: true }),
|
||||
},
|
||||
Mutation: {
|
||||
openStory: openStoryStub,
|
||||
},
|
||||
});
|
||||
|
||||
const button = within(tabPane).getByText("Open Stream", {
|
||||
selector: "button",
|
||||
});
|
||||
button.props.onClick();
|
||||
|
||||
// Stream should then appear open.
|
||||
await waitForElement(() =>
|
||||
within(tabPane).getByText("Close Stream", {
|
||||
selector: "button",
|
||||
})
|
||||
);
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(openStoryStub.called).toBe(true);
|
||||
});
|
||||
@@ -137,6 +137,23 @@
|
||||
margin-left: calc(2 * var(--spacing-unit)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.directionColumn).tripleItemGutter {
|
||||
&:not(:empty) {
|
||||
margin-top: calc(-3 * var(--spacing-unit)) !important;
|
||||
}
|
||||
& > * {
|
||||
margin-top: calc(3 * var(--spacing-unit)) !important;
|
||||
}
|
||||
}
|
||||
&.directionColumn.tripleItemGutter {
|
||||
&:not(:empty) {
|
||||
margin-left: calc(-3 * var(--spacing-unit)) !important;
|
||||
}
|
||||
&.tripleItemGutter > * {
|
||||
margin-left: calc(3 * var(--spacing-unit)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.justifyFlexStart {
|
||||
|
||||
@@ -25,7 +25,7 @@ interface Props {
|
||||
| "space-evenly";
|
||||
alignItems?: "flex-start" | "flex-end" | "center" | "baseline" | "stretch";
|
||||
direction?: "row" | "column" | "row-reverse" | "column-reverse";
|
||||
itemGutter?: boolean | "half" | "double";
|
||||
itemGutter?: boolean | "half" | "double" | "triple";
|
||||
className?: string;
|
||||
wrap?: boolean | "reverse";
|
||||
|
||||
@@ -51,6 +51,7 @@ const Flex: StatelessComponent<Props> = props => {
|
||||
[classes.itemGutter]: itemGutter === true,
|
||||
[classes.halfItemGutter]: itemGutter === "half",
|
||||
[classes.doubleItemGutter]: itemGutter === "double",
|
||||
[classes.tripleItemGutter]: itemGutter === "triple",
|
||||
[classes.wrap]: wrap === true,
|
||||
[classes.wrapReverse]: wrap === "reverse",
|
||||
};
|
||||
|
||||
@@ -36,3 +36,11 @@
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
.triple {
|
||||
& > * {
|
||||
margin: 0 0 calc(3 * var(--spacing-unit)) 0 !important;
|
||||
}
|
||||
& > *:last-child {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ interface Props extends HTMLAttributes<HTMLSpanElement> {
|
||||
*/
|
||||
classes: typeof styles;
|
||||
|
||||
size?: "half" | "full" | "double" | "oneAndAHalf";
|
||||
size?: "half" | "full" | "double" | "triple" | "oneAndAHalf";
|
||||
|
||||
/** The name of the HorizontalGutter to render */
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { GQLCloseCommentingTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import * as settings from "talk-server/models/settings";
|
||||
import { translate } from "talk-server/services/i18n";
|
||||
|
||||
export const CloseCommenting: GQLCloseCommentingTypeResolver<
|
||||
settings.CloseCommenting
|
||||
> = {
|
||||
message: (closeCommenting, input, ctx) => {
|
||||
if (closeCommenting.message) {
|
||||
return closeCommenting.message;
|
||||
}
|
||||
|
||||
// Get the translation bundle.
|
||||
const bundle = ctx.i18n.getBundle(ctx.lang);
|
||||
|
||||
// Translate the default close message.
|
||||
return translate(
|
||||
bundle,
|
||||
"Comments are closed on this story.",
|
||||
"closeCommentingDefaultMessage"
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { GQLDisableCommentingTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import * as settings from "talk-server/models/settings";
|
||||
import { translate } from "talk-server/services/i18n";
|
||||
|
||||
export const DisableCommenting: GQLDisableCommentingTypeResolver<
|
||||
settings.DisableCommenting
|
||||
> = {
|
||||
message: (disableCommenting, input, ctx) => {
|
||||
if (disableCommenting.message) {
|
||||
return disableCommenting.message;
|
||||
}
|
||||
|
||||
// Get the translation bundle.
|
||||
const bundle = ctx.i18n.getBundle(ctx.lang);
|
||||
|
||||
// Translate the default close message.
|
||||
return translate(
|
||||
bundle,
|
||||
"Comments are closed on this story.",
|
||||
"disableCommentingDefaultMessage"
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -4,10 +4,12 @@ import { GQLResolver } from "talk-server/graph/tenant/schema/__generated__/types
|
||||
|
||||
import { AcceptCommentPayload } from "./AcceptCommentPayload";
|
||||
import { AuthIntegrations } from "./AuthIntegrations";
|
||||
import { CloseCommenting } from "./CloseCommenting";
|
||||
import { Comment } from "./Comment";
|
||||
import { CommentCounts } from "./CommentCounts";
|
||||
import { CommentModerationAction } from "./CommentModerationAction";
|
||||
import { CommentRevision } from "./CommentRevision";
|
||||
import { DisableCommenting } from "./DisableCommenting";
|
||||
import { FacebookAuthIntegration } from "./FacebookAuthIntegration";
|
||||
import { GoogleAuthIntegration } from "./GoogleAuthIntegration";
|
||||
import { ModerationQueue } from "./ModerationQueue";
|
||||
@@ -24,11 +26,13 @@ import { User } from "./User";
|
||||
const Resolvers: GQLResolver = {
|
||||
AcceptCommentPayload,
|
||||
AuthIntegrations,
|
||||
CloseCommenting,
|
||||
Comment,
|
||||
CommentCounts,
|
||||
CommentModerationAction,
|
||||
CommentRevision,
|
||||
Cursor,
|
||||
DisableCommenting,
|
||||
FacebookAuthIntegration,
|
||||
GoogleAuthIntegration,
|
||||
ModerationQueue,
|
||||
|
||||
@@ -780,7 +780,7 @@ type DisableCommenting {
|
||||
message will be shown above the comment stream while
|
||||
commenting is disabled site-wide.
|
||||
"""
|
||||
message: String
|
||||
message: String!
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -912,7 +912,7 @@ type CloseCommenting {
|
||||
message when provided will be the message that shows when the comment stream
|
||||
is closed for commenting.
|
||||
"""
|
||||
message: String
|
||||
message: String!
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
closeCommentingDefaultMessage = Comments are closed on this story.
|
||||
disableCommentingDefaultMessage = Comments are closed on this story.
|
||||
@@ -30,6 +30,10 @@ export type FacebookAuthIntegration = Omit<
|
||||
"callbackURL" | "redirectURL"
|
||||
>;
|
||||
|
||||
/**
|
||||
* AuthIntegrations are the set of configurations for the variations of
|
||||
* authentication solutions.
|
||||
*/
|
||||
export interface AuthIntegrations {
|
||||
local: GQLLocalAuthIntegration;
|
||||
sso: GQLSSOAuthIntegration;
|
||||
@@ -38,6 +42,9 @@ export interface AuthIntegrations {
|
||||
facebook: FacebookAuthIntegration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth is the set of configured authentication integrations.
|
||||
*/
|
||||
export type Auth = Omit<GQLAuth, "integrations"> & {
|
||||
/**
|
||||
* integrations are the set of configurations for the variations of
|
||||
@@ -46,11 +53,25 @@ export type Auth = Omit<GQLAuth, "integrations"> & {
|
||||
integrations: AuthIntegrations;
|
||||
};
|
||||
|
||||
/**
|
||||
* CloseCommenting contains settings related to the automatic closing of commenting on
|
||||
* Stories.
|
||||
*/
|
||||
export type CloseCommenting = Omit<GQLSettings["closeCommenting"], "message"> &
|
||||
Partial<Pick<GQLSettings["closeCommenting"], "message">>;
|
||||
|
||||
/**
|
||||
* DisableCommenting will disable commenting site-wide.
|
||||
*/
|
||||
export type DisableCommenting = Omit<
|
||||
GQLSettings["disableCommenting"],
|
||||
"message"
|
||||
> &
|
||||
Partial<Pick<GQLSettings["disableCommenting"], "message">>;
|
||||
|
||||
export type Settings = GlobalModerationSettings &
|
||||
Pick<
|
||||
GQLSettings,
|
||||
| "closeCommenting"
|
||||
| "disableCommenting"
|
||||
| "charCount"
|
||||
| "email"
|
||||
| "karma"
|
||||
@@ -62,7 +83,18 @@ export type Settings = GlobalModerationSettings &
|
||||
| "communityGuidelines"
|
||||
> & {
|
||||
/**
|
||||
* Set of configured authentication integrations.
|
||||
* auth is the set of configured authentication integrations.
|
||||
*/
|
||||
auth: Auth;
|
||||
|
||||
/**
|
||||
* closeCommenting contains settings related to the automatic closing of commenting on
|
||||
* Stories.
|
||||
*/
|
||||
closeCommenting: CloseCommenting;
|
||||
|
||||
/**
|
||||
* disableCommenting will disable commenting site-wide.
|
||||
*/
|
||||
disableCommenting: DisableCommenting;
|
||||
};
|
||||
|
||||
@@ -135,8 +135,8 @@ configure-configureQuery-errorLoadingProfile = Error loading configure
|
||||
configure-configureQuery-storyNotFound = Story not found
|
||||
|
||||
## Comment Stream
|
||||
configure-commentStream-title = Configure this Comment Stream
|
||||
configure-commentStream-apply = Apply
|
||||
configure-stream-title = Configure this Comment Stream
|
||||
configure-stream-apply = Apply
|
||||
|
||||
configure-premod-title = Enable Pre-Moderation
|
||||
configure-premod-description =
|
||||
@@ -154,3 +154,16 @@ configure-messageBox-preview = Preview
|
||||
configure-messageBox-selectAnIcon = Select an Icon
|
||||
configure-messageBox-noIcon = No Icon
|
||||
configure-messageBox-writeAMessage = Write a Message
|
||||
|
||||
configure-closeStream-title = Close Comment Stream
|
||||
configure-closeStream-description =
|
||||
This comment stream is currently open. By closing this comment stream,
|
||||
no new comments may be submitted and all previously submitted comments
|
||||
will still be displayed.
|
||||
configure-closeStream-closeStream = Close Stream
|
||||
|
||||
configure-openStream-title = Open Stream
|
||||
configure-openStream-description =
|
||||
This comment stream is currently closed. By opening this comment
|
||||
stream new comments may be submitted and displayed.
|
||||
configure-openStream-openStream = Open Stream
|
||||
|
||||
Reference in New Issue
Block a user