mirror of
https://github.com/wassname/talk.git
synced 2026-06-27 18:07:26 +08:00
[next] Implement configuration panes (#2173)
* feat: moderation config * feat: configure banned and suspect words * chore: upgrade react and test libs to the newest version <3 * chore: upgrade typescript + some refactor * feat: general, organization and advanced configuration panes * fix: translation * feat: speedup fetching markdown editor * feat: localize markdown editor * chore: refactor container names * chore: rename infobox to communityGuidelines * feat: closing comment streams duration config * test: add feature tests for configurations * fix: mock only console.error * chore: upgrade node * chore: require node >= 10 * fix: better validation and default values * feat: Make DurationField a general purpose component and reuse for Edit Comment Timeframe * test: add unit test for duration field * fix: patch for bug when built in production * chore: bump npm version to latest * fix: adapted Dockerfile to new version of node * refactor: harmonized seconds/milliseconds to seconds * fix: resolve bug from merge conflict
This commit is contained in:
@@ -6,7 +6,7 @@ job_environment: &job_environment
|
||||
job_defaults: &job_defaults
|
||||
working_directory: ~/coralproject/talk
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
- image: circleci/node:10
|
||||
environment:
|
||||
<<: *job_environment
|
||||
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
command: npm run build
|
||||
- run:
|
||||
name: Verify Bundle Size
|
||||
command: npm run bundlesize
|
||||
command: npx bundlesize
|
||||
- save_cache:
|
||||
key: v1-build-cache-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM node:8-alpine
|
||||
FROM node:10-alpine
|
||||
|
||||
# Install build dependancies.
|
||||
RUN apk --no-cache add git
|
||||
@@ -19,7 +19,7 @@ RUN NODE_ENV=development npm install && \
|
||||
npm run build && \
|
||||
npm prune --production
|
||||
|
||||
FROM node:8-alpine
|
||||
FROM node:10-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
|
||||
Generated
+167
-623
File diff suppressed because it is too large
Load Diff
+19
-17
@@ -11,8 +11,8 @@
|
||||
"url": "git://github.com/coralproject/talk.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0",
|
||||
"npm": ">=6.4.1"
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=6.7.0"
|
||||
},
|
||||
"bugs": "https://github.com/coralproject/talk/issues",
|
||||
"contributors": "https://github.com/coralproject/talk/graphs/contributors",
|
||||
@@ -21,7 +21,6 @@
|
||||
"build": "npm-run-all generate --parallel build:*",
|
||||
"build:client": "ts-node ./scripts/build.ts",
|
||||
"build:server": "gulp server",
|
||||
"bundlesize": "bundlesize",
|
||||
"generate": "npm-run-all --parallel generate:*",
|
||||
"generate:css-types": "tcm src/core/client/",
|
||||
"generate:relay-stream": "ts-node ./scripts/compileRelay --src ./src/core/client/stream --schema tenant",
|
||||
@@ -135,8 +134,8 @@
|
||||
"@types/cors": "^2.8.4",
|
||||
"@types/cross-spawn": "^6.0.0",
|
||||
"@types/dotenv": "^4.0.3",
|
||||
"@types/enzyme": "^3.1.11",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.2",
|
||||
"@types/enzyme": "^3.1.15",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.3",
|
||||
"@types/escape-string-regexp": "^1.0.0",
|
||||
"@types/eventemitter2": "^4.1.0",
|
||||
"@types/express": "^4.16.0",
|
||||
@@ -165,15 +164,18 @@
|
||||
"@types/passport-local": "^1.0.33",
|
||||
"@types/passport-oauth2": "^1.4.5",
|
||||
"@types/passport-strategy": "^0.2.33",
|
||||
"@types/prop-types": "^15.5.8",
|
||||
"@types/react": "^16.7.20",
|
||||
"@types/react-copy-to-clipboard": "^4.2.5",
|
||||
"@types/react-dom": "^16.0.6",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"@types/react-relay": "^1.3.9",
|
||||
"@types/react-responsive": "^3.0.1",
|
||||
"@types/react-test-renderer": "^16.0.1",
|
||||
"@types/react-test-renderer": "^16.0.3",
|
||||
"@types/react-transition-group": "^2.0.14",
|
||||
"@types/recompose": "^0.26.5",
|
||||
"@types/relay-runtime": "^1.3.6",
|
||||
"@types/sane": "^2.0.0",
|
||||
"@types/simplemde": "^1.11.7",
|
||||
"@types/sinon": "^5.0.1",
|
||||
"@types/source-map-support": "^0.4.1",
|
||||
"@types/stack-utils": "^1.0.1",
|
||||
@@ -198,7 +200,6 @@
|
||||
"babel-plugin-use-lodash-es": "^0.2.0",
|
||||
"babel-preset-react-optimize": "^1.0.1",
|
||||
"bowser": "^1.9.4",
|
||||
"bundlesize": "^0.17.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.1.2",
|
||||
"chalk": "^2.4.1",
|
||||
"chokidar": "^2.0.4",
|
||||
@@ -211,16 +212,16 @@
|
||||
"css-loader": "^1.0.1",
|
||||
"del": "^3.0.0",
|
||||
"docz": "^0.5.8",
|
||||
"enzyme": "^3.7.0",
|
||||
"enzyme-adapter-react-16": "^1.6.0",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
"enzyme": "^3.8.0",
|
||||
"enzyme-adapter-react-16": "^1.7.1",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"eventemitter2": "^5.0.1",
|
||||
"final-form": "^4.8.1",
|
||||
"flat": "^4.1.0",
|
||||
"fluent": "^0.8.0",
|
||||
"fluent": "^0.10.0",
|
||||
"fluent-intl-polyfill": "^0.1.0",
|
||||
"fluent-langneg": "^0.1.0",
|
||||
"fluent-react": "^0.8.0",
|
||||
"fluent-react": "^0.8.3",
|
||||
"graphql-schema-typescript": "^1.2.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
@@ -256,15 +257,15 @@
|
||||
"pym.js": "^1.3.2",
|
||||
"querystringify": "^2.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.5.2",
|
||||
"react": "^16.7.0",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-dev-utils": "6.0.0-next.3e165448",
|
||||
"react-dom": "^16.5.2",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-final-form": "^3.6.4",
|
||||
"react-popper": "^1.3.2",
|
||||
"react-relay": "^1.7.0-rc.1",
|
||||
"react-responsive": "^5.0.0",
|
||||
"react-test-renderer": "^16.5.2",
|
||||
"react-test-renderer": "^16.7.0",
|
||||
"react-timeago": "^4.1.9",
|
||||
"react-transition-group": "^2.5.0",
|
||||
"react-with-state-props": "^2.0.4",
|
||||
@@ -274,6 +275,7 @@
|
||||
"relay-local-schema": "^0.7.0",
|
||||
"relay-runtime": "^1.7.0-rc.1",
|
||||
"sane": "^4.0.2",
|
||||
"simplemde": "^1.11.2",
|
||||
"simulant": "^0.2.2",
|
||||
"sinon": "^6.1.5",
|
||||
"style-loader": "^0.23.1",
|
||||
@@ -292,7 +294,7 @@
|
||||
"typed-css-modules": "^0.3.4",
|
||||
"typeface-manuale": "0.0.54",
|
||||
"typeface-source-sans-pro": "0.0.54",
|
||||
"typescript": "^3.0.3",
|
||||
"typescript": "3.1.6",
|
||||
"typescript-snapshots-plugin": "^1.2.0",
|
||||
"wait-for-expect": "^1.1.0",
|
||||
"webpack": "^4.27.1",
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
__webpack_public_path__ = JSON.parse(
|
||||
document.getElementById("config").innerText
|
||||
);
|
||||
__webpack_public_path__ =
|
||||
JSON.parse(document.getElementById("config").innerText) || "/";
|
||||
|
||||
@@ -41,7 +41,7 @@ class DecisionHistoryButton extends React.Component {
|
||||
<BaseButton
|
||||
onClick={() => this.toggleVisibilityOncePerFrame(toggleVisibility)}
|
||||
aria-controls={popoverID}
|
||||
forwardRef={forwardRef}
|
||||
ref={forwardRef}
|
||||
className={styles.historyIcon}
|
||||
data-testid="decisionHistory-toggle"
|
||||
>
|
||||
|
||||
@@ -4,14 +4,14 @@ exports[`renders correctly 1`] = `
|
||||
<div
|
||||
data-testid="authBox"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="center"
|
||||
>
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
<ForwardRef(forwardRef)
|
||||
className="AuthBox-container"
|
||||
size="double"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="center"
|
||||
>
|
||||
<div
|
||||
@@ -21,21 +21,21 @@ exports[`renders correctly 1`] = `
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<div>
|
||||
<withPropsOnChange(Typography)
|
||||
<ForwardRef(forwardRef)
|
||||
align="center"
|
||||
variant="heading3"
|
||||
>
|
||||
title
|
||||
</withPropsOnChange(Typography)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<withPropsOnChange(BrandName)
|
||||
align="center"
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
content
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -4,12 +4,12 @@ exports[`renders correctly 1`] = `
|
||||
<Localized
|
||||
id="navigation-signOutButton"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
<ForwardRef(forwardRef)
|
||||
className="SignOutButton-root"
|
||||
id="id"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Sign Out
|
||||
</withPropsOnChange(Button)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
`;
|
||||
|
||||
@@ -19,16 +19,13 @@ const mutation = graphql`
|
||||
updateSettings(input: $input) {
|
||||
settings {
|
||||
auth {
|
||||
...FacebookConfigContainer_auth
|
||||
...FacebookConfigContainer_authReadOnly
|
||||
...GoogleConfigContainer_auth
|
||||
...GoogleConfigContainer_authReadOnly
|
||||
...SSOConfigContainer_auth
|
||||
...SSOConfigContainer_authReadOnly
|
||||
...OIDCConfigContainer_auth
|
||||
...OIDCConfigContainer_authReadOnly
|
||||
...DisplayNamesConfigContainer_auth
|
||||
...AuthConfigContainer_auth
|
||||
}
|
||||
...ModerationConfigContainer_settings
|
||||
...GeneralConfigContainer_settings
|
||||
...OrganizationConfigContainer_settings
|
||||
...WordListConfigContainer_settings
|
||||
...AdvancedConfigContainer_settings
|
||||
}
|
||||
clientMutationId
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@ import React from "react";
|
||||
import App from "./components/App";
|
||||
import AuthCheckContainer from "./containers/AuthCheckContainer";
|
||||
import Community from "./routes/community/components/Community";
|
||||
import ConfigureModeration from "./routes/configure/components/Moderation";
|
||||
import ConfigureContainer from "./routes/configure/containers/ConfigureContainer";
|
||||
import ConfigureAuthRouteContainer from "./routes/configure/sections/auth/containers/AuthRouteContainer";
|
||||
import ConfigureAdvancedRouteContainer from "./routes/configure/sections/advanced/containers/AdvancedConfigRouteContainer";
|
||||
import ConfigureAuthRouteContainer from "./routes/configure/sections/auth/containers/AuthConfigRouteContainer";
|
||||
import ConfigureGeneralRouteContainer from "./routes/configure/sections/general/containers/GeneralConfigRouteContainer";
|
||||
import ConfigureModerationRouteContainer from "./routes/configure/sections/moderation/containers/ModerationConfigRouteContainer";
|
||||
import ConfigureOrganizationRouteContainer from "./routes/configure/sections/organization/containers/OrganizationRouteContainer";
|
||||
import ConfigureWordListRouteContainer from "./routes/configure/sections/wordList/containers/WordListRouteContainer";
|
||||
import LoginContainer from "./routes/login/containers/LoginContainer";
|
||||
import ModerateContainer from "./routes/moderate/containers/ModerateContainer";
|
||||
import {
|
||||
@@ -40,9 +44,28 @@ export default makeRouteConfig(
|
||||
<Route path="community" Component={Community} />
|
||||
<Route path="stories" Component={Stories} />
|
||||
<Route path="configure" Component={ConfigureContainer}>
|
||||
<Redirect from="/" to="/admin/configure/moderation" />
|
||||
<Route path="moderation" Component={ConfigureModeration} />
|
||||
<Redirect from="/" to="/admin/configure/general" />
|
||||
<Route
|
||||
path="general"
|
||||
{...ConfigureGeneralRouteContainer.routeConfig}
|
||||
/>
|
||||
<Route
|
||||
path="organization"
|
||||
{...ConfigureOrganizationRouteContainer.routeConfig}
|
||||
/>
|
||||
<Route
|
||||
path="moderation"
|
||||
{...ConfigureModerationRouteContainer.routeConfig}
|
||||
/>
|
||||
<Route
|
||||
path="wordList"
|
||||
{...ConfigureWordListRouteContainer.routeConfig}
|
||||
/>
|
||||
<Route path="auth" {...ConfigureAuthRouteContainer.routeConfig} />
|
||||
<Route
|
||||
path="advanced"
|
||||
{...ConfigureAdvancedRouteContainer.routeConfig}
|
||||
/>
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<MainLayout>
|
||||
<withPropsOnChange(Typography)
|
||||
<ForwardRef(forwardRef)
|
||||
variant="heading3"
|
||||
>
|
||||
Community
|
||||
</withPropsOnChange(Typography)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</MainLayout>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import Subheader from "../components/Subheader";
|
||||
|
||||
const ConfigurationSubHeader: StatelessComponent<{}> = () => (
|
||||
<Localized id="configure-configurationSubHeader" strong={<strong />}>
|
||||
<Subheader>Configuration</Subheader>
|
||||
</Localized>
|
||||
);
|
||||
|
||||
export default ConfigurationSubHeader;
|
||||
@@ -30,9 +30,25 @@ const Configure: StatelessComponent<Props> = ({
|
||||
<SideBar>
|
||||
<HorizontalGutter size="double">
|
||||
<Navigation>
|
||||
<Link to="/admin/configure/moderation">Moderation</Link>
|
||||
<Localized id="configure-sideBarNavigation-general">
|
||||
<Link to="/admin/configure/general">General</Link>
|
||||
</Localized>
|
||||
<Localized id="configure-sideBarNavigation-organization">
|
||||
<Link to="/admin/configure/organization">Organization</Link>
|
||||
</Localized>
|
||||
<Localized id="configure-sideBarNavigation-moderation">
|
||||
<Link to="/admin/configure/moderation">Moderation</Link>
|
||||
</Localized>
|
||||
<Localized id="configure-sideBarNavigation-bannedAndSuspectWords">
|
||||
<Link to="/admin/configure/wordList">
|
||||
Banned and Suspect Words
|
||||
</Link>
|
||||
</Localized>
|
||||
<Localized id="configure-sideBarNavigation-authentication">
|
||||
<Link to="/admin/configure/auth">Auth</Link>
|
||||
<Link to="/admin/configure/auth">Authentication</Link>
|
||||
</Localized>
|
||||
<Localized id="configure-sideBarNavigation-advanced">
|
||||
<Link to="/admin/configure/advanced">Advanced</Link>
|
||||
</Localized>
|
||||
</Navigation>
|
||||
</HorizontalGutter>
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
padding-bottom: calc(0.5 * var(--spacing-unit));
|
||||
border-bottom: 1px solid var(--palette-text-primary);
|
||||
margin-bottom: calc(1.5 * var(--spacing-unit));
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Typography } from "talk-ui/components";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import styles from "./Header.css";
|
||||
|
||||
const Header: StatelessComponent = ({ children }) => (
|
||||
<Typography variant="heading1" className={styles.root}>
|
||||
type Props = PropTypesOf<typeof Typography>;
|
||||
|
||||
const Header: StatelessComponent<Props> = ({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}) => (
|
||||
<Typography
|
||||
variant="heading1"
|
||||
className={cn(className, styles.root)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Typography } from "talk-ui/components";
|
||||
|
||||
import Header from "./Header";
|
||||
|
||||
const Moderation: StatelessComponent = ({ children }) => (
|
||||
<div>
|
||||
<Header>Perspective Toxic Comment Filter</Header>
|
||||
<Typography>
|
||||
Using the Perspective API, the Toxic Comment filter warns users when
|
||||
comments exceed the predefined toxicity threshold. Toxic comments will not
|
||||
be published and are placed in the Pending Queue for review by a
|
||||
moderator. If approved by a moderator, the comment will be published.
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Moderation;
|
||||
@@ -1,26 +1,18 @@
|
||||
.link {
|
||||
composes: sideNavigationItem from "talk-ui/shared/typography.css";
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
padding: calc(0.5 * var(--spacing-unit)) var(--spacing-unit)
|
||||
calc(0.5 * var(--spacing-unit)) calc(var(--spacing-unit) + 2px);
|
||||
margin-left: 2px;
|
||||
border-left: 1px solid var(--palette-grey-lighter);
|
||||
|
||||
color: var(--palette-text-primary);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-size: calc(18rem / var(--rem-base));
|
||||
line-height: calc(20em / 18);
|
||||
letter-spacing: calc(0.2em / 18);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.linkActive {
|
||||
font-weight: var(--font-weight-bold);
|
||||
composes: sideNavigationActive from "talk-ui/shared/typography.css";
|
||||
margin-left: 0px;
|
||||
border-left: calc(0.5 * var(--spacing-unit)) solid var(--palette-brand-main);
|
||||
padding-left: var(--spacing-unit);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { parseStringBool } from "talk-framework/lib/form";
|
||||
import { Validator } from "talk-framework/lib/validation";
|
||||
import { RadioButton } from "talk-ui/components";
|
||||
|
||||
interface Props {
|
||||
validate?: Validator;
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
invert?: boolean;
|
||||
}
|
||||
|
||||
const OnOffField: StatelessComponent<Props> = ({
|
||||
name,
|
||||
disabled,
|
||||
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>
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default OnOffField;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { parseStringBool } from "talk-framework/lib/form";
|
||||
import { Validator } from "talk-framework/lib/validation";
|
||||
import { RadioButton } from "talk-ui/components";
|
||||
|
||||
interface Props {
|
||||
validate?: Validator;
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
invert?: boolean;
|
||||
}
|
||||
|
||||
const PermissionField: StatelessComponent<Props> = ({
|
||||
name,
|
||||
disabled,
|
||||
invert = false,
|
||||
}) => (
|
||||
<div>
|
||||
<Field name={name} type="radio" parse={parseStringBool} value={!invert}>
|
||||
{({ input }) => (
|
||||
<Localized id="configure-permissionField-allow">
|
||||
<RadioButton
|
||||
id={`${input.name}-allow`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
value={input.value}
|
||||
>
|
||||
Allow
|
||||
</RadioButton>
|
||||
</Localized>
|
||||
)}
|
||||
</Field>
|
||||
<Field name={name} type="radio" parse={parseStringBool} value={invert}>
|
||||
{({ input }) => (
|
||||
<Localized id="configure-permissionField-dontAllow">
|
||||
<RadioButton
|
||||
id={`${input.name}-dontAllow`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
value={input.value}
|
||||
>
|
||||
Don't allow
|
||||
</RadioButton>
|
||||
</Localized>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PermissionField;
|
||||
@@ -0,0 +1,5 @@
|
||||
.root {
|
||||
padding-bottom: calc(0.25 * var(--spacing-unit));
|
||||
border-bottom: 1px solid var(--palette-divider);
|
||||
margin-bottom: calc(0.75 * var(--spacing-unit));
|
||||
}
|
||||
+3
-3
@@ -3,13 +3,13 @@ import { createRenderer } from "react-test-renderer/shallow";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Moderation from "./Moderation";
|
||||
import Subheader from "./Subheader";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof Moderation> = {
|
||||
const props: PropTypesOf<typeof Subheader> = {
|
||||
children: "child",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<Moderation {...props} />);
|
||||
renderer.render(<Subheader {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Typography } from "talk-ui/components";
|
||||
|
||||
import styles from "./Subheader.css";
|
||||
|
||||
const Subheader: StatelessComponent = ({ children }) => (
|
||||
<Typography variant="heading3" className={styles.root}>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
export default Subheader;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { ValidationMessage as UIValidationMessage } from "talk-ui/components";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
import styles from "./ValidationMessage.css";
|
||||
|
||||
interface Props extends PropTypesOf<typeof UIValidationMessage> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ValidationMessage: StatelessComponent<Props> = ({
|
||||
children,
|
||||
...rest
|
||||
}) => (
|
||||
<UIValidationMessage {...rest} className={styles.root}>
|
||||
{children}
|
||||
</UIValidationMessage>
|
||||
);
|
||||
|
||||
export default ValidationMessage;
|
||||
+2
-2
@@ -4,7 +4,7 @@ exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="ConfigBox-root"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
className="ConfigBox-title"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
@@ -18,7 +18,7 @@ exports[`renders correctly 1`] = `
|
||||
topRight
|
||||
</span>
|
||||
</div>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<div
|
||||
className="ConfigBox-content"
|
||||
>
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Typography)
|
||||
<ForwardRef(forwardRef)
|
||||
className="Header-root"
|
||||
variant="heading1"
|
||||
>
|
||||
child
|
||||
</withPropsOnChange(Typography)>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
className="Layout-root"
|
||||
>
|
||||
child
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div>
|
||||
<Header>
|
||||
Perspective Toxic Comment Filter
|
||||
</Header>
|
||||
<withPropsOnChange(Typography)>
|
||||
Using the Perspective API, the Toxic Comment filter warns users when comments exceed the predefined toxicity threshold. Toxic comments will not be published and are placed in the Pending Queue for review by a moderator. If approved by a moderator, the comment will be published.
|
||||
</withPropsOnChange(Typography)>
|
||||
</div>
|
||||
`;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
className="Subheader-root"
|
||||
variant="heading3"
|
||||
>
|
||||
child
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
@@ -102,7 +102,7 @@ class ConfigureContainer extends React.Component<Props> {
|
||||
private addSubmitHook: AddSubmitHook = hook => {
|
||||
this.submitHooks.push(hook);
|
||||
return () => {
|
||||
this.submitHooks = this.submitHooks.filter(h => h === hook);
|
||||
this.submitHooks = this.submitHooks.filter(h => h !== hook);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import CustomCSSConfigContainer from "../containers/CustomCSSConfigContainer";
|
||||
import PermittedDomainsConfigContainer from "../containers/PermittedDomainsConfigContainer";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
settings: PropTypesOf<typeof CustomCSSConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof PermittedDomainsConfigContainer>["settings"];
|
||||
onInitValues: (values: any) => void;
|
||||
}
|
||||
|
||||
const AdvancedConfig: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
settings,
|
||||
onInitValues,
|
||||
}) => (
|
||||
<HorizontalGutter size="double" data-testid="configure-advancedContainer">
|
||||
<CustomCSSConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<PermittedDomainsConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default AdvancedConfig;
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
TextField,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const CustomCSSConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<FormField>
|
||||
<HorizontalGutter size="full">
|
||||
<Localized id="configure-advanced-customCSS">
|
||||
<Header container={<label htmlFor="configure-advanced-customCssUrl" />}>
|
||||
Custom CSS
|
||||
</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-advanced-customCSS-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
URL of a CSS stylesheet that will override default Embed Stream
|
||||
styles. Can be internal or external.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Field name="customCssUrl">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id={`configure-advanced-${input.name}`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
</FormField>
|
||||
);
|
||||
|
||||
export default CustomCSSConfig;
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { formatStringList, parseStringList } from "talk-framework/lib/form";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
TextField,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const PermittedDomainsConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<FormField>
|
||||
<HorizontalGutter size="full">
|
||||
<Localized id="configure-advanced-permittedDomains">
|
||||
<Header container={<label htmlFor="configure-advanced-domains" />}>
|
||||
Permitted Domains
|
||||
</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-advanced-permittedDomains-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Domains where your Talk instance is allowed to be embedded. Typical
|
||||
use is localhost, staging.yourdomain.com, yourdomain.com, etc.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Field name="domains" parse={parseStringList} format={formatStringList}>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id={`configure-advanced-${input.name}`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
</FormField>
|
||||
);
|
||||
|
||||
export default PermittedDomainsConfig;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AdvancedConfigContainer_settings as SettingsData } from "talk-admin/__generated__/AdvancedConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import AdvancedConfig from "../components/AdvancedConfig";
|
||||
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
class AdvancedConfigContainer extends React.Component<Props> {
|
||||
public static routeConfig: RouteProps;
|
||||
private initialValues = {};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.form.initialize(this.initialValues);
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<AdvancedConfig
|
||||
disabled={this.props.submitting}
|
||||
settings={this.props.settings}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment AdvancedConfigContainer_settings on Settings {
|
||||
...CustomCSSConfigContainer_settings
|
||||
...PermittedDomainsConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(AdvancedConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AdvancedConfigRouteContainerQueryResponse } from "talk-admin/__generated__/AdvancedConfigRouteContainerQuery.graphql";
|
||||
import { withRouteConfig } from "talk-framework/lib/router";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import AdvancedConfigContainer from "./AdvancedConfigContainer";
|
||||
|
||||
interface Props {
|
||||
data: AdvancedConfigRouteContainerQueryResponse | null;
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
}
|
||||
|
||||
class AdvancedConfigRouteContainer extends React.Component<Props> {
|
||||
public render() {
|
||||
if (!this.props.data) {
|
||||
return (
|
||||
<Delay>
|
||||
<Spinner />
|
||||
</Delay>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<AdvancedConfigContainer
|
||||
settings={this.props.data.settings}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withRouteConfig({
|
||||
query: graphql`
|
||||
query AdvancedConfigRouteContainerQuery {
|
||||
settings {
|
||||
...AdvancedConfigContainer_settings
|
||||
}
|
||||
}
|
||||
`,
|
||||
cacheConfig: { force: true },
|
||||
})(AdvancedConfigRouteContainer);
|
||||
|
||||
export default enhanced;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { CustomCSSConfigContainer_settings as SettingsData } from "talk-admin/__generated__/CustomCSSConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import CustomCSSConfig from "../components/CustomCSSConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class CustomCSSConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <CustomCSSConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment CustomCSSConfigContainer_settings on Settings {
|
||||
customCssUrl
|
||||
}
|
||||
`,
|
||||
})(CustomCSSConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { PermittedDomainsConfigContainer_settings as SettingsData } from "talk-admin/__generated__/PermittedDomainsConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import PermittedDomainsConfig from "../components/PermittedDomainsConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class PermittedDomainsConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <PermittedDomainsConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment PermittedDomainsConfigContainer_settings on Settings {
|
||||
domains
|
||||
}
|
||||
`,
|
||||
})(PermittedDomainsConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+6
-2
@@ -13,7 +13,11 @@ interface Props {
|
||||
onInitValues: (values: any) => void;
|
||||
}
|
||||
|
||||
const Auth: StatelessComponent<Props> = ({ disabled, auth, onInitValues }) => (
|
||||
const AuthConfig: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
auth,
|
||||
onInitValues,
|
||||
}) => (
|
||||
<HorizontalGutter size="double" data-testid="configure-authContainer">
|
||||
<DisplayNamesConfigContainer
|
||||
disabled={disabled}
|
||||
@@ -28,4 +32,4 @@ const Auth: StatelessComponent<Props> = ({ disabled, auth, onInitValues }) => (
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default Auth;
|
||||
export default AuthConfig;
|
||||
@@ -6,7 +6,7 @@ import { Field } from "react-final-form";
|
||||
import { Validator } from "talk-framework/lib/validation";
|
||||
import { FormField, InputLabel, TextField } from "talk-ui/components";
|
||||
|
||||
import ValidationMessage from "./ValidationMessage";
|
||||
import ValidationMessage from "../../../components/ValidationMessage";
|
||||
|
||||
interface Props {
|
||||
validate?: Validator;
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { Field } from "react-final-form";
|
||||
import { Validator } from "talk-framework/lib/validation";
|
||||
import { FormField, InputLabel, TextField } from "talk-ui/components";
|
||||
|
||||
import ValidationMessage from "./ValidationMessage";
|
||||
import ValidationMessage from "../../../components/ValidationMessage";
|
||||
|
||||
interface Props {
|
||||
validate?: Validator;
|
||||
|
||||
+3
-4
@@ -2,6 +2,7 @@ import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { parseStringBool } from "talk-framework/lib/form";
|
||||
import {
|
||||
Flex,
|
||||
FormField,
|
||||
@@ -16,20 +17,18 @@ interface Props {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const parseStringBool = (v: string) => v === "true";
|
||||
|
||||
const DisplayNamesConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
<Localized id="configure-auth-displayNamesConfig-title">
|
||||
<Header>Display Names</Header>
|
||||
</Localized>
|
||||
<Localized id="configure-auth-displayNamesConfig-explanationShort">
|
||||
<Typography>
|
||||
<Typography variant="detail">
|
||||
Some AUTH integrations include a Display Name as well as a User Name.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Localized id="configure-auth-displayNamesConfig-explanationLong">
|
||||
<Typography>
|
||||
<Typography variant="detail">
|
||||
A User Name has to be unique (there can only be one Juan_Doe, for
|
||||
example), whereas a Display Name does not. If your AUTH provider allows
|
||||
for Display Names, you can enable this option. This allows for fewer
|
||||
|
||||
@@ -28,7 +28,8 @@ import ConfigDescription from "./ConfigDescription";
|
||||
import RedirectField from "./RedirectField";
|
||||
import RegistrationField from "./RegistrationField";
|
||||
import TargetFilterField from "./TargetFilterField";
|
||||
import ValidationMessage from "./ValidationMessage";
|
||||
|
||||
import ValidationMessage from "../../../components/ValidationMessage";
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { ValidationMessage as UIValidationMessage } from "talk-ui/components";
|
||||
|
||||
import styles from "./ValidationMessage.css";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ValidationMessage: StatelessComponent<Props> = ({ children }) => (
|
||||
<UIValidationMessage className={styles.root}>{children}</UIValidationMessage>
|
||||
);
|
||||
|
||||
export default ValidationMessage;
|
||||
+6
-6
@@ -5,7 +5,7 @@ import { get, merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AuthContainer_auth as AuthData } from "talk-admin/__generated__/AuthContainer_auth.graphql";
|
||||
import { AuthConfigContainer_auth as AuthData } from "talk-admin/__generated__/AuthConfigContainer_auth.graphql";
|
||||
import { TalkContext, withContext } from "talk-framework/lib/bootstrap";
|
||||
import { getMessage } from "talk-framework/lib/i18n";
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
SubmitHook,
|
||||
withSubmitHookContext,
|
||||
} from "../../../submitHook";
|
||||
import Auth from "../components/Auth";
|
||||
import AuthConfig from "../components/AuthConfig";
|
||||
|
||||
interface Props {
|
||||
localeBundles: TalkContext["localeBundles"];
|
||||
@@ -26,7 +26,7 @@ interface Props {
|
||||
auth: AuthData;
|
||||
}
|
||||
|
||||
class AuthContainer extends React.Component<Props> {
|
||||
class AuthConfigContainer extends React.Component<Props> {
|
||||
public static routeConfig: RouteProps;
|
||||
private initialValues = {};
|
||||
private removeSubmitHook: RemoveSubmitHook;
|
||||
@@ -85,7 +85,7 @@ class AuthContainer extends React.Component<Props> {
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Auth
|
||||
<AuthConfig
|
||||
disabled={this.props.submitting}
|
||||
auth={this.props.auth}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
@@ -96,7 +96,7 @@ class AuthContainer extends React.Component<Props> {
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
auth: graphql`
|
||||
fragment AuthContainer_auth on Auth {
|
||||
fragment AuthConfigContainer_auth on Auth {
|
||||
...FacebookConfigContainer_auth
|
||||
...FacebookConfigContainer_authReadOnly
|
||||
...GoogleConfigContainer_auth
|
||||
@@ -111,7 +111,7 @@ const enhanced = withFragmentContainer<Props>({
|
||||
`,
|
||||
})(
|
||||
withSubmitHookContext(addSubmitHook => ({ addSubmitHook }))(
|
||||
withContext(({ localeBundles }) => ({ localeBundles }))(AuthContainer)
|
||||
withContext(({ localeBundles }) => ({ localeBundles }))(AuthConfigContainer)
|
||||
)
|
||||
);
|
||||
|
||||
+6
-6
@@ -2,14 +2,14 @@ import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AuthRouteContainerQueryResponse } from "talk-admin/__generated__/AuthRouteContainerQuery.graphql";
|
||||
import { AuthConfigRouteContainerQueryResponse } from "talk-admin/__generated__/AuthConfigRouteContainerQuery.graphql";
|
||||
import { withRouteConfig } from "talk-framework/lib/router";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import AuthContainer from ".//AuthContainer";
|
||||
import AuthConfigContainer from "./AuthConfigContainer";
|
||||
|
||||
interface Props {
|
||||
data: AuthRouteContainerQueryResponse | null;
|
||||
data: AuthConfigRouteContainerQueryResponse | null;
|
||||
form: FormApi;
|
||||
submitting?: boolean;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class AuthRouteContainer extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<AuthContainer
|
||||
<AuthConfigContainer
|
||||
auth={this.props.data.settings.auth}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
@@ -35,10 +35,10 @@ class AuthRouteContainer extends React.Component<Props> {
|
||||
|
||||
const enhanced = withRouteConfig({
|
||||
query: graphql`
|
||||
query AuthRouteContainerQuery {
|
||||
query AuthConfigRouteContainerQuery {
|
||||
settings {
|
||||
auth {
|
||||
...AuthContainer_auth
|
||||
...AuthConfigContainer_auth
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent, Suspense } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import {
|
||||
HorizontalGutter,
|
||||
Spinner,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
import LazyMarkdown from "./LazyMarkdown";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const ClosedStreamMessageConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
<Localized id="configure-general-closedStreamMessage-title">
|
||||
<Header
|
||||
container={
|
||||
<label htmlFor="configure-general-closedStreamMessage-content" />
|
||||
}
|
||||
>
|
||||
Closed Stream Message
|
||||
</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-general-closedStreamMessage-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Write a message to appear after a story is closed for commenting.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Field name="closedMessage">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyMarkdown
|
||||
id="configure-general-closedStreamMessage-content"
|
||||
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 ClosedStreamMessageConfig;
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { DURATION_UNIT, DurationField } from "talk-framework/components";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import { Field } from "react-final-form";
|
||||
import OnOffField from "talk-admin/routes/configure/components/OnOffField";
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
validateWholeNumberGreaterThan,
|
||||
} from "talk-framework/lib/validation";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const ClosingCommentStreamsConfig: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
}) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<Localized id="configure-general-closingCommentStreams-title">
|
||||
<Header container="legend">Closing Comment Streams</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-general-closingCommentStreams-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Set comment streams to close after a defined period of time after a
|
||||
story’s publication
|
||||
</Typography>
|
||||
</Localized>
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-general-closingCommentStreams-closeCommentsAutomatically">
|
||||
<InputLabel container="legend">Close Comments Automatically</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="autoCloseStream" disabled={disabled} />
|
||||
</FormField>
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-general-closingCommentStreams-closeCommentsAfter">
|
||||
<InputLabel container="legend">Close Comments After</InputLabel>
|
||||
</Localized>
|
||||
|
||||
<Field
|
||||
name="closedTimeout"
|
||||
validate={composeValidators(
|
||||
required,
|
||||
validateWholeNumberGreaterThan(0)
|
||||
)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<DurationField
|
||||
units={[
|
||||
DURATION_UNIT.HOURS,
|
||||
DURATION_UNIT.DAYS,
|
||||
DURATION_UNIT.WEEKS,
|
||||
]}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default ClosingCommentStreamsConfig;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
.commentEditingTextInput {
|
||||
width: 120px;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { DURATION_UNIT, DurationField } from "talk-framework/components";
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
validateWholeNumberGreaterThanOrEqual,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const CommentEditingConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
<Localized id="configure-general-commentEditing-title">
|
||||
<Header>Comment Editing</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-general-commentEditing-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Set a limit on how long commenters have to edit their comments sitewide.
|
||||
Edited comments are marked as (Edited) on the comment stream and the
|
||||
moderation panel.
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-general-commentEditing-commentEditTimeFrame">
|
||||
<InputLabel container="legend">Comment Edit Timeframe</InputLabel>
|
||||
</Localized>
|
||||
<Field
|
||||
name="editCommentWindowLength"
|
||||
validate={composeValidators(
|
||||
required,
|
||||
validateWholeNumberGreaterThanOrEqual(0)
|
||||
)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<DurationField
|
||||
units={[
|
||||
DURATION_UNIT.SECONDS,
|
||||
DURATION_UNIT.MINUTES,
|
||||
DURATION_UNIT.HOURS,
|
||||
]}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default CommentEditingConfig;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
.commentLengthTextInput {
|
||||
width: 150px;
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import {
|
||||
composeValidators,
|
||||
createValidator,
|
||||
validateWholeNumberGreaterThan,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
TextField,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
import OnOffField from "../../../components/OnOffField";
|
||||
|
||||
import { formatEmpty, parseEmptyAsNull } from "talk-framework/lib/form";
|
||||
import styles from "./CommentLengthConfig.css";
|
||||
|
||||
const validateMaxLongerThanMin = createValidator(
|
||||
(v, values) =>
|
||||
v === null ||
|
||||
values.charCount.min === null ||
|
||||
parseInt(v, 10) > parseInt(values.charCount.min, 10),
|
||||
// tslint:disable-next-line:jsx-wrap-multiline
|
||||
<Localized id="configure-general-commentLength-validateLongerThanMin">
|
||||
<span>Please enter a number longer than the minimum length</span>
|
||||
</Localized>
|
||||
);
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const CommentLengthConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<Localized id="configure-general-commentLength-title">
|
||||
<Header container="legend">Comment Length</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-general-commentLength-setLimit"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Set a limit on the length of comments sitewide
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-general-commentLength-limitCommentLength">
|
||||
<InputLabel>Limit Comment Length</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="charCount.enabled" disabled={disabled} />
|
||||
</FormField>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-general-commentLength-minCommentLength">
|
||||
<InputLabel htmlFor="configure-general-commentLength-min">
|
||||
Minimum Comment Length
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Field
|
||||
name="charCount.min"
|
||||
validate={validateWholeNumberGreaterThan(0)}
|
||||
parse={parseEmptyAsNull}
|
||||
format={formatEmpty}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Localized
|
||||
id="configure-general-commentLength-textField"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<TextField
|
||||
id="configure-general-commentLength-min"
|
||||
className={styles.commentLengthTextInput}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
adornment={
|
||||
<Localized id="configure-general-commentLength-characters">
|
||||
<Typography variant="bodyCopy">Characters</Typography>
|
||||
</Localized>
|
||||
}
|
||||
placeholder={"No limit"}
|
||||
textAlignCenter
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Localized id="configure-general-commentLength-maxCommentLength">
|
||||
<InputLabel htmlFor="configure-general-commentLength-max">
|
||||
Maximum Comment Length
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Field
|
||||
name="charCount.max"
|
||||
validate={composeValidators(
|
||||
validateWholeNumberGreaterThan(0),
|
||||
validateMaxLongerThanMin
|
||||
)}
|
||||
parse={parseEmptyAsNull}
|
||||
format={formatEmpty}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Localized
|
||||
id="configure-general-commentLength-textField"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<TextField
|
||||
id="configure-general-commentLength-max"
|
||||
className={styles.commentLengthTextInput}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
adornment={
|
||||
<Localized id="configure-general-commentLength-characters">
|
||||
<Typography variant="bodyCopy">Characters</Typography>
|
||||
</Localized>
|
||||
}
|
||||
placeholder={"No limit"}
|
||||
textAlignCenter
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default CommentLengthConfig;
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import ClosedStreamMessageConfigContainer from "../containers/ClosedStreamMessageConfigContainer";
|
||||
import ClosingCommentStreamsConfigContainer from "../containers/ClosingCommentStreamsConfigContainer";
|
||||
import CommentEditingConfigContainer from "../containers/CommentEditingConfigContainer";
|
||||
import CommentLengthConfigContainer from "../containers/CommentLengthConfigContainer";
|
||||
import GuidelinesConfigContainer from "../containers/GuidelinesConfigContainer";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
settings: PropTypesOf<typeof GuidelinesConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof CommentLengthConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof CommentEditingConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosedStreamMessageConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"];
|
||||
onInitValues: (values: any) => void;
|
||||
}
|
||||
|
||||
const General: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
settings,
|
||||
onInitValues,
|
||||
}) => (
|
||||
<HorizontalGutter size="double" data-testid="configure-generalContainer">
|
||||
<GuidelinesConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<CommentLengthConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<CommentEditingConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<ClosingCommentStreamsConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<ClosedStreamMessageConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default General;
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent, Suspense } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { ExternalLink } from "talk-framework/lib/i18n/components";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
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 GuidelinesConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<Localized id="configure-general-guidelines-title">
|
||||
<Header container="legend">Community Guidelines Summary</Header>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-general-guidelines-showCommunityGuidelines">
|
||||
<InputLabel container="legend">
|
||||
Show Community Guidelines Summary
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="communityGuidelinesEnable" disabled={disabled} />
|
||||
</FormField>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-general-guidelines-title">
|
||||
<InputLabel htmlFor="configure-general-guidelines-content">
|
||||
Community Guidelines Summary
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-general-guidelines-explanation"
|
||||
strong={<strong />}
|
||||
externalLink={<ExternalLink href="#" />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Write a summary of your community guidelines that will appear at the
|
||||
top of each comment stream sitewide. Your summary can be formatted
|
||||
using Markdown Syntax. More information on how to use Markdown can be
|
||||
found here.
|
||||
</Typography>
|
||||
</Localized>
|
||||
</FormField>
|
||||
|
||||
<Field name="communityGuidelines">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyMarkdown
|
||||
id="configure-general-guidelines-content"
|
||||
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 GuidelinesConfig;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
export function loadMarkdownEditor() {
|
||||
return import("talk-framework/components/loadables/MarkdownEditor" /* webpackChunkName: "markdownEditor" */);
|
||||
}
|
||||
|
||||
export default React.lazy(loadMarkdownEditor);
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { ClosedStreamMessageConfigContainer_settings as SettingsData } from "talk-admin/__generated__/ClosedStreamMessageConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import ClosedStreamMessageConfig from "../components/ClosedStreamMessageConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class ClosedStreamMessageConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <ClosedStreamMessageConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment ClosedStreamMessageConfigContainer_settings on Settings {
|
||||
closedMessage
|
||||
}
|
||||
`,
|
||||
})(ClosedStreamMessageConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { ClosingCommentStreamsConfigContainer_settings as SettingsData } from "talk-admin/__generated__/ClosingCommentStreamsConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import ClosingCommentStreamsConfig from "../components/ClosingCommentStreamsConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class ClosingCommentStreamsConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <ClosingCommentStreamsConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment ClosingCommentStreamsConfigContainer_settings on Settings {
|
||||
autoCloseStream
|
||||
closedTimeout
|
||||
}
|
||||
`,
|
||||
})(ClosingCommentStreamsConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { CommentEditingConfigContainer_settings as SettingsData } from "talk-admin/__generated__/CommentEditingConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import CommentEditingConfig from "../components/CommentEditingConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class CommentEditingConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <CommentEditingConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment CommentEditingConfigContainer_settings on Settings {
|
||||
editCommentWindowLength
|
||||
}
|
||||
`,
|
||||
})(CommentEditingConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { CommentLengthConfigContainer_settings as SettingsData } from "talk-admin/__generated__/CommentLengthConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import CommentLengthConfig from "../components/CommentLengthConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class CommentLengthConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <CommentLengthConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment CommentLengthConfigContainer_settings on Settings {
|
||||
charCount {
|
||||
enabled
|
||||
min
|
||||
max
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(CommentLengthConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { GeneralConfigContainer_settings as SettingsData } from "talk-admin/__generated__/GeneralConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import GeneralConfig from "../components/GeneralConfig";
|
||||
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
class GeneralConfigContainer extends React.Component<Props> {
|
||||
public static routeConfig: RouteProps;
|
||||
private initialValues = {};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.form.initialize(this.initialValues);
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<GeneralConfig
|
||||
disabled={this.props.submitting}
|
||||
settings={this.props.settings}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment GeneralConfigContainer_settings on Settings {
|
||||
...GuidelinesConfigContainer_settings
|
||||
...CommentLengthConfigContainer_settings
|
||||
...CommentEditingConfigContainer_settings
|
||||
...ClosedStreamMessageConfigContainer_settings
|
||||
...ClosingCommentStreamsConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(GeneralConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { GeneralConfigRouteContainerQueryResponse } from "talk-admin/__generated__/GeneralConfigRouteContainerQuery.graphql";
|
||||
import { withRouteConfig } from "talk-framework/lib/router";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import { loadMarkdownEditor } from "../components/LazyMarkdown";
|
||||
import GeneralConfigContainer from "./GeneralConfigContainer";
|
||||
|
||||
interface Props {
|
||||
data: GeneralConfigRouteContainerQueryResponse | null;
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
}
|
||||
|
||||
class GeneralConfigRouteContainer extends React.Component<Props> {
|
||||
public render() {
|
||||
if (!this.props.data) {
|
||||
return (
|
||||
<Delay>
|
||||
<Spinner />
|
||||
</Delay>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<GeneralConfigContainer
|
||||
settings={this.props.data.settings}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withRouteConfig({
|
||||
getQuery: () => {
|
||||
// Start prefetching markdown editor.
|
||||
loadMarkdownEditor();
|
||||
|
||||
// Fetch graphql data.
|
||||
return graphql`
|
||||
query GeneralConfigRouteContainerQuery {
|
||||
settings {
|
||||
...GeneralConfigContainer_settings
|
||||
}
|
||||
}
|
||||
`;
|
||||
},
|
||||
cacheConfig: { force: true },
|
||||
})(GeneralConfigRouteContainer);
|
||||
|
||||
export default enhanced;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { GuidelinesConfigContainer_settings as SettingsData } from "talk-admin/__generated__/GuidelinesConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import GuidelinesConfig from "../components/GuidelinesConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class GuidelinesConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <GuidelinesConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment GuidelinesConfigContainer_settings on Settings {
|
||||
communityGuidelinesEnable
|
||||
communityGuidelines
|
||||
}
|
||||
`,
|
||||
})(GuidelinesConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { identity } from "lodash";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { Validator } from "talk-framework/lib/validation";
|
||||
import { FormField, InputLabel, TextField } from "talk-ui/components";
|
||||
|
||||
import ValidationMessage from "../../../components/ValidationMessage";
|
||||
|
||||
interface Props {
|
||||
validate?: Validator;
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const APIKeyField: StatelessComponent<Props> = ({
|
||||
name,
|
||||
disabled,
|
||||
validate,
|
||||
}) => (
|
||||
<FormField>
|
||||
<Field name={name} parse={identity} validate={validate}>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Localized id="configure-moderation-apiKey">
|
||||
<InputLabel htmlFor={`configure-moderation-${input.name}`}>
|
||||
API Key
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<TextField
|
||||
id={`configure-moderation-${input.name}`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
);
|
||||
|
||||
export default APIKeyField;
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { ExternalLink } from "talk-framework/lib/i18n/components";
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
validateURL,
|
||||
Validator,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import ConfigurationSubHeader from "../../../components/ConfigurationSubHeader";
|
||||
import Header from "../../../components/Header";
|
||||
import OnOffField from "../../../components/OnOffField";
|
||||
import ValidationMessage from "../../../components/ValidationMessage";
|
||||
import APIKeyField from "./APIKeyField";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const AkismetConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
const validateWhenEnabled = (validator: Validator): Validator => (
|
||||
v,
|
||||
values
|
||||
) => {
|
||||
if (values.integrations.akismet.enabled) {
|
||||
return validator(v, values);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
return (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<Localized id="configure-moderation-akismet-title">
|
||||
<Header container="legend">Akismet Spam Detection Filter</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-moderation-akismet-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Submitted comments are passed to the Akismet API for spam detection.
|
||||
If a comment is determined to be spam, it will prompt the user,
|
||||
indicating that the comment might be considered spam. If the user
|
||||
continues after this point with the still spam-like comment, the
|
||||
comment will be marked as containing spam, will not be published and
|
||||
are placed in the Pending Queue for review by a moderator. If approved
|
||||
by a moderator, the comment will be published.
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-moderation-akismet-filter">
|
||||
<InputLabel container="legend">Spam Detection Filter</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="integrations.akismet.enabled" disabled={disabled} />
|
||||
</FormField>
|
||||
<div>
|
||||
<ConfigurationSubHeader />
|
||||
<Localized
|
||||
id="configure-moderation-akismet-accountNote"
|
||||
externalLink={<ExternalLink />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Note: You must add your active domain(s) in your Akismet account:
|
||||
https://akismet.com/account/
|
||||
</Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<APIKeyField
|
||||
name="integrations.akismet.key"
|
||||
disabled={disabled}
|
||||
validate={validateWhenEnabled(required)}
|
||||
/>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-moderation-akismet-siteURL">
|
||||
<InputLabel htmlFor="configure-moderation-akismet-site">
|
||||
Site URL
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Field
|
||||
name={"integrations.akismet.site"}
|
||||
validate={validateWhenEnabled(
|
||||
composeValidators(required, validateURL)
|
||||
)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id="configure-moderation-akismet-site"
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
export default AkismetConfig;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import ModerationConfig from "./ModerationConfig";
|
||||
|
||||
const ModerationConfigN = removeFragmentRefs(ModerationConfig);
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof ModerationConfigN> = {
|
||||
disabled: false,
|
||||
settings: {},
|
||||
onInitValues: noop,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<ModerationConfigN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import AkismetConfigContainer from "../containers/AkismetConfigContainer";
|
||||
import PerspectiveConfigContainer from "../containers/PerspectiveConfigContainer";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
settings: PropTypesOf<typeof AkismetConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof PerspectiveConfigContainer>["settings"];
|
||||
onInitValues: (values: any) => void;
|
||||
}
|
||||
|
||||
const ModerationConfig: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
settings,
|
||||
onInitValues,
|
||||
}) => (
|
||||
<HorizontalGutter size="double" data-testid="configure-moderationContainer">
|
||||
<PerspectiveConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<AkismetConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default ModerationConfig;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
.thresholdTextField {
|
||||
width: 60px;
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { formatPercentage, parsePercentage } from "talk-framework/lib/form";
|
||||
|
||||
import { ExternalLink } from "talk-framework/lib/i18n/components";
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
validatePercentage,
|
||||
validateURL,
|
||||
Validator,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
InputLabel,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import ConfigurationSubHeader from "../../../components/ConfigurationSubHeader";
|
||||
import Header from "../../../components/Header";
|
||||
import OnOffField from "../../../components/OnOffField";
|
||||
import PermissionField from "../../../components/PermissionField";
|
||||
import ValidationMessage from "../../../components/ValidationMessage";
|
||||
import APIKeyField from "./APIKeyField";
|
||||
|
||||
import styles from "./PerspectiveConfig.css";
|
||||
|
||||
/* TODO: use a common constants for both client and server. */
|
||||
const TOXICITY_DEFAULT = 80;
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
const validateWhenEnabled = (validator: Validator): Validator => (
|
||||
v,
|
||||
values
|
||||
) => {
|
||||
if (values.integrations.perspective.enabled) {
|
||||
return validator(v, values);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
return (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<Localized id="configure-moderation-perspective-title">
|
||||
<Header container="legend">Perspective Toxic Comment Filter</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-moderation-perspective-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Using the Perspective API, the Toxic Comment filter warns users when
|
||||
comments exceed the predefined toxicity threshold. Comments with a
|
||||
toxicity score above the threshold will not be published and are
|
||||
placed in the Pending Queue for review by a moderator. If approved by
|
||||
a moderator, the comment will be published.
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-moderation-perspective-filter">
|
||||
<InputLabel container="legend">Spam Detection Filter</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField
|
||||
name="integrations.perspective.enabled"
|
||||
disabled={disabled}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-moderation-perspective-toxicityThreshold">
|
||||
<InputLabel htmlFor="configure-moderation-perspective-threshold">
|
||||
Toxicity Threshold
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-moderation-perspective-toxicityThresholdDescription"
|
||||
$default={TOXICITY_DEFAULT}
|
||||
>
|
||||
<InputDescription>
|
||||
This value can be set a percentage between 0 and 100. This number
|
||||
represents the likelihood that a comment is toxic, according to
|
||||
Perspective API. Defaults to $default.
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
<Field
|
||||
name="integrations.perspective.threshold"
|
||||
parse={parsePercentage}
|
||||
format={formatPercentage}
|
||||
validate={validatePercentage(0, 1)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id="configure-moderation-perspective-threshold"
|
||||
className={styles.thresholdTextField}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
adornment={<Typography variant="bodyCopy">%</Typography>}
|
||||
placeholder={TOXICITY_DEFAULT.toString()}
|
||||
textAlignCenter
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<Localized id="configure-moderation-perspective-allowStoreCommentData">
|
||||
<InputLabel container="legend">
|
||||
Allow Google to Store Comment Data
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-moderation-perspective-allowStoreCommentDataDescription">
|
||||
<InputDescription>
|
||||
Stored comments will be used for future research and community model
|
||||
building purposes to improve the API over time
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
<div>
|
||||
<PermissionField
|
||||
name="integrations.perspective.doNotStore"
|
||||
disabled={disabled}
|
||||
invert
|
||||
/>
|
||||
</div>
|
||||
</FormField>
|
||||
<div>
|
||||
<ConfigurationSubHeader />
|
||||
<Localized
|
||||
id="configure-moderation-perspective-accountNote"
|
||||
externalLink={<ExternalLink />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
For additional information on how to set up the Perspective Toxic
|
||||
Comment Filter please visit:
|
||||
https://github.com/conversationai/perspectiveapi/blob/master/quickstart.md
|
||||
</Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<APIKeyField
|
||||
name="integrations.perspective.key"
|
||||
disabled={disabled}
|
||||
validate={validateWhenEnabled(required)}
|
||||
/>
|
||||
<FormField>
|
||||
<Localized id="configure-moderation-perspective-customEndpoint">
|
||||
<InputLabel htmlFor="configure-moderation-perspective-customEndpoint">
|
||||
Custom Endpoint
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-moderation-perspective-defaultEndpoint"
|
||||
$default="https://commentanalyzer.googleapis.com/v1alpha1"
|
||||
>
|
||||
<InputDescription>
|
||||
By default the endpoint is set to $default. You may override this
|
||||
here
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
<Field
|
||||
name="integrations.perspective.endpoint"
|
||||
validate={composeValidators(validateURL)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id="configure-moderation-perspective-customEndpoint"
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
export default PerspectiveConfig;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
data-testid="configure-moderationContainer"
|
||||
size="double"
|
||||
>
|
||||
<Relay(PerspectiveConfigContainer)
|
||||
disabled={false}
|
||||
onInitValues={[Function]}
|
||||
settings={Object {}}
|
||||
/>
|
||||
<Relay(AkismetConfigContainer)
|
||||
disabled={false}
|
||||
onInitValues={[Function]}
|
||||
settings={Object {}}
|
||||
/>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { AkismetConfigContainer_settings as SettingsData } from "talk-admin/__generated__/AkismetConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import AkismetConfig from "../components/AkismetConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class AkismetConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <AkismetConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment AkismetConfigContainer_settings on Settings {
|
||||
integrations {
|
||||
akismet {
|
||||
enabled
|
||||
key
|
||||
site
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(AkismetConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { ModerationConfigContainer_settings as SettingsData } from "talk-admin/__generated__/ModerationConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import ModerationConfig from "../components/ModerationConfig";
|
||||
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
class ModerationConfigContainer extends React.Component<Props> {
|
||||
public static routeConfig: RouteProps;
|
||||
private initialValues = {};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.form.initialize(this.initialValues);
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<ModerationConfig
|
||||
disabled={this.props.submitting}
|
||||
settings={this.props.settings}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment ModerationConfigContainer_settings on Settings {
|
||||
...AkismetConfigContainer_settings
|
||||
...PerspectiveConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(ModerationConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { ModerationConfigRouteContainerQueryResponse } from "talk-admin/__generated__/ModerationConfigRouteContainerQuery.graphql";
|
||||
import { withRouteConfig } from "talk-framework/lib/router";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import ModerationConfigContainer from "./ModerationConfigContainer";
|
||||
|
||||
interface Props {
|
||||
data: ModerationConfigRouteContainerQueryResponse | null;
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
}
|
||||
|
||||
class ModerationConfigRouteContainer extends React.Component<Props> {
|
||||
public render() {
|
||||
if (!this.props.data) {
|
||||
return (
|
||||
<Delay>
|
||||
<Spinner />
|
||||
</Delay>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ModerationConfigContainer
|
||||
settings={this.props.data.settings}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withRouteConfig({
|
||||
query: graphql`
|
||||
query ModerationConfigRouteContainerQuery {
|
||||
settings {
|
||||
...ModerationConfigContainer_settings
|
||||
}
|
||||
}
|
||||
`,
|
||||
cacheConfig: { force: true },
|
||||
})(ModerationConfigRouteContainer);
|
||||
|
||||
export default enhanced;
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { PerspectiveConfigContainer_settings as SettingsData } from "talk-admin/__generated__/PerspectiveConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import PerspectiveConfig from "../components/PerspectiveConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class PerspectiveConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <PerspectiveConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment PerspectiveConfigContainer_settings on Settings {
|
||||
integrations {
|
||||
perspective {
|
||||
enabled
|
||||
endpoint
|
||||
key
|
||||
threshold
|
||||
doNotStore
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(PerspectiveConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import OrganizationContactEmailConfigContainer from "../containers/OrganizationContactEmailConfigContainer";
|
||||
import OrganizationNameConfigContainer from "../containers/OrganizationNameConfigContainer";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
settings: PropTypesOf<typeof OrganizationNameConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof OrganizationContactEmailConfigContainer>["settings"];
|
||||
onInitValues: (values: any) => void;
|
||||
}
|
||||
|
||||
const OrganizationConfig: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
settings,
|
||||
onInitValues,
|
||||
}) => (
|
||||
<HorizontalGutter size="double" data-testid="configure-organizationContainer">
|
||||
<OrganizationNameConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<OrganizationContactEmailConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default OrganizationConfig;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { required } from "talk-framework/lib/validation";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
TextField,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<FormField>
|
||||
<HorizontalGutter size="full">
|
||||
<Localized id="configure-organization-email">
|
||||
<Header
|
||||
container={
|
||||
<label htmlFor="configure-organization-organizationContactEmail" />
|
||||
}
|
||||
>
|
||||
Organization Email
|
||||
</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-organization-emailExplanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">This E-Mail will be used</Typography>
|
||||
</Localized>
|
||||
<Field name="organizationContactEmail" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id={`configure-organization-${input.name}`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
</FormField>
|
||||
);
|
||||
|
||||
export default OrganizationNameConfig;
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { required } from "talk-framework/lib/validation";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
TextField,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const OrganizationNameConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<FormField>
|
||||
<HorizontalGutter size="full">
|
||||
<Localized id="configure-organization-name">
|
||||
<Header
|
||||
container={
|
||||
<label htmlFor="configure-organization-organizationName" />
|
||||
}
|
||||
>
|
||||
Organization Name
|
||||
</Header>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-organization-nameExplanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Typography variant="detail">
|
||||
Your organization name will appear on emails sent by Talk to your
|
||||
community and organization members
|
||||
</Typography>
|
||||
</Localized>
|
||||
<Field name="organizationName" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id={`configure-organization-${input.name}`}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
</FormField>
|
||||
);
|
||||
|
||||
export default OrganizationNameConfig;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { OrganizationConfigContainer_settings as SettingsData } from "talk-admin/__generated__/OrganizationConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import OrganizationConfig from "../components/OrganizationConfig";
|
||||
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
class OrganizationConfigContainer extends React.Component<Props> {
|
||||
public static routeConfig: RouteProps;
|
||||
private initialValues = {};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.form.initialize(this.initialValues);
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<OrganizationConfig
|
||||
disabled={this.props.submitting}
|
||||
settings={this.props.settings}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment OrganizationConfigContainer_settings on Settings {
|
||||
...OrganizationNameConfigContainer_settings
|
||||
...OrganizationContactEmailConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(OrganizationConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { OrganizationContactEmailConfigContainer_settings as SettingsData } from "talk-admin/__generated__/OrganizationContactEmailConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import OrganizationContactEmailConfig from "../components/OrganizationContactEmailConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class OrganizationContactEmailConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <OrganizationContactEmailConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment OrganizationContactEmailConfigContainer_settings on Settings {
|
||||
organizationContactEmail
|
||||
}
|
||||
`,
|
||||
})(OrganizationContactEmailConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { OrganizationNameConfigContainer_settings as SettingsData } from "talk-admin/__generated__/OrganizationNameConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import OrganizationNameConfig from "../components/OrganizationNameConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class OrganizationNameConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <OrganizationNameConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment OrganizationNameConfigContainer_settings on Settings {
|
||||
organizationName
|
||||
}
|
||||
`,
|
||||
})(OrganizationNameConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { OrganizationRouteContainerQueryResponse } from "talk-admin/__generated__/OrganizationRouteContainerQuery.graphql";
|
||||
import { withRouteConfig } from "talk-framework/lib/router";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import OrganizationConfigContainer from "./OrganizationConfigContainer";
|
||||
|
||||
interface Props {
|
||||
data: OrganizationRouteContainerQueryResponse | null;
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
}
|
||||
|
||||
class OrganizationRouteContainer extends React.Component<Props> {
|
||||
public render() {
|
||||
if (!this.props.data) {
|
||||
return (
|
||||
<Delay>
|
||||
<Spinner />
|
||||
</Delay>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<OrganizationConfigContainer
|
||||
settings={this.props.data.settings}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withRouteConfig({
|
||||
query: graphql`
|
||||
query OrganizationRouteContainerQuery {
|
||||
settings {
|
||||
...OrganizationConfigContainer_settings
|
||||
}
|
||||
}
|
||||
`,
|
||||
cacheConfig: { force: true },
|
||||
})(OrganizationRouteContainer);
|
||||
|
||||
export default enhanced;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
.textArea {
|
||||
margin-top: var(--spacing-unit);
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { ExternalLink } from "talk-framework/lib/i18n/components";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
InputLabel,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
import WordListTextArea from "./WordListTextArea";
|
||||
|
||||
import styles from "./BannedWordListConfig.css";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const BannedWordListConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
<Localized id="configure-wordList-banned-bannedWordsAndPhrases">
|
||||
<Header>Banned Words and Phrases</Header>
|
||||
</Localized>
|
||||
<Localized id="configure-wordList-banned-explanation" strong={<strong />}>
|
||||
<Typography variant="detail">
|
||||
Comments containing a word or phrase in the banned words list are
|
||||
automatically rejected and are not published.
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-wordList-banned-wordList">
|
||||
<InputLabel htmlFor="configure-wordlist-banned">
|
||||
Banned Word List
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-wordList-banned-wordListDetail"
|
||||
strong={<strong />}
|
||||
externalLink={<ExternalLink href="#" />}
|
||||
>
|
||||
<InputDescription>
|
||||
Separate banned words or phrases with a new line. Attempting to copy
|
||||
and paste a comma separated list? Learn how to convert your list to a
|
||||
new line separated list.
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
<div>
|
||||
<WordListTextArea
|
||||
id="configure-wordlist-banned"
|
||||
name={"wordList.banned"}
|
||||
disabled={disabled}
|
||||
className={styles.textArea}
|
||||
/>
|
||||
</div>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default BannedWordListConfig;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
.textArea {
|
||||
margin-top: var(--spacing-unit);
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { ExternalLink } from "talk-framework/lib/i18n/components";
|
||||
import {
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
InputLabel,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
import WordListTextArea from "./WordListTextArea";
|
||||
|
||||
import styles from "./SuspectWordListConfig.css";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const SuspectWordListConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
<Localized id="configure-wordList-suspect-bannedWordsAndPhrases">
|
||||
<Header>Suspect Words and Phrases</Header>
|
||||
</Localized>
|
||||
<Localized id="configure-wordList-suspect-explanation" strong={<strong />}>
|
||||
<Typography variant="detail">
|
||||
Comments containing a word or phrase in the Suspect Words List are
|
||||
placed into the Reported Queue for moderator review and are published
|
||||
(if comments are not pre-moderated).
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField>
|
||||
<Localized id="configure-wordList-suspect-wordList">
|
||||
<InputLabel htmlFor="configure-wordlist-suspect">
|
||||
Suspect Word List
|
||||
</InputLabel>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="configure-wordList-suspect-wordListDetail"
|
||||
strong={<strong />}
|
||||
externalLink={<ExternalLink href="#" />}
|
||||
>
|
||||
<InputDescription>
|
||||
Separate suspect words or phrases with a new line. Attempting to copy
|
||||
and paste a comma separated list? Learn how to convert your list to a
|
||||
new line separated list.
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
<div>
|
||||
<WordListTextArea
|
||||
id="configure-wordlist-suspect"
|
||||
name={"wordList.suspect"}
|
||||
disabled={disabled}
|
||||
className={styles.textArea}
|
||||
/>
|
||||
</div>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default SuspectWordListConfig;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import BannedWordListConfigContainer from "../containers/BannedWordListConfigContainer";
|
||||
import SuspectWordListConfigContainer from "../containers/SuspectWordListConfigContainer";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
settings: PropTypesOf<typeof SuspectWordListConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof BannedWordListConfigContainer>["settings"];
|
||||
onInitValues: (values: any) => void;
|
||||
}
|
||||
|
||||
const WordListConfig: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
settings,
|
||||
onInitValues,
|
||||
}) => (
|
||||
<HorizontalGutter size="double" data-testid="configure-wordListContainer">
|
||||
<BannedWordListConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<SuspectWordListConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default WordListConfig;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
.textArea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: calc(23 * var(--spacing-unit));
|
||||
padding: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import {
|
||||
formatNewLineDelimitedString,
|
||||
parseNewLineDelimitedString,
|
||||
} from "talk-framework/lib/form";
|
||||
import { Validator } from "talk-framework/lib/validation";
|
||||
|
||||
import ValidationMessage from "../../../components/ValidationMessage";
|
||||
|
||||
import styles from "./WordListTextArea.css";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
validate?: Validator;
|
||||
id?: string;
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const WordListTextArea: StatelessComponent<Props> = ({
|
||||
id,
|
||||
name,
|
||||
disabled,
|
||||
validate,
|
||||
className,
|
||||
}) => (
|
||||
<Field
|
||||
name={name}
|
||||
parse={parseNewLineDelimitedString}
|
||||
format={formatNewLineDelimitedString}
|
||||
validate={validate}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<textarea
|
||||
id={id}
|
||||
className={cn(className, styles.textArea)}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
value={input.value}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
|
||||
export default WordListTextArea;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { BannedWordListConfigContainer_settings as SettingsData } from "talk-admin/__generated__/BannedWordListConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import BannedWordListConfig from "../components/BannedWordListConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class BannedWordListConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <BannedWordListConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment BannedWordListConfigContainer_settings on Settings {
|
||||
wordList {
|
||||
banned
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(BannedWordListConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { SuspectWordListConfigContainer_settings as SettingsData } from "talk-admin/__generated__/SuspectWordListConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import SuspectWordListConfig from "../components/SuspectWordListConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class SuspectWordListConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <SuspectWordListConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment SuspectWordListConfigContainer_settings on Settings {
|
||||
wordList {
|
||||
suspect
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(SuspectWordListConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { RouteProps } from "found";
|
||||
import { merge } from "lodash";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { WordListConfigContainer_settings as SettingsData } from "talk-admin/__generated__/WordListConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import WordListConfig from "../components/WordListConfig";
|
||||
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
class WordListConfigContainer extends React.Component<Props> {
|
||||
public static routeConfig: RouteProps;
|
||||
private initialValues = {};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.form.initialize(this.initialValues);
|
||||
}
|
||||
|
||||
private handleOnInitValues = (values: any) => {
|
||||
this.initialValues = merge({}, this.initialValues, values);
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<WordListConfig
|
||||
disabled={this.props.submitting}
|
||||
settings={this.props.settings}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment WordListConfigContainer_settings on Settings {
|
||||
...SuspectWordListConfigContainer_settings
|
||||
...BannedWordListConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(WordListConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { WordListRouteContainerQueryResponse } from "talk-admin/__generated__/WordListRouteContainerQuery.graphql";
|
||||
import { withRouteConfig } from "talk-framework/lib/router";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import WordListConfigContainer from "./WordListConfigContainer";
|
||||
|
||||
interface Props {
|
||||
data: WordListRouteContainerQueryResponse | null;
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
}
|
||||
|
||||
class WordListRouteContainer extends React.Component<Props> {
|
||||
public render() {
|
||||
if (!this.props.data) {
|
||||
return (
|
||||
<Delay>
|
||||
<Spinner />
|
||||
</Delay>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<WordListConfigContainer
|
||||
settings={this.props.data.settings}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withRouteConfig({
|
||||
query: graphql`
|
||||
query WordListRouteContainerQuery {
|
||||
settings {
|
||||
...WordListConfigContainer_settings
|
||||
}
|
||||
}
|
||||
`,
|
||||
cacheConfig: { force: true },
|
||||
})(WordListRouteContainer);
|
||||
|
||||
export default enhanced;
|
||||
+6
-6
@@ -4,7 +4,7 @@ exports[`renders correctly 1`] = `
|
||||
<div
|
||||
data-testid="completeAccountBox"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="center"
|
||||
>
|
||||
<div
|
||||
@@ -13,21 +13,21 @@ exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="CompleteAccountBox-header"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
<ForwardRef(forwardRef)
|
||||
align="center"
|
||||
className="CompleteAccountBox-heading3"
|
||||
color="textLight"
|
||||
variant="heading3"
|
||||
>
|
||||
Finish Setting Up Your Account
|
||||
</withPropsOnChange(Typography)>
|
||||
<withPropsOnChange(Typography)
|
||||
</ForwardRef(forwardRef)>
|
||||
<ForwardRef(forwardRef)
|
||||
align="center"
|
||||
color="textLight"
|
||||
variant="heading1"
|
||||
>
|
||||
title
|
||||
</withPropsOnChange(Typography)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
<div
|
||||
className="CompleteAccountBox-main"
|
||||
@@ -35,6 +35,6 @@ exports[`renders correctly 1`] = `
|
||||
content
|
||||
</div>
|
||||
</div>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+2
-2
@@ -15,9 +15,9 @@ exports[`renders correctly 1`] = `
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon={
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
keyboard_arrow_right
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
}
|
||||
>
|
||||
2
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
alignItems="center"
|
||||
className="HorizontalSeparator-root"
|
||||
justifyContent="center"
|
||||
@@ -14,5 +14,5 @@ exports[`renders correctly 1`] = `
|
||||
>
|
||||
Or
|
||||
</div>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
+3
-3
@@ -12,10 +12,10 @@ exports[`renders correctly 1`] = `
|
||||
</Localized>
|
||||
}
|
||||
>
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
<ForwardRef(forwardRef)
|
||||
size="oneAndAHalf"
|
||||
>
|
||||
<withPropsOnChange(HorizontalGutter) />
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
<ForwardRef(forwardRef) />
|
||||
</ForwardRef(forwardRef)>
|
||||
</AuthBox>
|
||||
`;
|
||||
|
||||
+8
-8
@@ -9,17 +9,17 @@ exports[`renders correctly 1`] = `
|
||||
}
|
||||
id="moderate-acceptButton"
|
||||
>
|
||||
<withPropsOnChange(WithMouseHover)
|
||||
<ForwardRef(forwardRef)
|
||||
aria-label="Accept"
|
||||
className="AcceptButton-root"
|
||||
>
|
||||
<withPropsOnChange(Icon)
|
||||
<ForwardRef(forwardRef)
|
||||
className="AcceptButton-icon"
|
||||
size="lg"
|
||||
>
|
||||
done
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(WithMouseHover)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
`;
|
||||
|
||||
@@ -32,16 +32,16 @@ exports[`renders correctly inverted 1`] = `
|
||||
}
|
||||
id="moderate-acceptButton"
|
||||
>
|
||||
<withPropsOnChange(WithMouseHover)
|
||||
<ForwardRef(forwardRef)
|
||||
aria-label="Accept"
|
||||
className="AcceptButton-root AcceptButton-invert"
|
||||
>
|
||||
<withPropsOnChange(Icon)
|
||||
<ForwardRef(forwardRef)
|
||||
className="AcceptButton-icon"
|
||||
size="lg"
|
||||
>
|
||||
done
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(WithMouseHover)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
`;
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Typography)
|
||||
<ForwardRef(forwardRef)
|
||||
className="custom CommentContent-root"
|
||||
container="div"
|
||||
dangerouslySetInnerHTML={
|
||||
@@ -13,7 +13,7 @@ exports[`renders correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders empty words correctly 1`] = `
|
||||
<withPropsOnChange(Typography)
|
||||
<ForwardRef(forwardRef)
|
||||
className="custom CommentContent-root"
|
||||
container="div"
|
||||
dangerouslySetInnerHTML={
|
||||
|
||||
+6
-6
@@ -1,26 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
alignItems="center"
|
||||
>
|
||||
<withPropsOnChange(Icon)
|
||||
<ForwardRef(forwardRef)
|
||||
className="InReplyTo-icon"
|
||||
>
|
||||
reply
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
|
||||
<Localized
|
||||
id="moderate-inReplyTo"
|
||||
username={<Username />}
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
<ForwardRef(forwardRef)
|
||||
className="InReplyTo-inReplyTo"
|
||||
container="span"
|
||||
variant="timestamp"
|
||||
>
|
||||
Reply to <username><username>
|
||||
</withPropsOnChange(Typography)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
+70
-70
@@ -5,7 +5,7 @@ exports[`renders accepted correctly 1`] = `
|
||||
className="ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-id"
|
||||
>
|
||||
<withPropsOnChange(Flex)>
|
||||
<ForwardRef(forwardRef)>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
@@ -41,10 +41,10 @@ exports[`renders accepted correctly 1`] = `
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
<ForwardRef(forwardRef)
|
||||
anchor={true}
|
||||
color="primary"
|
||||
href="http://localhost/comment"
|
||||
@@ -59,24 +59,24 @@ exports[`renders accepted correctly 1`] = `
|
||||
</span>
|
||||
</Localized>
|
||||
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
arrow_forward
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(Button)>
|
||||
</withPropsOnChange(Flex)>
|
||||
<withPropsOnChange(Flex)
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<Relay(MarkersContainer)
|
||||
comment={Object {}}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
alignItems="center"
|
||||
className="ModerateCard-aside ModerateCard-asideWithoutReplyTo"
|
||||
direction="column"
|
||||
@@ -91,7 +91,7 @@ exports[`renders accepted correctly 1`] = `
|
||||
DECISION
|
||||
</div>
|
||||
</Localized>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<RejectButton
|
||||
@@ -103,9 +103,9 @@ exports[`renders accepted correctly 1`] = `
|
||||
invert={true}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</withPropsOnChange(Card)>
|
||||
`;
|
||||
|
||||
@@ -114,7 +114,7 @@ exports[`renders correctly 1`] = `
|
||||
className="ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-id"
|
||||
>
|
||||
<withPropsOnChange(Flex)>
|
||||
<ForwardRef(forwardRef)>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
@@ -150,10 +150,10 @@ exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
<ForwardRef(forwardRef)
|
||||
anchor={true}
|
||||
color="primary"
|
||||
href="http://localhost/comment"
|
||||
@@ -168,24 +168,24 @@ exports[`renders correctly 1`] = `
|
||||
</span>
|
||||
</Localized>
|
||||
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
arrow_forward
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(Button)>
|
||||
</withPropsOnChange(Flex)>
|
||||
<withPropsOnChange(Flex)
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<Relay(MarkersContainer)
|
||||
comment={Object {}}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
alignItems="center"
|
||||
className="ModerateCard-aside ModerateCard-asideWithoutReplyTo"
|
||||
direction="column"
|
||||
@@ -200,7 +200,7 @@ exports[`renders correctly 1`] = `
|
||||
DECISION
|
||||
</div>
|
||||
</Localized>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<RejectButton
|
||||
@@ -211,9 +211,9 @@ exports[`renders correctly 1`] = `
|
||||
invert={false}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</withPropsOnChange(Card)>
|
||||
`;
|
||||
|
||||
@@ -222,7 +222,7 @@ exports[`renders dangling correctly 1`] = `
|
||||
className="ModerateCard-root ModerateCard-dangling"
|
||||
data-testid="moderate-comment-comment-id"
|
||||
>
|
||||
<withPropsOnChange(Flex)>
|
||||
<ForwardRef(forwardRef)>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
@@ -258,10 +258,10 @@ exports[`renders dangling correctly 1`] = `
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
<ForwardRef(forwardRef)
|
||||
anchor={true}
|
||||
color="primary"
|
||||
href="http://localhost/comment"
|
||||
@@ -276,24 +276,24 @@ exports[`renders dangling correctly 1`] = `
|
||||
</span>
|
||||
</Localized>
|
||||
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
arrow_forward
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(Button)>
|
||||
</withPropsOnChange(Flex)>
|
||||
<withPropsOnChange(Flex)
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<Relay(MarkersContainer)
|
||||
comment={Object {}}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
alignItems="center"
|
||||
className="ModerateCard-aside ModerateCard-asideWithoutReplyTo"
|
||||
direction="column"
|
||||
@@ -308,7 +308,7 @@ exports[`renders dangling correctly 1`] = `
|
||||
DECISION
|
||||
</div>
|
||||
</Localized>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<RejectButton
|
||||
@@ -321,9 +321,9 @@ exports[`renders dangling correctly 1`] = `
|
||||
invert={false}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</withPropsOnChange(Card)>
|
||||
`;
|
||||
|
||||
@@ -332,7 +332,7 @@ exports[`renders rejected correctly 1`] = `
|
||||
className="ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-id"
|
||||
>
|
||||
<withPropsOnChange(Flex)>
|
||||
<ForwardRef(forwardRef)>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
@@ -368,10 +368,10 @@ exports[`renders rejected correctly 1`] = `
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
<ForwardRef(forwardRef)
|
||||
anchor={true}
|
||||
color="primary"
|
||||
href="http://localhost/comment"
|
||||
@@ -386,24 +386,24 @@ exports[`renders rejected correctly 1`] = `
|
||||
</span>
|
||||
</Localized>
|
||||
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
arrow_forward
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(Button)>
|
||||
</withPropsOnChange(Flex)>
|
||||
<withPropsOnChange(Flex)
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<Relay(MarkersContainer)
|
||||
comment={Object {}}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
alignItems="center"
|
||||
className="ModerateCard-aside ModerateCard-asideWithoutReplyTo"
|
||||
direction="column"
|
||||
@@ -418,7 +418,7 @@ exports[`renders rejected correctly 1`] = `
|
||||
DECISION
|
||||
</div>
|
||||
</Localized>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<RejectButton
|
||||
@@ -430,9 +430,9 @@ exports[`renders rejected correctly 1`] = `
|
||||
invert={false}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</withPropsOnChange(Card)>
|
||||
`;
|
||||
|
||||
@@ -441,7 +441,7 @@ exports[`renders reply correctly 1`] = `
|
||||
className="ModerateCard-root"
|
||||
data-testid="moderate-comment-comment-id"
|
||||
>
|
||||
<withPropsOnChange(Flex)>
|
||||
<ForwardRef(forwardRef)>
|
||||
<div
|
||||
className="ModerateCard-mainContainer"
|
||||
>
|
||||
@@ -482,10 +482,10 @@ exports[`renders reply correctly 1`] = `
|
||||
<div
|
||||
className="ModerateCard-footer"
|
||||
>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
<ForwardRef(forwardRef)
|
||||
anchor={true}
|
||||
color="primary"
|
||||
href="http://localhost/comment"
|
||||
@@ -500,24 +500,24 @@ exports[`renders reply correctly 1`] = `
|
||||
</span>
|
||||
</Localized>
|
||||
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
arrow_forward
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(Button)>
|
||||
</withPropsOnChange(Flex)>
|
||||
<withPropsOnChange(Flex)
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<Relay(MarkersContainer)
|
||||
comment={Object {}}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ModerateCard-separator"
|
||||
/>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
alignItems="center"
|
||||
className="ModerateCard-aside"
|
||||
direction="column"
|
||||
@@ -532,7 +532,7 @@ exports[`renders reply correctly 1`] = `
|
||||
DECISION
|
||||
</div>
|
||||
</Localized>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
>
|
||||
<RejectButton
|
||||
@@ -543,8 +543,8 @@ exports[`renders reply correctly 1`] = `
|
||||
invert={false}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(Flex)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</withPropsOnChange(Card)>
|
||||
`;
|
||||
|
||||
+16
-16
@@ -5,9 +5,9 @@ exports[`renders correctly 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/reported"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
flag
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-reported"
|
||||
>
|
||||
@@ -19,9 +19,9 @@ exports[`renders correctly 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/pending"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
access_time
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-pending"
|
||||
>
|
||||
@@ -33,9 +33,9 @@ exports[`renders correctly 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/unmoderated"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
forum
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-unmoderated"
|
||||
>
|
||||
@@ -47,9 +47,9 @@ exports[`renders correctly 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/rejected"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
cancel
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-rejected"
|
||||
>
|
||||
@@ -66,9 +66,9 @@ exports[`renders correctly with counts 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/reported"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
flag
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-reported"
|
||||
>
|
||||
@@ -85,9 +85,9 @@ exports[`renders correctly with counts 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/pending"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
access_time
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-pending"
|
||||
>
|
||||
@@ -104,9 +104,9 @@ exports[`renders correctly with counts 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/unmoderated"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
forum
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-unmoderated"
|
||||
>
|
||||
@@ -123,9 +123,9 @@ exports[`renders correctly with counts 1`] = `
|
||||
<NavigationLink
|
||||
to="/admin/moderate/rejected"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
<ForwardRef(forwardRef)>
|
||||
cancel
|
||||
</withPropsOnChange(Icon)>
|
||||
</ForwardRef(forwardRef)>
|
||||
<Localized
|
||||
id="moderate-navigation-rejected"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly with load more 1`] = `
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
<ForwardRef(forwardRef)
|
||||
className="Queue-root"
|
||||
size="double"
|
||||
>
|
||||
@@ -11,19 +11,19 @@ exports[`renders correctly with load more 1`] = `
|
||||
enter={false}
|
||||
exit={true}
|
||||
/>
|
||||
<withPropsOnChange(Flex)
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="center"
|
||||
>
|
||||
<withContext(WithInView)
|
||||
disableLoadMore={false}
|
||||
onLoadMore={[Function]}
|
||||
/>
|
||||
</withPropsOnChange(Flex)>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
exports[`renders correctly without load more 1`] = `
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
<ForwardRef(forwardRef)
|
||||
className="Queue-root"
|
||||
size="double"
|
||||
>
|
||||
@@ -33,5 +33,5 @@ exports[`renders correctly without load more 1`] = `
|
||||
enter={false}
|
||||
exit={true}
|
||||
/>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
</ForwardRef(forwardRef)>
|
||||
`;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user