[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:
Kiwi
2019-03-19 18:30:44 +01:00
committed by GitHub
parent 5a7340ac3a
commit 41db413bea
34 changed files with 783 additions and 208 deletions
@@ -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>
+2
View File
@@ -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">;
+10
View File
@@ -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>
);
};
@@ -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;
@@ -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`
@@ -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;
@@ -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 {
+2 -1
View File
@@ -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!
}
"""
+2
View File
@@ -0,0 +1,2 @@
closeCommentingDefaultMessage = Comments are closed on this story.
disableCommentingDefaultMessage = Comments are closed on this story.
+35 -3
View File
@@ -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;
};
+15 -2
View File
@@ -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