mirror of
https://github.com/wassname/talk.git
synced 2026-08-11 11:27:10 +08:00
[CORL-224] Flag Details (#2293)
* feat: flag details * fix: review * feat: implement flag details in admin * test: test flag details
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.category {
|
||||
color: var(--palette-error-darkest);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "talk-ui/components";
|
||||
|
||||
import styles from "./FlagDetailsCategory.css";
|
||||
|
||||
interface Props {
|
||||
category: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const FlagDetailsCategory: FunctionComponent<Props> = ({
|
||||
category,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<HorizontalGutter size="half">
|
||||
<Typography variant="bodyCopyBold" className={styles.category}>
|
||||
{category}
|
||||
</Typography>
|
||||
<HorizontalGutter size="half">{children}</HorizontalGutter>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlagDetailsCategory;
|
||||
@@ -0,0 +1,18 @@
|
||||
.user {
|
||||
font-size: calc(14rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: var(--font-family-serif);
|
||||
line-height: calc(16em / 14);
|
||||
letter-spacing: calc(0.2em / 14);
|
||||
color: var(--palette-text-primary);
|
||||
padding-right: calc(0.5 * var(--mini-unit));
|
||||
}
|
||||
.details {
|
||||
font-size: calc(14rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
line-height: calc(16em / 14);
|
||||
letter-spacing: calc(0.2em / 14);
|
||||
color: var(--palette-text-primary);
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import styles from "./FlagDetailsEntry.css";
|
||||
|
||||
interface Props {
|
||||
user: React.ReactNode;
|
||||
details?: React.ReactNode;
|
||||
}
|
||||
|
||||
const FlagDetailsEntry: FunctionComponent<Props> = ({ user, details }) => {
|
||||
return (
|
||||
<div>
|
||||
<span className={styles.user}>
|
||||
{user}
|
||||
{details && ":"}
|
||||
</span>
|
||||
{details && <span className={styles.details}>{details}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlagDetailsEntry;
|
||||
@@ -0,0 +1,16 @@
|
||||
.detailsButton {
|
||||
height: 100%;
|
||||
padding: 2px 0px;
|
||||
margin-left: var(--mini-unit);
|
||||
font-size: calc(12rem / var(--rem-base));
|
||||
letter-spacing: calc(-0.1em / 12);
|
||||
align-self: flex-end;
|
||||
text-transform: uppercase;
|
||||
&.detailsButtonColorRegular {
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.detailsText {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, useCallback, useState } from "react";
|
||||
|
||||
import { useUUID } from "talk-framework/hooks";
|
||||
import { Button, Flex, HorizontalGutter, Icon } from "talk-ui/components";
|
||||
|
||||
import styles from "./Markers.css";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
details: React.ReactElement | null;
|
||||
}
|
||||
|
||||
const Markers: FunctionComponent<Props> = ({ children, details }) => {
|
||||
const uuid = useUUID();
|
||||
const [showDetails, setShowDetails] = useState<boolean>(false);
|
||||
const toggleDetails = useCallback(() => setShowDetails(!showDetails), [
|
||||
showDetails,
|
||||
]);
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<Flex>
|
||||
<Flex itemGutter>{children}</Flex>
|
||||
{details && (
|
||||
<Button
|
||||
size="small"
|
||||
classes={{
|
||||
variantRegular: styles.detailsButton,
|
||||
colorRegular: styles.detailsButtonColorRegular,
|
||||
}}
|
||||
onClick={toggleDetails}
|
||||
aria-controls={uuid}
|
||||
aria-expanded={showDetails}
|
||||
>
|
||||
<Localized id="moderate-markers-details">
|
||||
<span className={styles.detailsText}>DETAILS</span>
|
||||
</Localized>
|
||||
<Icon>{showDetails ? "arrow_drop_up" : "arrow_drop_down"}</Icon>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
{showDetails && <div id={uuid}>{details}</div>}
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
export default Markers;
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { FlagDetailsContainer_comment as CommentData } from "talk-admin/__generated__/FlagDetailsContainer_comment.graphql";
|
||||
import NotAvailable from "talk-admin/components/NotAvailable";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { GQLCOMMENT_FLAG_REASON } from "talk-framework/schema";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import FlagDetailsCategory from "../components/FlagDetailsCategory";
|
||||
import FlagDetailsEntry from "../components/FlagDetailsEntry";
|
||||
|
||||
interface Props {
|
||||
comment: CommentData;
|
||||
}
|
||||
|
||||
export class FlagDetailsContainer extends React.Component<Props> {
|
||||
public render() {
|
||||
const nodes = this.props.comment.flags.nodes;
|
||||
const offensiveList: React.ReactElement[] = [];
|
||||
const spamList: React.ReactElement[] = [];
|
||||
nodes.forEach(n => {
|
||||
switch (n.reason) {
|
||||
case GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OFFENSIVE:
|
||||
offensiveList.push(
|
||||
<FlagDetailsEntry
|
||||
user={n.flagger ? n.flagger.username : <NotAvailable />}
|
||||
details={n.additionalDetails}
|
||||
/>
|
||||
);
|
||||
return;
|
||||
case GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_SPAM:
|
||||
spamList.push(
|
||||
<FlagDetailsEntry
|
||||
user={n.flagger ? n.flagger.username : <NotAvailable />}
|
||||
details={n.additionalDetails}
|
||||
/>
|
||||
);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (offensiveList.length + spamList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
{offensiveList.length > 0 && (
|
||||
<FlagDetailsCategory
|
||||
category={
|
||||
<Localized id="moderate-flagDetails-offensive">
|
||||
<span>Offensive</span>
|
||||
</Localized>
|
||||
}
|
||||
>
|
||||
{offensiveList}
|
||||
</FlagDetailsCategory>
|
||||
)}
|
||||
{spamList.length > 0 && (
|
||||
<FlagDetailsCategory
|
||||
category={
|
||||
<Localized id="moderate-flagDetails-span">
|
||||
<span>Spam</span>
|
||||
</Localized>
|
||||
}
|
||||
>
|
||||
{spamList}
|
||||
</FlagDetailsCategory>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
comment: graphql`
|
||||
fragment FlagDetailsContainer_comment on Comment {
|
||||
flags {
|
||||
nodes {
|
||||
flagger {
|
||||
username
|
||||
}
|
||||
reason
|
||||
additionalDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(FlagDetailsContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -4,12 +4,22 @@ import { graphql } from "react-relay";
|
||||
|
||||
import { MarkersContainer_comment as CommentData } from "talk-admin/__generated__/MarkersContainer_comment.graphql";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { Flex, Marker, MarkerCount } from "talk-ui/components";
|
||||
import { Marker, MarkerCount } from "talk-ui/components";
|
||||
import Markers from "../components/Markers";
|
||||
import FlagDetailsContainer from "./FlagDetailsContainer";
|
||||
|
||||
interface MarkersContainerProps {
|
||||
comment: CommentData;
|
||||
}
|
||||
|
||||
function hasDetails(c: CommentData) {
|
||||
return (
|
||||
c.actionCounts.flag.reasons.COMMENT_REPORTED_OFFENSIVE +
|
||||
c.actionCounts.flag.reasons.COMMENT_REPORTED_SPAM >
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
let keyCounter = 0;
|
||||
const markers: Array<(c: CommentData) => React.ReactElement<any> | null> = [
|
||||
c =>
|
||||
@@ -93,7 +103,17 @@ export class MarkersContainer extends React.Component<MarkersContainerProps> {
|
||||
public render() {
|
||||
const elements = markers.map(cb => cb(this.props.comment)).filter(m => m);
|
||||
if (elements.length) {
|
||||
return <Flex itemGutter>{elements}</Flex>;
|
||||
return (
|
||||
<Markers
|
||||
details={
|
||||
hasDetails(this.props.comment) ? (
|
||||
<FlagDetailsContainer comment={this.props.comment} />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{elements}
|
||||
</Markers>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -102,6 +122,7 @@ export class MarkersContainer extends React.Component<MarkersContainerProps> {
|
||||
const enhanced = withFragmentContainer<MarkersContainerProps>({
|
||||
comment: graphql`
|
||||
fragment MarkersContainer_comment on Comment {
|
||||
...FlagDetailsContainer_comment
|
||||
status
|
||||
actionCounts {
|
||||
flag {
|
||||
|
||||
+50
-6
@@ -1,8 +1,30 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders all markers 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
<Markers
|
||||
details={
|
||||
<Relay(FlagDetailsContainer)
|
||||
comment={
|
||||
Object {
|
||||
"actionCounts": Object {
|
||||
"flag": Object {
|
||||
"reasons": Object {
|
||||
"COMMENT_DETECTED_BANNED_WORD": 1,
|
||||
"COMMENT_DETECTED_LINKS": 1,
|
||||
"COMMENT_DETECTED_SPAM": 1,
|
||||
"COMMENT_DETECTED_SUSPECT_WORD": 1,
|
||||
"COMMENT_DETECTED_TOXIC": 1,
|
||||
"COMMENT_DETECTED_TRUST": 1,
|
||||
"COMMENT_REPORTED_OFFENSIVE": 2,
|
||||
"COMMENT_REPORTED_SPAM": 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": "PREMOD",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Localized
|
||||
id="moderate-marker-preMod"
|
||||
@@ -98,12 +120,34 @@ exports[`renders all markers 1`] = `
|
||||
3
|
||||
</withPropsOnChange(Count)>
|
||||
</withPropsOnChange(Marker)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Markers>
|
||||
`;
|
||||
|
||||
exports[`renders some markers 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
itemGutter={true}
|
||||
<Markers
|
||||
details={
|
||||
<Relay(FlagDetailsContainer)
|
||||
comment={
|
||||
Object {
|
||||
"actionCounts": Object {
|
||||
"flag": Object {
|
||||
"reasons": Object {
|
||||
"COMMENT_DETECTED_BANNED_WORD": 1,
|
||||
"COMMENT_DETECTED_LINKS": 0,
|
||||
"COMMENT_DETECTED_SPAM": 0,
|
||||
"COMMENT_DETECTED_SUSPECT_WORD": 0,
|
||||
"COMMENT_DETECTED_TOXIC": 1,
|
||||
"COMMENT_DETECTED_TRUST": 1,
|
||||
"COMMENT_REPORTED_OFFENSIVE": 2,
|
||||
"COMMENT_REPORTED_SPAM": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": "PREMOD",
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Localized
|
||||
id="moderate-marker-preMod"
|
||||
@@ -156,5 +200,5 @@ exports[`renders some markers 1`] = `
|
||||
2
|
||||
</withPropsOnChange(Count)>
|
||||
</withPropsOnChange(Marker)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</Markers>
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
GQLComment,
|
||||
GQLCOMMENT_FLAG_REASON,
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLCommentModerationAction,
|
||||
GQLCommentsConnection,
|
||||
@@ -424,6 +425,9 @@ export const baseComment = createFixture<GQLComment>({
|
||||
},
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
nodes: [],
|
||||
},
|
||||
story: stories[0],
|
||||
});
|
||||
|
||||
@@ -445,6 +449,20 @@ export const reportedComments = createFixtures<GQLComment>(
|
||||
},
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
nodes: [
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_SPAM,
|
||||
flagger: users.commenters[0],
|
||||
additionalDetails: "This looks like an ad",
|
||||
},
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_SPAM,
|
||||
flagger: users.commenters[1],
|
||||
additionalDetails: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "comment-1",
|
||||
@@ -461,6 +479,25 @@ export const reportedComments = createFixtures<GQLComment>(
|
||||
},
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
nodes: [
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OFFENSIVE,
|
||||
flagger: users.commenters[0],
|
||||
additionalDetails: "I find this offensive",
|
||||
},
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OFFENSIVE,
|
||||
flagger: users.commenters[1],
|
||||
additionalDetails: "Not like that",
|
||||
},
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OFFENSIVE,
|
||||
flagger: users.commenters[2],
|
||||
additionalDetails: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "comment-2",
|
||||
@@ -479,6 +516,20 @@ export const reportedComments = createFixtures<GQLComment>(
|
||||
},
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
nodes: [
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OFFENSIVE,
|
||||
flagger: users.commenters[0],
|
||||
additionalDetails: "I find this offensive",
|
||||
},
|
||||
{
|
||||
reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_SPAM,
|
||||
flagger: users.commenters[2],
|
||||
additionalDetails: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
baseComment
|
||||
|
||||
@@ -94,21 +94,53 @@ exports[`rejected queue accepts comment in rejected queue: dangling 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -262,21 +294,53 @@ exports[`rejected queue renders rejected queue with comments 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -417,21 +481,53 @@ exports[`rejected queue renders rejected queue with comments 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-1"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -578,34 +674,66 @@ exports[`rejected queue renders rejected queue with comments and load more 1`] =
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-2"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -762,21 +890,53 @@ exports[`reported queue accepts comment in reported queue: dangling 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -933,21 +1093,53 @@ exports[`reported queue rejects comment in reported queue: dangling 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1122,21 +1314,53 @@ exports[`reported queue renders reported queue with comments 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1277,21 +1501,53 @@ exports[`reported queue renders reported queue with comments 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-1"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1448,21 +1704,53 @@ exports[`reported queue renders reported queue with comments 2`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1603,21 +1891,53 @@ exports[`reported queue renders reported queue with comments 2`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-1"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1764,39 +2084,71 @@ exports[`reported queue renders reported queue with comments and load more 1`] =
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorPrimary Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
Pre-Mod
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<span
|
||||
className="Marker-root Marker-colorPrimary Marker-variantRegular"
|
||||
>
|
||||
Pre-Mod
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Offensive
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-2"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2172,21 +2524,53 @@ exports[`single comment view accepts single comment 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2308,21 +2692,53 @@ exports[`single comment view rejects single comment 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2479,21 +2895,53 @@ exports[`single comment view renders single comment view 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="Marker-root Marker-colorError Marker-variantRegular"
|
||||
>
|
||||
<span>
|
||||
Spam
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="Count-root"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="uuid-0"
|
||||
aria-expanded={false}
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Markers-detailsButtonColorRegular Button-variantRegular Markers-detailsButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="Markers-detailsText"
|
||||
>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -400,7 +400,7 @@ describe("reported queue", () => {
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}) as any,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
@@ -410,6 +410,52 @@ describe("reported queue", () => {
|
||||
await waitForElement(() => getByTestID("moderate-container"));
|
||||
expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot();
|
||||
});
|
||||
it("show details of comment with flags", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
moderationQueues: () =>
|
||||
pureMerge(emptyModerationQueues, {
|
||||
reported: {
|
||||
count: 1,
|
||||
comments: createQueryResolverStub<
|
||||
ModerationQueueToCommentsResolver
|
||||
>(({ variables }) => {
|
||||
expectAndFail(variables).toEqual({ first: 5 });
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
node: reportedComments[0],
|
||||
cursor: reportedComments[0].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: reportedComments[0].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const { getByTestID } = within(testRenderer.root);
|
||||
const reported = await waitForElement(() =>
|
||||
getByTestID(`moderate-comment-${reportedComments[0].id}`)
|
||||
);
|
||||
expect(
|
||||
within(reported).queryByText(
|
||||
reportedComments[0].flags.nodes[0].additionalDetails!
|
||||
)
|
||||
).toBeNull();
|
||||
within(reported)
|
||||
.getByText("Details", { selector: "button" })
|
||||
.props.onClick();
|
||||
within(reported).getByText(
|
||||
reportedComments[0].flags.nodes[0].additionalDetails!
|
||||
);
|
||||
});
|
||||
it("shows a moderate story", async () => {
|
||||
const {
|
||||
testRenderer,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as useEffectAfterMount } from "./useEffectAfterMount";
|
||||
export { default as usePrevious } from "./usePrevious";
|
||||
export { default as useEffectWhenChanged } from "./useEffectWhenChanged";
|
||||
export { default as useUUID } from "./useUUID";
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useTalkContext } from "talk-framework/lib/bootstrap";
|
||||
|
||||
/**
|
||||
* useUUID returns a unique identifier.
|
||||
*/
|
||||
export default function useUUID(): string {
|
||||
const { uuidGenerator } = useTalkContext();
|
||||
return useMemo(() => uuidGenerator(), []);
|
||||
}
|
||||
@@ -9,6 +9,17 @@
|
||||
url("typeface-source-sans-pro/files/source-sans-pro-latin-300.woff")
|
||||
format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Sans Pro";
|
||||
font-style: italic;
|
||||
font-display: block;
|
||||
font-weight: 300;
|
||||
src: local("Source Sans Pro Light "), local("Source Sans Pro-Light"),
|
||||
url("typeface-source-sans-pro/files/source-sans-pro-latin-300italic.woff2")
|
||||
format("woff2"),
|
||||
url("typeface-source-sans-pro/files/source-sans-pro-latin-300italic.woff")
|
||||
format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Sans Pro";
|
||||
@@ -22,6 +33,17 @@
|
||||
url("typeface-source-sans-pro/files/source-sans-pro-latin-400.woff")
|
||||
format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Sans Pro";
|
||||
font-style: italic;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: local("Source Sans Pro Light "), local("Source Sans Pro-Light"),
|
||||
url("typeface-source-sans-pro/files/source-sans-pro-latin-400italic.woff2")
|
||||
format("woff2"),
|
||||
url("typeface-source-sans-pro/files/source-sans-pro-latin-400italic.woff")
|
||||
format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Sans Pro";
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import Context from "talk-server/graph/tenant/context";
|
||||
import {
|
||||
CommentActionConnectionInput,
|
||||
retrieveCommentActionConnection,
|
||||
} from "talk-server/models/action/comment";
|
||||
|
||||
export default (ctx: Context) => ({
|
||||
connection: ({ first = 10, after, filter }: CommentActionConnectionInput) =>
|
||||
retrieveCommentActionConnection(ctx.mongo, ctx.tenant.id, {
|
||||
first,
|
||||
after,
|
||||
filter,
|
||||
}),
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import Context from "talk-server/graph/tenant/context";
|
||||
|
||||
import Auth from "./Auth";
|
||||
import CommentActions from "./CommentActions";
|
||||
import CommentModerationActions from "./CommentModerationActions";
|
||||
import Comments from "./Comments";
|
||||
import Stories from "./Stories";
|
||||
@@ -8,6 +9,7 @@ import Users from "./Users";
|
||||
|
||||
export default (ctx: Context) => ({
|
||||
Auth: Auth(ctx),
|
||||
CommentActions: CommentActions(ctx),
|
||||
CommentModerationActions: CommentModerationActions(ctx),
|
||||
Stories: Stories(ctx),
|
||||
Comments: Comments(ctx),
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
GQLComment,
|
||||
GQLCommentTypeResolver,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { decodeActionCounts } from "talk-server/models/action/comment";
|
||||
import {
|
||||
ACTION_TYPE,
|
||||
decodeActionCounts,
|
||||
} from "talk-server/models/action/comment";
|
||||
import * as comment from "talk-server/models/comment";
|
||||
import { getLatestRevision } from "talk-server/models/comment";
|
||||
import { createConnection } from "talk-server/models/helpers/connection";
|
||||
@@ -64,6 +67,15 @@ export const Comment: GQLCommentTypeResolver<comment.Comment> = {
|
||||
: createConnection(),
|
||||
// Action Counts are encoded, decode them for use with the GraphQL system.
|
||||
actionCounts: c => decodeActionCounts(c.actionCounts),
|
||||
flags: ({ id }, { first = 10, after }, ctx) =>
|
||||
ctx.loaders.CommentActions.connection({
|
||||
first,
|
||||
after,
|
||||
filter: {
|
||||
actionType: ACTION_TYPE.FLAG,
|
||||
commentID: id,
|
||||
},
|
||||
}),
|
||||
viewerActionPresence: (c, input, ctx) =>
|
||||
ctx.user ? ctx.loaders.Comments.retrieveMyActionPresence.load(c.id) : null,
|
||||
parentCount: c => (c.parentID ? c.grandparentIDs.length + 1 : 0),
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { GQLFlagTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import * as actions from "talk-server/models/action/comment";
|
||||
|
||||
export const Flag: GQLFlagTypeResolver<actions.CommentAction> = {
|
||||
flagger: ({ userID }, args, ctx) => {
|
||||
if (userID) {
|
||||
return ctx.loaders.Users.user.load(userID);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -13,6 +13,7 @@ import { CommentModerationAction } from "./CommentModerationAction";
|
||||
import { CommentRevision } from "./CommentRevision";
|
||||
import { DisableCommenting } from "./DisableCommenting";
|
||||
import { FacebookAuthIntegration } from "./FacebookAuthIntegration";
|
||||
import { Flag } from "./Flag";
|
||||
import { GoogleAuthIntegration } from "./GoogleAuthIntegration";
|
||||
import { ModerationQueue } from "./ModerationQueue";
|
||||
import { ModerationQueues } from "./ModerationQueues";
|
||||
@@ -42,6 +43,7 @@ const Resolvers: GQLResolver = {
|
||||
Cursor,
|
||||
DisableCommenting,
|
||||
FacebookAuthIntegration,
|
||||
Flag,
|
||||
GoogleAuthIntegration,
|
||||
ModerationQueue,
|
||||
ModerationQueues,
|
||||
|
||||
@@ -182,6 +182,54 @@ type FlagReasonActionCounts {
|
||||
COMMENT_DETECTED_SUSPECT_WORD: Int!
|
||||
}
|
||||
|
||||
type Flag {
|
||||
"""
|
||||
flagger is the User that created the Flag. If this is null, then the system
|
||||
created the Flag.
|
||||
"""
|
||||
flagger: User
|
||||
|
||||
"""
|
||||
reason is the selected reason why the Flag is being created.
|
||||
"""
|
||||
reason: COMMENT_FLAG_REASON!
|
||||
|
||||
"""
|
||||
additionalDetails stores information from the User as to why the Flag was
|
||||
created or is relevant.
|
||||
"""
|
||||
additionalDetails: String
|
||||
}
|
||||
|
||||
type FlagEdge {
|
||||
"""
|
||||
node is the Flag for this edge.
|
||||
"""
|
||||
node: Flag!
|
||||
|
||||
"""
|
||||
cursor is used in pagination.
|
||||
"""
|
||||
cursor: Cursor!
|
||||
}
|
||||
|
||||
type FlagsConnection {
|
||||
"""
|
||||
edges are a subset of FlagEdge's.
|
||||
"""
|
||||
edges: [FlagEdge!]!
|
||||
|
||||
"""
|
||||
nodes is a list of Flags.
|
||||
"""
|
||||
nodes: [Flag!]!
|
||||
|
||||
"""
|
||||
pageInfo is information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
FlagActionCounts stores all the counts for the counts for the flag action on a
|
||||
given item and the reason counts.
|
||||
@@ -1690,6 +1738,12 @@ type Comment {
|
||||
"""
|
||||
actionCounts: ActionCounts!
|
||||
|
||||
"""
|
||||
flags is the actual Flags that were left by the Users or the system.
|
||||
"""
|
||||
flags(first: Int = 10, after: Cursor): FlagsConnection!
|
||||
@auth(roles: [ADMIN, MODERATOR])
|
||||
|
||||
"""
|
||||
viewerActionPresence stores the presence information for all the actions
|
||||
left by the current User on this Comment.
|
||||
|
||||
@@ -5,14 +5,24 @@ import uuid from "uuid";
|
||||
|
||||
import { Omit, Sub } from "talk-common/types";
|
||||
import {
|
||||
GQLActionCounts,
|
||||
GQLActionPresence,
|
||||
GQLCOMMENT_FLAG_DETECTED_REASON,
|
||||
GQLCOMMENT_FLAG_REASON,
|
||||
GQLCOMMENT_FLAG_REPORTED_REASON,
|
||||
GQLDontAgreeActionCounts,
|
||||
GQLFlagActionCounts,
|
||||
GQLReactionActionCounts,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { createIndexFactory } from "talk-server/models/helpers/indexing";
|
||||
import { FilterQuery } from "talk-server/models/helpers/query";
|
||||
import {
|
||||
Connection,
|
||||
ConnectionInput,
|
||||
resolveConnection,
|
||||
} from "talk-server/models/helpers/connection";
|
||||
import {
|
||||
createConnectionOrderVariants,
|
||||
createIndexFactory,
|
||||
} from "talk-server/models/helpers/indexing";
|
||||
import Query, { FilterQuery } from "talk-server/models/helpers/query";
|
||||
import { TenantResource } from "talk-server/models/tenant";
|
||||
|
||||
function collection(mongo: Db) {
|
||||
@@ -40,6 +50,27 @@ export enum ACTION_TYPE {
|
||||
|
||||
export type EncodedCommentActionCounts = Record<string, number>;
|
||||
|
||||
export type FlagActionCounts = GQLFlagActionCounts;
|
||||
|
||||
export interface ActionCounts {
|
||||
/**
|
||||
* reaction returns the counts for the reaction action on an item.
|
||||
*/
|
||||
reaction: GQLReactionActionCounts;
|
||||
|
||||
/**
|
||||
* dontAgree returns the counts for the dontAgree action on an item. This edge is
|
||||
* restricted to administrators and moderators.
|
||||
*/
|
||||
dontAgree: GQLDontAgreeActionCounts;
|
||||
|
||||
/**
|
||||
* flag returns the counts for the flag action on an item. This edge is
|
||||
* restricted to administrators and moderators.
|
||||
*/
|
||||
flag: FlagActionCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* FLAG_REASON is the reason that a given Flag has been created.
|
||||
*/
|
||||
@@ -115,6 +146,19 @@ export async function createCommentActionIndexes(mongo: Db) {
|
||||
{ tenantID: 1, actionType: 1, commentID: 1, userID: 1 },
|
||||
{ background: true }
|
||||
);
|
||||
|
||||
const variants = createConnectionOrderVariants<Readonly<CommentAction>>(
|
||||
[{ createdAt: -1 }],
|
||||
{ background: true }
|
||||
);
|
||||
|
||||
// Connection pagination.
|
||||
// { ...connectionParams }
|
||||
await variants(createIndex, {
|
||||
tenantID: 1,
|
||||
actionType: 1,
|
||||
commentID: 1,
|
||||
});
|
||||
}
|
||||
|
||||
const ActionSchema = [
|
||||
@@ -254,6 +298,38 @@ export async function createActions(
|
||||
);
|
||||
}
|
||||
|
||||
export type CommentActionConnectionInput = ConnectionInput<CommentAction>;
|
||||
|
||||
export async function retrieveCommentActionConnection(
|
||||
mongo: Db,
|
||||
tenantID: string,
|
||||
input: CommentActionConnectionInput
|
||||
): Promise<Readonly<Connection<Readonly<CommentAction>>>> {
|
||||
// Create the query.
|
||||
const query = new Query(collection(mongo)).where({ tenantID });
|
||||
|
||||
// If a filter is being applied, filter it as well.
|
||||
if (input.filter) {
|
||||
query.where(input.filter);
|
||||
}
|
||||
|
||||
return retrieveConnection(input, query);
|
||||
}
|
||||
|
||||
async function retrieveConnection(
|
||||
input: CommentActionConnectionInput,
|
||||
query: Query<CommentAction>
|
||||
): Promise<Readonly<Connection<Readonly<CommentAction>>>> {
|
||||
// Apply the pagination arguments to the query.
|
||||
query.orderBy({ createdAt: -1 });
|
||||
if (input.after) {
|
||||
query.where({ createdAt: { $lt: input.after as Date } });
|
||||
}
|
||||
|
||||
// Return a connection.
|
||||
return resolveConnection(query, input, action => action.createdAt);
|
||||
}
|
||||
|
||||
export async function retrieveUserAction(
|
||||
mongo: Db,
|
||||
tenantID: string,
|
||||
@@ -502,7 +578,7 @@ function decodeActionCountKey(key: string): DecodedActionCountKey {
|
||||
/**
|
||||
* createEmptyActionCounts creates a default/empty set of action counts.
|
||||
*/
|
||||
function createEmptyActionCounts(): GQLActionCounts {
|
||||
function createEmptyActionCounts(): ActionCounts {
|
||||
return {
|
||||
reaction: {
|
||||
total: 0,
|
||||
@@ -557,9 +633,9 @@ export function countTotalActionCounts(
|
||||
*/
|
||||
export function decodeActionCounts(
|
||||
encodedActionCounts: EncodedCommentActionCounts
|
||||
): GQLActionCounts {
|
||||
): ActionCounts {
|
||||
// Default all the action counts to zero.
|
||||
const actionCounts: GQLActionCounts = createEmptyActionCounts();
|
||||
const actionCounts: ActionCounts = createEmptyActionCounts();
|
||||
|
||||
// Loop over all the encoded action counts to extract each of the action
|
||||
// counts as they are encoded.
|
||||
@@ -575,7 +651,7 @@ export function decodeActionCounts(
|
||||
}
|
||||
|
||||
function incrementActionCounts(
|
||||
actionCounts: GQLActionCounts,
|
||||
actionCounts: ActionCounts,
|
||||
actionType: ACTION_TYPE,
|
||||
reason: GQLCOMMENT_FLAG_REASON | undefined,
|
||||
count: number = 1
|
||||
|
||||
@@ -317,6 +317,10 @@ moderate-marker-karma = Karma
|
||||
moderate-marker-bodyCount = Body Count
|
||||
moderate-marker-offensive = Offensive
|
||||
|
||||
moderate-markers-details = Details
|
||||
moderate-flagDetails-offensive = Offensive
|
||||
moderate-flagDetails-spam = Spam
|
||||
|
||||
moderate-emptyQueue-pending = Nicely done! There are no more pending comments to moderate.
|
||||
moderate-emptyQueue-reported = Nicely done! There are no more reported comments to moderate.
|
||||
moderate-emptyQueue-unmoderated = Nicely done! All comments have been moderated.
|
||||
|
||||
Reference in New Issue
Block a user