Skip to content

Commit 65ac587

Browse files
authored
Merge pull request #4764 from coralproject/develop
v9.9.1
2 parents 78915ff + 69945be commit 65ac587

File tree

45 files changed

+732
-101
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+732
-101
lines changed

client/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coralproject/talk",
3-
"version": "9.9.0",
3+
"version": "9.9.1",
44
"author": "The Coral Project",
55
"homepage": "https://coralproject.net/",
66
"sideEffects": [

client/src/core/client/admin/routes/Configure/sections/Moderation/EmailDomainConfigContainer.tsx

+107-6
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
import { Localized } from "@fluent/react/compat";
2-
import React, { FunctionComponent, useState } from "react";
2+
import React, { FunctionComponent, useCallback, useState } from "react";
33
import { Field } from "react-final-form";
44
import { graphql } from "relay-runtime";
55

6+
import { DISPOSABLE_EMAIL_DOMAINS_LIST_URL } from "coral-common/common/lib/constants";
67
import { formatStringList, parseStringList } from "coral-framework/lib/form";
7-
import { withFragmentContainer } from "coral-framework/lib/relay";
8+
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
89
import { validateEmailDomainList } from "coral-framework/lib/validation";
910
import { AddIcon, ButtonSvgIcon } from "coral-ui/components/icons";
1011
import {
1112
Button,
13+
CallOut,
14+
FieldSet,
1215
Flex,
1316
FormField,
1417
FormFieldDescription,
1518
FormFieldHeader,
1619
HelperText,
1720
Label,
1821
Textarea,
22+
TextLink,
1923
} from "coral-ui/components/v2";
2024

2125
import { EmailDomainConfigContainer_settings } from "coral-admin/__generated__/EmailDomainConfigContainer_settings.graphql";
2226

2327
import ConfigBox from "../../ConfigBox";
2428
import Header from "../../Header";
29+
import OnOffField from "../../OnOffField";
2530
import ValidationMessage from "../../ValidationMessage";
2631
import EmailDomainTableContainer from "./EmailDomainTableContainer";
32+
import RefreshDisposableEmailDomainsMutation from "./RefreshDisposableEmailDomainsMutation";
2733

2834
import styles from "./EmailDomainConfigContainer.css";
2935

@@ -36,6 +42,9 @@ interface Props {
3642
graphql`
3743
fragment EmailDomainConfigContainer_formValues on Settings {
3844
protectedEmailDomains
45+
disposableEmailDomains {
46+
enabled
47+
}
3948
}
4049
`;
4150

@@ -45,6 +54,43 @@ const EmailDomainConfigContainer: FunctionComponent<Props> = ({
4554
}) => {
4655
const { protectedEmailDomains } = settings;
4756
const [showDomainList, setShowDomainList] = useState(false);
57+
const [
58+
refreshDisposableEmailDomainsError,
59+
setRefreshDisposableEmailDomainsError,
60+
] = useState<string | null>(null);
61+
const [
62+
refreshingDisposableEmailDomains,
63+
setRefreshingDisposableEmailDomains,
64+
] = useState(false);
65+
66+
const refreshEmailDomains = useMutation(
67+
RefreshDisposableEmailDomainsMutation
68+
);
69+
70+
const refreshDisposableEmailDomains = useCallback(async () => {
71+
try {
72+
setRefreshingDisposableEmailDomains(true);
73+
await refreshEmailDomains();
74+
setTimeout(() => {
75+
setRefreshingDisposableEmailDomains(false);
76+
}, 1500);
77+
} catch (e) {
78+
setRefreshDisposableEmailDomainsError(
79+
`Error refreshing disposable domains: ${e}`
80+
);
81+
setRefreshingDisposableEmailDomains(false);
82+
}
83+
}, [refreshEmailDomains]);
84+
85+
const EmailDomainsListLink = () => {
86+
return (
87+
<Localized id="configure-moderation-emailDomains-disposableEmailDomains-list-linkText">
88+
<TextLink target="_blank" href={`${DISPOSABLE_EMAIL_DOMAINS_LIST_URL}`}>
89+
{"disposable-email-domains"}
90+
</TextLink>
91+
</Localized>
92+
);
93+
};
4894

4995
return (
5096
<ConfigBox
@@ -92,11 +138,11 @@ const EmailDomainConfigContainer: FunctionComponent<Props> = ({
92138
<Label component="legend">Exceptions</Label>
93139
</Localized>
94140
</FormFieldHeader>
95-
<Localized id="configure-moderation-emailDomains-exceptions-helperText">
141+
<Localized id="configure-moderation-emailDomains-exceptions-ban-premod-helperText">
96142
<HelperText>
97-
These domains cannot be banned. Domains should be written without
98-
www, for example "gmail.com". Separate domains with a comma and a
99-
space.
143+
These domains cannot be banned or pre-moderated. Domains should be
144+
written without www, for example "gmail.com". Separate domains with
145+
a comma and a space.
100146
</HelperText>
101147
</Localized>
102148
<Field
@@ -124,6 +170,58 @@ const EmailDomainConfigContainer: FunctionComponent<Props> = ({
124170
)}
125171
</Field>
126172
</FormField>
173+
<FormField container={<FieldSet />}>
174+
<FormFieldHeader>
175+
<Localized id="configure-moderation-emailDomains-disposableEmailDomains-enabled">
176+
<Label component="legend">Disposable email domains</Label>
177+
</Localized>
178+
<Localized id="configure-moderation-emailDomains-disposableEmailDomains-helper-text">
179+
<HelperText>
180+
If a new user registers using a disposable email address, set
181+
their status to 'always pre-moderate comments.' Accounts with
182+
disposable email addresses can have a high spam / troll
183+
correlation.
184+
</HelperText>
185+
</Localized>
186+
</FormFieldHeader>
187+
<OnOffField name="disposableEmailDomains.enabled" disabled={disabled} />
188+
<Localized
189+
id="configure-moderation-emailDomains-disposableEmailDomains-update-button-helper-text"
190+
elems={{
191+
link: <EmailDomainsListLink />,
192+
}}
193+
>
194+
<HelperText>
195+
The email domains come from the <EmailDomainsListLink /> list, which
196+
is regularly updated. Use the button below to import their latest
197+
list.
198+
</HelperText>
199+
</Localized>
200+
<Localized
201+
id={`${
202+
refreshingDisposableEmailDomains
203+
? "configure-moderation-emailDomains-disposableEmailDomains-updating"
204+
: "configure-moderation-emailDomains-disposableEmailDomains-update-button"
205+
}`}
206+
>
207+
<Button
208+
onClick={refreshDisposableEmailDomains}
209+
disabled={
210+
!settings.disposableEmailDomains?.enabled ||
211+
refreshingDisposableEmailDomains
212+
}
213+
>
214+
{refreshingDisposableEmailDomains
215+
? "Updating"
216+
: "Update disposable domains"}
217+
</Button>
218+
</Localized>
219+
{refreshDisposableEmailDomainsError && (
220+
<CallOut fullWidth color="error">
221+
{refreshDisposableEmailDomainsError}
222+
</CallOut>
223+
)}
224+
</FormField>
127225
</ConfigBox>
128226
);
129227
};
@@ -132,6 +230,9 @@ const enhanced = withFragmentContainer<Props>({
132230
settings: graphql`
133231
fragment EmailDomainConfigContainer_settings on Settings {
134232
protectedEmailDomains
233+
disposableEmailDomains {
234+
enabled
235+
}
135236
...EmailDomainTableContainer_settings
136237
}
137238
`,

client/src/core/client/admin/routes/Configure/sections/Moderation/PremoderateEmailAddressConfig.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,13 @@ const PremoderateEmailAddressConfig: FunctionComponent<Props> = ({
7070
<Localized id="configure-moderation-premoderateEmailAliases-enabled">
7171
<Label component="legend">Pre-moderate email aliases</Label>
7272
</Localized>
73-
<Localized id="configure-moderation-premoderateEmailAliases-enabled-description">
73+
<Localized id="configure-moderation-premoderateEmailAliases-enabled-description-ifThePreviousAccountWas">
7474
<HelperText>
7575
If a user signs up for a new account with an email address that is
7676
an alias (using a + sign) of an existing account, set their status
77-
to pre-moderate comments. Email aliases are commonly used by
78-
spammers and trolls to evade bans.
77+
to pre-moderate comments. If the previous account was banned, the
78+
new account will be banned as well. Email aliases are commonly
79+
used by spammers and trolls to evade bans.
7980
</HelperText>
8081
</Localized>
8182
</FormFieldHeader>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { graphql } from "react-relay";
2+
import { Environment } from "relay-runtime";
3+
4+
import {
5+
commitMutationPromiseNormalized,
6+
createMutation,
7+
MutationInput,
8+
} from "coral-framework/lib/relay";
9+
10+
import { RefreshDisposableEmailDomainsMutation as MutationTypes } from "coral-admin/__generated__/RefreshDisposableEmailDomainsMutation.graphql";
11+
12+
let clientMutationId = 0;
13+
14+
const RefreshDisposableEmailDomainsMutation = createMutation(
15+
"refreshDisposableEmailDomains",
16+
(environment: Environment, input: MutationInput<MutationTypes>) =>
17+
commitMutationPromiseNormalized<MutationTypes>(environment, {
18+
mutation: graphql`
19+
mutation RefreshDisposableEmailDomainsMutation(
20+
$input: RefreshDisposableEmailDomainsInput!
21+
) {
22+
refreshDisposableEmailDomains(input: $input) {
23+
clientMutationId
24+
}
25+
}
26+
`,
27+
variables: {
28+
input: {
29+
...input,
30+
clientMutationId: (clientMutationId++).toString(),
31+
},
32+
},
33+
})
34+
);
35+
36+
export default RefreshDisposableEmailDomainsMutation;

client/src/core/client/framework/testHelpers/denormalize.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export function denormalizeStory(story: Fixture<GQLStory>) {
7979
commentCounts: {
8080
...story.commentCounts,
8181
totalPublished: commentEdges.length,
82+
totalPublishedAndVisible: commentEdges.length,
8283
tags: {
8384
...(story.commentCounts && story.commentCounts.tags),
8485
FEATURED: featuredCommentsCount,

client/src/core/client/stream/tabs/Comments/Stream/AllCommentsTab/AllCommentsTabContainer.tsx

-7
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,6 @@ const enhanced = withPaginationContainer<
645645
}
646646
mode
647647
}
648-
commentCounts {
649-
totalPublished
650-
tags {
651-
REVIEW
652-
QUESTION
653-
}
654-
}
655648
comments(
656649
first: $count
657650
after: $cursor

client/src/core/client/stream/tabs/Comments/Stream/PreviousCountSpyContainer.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const PreviousCountSpyContainer: FunctionComponent<Props> = ({
5151
}
5252

5353
// value is the current comment count as a string to be stored in storage.
54-
const value = story.commentCounts.totalPublished.toString();
54+
const value = story.commentCounts.totalPublishedAndVisible.toString();
5555

5656
/**
5757
* update will take the current published comment count and update it in
@@ -71,7 +71,7 @@ const PreviousCountSpyContainer: FunctionComponent<Props> = ({
7171
}, [
7272
featureFlags,
7373
localStorage,
74-
story.commentCounts.totalPublished,
74+
story.commentCounts.totalPublishedAndVisible,
7575
story.id,
7676
story.isClosed,
7777
]);
@@ -85,7 +85,7 @@ const enhanced = withFragmentContainer<Props>({
8585
id
8686
isClosed
8787
commentCounts {
88-
totalPublished
88+
totalPublishedAndVisible
8989
}
9090
}
9191
`,

client/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export const StreamContainer: FunctionComponent<Props> = (props) => {
178178
const warned = !!props.viewer?.status.current.includes(GQLUSER_STATUS.WARNED);
179179
const modMessaged = !!props.viewer?.status.modMessage.active;
180180

181-
const allCommentsCount = props.story.commentCounts.totalPublished;
181+
const allCommentsCount = props.story.commentCounts.totalPublishedAndVisible;
182182
const featuredCommentsCount = props.story.commentCounts.tags.FEATURED;
183183
const unansweredCommentsCount = props.story.commentCounts.tags.UNANSWERED;
184184

@@ -631,7 +631,7 @@ const enhanced = withFragmentContainer<Props>({
631631
mode
632632
}
633633
commentCounts {
634-
totalPublished
634+
totalPublishedAndVisible
635635
tags {
636636
FEATURED
637637
UNANSWERED

client/src/core/client/stream/tabs/Comments/helpers/incrementStoryCommentCounts.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ export default function incrementStoryCommentCounts(
1717
if (story) {
1818
const commentCounts = story.getLinkedRecord("commentCounts");
1919
if (commentCounts) {
20-
// Increment totalPublished.
21-
const currentTotalPublished = commentCounts.getValue(
22-
"totalPublished"
20+
// Increment totalPublishedAndVisible.
21+
const currentTotalPublishedAndVisible = commentCounts.getValue(
22+
"totalPublishedAndVisible"
2323
) as number;
24-
commentCounts.setValue(currentTotalPublished + 1, "totalPublished");
24+
commentCounts.setValue(
25+
currentTotalPublishedAndVisible + 1,
26+
"totalPublishedAndVisible"
27+
);
2528

2629
// Now increment tag counts.
2730
const commentCountsTags = commentCounts.getLinkedRecord("tags");

client/src/core/client/stream/tabs/Discussions/StoryRowContainer.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const StoryRowContainer: FunctionComponent<Props> = ({
5858
CLASSES.discussions.story.commentsCount
5959
)}
6060
>
61-
{story.commentCounts.totalPublished}
61+
{story.commentCounts.totalPublishedAndVisible}
6262
</span>
6363
</Flex>
6464
</Flex>
@@ -81,7 +81,7 @@ const enhanced = withFragmentContainer<Props>({
8181
publishedAt
8282
}
8383
commentCounts {
84-
totalPublished
84+
totalPublishedAndVisible
8585
}
8686
}
8787
`,

client/src/core/client/stream/test/fixtures.ts

+1
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ export const baseStory = createFixture<GQLStory>({
408408
},
409409
commentCounts: {
410410
totalPublished: 0,
411+
totalPublishedAndVisible: 0,
411412
tags: {
412413
FEATURED: 0,
413414
UNANSWERED: 0,

client/src/core/client/test/helpers/fixture.ts

+1
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ export function createStory(
299299
isArchiving: false,
300300
commentCounts: {
301301
totalPublished: 0,
302+
totalPublishedAndVisible: 0,
302303
tags: {
303304
FEATURED: 0,
304305
UNANSWERED: 0,

common/lib/constants.ts

+13
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ export const COMMENT_REPEAT_POST_DURATION = 6 * TIME.MINUTE;
9292
*/
9393
export const COUNTS_V2_CACHE_DURATION = 1 * TIME.DAY;
9494

95+
/**
96+
* DISPOSABLE_EMAIL_DOMAINS_LIST_URL is the url for where we grab disposable email
97+
* domains and add them to Redis for moderation usage
98+
*/
99+
export const DISPOSABLE_EMAIL_DOMAINS_LIST_URL =
100+
"https://disposable.github.io/disposable-email-domains/domains_mx.json";
101+
102+
/**
103+
* DISPOSABLE_EMAIL_DOMAINS_REDIS_KEY is the pattern added to disposable email domains
104+
* when they are added to Redis, to identify them
105+
*/
106+
export const DISPOSABLE_EMAIL_DOMAINS_REDIS_KEY = "disposableDomains";
107+
95108
/**
96109
* SPOILER_CLASSNAME is the classname that is attached to spoilers.
97110
*/

common/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "common",
3-
"version": "9.9.0",
3+
"version": "9.9.1",
44
"description": "",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

config/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "common",
3-
"version": "9.9.0",
3+
"version": "9.9.1",
44
"description": "",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

0 commit comments

Comments
 (0)