From ea04c8f7a895aeb8da72b4947e7e80d0e4b8e19b Mon Sep 17 00:00:00 2001 From: Marcus Haddon Date: Wed, 5 Jul 2023 11:15:12 -0700 Subject: [PATCH 001/100] only show multi site ban options to multi site users --- src/core/client/admin/components/BanModal.tsx | 111 ++++++++++-------- .../admin/test/community/banUser.spec.tsx | 33 ++++++ 2 files changed, 92 insertions(+), 52 deletions(-) diff --git a/src/core/client/admin/components/BanModal.tsx b/src/core/client/admin/components/BanModal.tsx index d02e722c9c..d87e57e173 100644 --- a/src/core/client/admin/components/BanModal.tsx +++ b/src/core/client/admin/components/BanModal.tsx @@ -322,59 +322,66 @@ const BanModal: FunctionComponent = ({ > {/* BAN FROM/REJECT COMMENTS */} - {/* ban from header */} - - - - - {/* sites options */} - {showAllSitesOption && ( - - - - setUpdateType(UpdateType.ALL_SITES) - } - disabled={userBanStatus?.active} - > - All sites - - - - )} - - - - setUpdateType(UpdateType.SPECIFIC_SITES) - } - > - Specific Sites - + {isMultisite && ( + <> + + - - {!viewerIsScoped && userHasAnyBan && ( - - - - setUpdateType(UpdateType.NO_SITES) - } - > - No Sites - - - - )} - + + {/* sites options */} + {showAllSitesOption && ( + + + + setUpdateType(UpdateType.ALL_SITES) + } + disabled={userBanStatus?.active} + > + All sites + + + + )} + + + + setUpdateType(UpdateType.SPECIFIC_SITES) + } + > + Specific Sites + + + + {!viewerIsScoped && userHasAnyBan && ( + + + + setUpdateType(UpdateType.NO_SITES) + } + > + No Sites + + + + )} + + + )} {/* reject comments option */} {updateType !== UpdateType.NO_SITES && ( { expect(resolvers.Mutation!.updateUserBan!.called).toBe(true); }); +it.only("displays limited options for single site tenants", async () => { + const resolvers = createResolversStub({ + Query: { + settings: () => settings, // base settings has multisite: false + }, + }); + + const { container } = await createTestRenderer({ + resolvers, + }); + + const userRow = within(container).getByRole("row", { + name: "Isabelle isabelle@test.com 07/06/18, 06:24 PM Commenter Active", + }); + userEvent.click( + within(userRow).getByRole("button", { name: "Change user status" }) + ); + + const dropdown = within(userRow).getByLabelText( + "A dropdown to change the user status" + ); + fireEvent.click(within(dropdown).getByRole("button", { name: "Manage Ban" })); + + // MARCUS: why is this passing? + const modal = screen.getByLabelText("Are you sure you want to ban Isabelle?"); + expect(modal).toBeInTheDocument(); + expect(screen.queryByText("All sites")).not.toBeInTheDocument(); + expect(screen.queryByText("Specific sites")).not.toBeInTheDocument(); + expect( + screen.queryByText("Are you sure you want to ban Isabelle?") + ).not.toBeInTheDocument(); +}); + it("site moderators can unban users on their sites but not sites out of their scope", async () => { const user = users.siteBannedCommenter; const resolvers = createResolversStub({ From 3a8231647517f027e352afde3d39480227383207 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Wed, 5 Jul 2023 15:49:27 -0400 Subject: [PATCH 002/100] initial spam ban changes --- .../ModerationActionBanButton.tsx | 4 +- .../ModerationActionBanContainer.tsx | 15 +- .../ModerationDropdownContainer.tsx | 3 + .../UserBanPopoverContainer.css | 30 +++- .../UserBanPopoverContainer.tsx | 136 +++++++++++++++--- src/locales/en-US/stream.ftl | 13 +- 6 files changed, 173 insertions(+), 28 deletions(-) diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanButton.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanButton.tsx index 9a6e098478..becde7287e 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanButton.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanButton.tsx @@ -20,9 +20,9 @@ const ModerationActionBanButton: FunctionComponent = ({ showSpinner = false, }) => { const localizationId = allSiteBan - ? "comments-moderationDropdown-ban" + ? "comments-moderationDropdown-spam-ban" : "comments-moderationDropdown-siteBan"; - const defaultText = allSiteBan ? "Ban User" : "Site Ban"; + const defaultText = allSiteBan ? "Spam ban" : "Site ban"; return ( = ({ {settings?.multisite ? ( <> - - {!viewerScoped && ( + {viewerScoped ? ( + + ) : ( = ({ }) => { const emitShowEvent = useViewerEvent(ShowModerationPopoverEvent); const [view, setView] = useState("MODERATE"); + const onBan = useCallback(() => { setView("BAN"); scheduleUpdate(); @@ -72,6 +73,7 @@ const ModerationDropdownContainer: FunctionComponent = ({ ) : ( ({ settings: graphql` fragment ModerationDropdownContainer_settings on Settings { ...ModerationActionsContainer_settings + ...UserBanPopoverContainer_settings } `, viewer: graphql` diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css index 673140bf86..b5400ecf1b 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css @@ -10,8 +10,36 @@ line-height: 1; color: var(--palette-text-500); +} + +.header { + font-family: var(--font-family-primary); + font-weight: var(--font-weight-primary-bold); + font-size: var(--font-size-2); + color: var(--palette-text-500); + margin-bottom: var(--spacing-1); + margin-top: var(--spacing-3); +} - margin-bottom: var(--spacing-3); +.orderedList { + margin: 0; + padding-left: 16px; +} + +.callOut { + padding: var(--spacing-1); + font-weight: var(--font-weight-primary-semi-bold); + margin-top: var(--spacing-3); + font-size: var(--font-size-1); +} + +.confirmationInput { + box-sizing: border-box; + font-family: var(--font-family-primary); + font-size: var(--font-size-2); + line-height: 2.25; + padding-left: var(--spacing-2); + width: 100%; } .description { diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx index 6c356c0c68..84d3449954 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx @@ -1,15 +1,27 @@ import { Localized } from "@fluent/react/compat"; import cn from "classnames"; -import React, { FunctionComponent, useCallback } from "react"; +import React, { + FunctionComponent, + useCallback, + useMemo, + useState, +} from "react"; import { graphql } from "react-relay"; +import { useModerationLink } from "coral-framework/hooks"; import { useCoralContext } from "coral-framework/lib/bootstrap"; import { getMessage } from "coral-framework/lib/i18n"; -import { useMutation, withFragmentContainer } from "coral-framework/lib/relay"; +import { + useLocal, + useMutation, + withFragmentContainer, +} from "coral-framework/lib/relay"; import CLASSES from "coral-stream/classes"; -import { Box, Button, Flex } from "coral-ui/components/v2"; +import { Box, Button, CallOut, Divider, Flex } from "coral-ui/components/v2"; import { UserBanPopoverContainer_comment } from "coral-stream/__generated__/UserBanPopoverContainer_comment.graphql"; +import { UserBanPopoverContainer_local } from "coral-stream/__generated__/UserBanPopoverContainer_local.graphql"; +import { UserBanPopoverContainer_settings } from "coral-stream/__generated__/UserBanPopoverContainer_settings.graphql"; import { UserBanPopoverContainer_story } from "coral-stream/__generated__/UserBanPopoverContainer_story.graphql"; import RejectCommentMutation from "../ModerationDropdown/RejectCommentMutation"; @@ -20,27 +32,52 @@ import styles from "./UserBanPopoverContainer.css"; interface Props { onDismiss: () => void; comment: UserBanPopoverContainer_comment; + settings: UserBanPopoverContainer_settings; story: UserBanPopoverContainer_story; siteBan: boolean; } const UserBanPopoverContainer: FunctionComponent = ({ comment, + settings, story, onDismiss, siteBan, }) => { + const [{ accessToken }] = useLocal(graphql` + fragment UserBanPopoverContainer_local on Local { + accessToken + } + `); + const user = comment.author!; const rejected = comment.status === "REJECTED"; const reject = useMutation(RejectCommentMutation); const banUser = useMutation(BanUserMutation); const { localeBundles } = useCoralContext(); + const [spamBanConfirmation, setSpamBanConfirmation] = useState(""); + + const linkModerateComment = useModerationLink({ commentID: comment.id }); + const moderationLinkSuffix = + !!accessToken && + settings.auth.integrations.sso.enabled && + settings.auth.integrations.sso.targetFilter.admin && + `#accessToken=${accessToken}`; + + const gotoModerateCommentHref = useMemo(() => { + let ret = linkModerateComment; + if (moderationLinkSuffix) { + ret += moderationLinkSuffix; + } + + return ret; + }, [linkModerateComment, moderationLinkSuffix]); const onBan = useCallback(() => { void banUser({ userID: user.id, commentID: comment.id, - rejectExistingComments: false, + rejectExistingComments: siteBan ? false : true, message: getMessage( localeBundles, "common-banEmailTemplate", @@ -85,19 +122,49 @@ const UserBanPopoverContainer: FunctionComponent = ({ ) : ( - -
Ban {user.username}?
-
+ <> + +
Spam ban
+
+ +
Username
+
+
{user.username}
+ +
Spam ban will
+
+
+
    + +
  1. Ban this account from the comments
  2. +
    + +
  3. Reject all comments written by this account
  4. +
    +
+
+ + + {/* TODO: Add icon */} + Only for use on obvious spam accounts + + + +
Type in "spam ban" to confirm
+
+ setSpamBanConfirmation(e.target.value)} + > + )} - - - Once banned, this user will no longer be able to comment, use - reactions, or report comments. - - = ({
+ {!siteBan && ( + <> + + + + For more context, go to{" "} + + + + + )} ); }; @@ -153,6 +243,20 @@ const enhanced = withFragmentContainer({ } } `, + settings: graphql` + fragment UserBanPopoverContainer_settings on Settings { + auth { + integrations { + sso { + enabled + targetFilter { + admin + } + } + } + } + } + `, })(UserBanPopoverContainer); export default enhanced; diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index 1ad3213f22..ebccb1895c 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -255,6 +255,14 @@ comments-userIgnorePopover-description = comments-userIgnorePopover-ignore = Ignore comments-userIgnorePopover-cancel = Cancel +comments-userSpamBanPopover-title = Spam ban +comments-userSpamBanPopover-header-username = Username +comments-userSpamBanPopover-header-description = Spam ban will +comments-userSpamBanPopover-callout = Only for use on obvious spam accounts +comments-userSpamBanPopover-description-list-banFromComments = Ban this account from the comments +comments-userSpamBanPopover-description-list-rejectAllComments = Reject all comments written by this account +comments-userSpamBanPopover-confirmation = Type in "spam ban" to confirm + comments-userBanPopover-title = Ban {$username}? comments-userSiteBanPopover-title = Ban {$username} from this site? comments-userBanPopover-description = @@ -272,8 +280,9 @@ comments-moderationDropdown-approve = Approve comments-moderationDropdown-approved = Approved comments-moderationDropdown-reject = Reject comments-moderationDropdown-rejected = Rejected -comments-moderationDropdown-ban = Ban User -comments-moderationDropdown-siteBan = Site Ban +comments-moderationDropdown-spam-ban = Spam ban +comments-moderationDropdown-ban = Ban user +comments-moderationDropdown-siteBan = Site ban comments-moderationDropdown-banned = Banned comments-moderationDropdown-goToModerate = comments-moderationDropdown-moderationView = Moderation view From 13b056dd23a7d1e1c515f9b1865621bc9a7ceab2 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Mon, 10 Jul 2023 11:54:01 -0400 Subject: [PATCH 003/100] add spam ban confirm view --- src/core/client/stream/local/local.graphql | 4 + .../Comments/Comment/CommentContainer.tsx | 4 + .../ModerationDropdown/CaretContainer.tsx | 4 + .../ModerationActionBanContainer.tsx | 18 +- .../ModerationActionsContainer.tsx | 1 + .../ModerationDropdownContainer.tsx | 9 +- .../ModerationRejectedTombstoneContainer.css | 5 + .../ModerationRejectedTombstoneContainer.tsx | 74 +++- .../RejectCommentMutation.ts | 9 +- .../UserBanPopoverContainer.css | 11 + .../UserBanPopoverContainer.tsx | 325 ++++++++++++------ .../tabs/Comments/Comment/setSpamBanned.ts | 18 + src/core/server/services/users/users.ts | 12 +- 13 files changed, 350 insertions(+), 144 deletions(-) create mode 100644 src/core/client/stream/tabs/Comments/Comment/setSpamBanned.ts diff --git a/src/core/client/stream/local/local.graphql b/src/core/client/stream/local/local.graphql index 8bf34e1830..b734e14b93 100644 --- a/src/core/client/stream/local/local.graphql +++ b/src/core/client/stream/local/local.graphql @@ -64,6 +64,10 @@ extend type Comment { # Remember last viewer action that could have caused a status change. lastViewerAction: COMMENT_VIEWER_ACTION + # If the comment was spam banned and rejected. Used for showing spam banned + # confirmation in moderation rejected tombstone container. + spamBanned: Boolean + # If true then Comment came in live. enteredLive: Boolean diff --git a/src/core/client/stream/tabs/Comments/Comment/CommentContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/CommentContainer.tsx index acbaa157ec..93e9c24a78 100644 --- a/src/core/client/stream/tabs/Comments/Comment/CommentContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/CommentContainer.tsx @@ -399,6 +399,8 @@ export const CommentContainer: FunctionComponent = ({ ); } @@ -805,6 +807,7 @@ const enhanced = withShowAuthPopupMutation( ...ReportFlowContainer_viewer ...ReportButton_viewer ...CaretContainer_viewer + ...ModerationRejectedTombstoneContainer_viewer } `, story: graphql` @@ -826,6 +829,7 @@ const enhanced = withShowAuthPopupMutation( ...PermalinkButtonContainer_story ...ReplyCommentFormContainer_story ...UserTagsContainer_story + ...ModerationRejectedTombstoneContainer_story } `, comment: graphql` diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx index 02c6b490e3..7deea26ad9 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx @@ -26,6 +26,8 @@ interface Props { story: CaretContainer_story; viewer: CaretContainer_viewer; settings: CaretContainer_settings; + view?: "MODERATE" | "BAN" | "SITE_BAN" | "CONFIRM_BAN"; + open?: boolean; } const CaretContainer: FunctionComponent = (props) => { @@ -37,6 +39,7 @@ const CaretContainer: FunctionComponent = (props) => { > ( @@ -48,6 +51,7 @@ const CaretContainer: FunctionComponent = (props) => { settings={props.settings} onDismiss={toggleVisibility} scheduleUpdate={scheduleUpdate} + view={props.view} /> )} diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanContainer.tsx index 9bfdc362e4..550c8528e7 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanContainer.tsx @@ -59,26 +59,26 @@ const ModerationActionBanContainer: FunctionComponent = ({ {settings?.multisite ? ( <> - {viewerScoped ? ( + {viewerScoped && ( - ) : ( - )} + + ) : ( <> = ({ commentID: comment.id, commentRevisionID: comment.revision.id, storyID: story.id, + spamBan: false, }); }, [approve, comment, story]); const onFeature = useCallback(() => { diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx index c03465459a..08ea9127da 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx @@ -20,7 +20,7 @@ import { ModerationDropdownContainer_viewer } from "coral-stream/__generated__/M import UserBanPopoverContainer from "../UserBanPopover/UserBanPopoverContainer"; import ModerationActionsContainer from "./ModerationActionsContainer"; -type View = "MODERATE" | "BAN" | "SITE_BAN"; +type View = "MODERATE" | "BAN" | "SITE_BAN" | "CONFIRM_BAN"; interface Props { comment: ModerationDropdownContainer_comment; @@ -29,6 +29,7 @@ interface Props { settings: ModerationDropdownContainer_settings; onDismiss: () => void; scheduleUpdate: () => void; + view?: View; } const ModerationDropdownContainer: FunctionComponent = ({ @@ -38,9 +39,10 @@ const ModerationDropdownContainer: FunctionComponent = ({ settings, onDismiss, scheduleUpdate, + view: viewProp, }) => { const emitShowEvent = useViewerEvent(ShowModerationPopoverEvent); - const [view, setView] = useState("MODERATE"); + const [view, setView] = useState(viewProp ?? "MODERATE"); const onBan = useCallback(() => { setView("BAN"); @@ -75,8 +77,10 @@ const ModerationDropdownContainer: FunctionComponent = ({ comment={comment} settings={settings} story={story} + viewer={viewer} onDismiss={onDismiss} siteBan={view === "SITE_BAN"} + view={view} /> )} @@ -118,6 +122,7 @@ const enhanced = withFragmentContainer({ viewer: graphql` fragment ModerationDropdownContainer_viewer on User { ...ModerationActionsContainer_viewer + ...UserBanPopoverContainer_viewer } `, })(ModerationDropdownContainer); diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.css b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.css index c6d47a18f8..5baeae9985 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.css +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.css @@ -1,3 +1,8 @@ .icon { margin-left: var(--spacing-1); } + +.rejectedContainer { + margin-right: auto; + width: 100%; +} diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx index 498183763d..fca67cd515 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx @@ -11,19 +11,26 @@ import { Button, Tombstone } from "coral-ui/components/v3"; import { ModerationRejectedTombstoneContainer_comment as CommentData } from "coral-stream/__generated__/ModerationRejectedTombstoneContainer_comment.graphql"; import { ModerationRejectedTombstoneContainer_local } from "coral-stream/__generated__/ModerationRejectedTombstoneContainer_local.graphql"; import { ModerationRejectedTombstoneContainer_settings as SettingsData } from "coral-stream/__generated__/ModerationRejectedTombstoneContainer_settings.graphql"; +import { ModerationRejectedTombstoneContainer_story as StoryData } from "coral-stream/__generated__/ModerationRejectedTombstoneContainer_story.graphql"; +import { ModerationRejectedTombstoneContainer_viewer as ViewerData } from "coral-stream/__generated__/ModerationRejectedTombstoneContainer_viewer.graphql"; import computeCommentElementID from "../computeCommentElementID"; +import CaretContainer from "./CaretContainer"; import styles from "./ModerationRejectedTombstoneContainer.css"; interface Props { comment: CommentData; settings: SettingsData; + story: StoryData; + viewer: ViewerData; } const ModerationRejectedTombstoneContainer: FunctionComponent = ({ comment, settings, + story, + viewer, }) => { const [{ accessToken }] = useLocal(graphql` @@ -55,26 +62,44 @@ const ModerationRejectedTombstoneContainer: FunctionComponent = ({ fullWidth noBottomBorder > - -
You have rejected this comment.
-
- - + {comment.spamBanned && ( + + + + )} + ); }; @@ -83,6 +108,8 @@ const enhanced = withFragmentContainer({ comment: graphql` fragment ModerationRejectedTombstoneContainer_comment on Comment { id + spamBanned + ...CaretContainer_comment } `, settings: graphql` @@ -97,6 +124,17 @@ const enhanced = withFragmentContainer({ } } } + ...CaretContainer_settings + } + `, + viewer: graphql` + fragment ModerationRejectedTombstoneContainer_viewer on User { + ...CaretContainer_viewer + } + `, + story: graphql` + fragment ModerationRejectedTombstoneContainer_story on Story { + ...CaretContainer_story } `, })(ModerationRejectedTombstoneContainer); diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/RejectCommentMutation.ts b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/RejectCommentMutation.ts index d71d6f09be..fcaed222c3 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/RejectCommentMutation.ts +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/RejectCommentMutation.ts @@ -19,7 +19,11 @@ const RejectCommentMutation = createMutation( "rejectComment", async ( environment: Environment, - input: MutationInput & { storyID: string; noEmit?: boolean }, + input: MutationInput & { + storyID: string; + noEmit?: boolean; + spamBan: boolean; + }, { eventEmitter }: CoralContext ) => { let rejectCommentEvent: ReturnType | null = @@ -92,6 +96,9 @@ const RejectCommentMutation = createMutation( .getRootField("rejectComment")! .getLinkedRecord("comment")!; comment.setValue("REJECT", "lastViewerAction"); + if (input.spamBan) { + comment.setValue(true, "spamBanned"); + } }, } ); diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css index b5400ecf1b..b5fdbca2f6 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css @@ -1,6 +1,7 @@ .root { width: 280px; max-width: 80vw; + text-align: left; } .title { @@ -54,3 +55,13 @@ .actions { padding-top: var(--spacing-3); } + +.link { + display: inline; + vertical-align: baseline; + white-space: break-spaces; +} + +.container { + margin-top: var(--spacing-2); +} diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx index 84d3449954..684aa481bb 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx @@ -23,8 +23,10 @@ import { UserBanPopoverContainer_comment } from "coral-stream/__generated__/User import { UserBanPopoverContainer_local } from "coral-stream/__generated__/UserBanPopoverContainer_local.graphql"; import { UserBanPopoverContainer_settings } from "coral-stream/__generated__/UserBanPopoverContainer_settings.graphql"; import { UserBanPopoverContainer_story } from "coral-stream/__generated__/UserBanPopoverContainer_story.graphql"; +import { UserBanPopoverContainer_viewer } from "coral-stream/__generated__/UserBanPopoverContainer_viewer.graphql"; import RejectCommentMutation from "../ModerationDropdown/RejectCommentMutation"; +import { SetSpamBanned } from "../setSpamBanned"; import BanUserMutation from "./BanUserMutation"; import styles from "./UserBanPopoverContainer.css"; @@ -34,31 +36,40 @@ interface Props { comment: UserBanPopoverContainer_comment; settings: UserBanPopoverContainer_settings; story: UserBanPopoverContainer_story; + viewer: UserBanPopoverContainer_viewer; siteBan: boolean; + view: "MODERATE" | "BAN" | "SITE_BAN" | "CONFIRM_BAN"; } const UserBanPopoverContainer: FunctionComponent = ({ comment, settings, story, + viewer, onDismiss, siteBan, + view, }) => { const [{ accessToken }] = useLocal(graphql` fragment UserBanPopoverContainer_local on Local { accessToken } `); + const setSpamBanned = useMutation(SetSpamBanned); const user = comment.author!; + const viewerScoped = + viewer?.moderationScopes && viewer.moderationScopes.scoped; const rejected = comment.status === "REJECTED"; const reject = useMutation(RejectCommentMutation); const banUser = useMutation(BanUserMutation); - const { localeBundles } = useCoralContext(); + const { localeBundles, rootURL } = useCoralContext(); const [spamBanConfirmation, setSpamBanConfirmation] = useState(""); const linkModerateComment = useModerationLink({ commentID: comment.id }); - const moderationLinkSuffix = + const linkCommunitySection = rootURL + "/admin/community"; + + const adminLinkSuffix = !!accessToken && settings.auth.integrations.sso.enabled && settings.auth.integrations.sso.targetFilter.admin && @@ -66,36 +77,51 @@ const UserBanPopoverContainer: FunctionComponent = ({ const gotoModerateCommentHref = useMemo(() => { let ret = linkModerateComment; - if (moderationLinkSuffix) { - ret += moderationLinkSuffix; + if (adminLinkSuffix) { + ret += adminLinkSuffix; } return ret; - }, [linkModerateComment, moderationLinkSuffix]); + }, [linkModerateComment, adminLinkSuffix]); + + const gotoCommunitySectionHref = useMemo(() => { + let ret = linkCommunitySection; + if (adminLinkSuffix) { + ret += adminLinkSuffix; + } - const onBan = useCallback(() => { - void banUser({ - userID: user.id, - commentID: comment.id, - rejectExistingComments: siteBan ? false : true, - message: getMessage( - localeBundles, - "common-banEmailTemplate", - "Someone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, react or report comments", - { username: user.username } - ), - siteIDs: siteBan ? [story.site.id] : [], - }); + return ret; + }, [linkCommunitySection, adminLinkSuffix]); - if (!rejected && comment.revision) { - void reject({ + const onBan = useCallback(async () => { + try { + await banUser({ + userID: user.id, commentID: comment.id, - commentRevisionID: comment.revision.id, - storyID: story.id, - noEmit: true, + rejectExistingComments: !siteBan, + message: getMessage( + localeBundles, + "common-banEmailTemplate", + "Someone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, react or report comments", + { username: user.username } + ), + siteIDs: siteBan || viewerScoped ? [story.site.id] : [], }); + if (!rejected && comment.revision) { + await reject({ + commentID: comment.id, + commentRevisionID: comment.revision.id, + storyID: story.id, + noEmit: true, + spamBan: !siteBan, + }); + } + } catch (e) { + // error + } + if (siteBan) { + onDismiss(); } - onDismiss(); }, [ user.id, user.username, @@ -108,100 +134,51 @@ const UserBanPopoverContainer: FunctionComponent = ({ story.site.id, story.id, reject, + viewerScoped, ]); - return ( - - {siteBan ? ( - -
- Ban {user.username} from this site? -
+ if (view === "CONFIRM_BAN") { + return ( + + +
{user.username} is now banned
- ) : ( - <> - -
Spam ban
-
- -
Username
+ + +
+ This account can no longer comment, use reactions, or report + comments +
-
{user.username}
- -
Spam ban will
+ +
All comments by this account have been rejected
-
-
    - -
  1. Ban this account from the comments
  2. -
    - -
  3. Reject all comments written by this account
  4. -
    -
-
- - + + + - setSpamBanConfirmation(e.target.value)} - > - - )} - - - - - - - - - {!siteBan && ( - <> - + + + - - For more context, go to{" "} +
+ You can still review this account's history by searching in + Coral's{" "} - +
+
+
+ ); + } + + return ( + + { + <> + {siteBan ? ( + +
+ Ban {user.username} from this site? +
+
+ ) : ( + <> + +
Spam ban
+
+ +
Username
+
+
{user.username}
+ +
Spam ban will
+
+
+
    + +
  1. Ban this account from the comments
  2. +
    + +
  3. Reject all comments written by this account
  4. +
    +
+
+ + + {/* TODO: Add icon */} + Only for use on obvious spam accounts + + + +
+ Type in "spam ban" to confirm +
+
+ setSpamBanConfirmation(e.target.value)} + > + + )} + + + + + + + + + {!siteBan && ( + <> + + + + For more context, go to{" "} + + + + + )} - )} + }
); }; @@ -257,6 +351,13 @@ const enhanced = withFragmentContainer({ } } `, + viewer: graphql` + fragment UserBanPopoverContainer_viewer on User { + moderationScopes { + scoped + } + } + `, })(UserBanPopoverContainer); export default enhanced; diff --git a/src/core/client/stream/tabs/Comments/Comment/setSpamBanned.ts b/src/core/client/stream/tabs/Comments/Comment/setSpamBanned.ts new file mode 100644 index 0000000000..4c7c786e22 --- /dev/null +++ b/src/core/client/stream/tabs/Comments/Comment/setSpamBanned.ts @@ -0,0 +1,18 @@ +import { commitLocalUpdate, Environment } from "relay-runtime"; + +import { createMutation } from "coral-framework/lib/relay"; + +export interface Input { + commentID: string; +} + +export async function commit(environment: Environment, input: Input) { + return commitLocalUpdate(environment, (store) => { + const comment = store.get(input.commentID); + if (comment) { + comment.setValue(false, "spamBanned"); + } + }); +} + +export const SetSpamBanned = createMutation("SetSpamBanned", commit); diff --git a/src/core/server/services/users/users.ts b/src/core/server/services/users/users.ts index c2932db8cf..76cb623510 100644 --- a/src/core/server/services/users/users.ts +++ b/src/core/server/services/users/users.ts @@ -28,7 +28,7 @@ import { ModeratorCannotBeBannedOnSiteError, PasswordIncorrect, TokenNotFoundError, - UserAlreadyBannedError, + // UserAlreadyBannedError, UserAlreadyPremoderated, UserAlreadySuspendedError, UserBioTooLongError, @@ -1441,7 +1441,15 @@ export async function ban( // Check to see if the User is currently banned. const banStatus = consolidateUserBanStatus(targetUser.status.ban); if (banStatus.active) { - throw new UserAlreadyBannedError(); + if (rejectExistingComments) { + await rejector.add({ + tenantID: tenant.id, + authorID: userID, + moderatorID: banner.id, + }); + } + return targetUser; + // throw new UserAlreadyBannedError(); } // Ban the user. From 3e36dd930c8f8922b7ac38fa5fd28059b5ee0632 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Mon, 10 Jul 2023 12:59:17 -0400 Subject: [PATCH 004/100] update moderationdropdown view logic --- .../ModerationDropdown/CaretContainer.tsx | 21 +++++++++++++++---- .../ModerationDropdownContainer.tsx | 13 ++++++++---- .../ModerationRejectedTombstoneContainer.tsx | 2 +- .../UserBanPopoverContainer.tsx | 6 +++--- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx index 7deea26ad9..f0f3cf82ed 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.tsx @@ -3,7 +3,7 @@ import cn from "classnames"; import React, { FunctionComponent } from "react"; import { graphql } from "react-relay"; -import { withFragmentContainer } from "coral-framework/lib/relay"; +import { useMutation, withFragmentContainer } from "coral-framework/lib/relay"; import CLASSES from "coral-stream/classes"; import { BaseButton, @@ -17,7 +17,10 @@ import { CaretContainer_settings } from "coral-stream/__generated__/CaretContain import { CaretContainer_story } from "coral-stream/__generated__/CaretContainer_story.graphql"; import { CaretContainer_viewer } from "coral-stream/__generated__/CaretContainer_viewer.graphql"; -import ModerationDropdownContainer from "./ModerationDropdownContainer"; +import { SetSpamBanned } from "../setSpamBanned"; +import ModerationDropdownContainer, { + ModerationDropdownView, +} from "./ModerationDropdownContainer"; import styles from "./CaretContainer.css"; @@ -26,12 +29,14 @@ interface Props { story: CaretContainer_story; viewer: CaretContainer_viewer; settings: CaretContainer_settings; - view?: "MODERATE" | "BAN" | "SITE_BAN" | "CONFIRM_BAN"; + view?: ModerationDropdownView; open?: boolean; } const CaretContainer: FunctionComponent = (props) => { const popoverID = `comments-moderationMenu-${props.comment.id}`; + const setSpamBanned = useMutation(SetSpamBanned); + return ( = (props) => { placement="bottom-end" description="A popover menu to moderate the comment" body={({ toggleVisibility, scheduleUpdate }) => ( - + { + if (props.comment.spamBanned) { + void setSpamBanned({ commentID: props.comment.id }); + } + toggleVisibility(); + }} + > ({ comment: graphql` fragment CaretContainer_comment on Comment { id + spamBanned ...ModerationDropdownContainer_comment } `, diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx index 08ea9127da..3508655484 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationDropdownContainer.tsx @@ -20,7 +20,11 @@ import { ModerationDropdownContainer_viewer } from "coral-stream/__generated__/M import UserBanPopoverContainer from "../UserBanPopover/UserBanPopoverContainer"; import ModerationActionsContainer from "./ModerationActionsContainer"; -type View = "MODERATE" | "BAN" | "SITE_BAN" | "CONFIRM_BAN"; +export type ModerationDropdownView = + | "MODERATE" + | "BAN" + | "SITE_BAN" + | "CONFIRM_BAN"; interface Props { comment: ModerationDropdownContainer_comment; @@ -29,7 +33,7 @@ interface Props { settings: ModerationDropdownContainer_settings; onDismiss: () => void; scheduleUpdate: () => void; - view?: View; + view?: ModerationDropdownView; } const ModerationDropdownContainer: FunctionComponent = ({ @@ -42,7 +46,9 @@ const ModerationDropdownContainer: FunctionComponent = ({ view: viewProp, }) => { const emitShowEvent = useViewerEvent(ShowModerationPopoverEvent); - const [view, setView] = useState(viewProp ?? "MODERATE"); + const [view, setView] = useState( + viewProp ?? "MODERATE" + ); const onBan = useCallback(() => { setView("BAN"); @@ -79,7 +85,6 @@ const ModerationDropdownContainer: FunctionComponent = ({ story={story} viewer={viewer} onDismiss={onDismiss} - siteBan={view === "SITE_BAN"} view={view} /> )} diff --git a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx index fca67cd515..713eca6a2b 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.tsx @@ -92,7 +92,7 @@ const ModerationRejectedTombstoneContainer: FunctionComponent = ({ = ({ @@ -47,7 +47,6 @@ const UserBanPopoverContainer: FunctionComponent = ({ story, viewer, onDismiss, - siteBan, view, }) => { const [{ accessToken }] = useLocal(graphql` @@ -56,6 +55,7 @@ const UserBanPopoverContainer: FunctionComponent = ({ } `); const setSpamBanned = useMutation(SetSpamBanned); + const siteBan = view === "SITE_BAN"; const user = comment.author!; const viewerScoped = From 4b4cbdc55f99be4aef1507318d8911cba351d6f0 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Mon, 10 Jul 2023 14:51:00 -0400 Subject: [PATCH 005/100] add more localization; fix tests --- .../CreateCommentReplyMutation.ts | 1 + .../UserBanPopoverContainer.tsx | 50 +++++++++++-------- .../PostCommentForm/CreateCommentMutation.ts | 1 + .../test/comments/stream/moderation.spec.tsx | 41 +++++++++------ src/locales/en-US/stream.ftl | 9 ++++ 5 files changed, 66 insertions(+), 36 deletions(-) diff --git a/src/core/client/stream/tabs/Comments/Comment/ReplyCommentForm/CreateCommentReplyMutation.ts b/src/core/client/stream/tabs/Comments/Comment/ReplyCommentForm/CreateCommentReplyMutation.ts index c297cdc051..42d2ab7b0f 100644 --- a/src/core/client/stream/tabs/Comments/Comment/ReplyCommentForm/CreateCommentReplyMutation.ts +++ b/src/core/client/stream/tabs/Comments/Comment/ReplyCommentForm/CreateCommentReplyMutation.ts @@ -353,6 +353,7 @@ async function commit( pending: false, lastViewerAction: "CREATE", hasTraversalFocus: false, + spamBanned: false, author: { id: viewer.id, username: viewer.username || null, diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx index 064d77b1d5..2987801577 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx @@ -140,17 +140,20 @@ const UserBanPopoverContainer: FunctionComponent = ({ if (view === "CONFIRM_BAN") { return ( - +
{user.username} is now banned
- +
This account can no longer comment, use reactions, or report comments
- +
All comments by this account have been rejected
@@ -159,7 +162,7 @@ const UserBanPopoverContainer: FunctionComponent = ({ itemGutter="half" className={styles.actions} > - + - - + +
); @@ -243,16 +250,15 @@ const UserBanPopoverContainer: FunctionComponent = ({
-
- Type in "spam ban" to confirm -
+
Type in "spam" to confirm
setSpamBanConfirmation(e.target.value)} - > + /> )} = ({
- - + + )} diff --git a/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/CreateCommentMutation.ts b/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/CreateCommentMutation.ts index 380b24569f..e256e4de05 100644 --- a/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/CreateCommentMutation.ts +++ b/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/CreateCommentMutation.ts @@ -303,6 +303,7 @@ export const CreateCommentMutation = createMutation( pending: false, lastViewerAction: "CREATE", hasTraversalFocus: false, + spamBanned: false, author: { id: viewer.id, username: viewer.username || null, diff --git a/src/core/client/stream/test/comments/stream/moderation.spec.tsx b/src/core/client/stream/test/comments/stream/moderation.spec.tsx index fd81da1103..c3e154dc6e 100644 --- a/src/core/client/stream/test/comments/stream/moderation.spec.tsx +++ b/src/core/client/stream/test/comments/stream/moderation.spec.tsx @@ -230,7 +230,7 @@ it("reject comment", async () => { ); }); -it("ban user", async () => { +it("spam ban user", async () => { await act(async () => { await createTestRenderer({ resolvers: createResolversStub({ @@ -244,7 +244,7 @@ it("ban user", async () => { banUser: ({ variables }) => { expectAndFail(variables).toMatchObject({ userID: firstComment.author!.id, - rejectExistingComments: false, + rejectExistingComments: true, siteIDs: [], }); return { @@ -280,15 +280,25 @@ it("ban user", async () => { }); await waitFor(() => { expect( - within(comment).getByRole("button", { name: "Ban User" }) + within(comment).getByRole("button", { name: "Spam ban" }) ).not.toBeDisabled(); }); // this is not multisite, so there should be no Site Ban option expect( - within(comment).queryByRole("button", { name: "Site Ban" }) + within(comment).queryByRole("button", { name: "Site ban" }) ).not.toBeInTheDocument(); - fireEvent.click(within(comment).getByRole("button", { name: "Ban User" })); + fireEvent.click(within(comment).getByRole("button", { name: "Spam ban" })); + const banButtonDialog = await screen.findByRole("button", { name: "Ban" }); + expect(banButtonDialog).toBeDisabled(); + + const input = screen.getByTestId("userSpamBanConfirmation"); + fireEvent.change(input, { target: { value: "spam" } }); + + await waitFor(() => { + expect(banButtonDialog).toBeEnabled(); + }); + fireEvent.click(banButtonDialog); expect( await within(tabPane).findByText("You have rejected this comment.") @@ -315,10 +325,10 @@ it("cancel ban user", async () => { }); await waitFor(() => { expect( - within(comment).getByRole("button", { name: "Ban User" }) + within(comment).getByRole("button", { name: "Spam ban" }) ).not.toBeDisabled(); }); - fireEvent.click(within(comment).getByRole("button", { name: "Ban User" })); + fireEvent.click(within(comment).getByRole("button", { name: "Spam ban" })); const cancelButtonDialog = await screen.findByRole("button", { name: "Cancel", }); @@ -382,14 +392,14 @@ it("site moderator can site ban commenter", async () => { }); // Site moderator has Site Ban option but not Ban User option const siteBanButton = await within(comment).findByRole("button", { - name: "Site Ban", + name: "Site ban", }); await waitFor(() => { expect(siteBanButton).not.toBeDisabled(); }); expect( - within(comment).queryByRole("button", { name: "Ban User" }) - ).not.toBeInTheDocument(); + within(comment).queryByRole("button", { name: "Spam ban" }) + ).toBeInTheDocument(); fireEvent.click(siteBanButton); expect( await screen.findByText("Ban Markus from this site?") @@ -397,10 +407,13 @@ it("site moderator can site ban commenter", async () => { const banButtonDialog = screen.getByRole("button", { name: "Ban" }); fireEvent.click(banButtonDialog); - expect( - within(tabPane).getByText("You have rejected this comment.") - ).toBeVisible(); - const link = within(tabPane).getByRole("link", { + await waitFor(() => { + expect( + within(tabPane).getByText("You have rejected this comment.") + ).toBeVisible(); + }); + + const link = await within(tabPane).findByRole("link", { name: "Go to moderate to review this decision", }); expect(link).toHaveAttribute( diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index ebccb1895c..a466c93b73 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -271,6 +271,15 @@ comments-userBanPopover-description = This comment will also be rejected. comments-userBanPopover-cancel = Cancel comments-userBanPopover-ban = Ban +comments-userBanPopover-moreContext = For more context, go to +comments-userBanPopover-moderationView = Moderation view + +comments-userSiteBanPopover-confirm-title = {$username} is now banned +comments-userSiteBanPopover-confirm-spam-banned = This account can no longer comment, use reactions, or report comments +comments-userSiteBanPopover-confirm-comments-rejected = All comments by this account have been rejected +comments-userSiteBanPopover-confirm-closeButton = Close +comments-userSiteBanPopover-confirm-reviewAccountHistory = You can still review this account's history by searching in Coral's +comments-userSiteBanPopover-confirm-communitySection = Community section comments-moderationDropdown-popover = .description = A popover menu to moderate the comment From ffe2757977c5af5eb44e83ee3085a79c2bc39127 Mon Sep 17 00:00:00 2001 From: Marcus Haddon Date: Mon, 10 Jul 2023 12:20:18 -0700 Subject: [PATCH 006/100] frontend only solution to removing banned user's comments from queue --- buildlog | 606 ++++++++++++++++++ codegen.ts | 27 + .../admin/components/BanUserMutation.ts | 11 +- .../ModerateCard/ModerateCardContainer.tsx | 1 + src/core/client/admin/local/local.graphql | 4 + .../admin/mutations/ApproveCommentMutation.ts | 1 + .../Moderate/Queue/ApprovedQueueRoute.tsx | 3 + .../admin/routes/Moderate/Queue/Queue.tsx | 15 +- .../routes/Moderate/Queue/QueueRoute.tsx | 6 + .../tabs/Comments/Stream/StreamContainer.tsx | 1 + src/core/server/services/users/users.ts | 4 + 11 files changed, 676 insertions(+), 3 deletions(-) create mode 100644 buildlog create mode 100644 codegen.ts diff --git a/buildlog b/buildlog new file mode 100644 index 0000000000..525300e1ae --- /dev/null +++ b/buildlog @@ -0,0 +1,606 @@ + +> @coralproject/talk@8.2.3 build:development +> NODE_ENV=development npm-run-all generate --parallel build:client build:server + + +> @coralproject/talk@8.2.3 generate +> npm-run-all generate:css-types generate:relay generate:schema:v2 + + +> @coralproject/talk@8.2.3 generate:css-types +> tcm src/core/client/ + +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/App/MainLayout.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/download/Download/DownloadRoute.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/download/Download/DownloadDescription.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/App/App.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/NotFound.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/password/Reset/Reset.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/notifications/Unsubscribe/UnsubscribeRoute.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/download/Download/DownloadForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/email/Confirm/ConfirmRoute.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/email/Confirm/Confirm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/notifications/Unsubscribe/Unsubscribe.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/ApprovedIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/DecisionList.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/DecisionHistoryButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/DecisionHistoryLoading.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/account/routes/password/Reset/ResetRoute.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/DotDivider.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/Empty.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/GoToCommentLink.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/Main.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/RejectedIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/ShowMoreButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/Timestamp.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/Main.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/Title.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/Username.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/Version.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/UserMenu/UserMenu.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/BanModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/Comment/CommentContent.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ButtonPadding.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/Comment/InReplyTo.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/AuthBox.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/Comment/UsernameButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/Comment/Username.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ConversationModal/ConversationModalCommentContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ConversationModal/ConversationModalContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ConversationModal/ConversationModalRepliesContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ConversationModal/ConversationModalQuery.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ConversationModal/ConversationModalHeaderContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/MainLayout.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ExperimentalCallOut.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/MediaContainer/Media.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModalHeader.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModalHeaderUsername.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModalBodyText.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/AutomatedActionsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/CommentAuthorContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/ApproveButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/FlagDetailsCategory.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/FeatureButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/LinkDetailsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/FlagDetailsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/Markers.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/ModerateCard.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/ModeratedByContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/ReactionDetailsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/ToxicityLabel.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/None.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/ModerateCard/RejectButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/NotAvailable.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/NoTextContent.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/PaginatedSelect/PaginatedSelect.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/SiteSearch/SiteFilterOption.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/SiteSearch/SiteSearchTextField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/StoryInfoDrawer/Select.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/SiteSearch/SiteSearchListContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/StoryInfoDrawer/ModerateStoryButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/StoryInfoDrawer/ArchiveStoryActionsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/StoryInfoDrawer/StoryInfoDrawer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/StoryInfoDrawer/StoryStatus.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/StoryInfoDrawer/StorySettingsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/Timeline/Circle.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/ArchivedCommentsThresholdNotification.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/MemberBioContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/StoryInfoDrawer/StoryInfoDrawerContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/Timeline/Line.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/ModeratorNote.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserDrawerAccountHistory.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/RecentHistory.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/Tabs.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserDrawerNotesContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserHistoryDrawer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserHistoryDrawerAllComments.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserDrawerAccountHistoryQuery.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserHistoryDrawerQuery.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserHistoryDrawerAllCommentsQuery.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserDrawerNotesQuery.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserHistoryDrawerContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UsernameChangeAction.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserHistoryDrawerRejectedCommentsQuery.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserStatusDetailsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserRole/SiteModeratorActionsModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserRole/UserRoleText.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserRole/SiteRoleActions.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserRole/UserRoleChange.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserRole/SiteRoleModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserRole/UserRoleChangeButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/ChangeStatusModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/SuspendModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserHistoryDrawer/UserHistoryDrawerRejectedComments.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/SuspendForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/UserStatusChange.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/UserStatusSitesList.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/WarnForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/AuthCheck/NetworkError.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/AuthCheck/Restricted.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/ModMessageForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/Community.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/InviteUsers/InviteUsers.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/InviteUsers/InviteUsersForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/EmptyMessage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/InviteUsers/RoleField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/InviteUsers/InviteUsersModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/UserTableFilter.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/InviteUsers/Success.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/UserTable.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/components/UserStatus/UserStatus.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Community/UserRow.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/ConfigBox.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/Link.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/HelperText.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/Main.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/Header.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/Navigation.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/HorizontalRule.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Auth/ConfigDescription.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Auth/AccountFeaturesConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Auth/SSOSigningSecretRotation/DateField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/SectionContent.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Auth/LocalAuthConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/Layout.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Auth/SSOSigningSecretRotation/RotationDropdown.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Auth/SSOSigningSecretRotation/StatusField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/BadgeConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Auth/SSOSigningSecretRotation/SSOSigningSecretCard.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Email/EmailConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/MediaLinksConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/GeneralConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/AnnouncementConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/AnnouncementFormModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/CommentLengthConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/ReactionConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Moderation/AllSpecificOffSitesField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Moderation/EmailDomainConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/General/RTEConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Moderation/NewCommentersConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Moderation/RecentCommentHistoryConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Moderation/PerspectiveConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/ModerationPhases/ConfigureExternalModerationPhase/RotateSigningSecretModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/ModerationPhases/ExternalModerationPhaseRow.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Moderation/ExternalLinksConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Sites/EmbedCode.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/ModerationPhases/StatusMarker.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Slack/SlackConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/Slack/SlackChannel.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/WebhookEndpoints/ConfigureWebhookEndpoint/RotateSigningSecretModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/WordList/BannedWordListConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/WebhookEndpoints/WebhookEndpointRow.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/WordList/SuspectWordListConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/Subheader.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/WordList/WordListTextArea.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/SideBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/SubLink.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/ValidationMessage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/TextFieldWithValidation.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/components/DashboardBox.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/ControlPanel/ControlPanel.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/WebhookEndpoints/StatusMarker.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Configure/sections/WebhookEndpoints/ConfigureWebhookEndpointForm/EventsSelectField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/components/DashboardComponentHeading.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/components/Loader.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/components/SiteDashboardTimestamp.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/components/TodayValue.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/components/TodayDashboardBox.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/Dashboard.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/components/TodayCompareValue.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/ForgotPassword/ForgotPassword.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/DashboardSiteContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/DashboardContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/sections/Today.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/sections/SignupActivity.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Invite/InviteCompleteForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/ForgotPassword/ForgotPasswordContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/views/AddEmailAddress/UnorderedList/ListItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/sections/CommentActivityTooltip.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Dashboard/sections/CommentActivity.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Invite/InviteLayout.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/views/SignIn/SignIn.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/CompleteAccountBox.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/views/LinkAccount/HorizontalSeparator.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/views/SignIn/HorizontalSeparator.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Invite/Success.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Moderate.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/views/SignIn/Version.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Login/views/AddEmailAddress/UnorderedList/UnorderedList.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/Field.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/Group.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/SeeAllOption.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/Bar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/ModerateAllOption.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/variables.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/OptionDetail.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Queue/ForReviewQueueRoute/ForReviewQueueRoute.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/HotkeysModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/ModerateSearchBar/Option.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Queue/EmptyMessage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Queue/QueueWrapper.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Queue/Queue.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Queue/ForReviewQueueRoute/ForReviewQueueRowContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/SingleModerate/SingleModerate.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/SectionSelector/SectionSelector.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/SectionSelector/SectionSelectorSection.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/NotFound.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/SiteSelector/SiteSelectorSite.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Queue/QueueSort/QueueSort.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/Queue/ForReviewQueueRoute/ReviewButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Moderate/SiteSelector/SiteSelector.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Stories/EmptyMessage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Stories/NoMatchMessage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Stories/StoryRow.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Stories/StoryStatus/StoryStatusText.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Stories/StoryTableFilter.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/App/App.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Stories/StoryTable.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/Header/Bar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/Header/SubBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/Main.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/Header/Subtitle.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/OrSeparator.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/Header/Title.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/AddEmailAddress/UnorderedList/UnorderedList.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/AddEmailAddress/AddEmailAddress.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/routes/Stories/Stories.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/AddEmailAddress/UnorderedList/ListItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/UsernameField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/CreateUsername/CreateUsername.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/ForgotPassword/CheckEmail.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/LinkAccount/LinkAccountContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/ForgotPassword/ForgotPasswordForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/SignIn/SignIn.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/SignIn/SignInWithEmail.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/SignUp/SignInWithLocalHeader.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/SignUp/SignUp.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/SignUp/SignUpWithEmail.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/DurationField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/FadeInTransition.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/FacebookButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/components/SetPasswordField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/GoogleButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/Markdown.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/OIDCButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/Frame.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/DecisionItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/install/App/App.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/install/App/Header.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/lib/i18n/components/ExternalLink.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/install/App/MainBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/App/TabBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/install/App/Wizard.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/KeyboardShortcuts/KeyboardShortcuts.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/auth/views/CreatePassword/CreatePassword.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/KeyboardShortcuts/MobileToolbar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/MessageBox/MessageBox.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/Media/GiphyMedia.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/Footer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/UserBox/UserBoxAuthenticated.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/NetworkError.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/MessageBox/MessageBoxIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/UserBox/UserBoxUnauthenticated.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/AnsweredTag.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ArchivedReportFlow/ArchivedReportFlowContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/shared/htmlContent.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/Comment.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/EditedMarker.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/Media/YouTubeMedia.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/CommentToggle.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/EditCommentForm/EditCommentForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/FeaturedTag.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/BadgeTagContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/IndentedComment.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/CommentContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/InReplyTo.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmation.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/MediaSection/MediaSectionContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionBanButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaPreview.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/CaretContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmationIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationActionsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ModerationDropdown/ModerationRejectedTombstoneContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/PermalinkButton/PermalinkButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ReactionButton/ReactionButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/MediaConfirmation/MediaConfirmPrompt.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/PermalinkButton/PermalinkPopover.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/PermalinkButton/PermalinkCopyButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/RemoveAnswered.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ReplyButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ReplyCommentForm/ReplyEditedWarningContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ReportFlow/ReportButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ReportFlow/ReportCommentForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ReplyCommentForm/ReplyingTo.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/ShowConversationLink.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/UserIgnorePopover/UserIgnorePopoverContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/UserPopover/UserPopoverOverviewContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/ExternalImageInput/ExternalImageInput.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/Username/Username.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Comment/UserTagsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/GiphyInput/GifPreview.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/IgnoredTombstoneOrHideContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/PermalinkView/ConversationThreadContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/GiphyInput/GiphyAttribution.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/PermalinkView/PermalinkViewContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/GiphyInput/GiphyInput.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/PermalinkView/Timeline/Line.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Indent.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/ReplyList/ReplyList.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AccountStatusCalloutMessage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/PermalinkView/Timeline/Circle.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/ReplyList/ReplyListCommentContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AddACommentButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AllCommentsTab/AllCommentsTabCommentContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/App/App.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AllCommentsTab/RatingsFilterMenu.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/RTE/RTEButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AnsweredCommentsTab/AnsweredCommentContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/BannedInfo.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/CommentForm/CommentForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AnsweredCommentsTab/AnsweredCommentsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/CommentForm/RatingInput.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/CommunityGuidelines/CommunityGuidelines.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/DeleteAccount/StreamDeletionRequestCalloutContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/Divider.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/Announcement/Announcement.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AllCommentsTab/AllCommentsTabVirtualizedComments.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/FeaturedComments/FeaturedCommentContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/FeaturedComments/FeaturedBy.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/MessageBoxContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/PostCommentFormClosed.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/FeaturedCommentTooltip.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/PostCommentForm.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/PostCommentInput.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/PostCommentFormFake.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/PostCommentForm/PostReviewOrQuestion.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/UnansweredCommentsTab/UnansweredCommentsTabCommentContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/SortMenu.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/StreamContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/FeaturedComments/FeaturedCommentsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/ViewersWatchingContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/AddMessage/shared.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/AddMessage/MessageBoxConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/AddMessage/AddMessageContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/ArchivedConfigurationContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/AddMessage/AddMessageOpen.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/ConfigureStream/ToggleConfig.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/ConfigureStream/WidthLimitedDescription.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/OpenOrCloseStream/CloseStream.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/ConfigureStream/ConfigureStream.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/OpenOrCloseStream/OpenOrCloseStreamContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/HorizontalRule.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/OpenOrCloseStream/OpenStream.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/DisableQA.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/ExpertSearchItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/EnableQA.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/ExpertListItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/SuspendedInfo/SuspendedInfo.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/NoLongerAnExpert.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/QAConfigContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Discussions/DiscussionsHeader.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/ExpertSelectionContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Discussions/StoryRowContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/DeletionRequest/DeletionRequestCalloutContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Configure/Q&A/ExpertSearchList.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Discussions/MostActiveDiscussionsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Discussions/MyOngoingDiscussionsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/MyComments/CommentHistory.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/MyComments/HistoryComment.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/MyComments/ArchivedCommentsThresholdNotification.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/MyComments/HistoryCommentFooterContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/MyComments/DownloadCommentsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Preferences/IgnoreUserListItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/MyComments/MyCommentsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Preferences/BioContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/AccountSettingsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/ChangeEmail/ChangeEmailContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Preferences/NotificationSettingsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Preferences/MediaSettingsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Preferences/IgnoreUserSettingsContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/DeleteAccount/DeleteAccountContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/DeleteAccount/DeleteAccountModal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/ChangePassword.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/ChangeUsername/ChangeUsernameContainer.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/DeleteAccount/Pages/Common/Page.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/DeleteAccount/Pages/ConfirmPage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/DeleteAccount/Pages/Common/PageStepBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AbsoluteTime/AbsoluteTime.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AppBar/Begin.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Profile/Settings/Username.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AppBar/End.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AppBar/AppBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AppBar/Divider.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AppBar/NavigationItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AppNotification/AppNotification.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AriaInfo/AriaInfo.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/AppBar/Navigation.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Brand/BrandMark.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/BaseButton/BaseButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Backdrop/Backdrop.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Box/Box.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Brand/LogoHorizontal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Button/ButtonIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Brand/BrandName.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/CallOut/CallOut.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Card/Card.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Button/Button.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Card/CloseButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/CheckBox/CheckBox.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Divider/Divider.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Counter/Counter.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Dropdown/Button.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/CheckIcon/CheckIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/DurationField/DurationField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Dropdown/Dropdown.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Dropdown/Divider.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Flex/Flex.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/FormFieldFooter/FormFieldFooter.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/FormFieldDescription/FormFieldDescription.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/FormField/FormField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/FieldSet/FieldSet.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/FormFieldHeader/FormFieldHeader.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/HelperText/HelperText.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/HorizontalGutter/HorizontalGutter.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/HorizontalRule/HorizontalRule.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Hidden/Hidden.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/InputLabel/InputLabel.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Label/Label.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/ListGroup/ListGroupRow.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/ListGroup/ListGroup.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Marker/Count.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/InputDescription/InputDescription.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Message/Message.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Marker/Marker.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Message/MessageIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Modal/Modal.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Modal/ModalHeader.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Modal/Modal.mdx.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/NoScroll/NoScroll.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Popover/Arrow.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Icon/Icon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/PasswordField/PasswordField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Popover/Popover.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/RelativeTime/RelativeTime.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/RadioButton/RadioButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/SelectField/SelectField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Steps/Circle.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Steps/Step.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Steps/StepBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/SubBar/Navigation.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Spinner/Spinner.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Steps/Line.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Table/Table.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/SubBar/SubBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Table/TableCell.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/SubBar/NavigationItem.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Table/TableHead.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Table/TableBody.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Table/TableRow.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Tabs/Tab.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Tabs/TabBar.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/TextField/TextField.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/TileSelector/TileSelector.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/TextFieldAdornment/TextFieldAdornment.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Timestamp/Timestamp.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/TileSelector/TileOption.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Textarea/Textarea.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Tag/Tag.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Tooltip/Tooltip.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/Button/Button.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Tooltip/TooltipButton.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/Link/Link.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/Typography/Typography.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v2/TextLink/TextLink.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/CallOut/CallOut.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/StarRating/StarRatingIcon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/ArchivedMarker/ArchivedMarker.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/ExperimentalTag/ExperimentalTag.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/StarRating/StarRating.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/Tombstone/Tombstone.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/theme/mixins.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/QueryError/QueryError.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/theme/typography.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/Tombstone/TombstoneWrapper.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/CSSLoadError.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/admin/App/DecisionHistory/Info.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/shared/icon.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/framework/components/loadables/MarkdownEditor/MarkdownEditor.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/HTMLContent/HTMLContent.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/common/MessageBox/MessageBoxContent.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/ValidationMessage/ValidationMessage.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/ui/components/v3/TextArea/TextArea.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/RTE/RTE.css.d.ts +Wrote /Users/marcus.haddon/coral/talk/src/core/client/stream/tabs/Comments/Stream/AllCommentsTab/AllCommentsTabContainer.css.d.ts + +> @coralproject/talk@8.2.3 generate:relay +> npm-run-all --parallel generate:relay:* + + +> @coralproject/talk@8.2.3 generate:relay:admin +> ts-node --transpile-only ./scripts/compileRelay --bundle admin + + +> @coralproject/talk@8.2.3 generate:relay:framework +> ts-node --transpile-only ./scripts/compileRelay --bundle framework + + +> @coralproject/talk@8.2.3 generate:relay:account +> ts-node --transpile-only ./scripts/compileRelay --bundle account + + +> @coralproject/talk@8.2.3 generate:relay:auth +> ts-node --transpile-only ./scripts/compileRelay --bundle auth + + +> @coralproject/talk@8.2.3 generate:relay:stream +> ts-node --transpile-only ./scripts/compileRelay --bundle stream + + +> @coralproject/talk@8.2.3 generate:relay:install +> ts-node --transpile-only ./scripts/compileRelay --bundle install + + +Writing ts + +Writing ts + +Writing ts +Unchanged: 0 files +Unchanged: 0 files + +Writing ts +Unchanged: 0 files +Unchanged: 22 files + +Writing ts + +Writing ts +Unchanged: 341 files +Unchanged: 346 files + +> @coralproject/talk@8.2.3 generate:schema:v2 +> graphql-codegen --config codegen.yml + +[11:59:09] Parse configuration [started] +[11:59:09] Parse configuration [completed] +[11:59:09] Generate outputs [started] +[11:59:09] Generate src/core/server/graph/schema/__generated__/types.ts [started] +[11:59:09] Load GraphQL schemas [started] +[11:59:09] Load GraphQL schemas [completed] +[11:59:09] Load GraphQL documents [started] +[11:59:09] Load GraphQL documents [completed] +[11:59:09] Generate [started] +[11:59:10] Generate [completed] +[11:59:10] Generate src/core/server/graph/schema/__generated__/types.ts [completed] +[11:59:10] Generate outputs [completed] + +> @coralproject/talk@8.2.3 build:server +> gulp server + + +> @coralproject/talk@8.2.3 build:client +> ts-node --transpile-only ./scripts/build.ts + +[11:59:12] Using gulpfile ~/coral/talk/gulpfile.js +[11:59:12] Starting 'server'... +[11:59:12] Starting 'server:schema'... +SKIPPING OLD GENERATE SCHEMA TYPES script +[11:59:12] Finished 'server:schema' after 1.07 ms +[11:59:12] Starting 'server:scripts'... +[11:59:12] Starting 'server:static'... +[11:59:12] Starting 'locales'... +Creating development build... +{"name":"coral","hostname":"marcus-haddon","pid":14334,"level":20,"config":"{\n \"env\": \"development\",\n \"port\": 3000,\n \"dev_port\": 8080,\n \"defaultLocale\": \"en-US\",\n \"fallbackLocale\": \"en-US\",\n \"generateReport\": false,\n \"disableSourcemaps\": false,\n \"disableMinimize\": false,\n \"disableChunkSplitting\": false,\n \"enableTreeShake\": false,\n \"enableReactProfiler\": false,\n \"maxCores\": 12\n}","msg":"loaded configuration","time":"2023-06-27T18:59:12.472Z","v":0} +[11:59:13] Finished 'locales' after 1 s +[11:59:13] Finished 'server:static' after 1.07 s +src/core/server/graph/resolvers/Subscription/commentEdited.ts(27,6): error TS6196: 'MyType' is declared but never used. +src/core/server/graph/schema/schema.spec.ts(21,5): error TS2322: Type '"__typename" | "COMMENT_REPORTED_OFFENSIVE" | "COMMENT_REPORTED_ABUSIVE" | "COMMENT_REPORTED_SPAM" | "COMMENT_REPORTED_OTHER" | "COMMENT_REPORTED_BIO" | "COMMENT_DETECTED_TOXIC" | "COMMENT_DETECTED_SPAM" | "COMMENT_DETECTED_LINKS" | "COMMENT_DETECTED_BANNED_WORD" | "COMMENT_DETECTED_SUSPECT_WORD" | "COMMENT_DETECTED_RECENT_HISTORY" | "COMMENT_DETECTED_PREMOD_USER" | "COMMENT_DETECTED_NEW_COMMENTER" | "COMMENT_DETECTED_REPEAT_POST" | undefined' is not assignable to type '"COMMENT_REPORTED_OFFENSIVE" | "COMMENT_REPORTED_ABUSIVE" | "COMMENT_REPORTED_SPAM" | "COMMENT_REPORTED_OTHER" | "COMMENT_REPORTED_BIO" | "COMMENT_DETECTED_TOXIC" | "COMMENT_DETECTED_SPAM" | "COMMENT_DETECTED_LINKS" | "COMMENT_DETECTED_BANNED_WORD" | "COMMENT_DETECTED_SUSPECT_WORD" | "COMMENT_DETECTED_RECENT_HISTORY" | "COMMENT_DETECTED_PREMOD_USER" | "COMMENT_DETECTED_NEW_COMMENTER" | "COMMENT_DETECTED_REPEAT_POST"'. + Type 'undefined' is not assignable to type '"COMMENT_REPORTED_OFFENSIVE" | "COMMENT_REPORTED_ABUSIVE" | "COMMENT_REPORTED_SPAM" | "COMMENT_REPORTED_OTHER" | "COMMENT_REPORTED_BIO" | "COMMENT_DETECTED_TOXIC" | "COMMENT_DETECTED_SPAM" | "COMMENT_DETECTED_LINKS" | "COMMENT_DETECTED_BANNED_WORD" | "COMMENT_DETECTED_SUSPECT_WORD" | "COMMENT_DETECTED_RECENT_HISTORY" | "COMMENT_DETECTED_PREMOD_USER" | "COMMENT_DETECTED_NEW_COMMENTER" | "COMMENT_DETECTED_REPEAT_POST"'. +TypeScript: 2 semantic errors +TypeScript: emit succeeded (with errors) diff --git a/codegen.ts b/codegen.ts new file mode 100644 index 0000000000..71325ae5ae --- /dev/null +++ b/codegen.ts @@ -0,0 +1,27 @@ +import { CodegenConfig } from "@graphql-codegen/cli"; +// import prefixType from "./scripts/prefixTypes"; + +const config: CodegenConfig = { + schema: "src/core/server/graph/schema/schema.graphql", + generates: { + "src/core/server/graph/schema/__generated__/types.ts": { + plugins: [ + "typescript", + "typescript-operations", + "typescript-resolvers", + "typescript-document-nodes", + ], + config: { + contextType: "GraphContext", + preResolveTypes: + 'import GraphContext from "coral-server/graph/context";', + namingConvention: { + typeNames: (name) => `GQL${name}`, + enumvValues: "keep", + }, + }, + }, + }, +}; + +export default config; diff --git a/src/core/client/admin/components/BanUserMutation.ts b/src/core/client/admin/components/BanUserMutation.ts index e1ff1ebd38..d8239436f4 100644 --- a/src/core/client/admin/components/BanUserMutation.ts +++ b/src/core/client/admin/components/BanUserMutation.ts @@ -1,5 +1,6 @@ +/* eslint-disable */ import { graphql } from "react-relay"; -import { Environment } from "relay-runtime"; +import { commitLocalUpdate, Environment } from "relay-runtime"; import { getViewer } from "coral-framework/helpers"; import { @@ -18,6 +19,14 @@ const BanUserMutation = createMutation( "banUser", (environment: Environment, input: MutationInput) => { const viewer = getViewer(environment)!; + if (input.rejectExistingComments) { + // BOOKMARCUS + commitLocalUpdate(environment, (store) => { + const record = store.get(input.userID); + record?.setValue(true, "allCommentsRejected"); + }); + } + return commitMutationPromiseNormalized(environment, { mutation: graphql` mutation BanUserMutation($input: BanUserInput!) { diff --git a/src/core/client/admin/components/ModerateCard/ModerateCardContainer.tsx b/src/core/client/admin/components/ModerateCard/ModerateCardContainer.tsx index 8f7a4625f2..682ad3df6c 100644 --- a/src/core/client/admin/components/ModerateCard/ModerateCardContainer.tsx +++ b/src/core/client/admin/components/ModerateCard/ModerateCardContainer.tsx @@ -390,6 +390,7 @@ const enhanced = withFragmentContainer({ id email username + allCommentsRejected status { current ban { diff --git a/src/core/client/admin/local/local.graphql b/src/core/client/admin/local/local.graphql index 27053aead7..98f9e0c773 100644 --- a/src/core/client/admin/local/local.graphql +++ b/src/core/client/admin/local/local.graphql @@ -25,6 +25,10 @@ extend type CommentsConnection { viewNewEdges: [CommentEdge!] } +extend type User { + allCommentsRejected: Boolean +} + extend type Local { redirectPath: String authView: View diff --git a/src/core/client/admin/mutations/ApproveCommentMutation.ts b/src/core/client/admin/mutations/ApproveCommentMutation.ts index fb3d76eab0..9c4d49f304 100644 --- a/src/core/client/admin/mutations/ApproveCommentMutation.ts +++ b/src/core/client/admin/mutations/ApproveCommentMutation.ts @@ -81,6 +81,7 @@ const ApproveCommentMutation = createMutation( section: input.section, }, optimisticUpdater: (store) => { + // BOOKMARCUS const proxy = store.get(input.commentID)!; proxy.setValue("APPROVED", "status"); proxy.setValue(true, "viewerDidModerate"); diff --git a/src/core/client/admin/routes/Moderate/Queue/ApprovedQueueRoute.tsx b/src/core/client/admin/routes/Moderate/Queue/ApprovedQueueRoute.tsx index 9943a48c7a..d8357c8eac 100644 --- a/src/core/client/admin/routes/Moderate/Queue/ApprovedQueueRoute.tsx +++ b/src/core/client/admin/routes/Moderate/Queue/ApprovedQueueRoute.tsx @@ -130,6 +130,9 @@ const enhanced = withPaginationContainer< edges { node { id + author { + allCommentsRejected + } ...ModerateCardContainer_comment } } diff --git a/src/core/client/admin/routes/Moderate/Queue/Queue.tsx b/src/core/client/admin/routes/Moderate/Queue/Queue.tsx index 86b4c28ca5..f043744721 100644 --- a/src/core/client/admin/routes/Moderate/Queue/Queue.tsx +++ b/src/core/client/admin/routes/Moderate/Queue/Queue.tsx @@ -25,7 +25,10 @@ import styles from "./Queue.css"; interface Props { comments: Array< - { id: string } & PropTypesOf["comment"] + { + id: string; + author: { allCommentsRejected: boolean | null } | null; + } & PropTypesOf["comment"] >; settings: PropTypesOf["settings"]; viewer: PropTypesOf["viewer"]; @@ -41,7 +44,7 @@ interface Props { const Queue: FunctionComponent = ({ settings, - comments, + comments: unfilteredComments, viewer, hasLoadMore: hasMore, disableLoadMore, @@ -61,8 +64,16 @@ const Queue: FunctionComponent = ({ useState(false); const [conversationCommentID, setConversationCommentID] = useState(""); const [hasModerated, setHasModerated] = useState(false); + const [comments, setComments] = useState([]); const memoize = useMemoizer(); + useEffect(() => { + const filteredComments = unfilteredComments.filter( + (comment) => !comment.author?.allCommentsRejected + ); + setComments(filteredComments); + }, [unfilteredComments]); + // So we can register hotkeys for the first comment without immediately pulling focus useEffect(() => { if (comments.length > 0) { diff --git a/src/core/client/admin/routes/Moderate/Queue/QueueRoute.tsx b/src/core/client/admin/routes/Moderate/Queue/QueueRoute.tsx index 299d6069e7..da8c6df8c4 100644 --- a/src/core/client/admin/routes/Moderate/Queue/QueueRoute.tsx +++ b/src/core/client/admin/routes/Moderate/Queue/QueueRoute.tsx @@ -281,12 +281,18 @@ const createQueueRoute = ( cursor node { id + author { + allCommentsRejected + } ...ModerateCardContainer_comment } } edges { node { id + author { + allCommentsRejected + } ...ModerateCardContainer_comment } } diff --git a/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx b/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx index e55f569a31..d50e1a7372 100644 --- a/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx @@ -249,6 +249,7 @@ export const StreamContainer: FunctionComponent = (props) => { return ( <> + "HERES THIS STUPID BULLSHIT" Date: Mon, 10 Jul 2023 13:23:23 -0700 Subject: [PATCH 007/100] remove commnet --- src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx b/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx index d50e1a7372..e55f569a31 100644 --- a/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Stream/StreamContainer.tsx @@ -249,7 +249,6 @@ export const StreamContainer: FunctionComponent = (props) => { return ( <> - "HERES THIS STUPID BULLSHIT" Date: Mon, 10 Jul 2023 13:25:29 -0700 Subject: [PATCH 008/100] remove console.logs --- src/core/server/services/users/users.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/server/services/users/users.ts b/src/core/server/services/users/users.ts index d3181a8462..c2932db8cf 100644 --- a/src/core/server/services/users/users.ts +++ b/src/core/server/services/users/users.ts @@ -1389,8 +1389,6 @@ export async function ban( siteIDs?: string[] | null, now = new Date() ) { - /* eslint-disable */ - console.log("BANNING USER"); // site moderators must provide at least one site ID to ban the user on // otherwise, they would be performing an organization wide ban. if ( @@ -1426,7 +1424,6 @@ export async function ban( now ); if (rejectExistingComments) { - console.log("REJECT THEM ALL"); await rejector.add({ tenantID: tenant.id, authorID: userID, @@ -1487,7 +1484,6 @@ export async function ban( } if (rejectExistingComments) { - console.log("REJECTING EXISTING COMMENTS"); await rejector.add({ tenantID: tenant.id, authorID: userID, From 8c2e2f40d35607696712532263c94aa649af5f0b Mon Sep 17 00:00:00 2001 From: Marcus Haddon Date: Mon, 10 Jul 2023 13:28:52 -0700 Subject: [PATCH 009/100] delete comments --- src/core/client/admin/components/BanUserMutation.ts | 1 - src/core/client/admin/mutations/ApproveCommentMutation.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/core/client/admin/components/BanUserMutation.ts b/src/core/client/admin/components/BanUserMutation.ts index d8239436f4..fbe686385f 100644 --- a/src/core/client/admin/components/BanUserMutation.ts +++ b/src/core/client/admin/components/BanUserMutation.ts @@ -20,7 +20,6 @@ const BanUserMutation = createMutation( (environment: Environment, input: MutationInput) => { const viewer = getViewer(environment)!; if (input.rejectExistingComments) { - // BOOKMARCUS commitLocalUpdate(environment, (store) => { const record = store.get(input.userID); record?.setValue(true, "allCommentsRejected"); diff --git a/src/core/client/admin/mutations/ApproveCommentMutation.ts b/src/core/client/admin/mutations/ApproveCommentMutation.ts index 9c4d49f304..fb3d76eab0 100644 --- a/src/core/client/admin/mutations/ApproveCommentMutation.ts +++ b/src/core/client/admin/mutations/ApproveCommentMutation.ts @@ -81,7 +81,6 @@ const ApproveCommentMutation = createMutation( section: input.section, }, optimisticUpdater: (store) => { - // BOOKMARCUS const proxy = store.get(input.commentID)!; proxy.setValue("APPROVED", "status"); proxy.setValue(true, "viewerDidModerate"); From 7b66db06999eb489aa5b4cb9ef661d621168f129 Mon Sep 17 00:00:00 2001 From: Marcus Haddon Date: Mon, 10 Jul 2023 13:55:58 -0700 Subject: [PATCH 010/100] include comment.author.allCommentsRejected to single moderate route --- .../routes/Moderate/SingleModerate/SingleModerateRoute.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/client/admin/routes/Moderate/SingleModerate/SingleModerateRoute.tsx b/src/core/client/admin/routes/Moderate/SingleModerate/SingleModerateRoute.tsx index 2353c7fe5a..cefa6cdc9c 100644 --- a/src/core/client/admin/routes/Moderate/SingleModerate/SingleModerateRoute.tsx +++ b/src/core/client/admin/routes/Moderate/SingleModerate/SingleModerateRoute.tsx @@ -59,6 +59,9 @@ const enhanced = withRouteConfig({ query SingleModerateRouteQuery($commentID: ID!) { comment(id: $commentID) { id + author { + allCommentsRejected + } ...ModerateCardContainer_comment } settings { From 0ba89d69c9d03d9f363d9f993745e60d5f96be57 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Tue, 11 Jul 2023 09:11:25 -0400 Subject: [PATCH 011/100] add error for cannot ban mod account with privileges --- .../UserBanPopoverContainer.css | 6 +++ .../UserBanPopoverContainer.tsx | 13 ++++- src/core/common/errors.ts | 6 +++ src/core/server/errors/index.ts | 8 ++++ src/core/server/services/users/users.ts | 47 ++++++++++++++----- src/locales/en-US/stream.ftl | 3 +- 6 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css index b5fdbca2f6..e111b71fbb 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.css @@ -65,3 +65,9 @@ .container { margin-top: var(--spacing-2); } + +.error { + color: var(--palette-error-500); + margin-top: var(--spacing-2); + font-weight: var(--font-weight-primary-semi-bold); +} diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx index 2987801577..ca793d0aad 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx @@ -65,6 +65,7 @@ const UserBanPopoverContainer: FunctionComponent = ({ const banUser = useMutation(BanUserMutation); const { localeBundles, rootURL } = useCoralContext(); const [spamBanConfirmation, setSpamBanConfirmation] = useState(""); + const [banError, setBanError] = useState(null); const linkModerateComment = useModerationLink({ commentID: comment.id }); const linkCommunitySection = rootURL + "/admin/community"; @@ -117,7 +118,14 @@ const UserBanPopoverContainer: FunctionComponent = ({ }); } } catch (e) { - // error + if (e.message === "CANNOT_BAN_ACCOUNT_WITH_MOD_PRIVILEGES") { + const errorMessage = getMessage( + localeBundles, + "comments-userBanPopover-moderator-ban-error", + "Cannot ban accounts with moderator privileges" + ); + setBanError(errorMessage); + } } if (siteBan) { onDismiss(); @@ -135,6 +143,7 @@ const UserBanPopoverContainer: FunctionComponent = ({ story.id, reject, viewerScoped, + setBanError, ]); if (view === "CONFIRM_BAN") { @@ -259,6 +268,8 @@ const UserBanPopoverContainer: FunctionComponent = ({ placeholder="" onChange={(e) => setSpamBanConfirmation(e.target.value)} /> + {/* TODO: Add icon */} + {banError &&
{banError}
} )} { + return siteIDs.includes(site); + } + ); + if (siteIDInModScopes) { + throw new CannotBanAccountWithModPrivilegesError(); + } + } } let user: Readonly; diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index a466c93b73..360ca81169 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -261,7 +261,7 @@ comments-userSpamBanPopover-header-description = Spam ban will comments-userSpamBanPopover-callout = Only for use on obvious spam accounts comments-userSpamBanPopover-description-list-banFromComments = Ban this account from the comments comments-userSpamBanPopover-description-list-rejectAllComments = Reject all comments written by this account -comments-userSpamBanPopover-confirmation = Type in "spam ban" to confirm +comments-userSpamBanPopover-confirmation = Type in "spam" to confirm comments-userBanPopover-title = Ban {$username}? comments-userSiteBanPopover-title = Ban {$username} from this site? @@ -271,6 +271,7 @@ comments-userBanPopover-description = This comment will also be rejected. comments-userBanPopover-cancel = Cancel comments-userBanPopover-ban = Ban +comments-userBanPopover-moderator-ban-error = Cannot ban accounts with moderator privileges comments-userBanPopover-moreContext = For more context, go to comments-userBanPopover-moderationView = Moderation view From 36cda701ef68ed6807a0dbc23e240f9a023130c7 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Tue, 11 Jul 2023 09:50:53 -0400 Subject: [PATCH 012/100] test spam ban details and confirmation view --- .../test/comments/stream/moderation.spec.tsx | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/core/client/stream/test/comments/stream/moderation.spec.tsx b/src/core/client/stream/test/comments/stream/moderation.spec.tsx index c3e154dc6e..5b41824e80 100644 --- a/src/core/client/stream/test/comments/stream/moderation.spec.tsx +++ b/src/core/client/stream/test/comments/stream/moderation.spec.tsx @@ -290,11 +290,34 @@ it("spam ban user", async () => { fireEvent.click(within(comment).getByRole("button", { name: "Spam ban" })); const banButtonDialog = await screen.findByRole("button", { name: "Ban" }); + + // Ban button should be disabled at first expect(banButtonDialog).toBeDisabled(); + // Spam ban option includes all details + expect( + screen.getByText("Ban this account from the comments") + ).toBeInTheDocument(); + expect( + screen.getByText("Reject all comments written by this account") + ).toBeInTheDocument(); + expect( + screen.getByText("Only for use on obvious spam accounts") + ).toBeInTheDocument(); + expect(screen.getByText("For more context, go to")).toBeInTheDocument(); + const moderationViewLink = screen.getByRole("link", { + name: "Moderation view", + }); + expect(moderationViewLink).toBeInTheDocument(); + expect(moderationViewLink).toHaveAttribute( + "href", + `/admin/moderate/comment/${firstComment.id}` + ); + const input = screen.getByTestId("userSpamBanConfirmation"); fireEvent.change(input, { target: { value: "spam" } }); + // After "spam" is typed in, Ban button should be enabled await waitFor(() => { expect(banButtonDialog).toBeEnabled(); }); @@ -303,6 +326,31 @@ it("spam ban user", async () => { expect( await within(tabPane).findByText("You have rejected this comment.") ).toBeVisible(); + + // spam ban confirmation should be shown + expect(screen.getByText("Markus is now banned")).toBeInTheDocument(); + expect( + screen.getByText( + "This account can no longer comment, use reactions, or report comments" + ) + ).toBeInTheDocument(); + expect( + screen.getByText("All comments by this account have been rejected") + ).toBeInTheDocument(); + expect( + screen.getByText( + "You can still review this account's history by searching in Coral's" + ) + ).toBeInTheDocument(); + const communityLink = screen.getByRole("link", { name: "Community section" }); + expect(communityLink).toBeInTheDocument(); + expect(communityLink).toHaveAttribute("href", "/admin/community"); + const closeButton = screen.getByRole("button", { name: "Close" }); + fireEvent.click(closeButton); + + // spam ban comfirmation should no longer be shown after Close button clicked + expect(screen.queryByText("Markus is now banned")).toBeNull(); + expect(screen.queryByText("You have rejected this comment.")).toBeDefined(); }); it("cancel ban user", async () => { @@ -390,13 +438,14 @@ it("site moderator can site ban commenter", async () => { await act(async () => { userEvent.click(caretButton); }); - // Site moderator has Site Ban option but not Ban User option + // Site moderator has Site Ban option const siteBanButton = await within(comment).findByRole("button", { name: "Site ban", }); await waitFor(() => { expect(siteBanButton).not.toBeDisabled(); }); + // Site moderator also has Spam ban option expect( within(comment).queryByRole("button", { name: "Spam ban" }) ).toBeInTheDocument(); From e62f46ee3383e717c12e32bb1e57c52f83a322fe Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Tue, 11 Jul 2023 09:58:27 -0400 Subject: [PATCH 013/100] add test for site mod can spam ban commenter --- .../test/comments/stream/moderation.spec.tsx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/core/client/stream/test/comments/stream/moderation.spec.tsx b/src/core/client/stream/test/comments/stream/moderation.spec.tsx index 5b41824e80..a94ad1356e 100644 --- a/src/core/client/stream/test/comments/stream/moderation.spec.tsx +++ b/src/core/client/stream/test/comments/stream/moderation.spec.tsx @@ -471,6 +471,85 @@ it("site moderator can site ban commenter", async () => { ); }); +it("site moderator can spam ban commenter", async () => { + await act(async () => { + await createTestRenderer({ + resolvers: createResolversStub({ + Query: { + user: ({ variables }) => { + expectAndFail(variables.id).toBe(firstComment.author!.id); + return firstComment.author!; + }, + settings: () => settingsWithMultisite, + viewer: () => moderators[1], + }, + Mutation: { + banUser: ({ variables }) => { + expectAndFail(variables).toMatchObject({ + userID: firstComment.author!.id, + rejectExistingComments: true, + siteIDs: ["site-id"], + }); + return { + user: pureMerge(firstComment.author, { + status: { + ban: { + active: true, + }, + }, + }), + }; + }, + rejectComment: ({ variables }) => { + expectAndFail(variables).toMatchObject({ + commentID: firstComment.id, + commentRevisionID: firstComment.revision!.id, + }); + return { + comment: pureMerge(firstComment, { + status: GQLCOMMENT_STATUS.REJECTED, + }), + }; + }, + }, + }), + }); + }); + const tabPane = await screen.findByTestId("current-tab-pane"); + + const comment = screen.getByTestId(`comment-${firstComment.id}`); + const caretButton = within(comment).getByLabelText("Moderate"); + await act(async () => { + userEvent.click(caretButton); + }); + // Site moderator has Spam ban option + const spamBanButton = await within(comment).findByRole("button", { + name: "Spam ban", + }); + fireEvent.click(spamBanButton); + + const input = screen.getByTestId("userSpamBanConfirmation"); + fireEvent.change(input, { target: { value: "spam" } }); + + const banButtonDialog = screen.getByRole("button", { name: "Ban" }); + fireEvent.click(banButtonDialog); + await waitFor(() => { + expect( + within(tabPane).getByText("You have rejected this comment.") + ).toBeVisible(); + }); + + expect(screen.getByText("Markus is now banned")).toBeInTheDocument(); + + const link = await within(tabPane).findByRole("link", { + name: "Go to moderate to review this decision", + }); + expect(link).toHaveAttribute( + "href", + `/admin/moderate/comment/${firstComment.id}` + ); +}); + it("can copy comment embed code", async () => { const jsdomPrompt = window.prompt; window.prompt = jest.fn(() => null); From d155a82c6f56df88184e3afad1b150b070a33df6 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Tue, 11 Jul 2023 11:18:16 -0400 Subject: [PATCH 014/100] add test for mod cannot ban mod with site privileges error --- .../test/comments/stream/moderation.spec.tsx | 71 ++++++++++++++++++- src/core/client/stream/test/fixtures.ts | 57 +++++++++------ 2 files changed, 104 insertions(+), 24 deletions(-) diff --git a/src/core/client/stream/test/comments/stream/moderation.spec.tsx b/src/core/client/stream/test/comments/stream/moderation.spec.tsx index a94ad1356e..b5bca39bf9 100644 --- a/src/core/client/stream/test/comments/stream/moderation.spec.tsx +++ b/src/core/client/stream/test/comments/stream/moderation.spec.tsx @@ -7,7 +7,9 @@ import { } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { ERROR_CODES } from "coral-common/errors"; import { pureMerge } from "coral-common/utils"; +import { InvalidRequestError } from "coral-framework/lib/errors"; import { GQLCOMMENT_STATUS, GQLResolver } from "coral-framework/schema"; import { createResolversStub, @@ -37,10 +39,12 @@ function createStory() { const story = createStory(); const firstComment = story.comments.edges[0].node; +const thirdComment = story.comments.edges[2].node; const viewer = moderators[0]; async function createTestRenderer( - params: CreateTestRendererParams = {} + params: CreateTestRendererParams = {}, + options: { muteNetworkErrors?: boolean } = {} ) { const { context } = createContext({ ...params, @@ -54,6 +58,7 @@ async function createTestRenderer( }), params.resolvers ), + muteNetworkErrors: options.muteNetworkErrors, initLocalState: (localRecord, source, environment) => { localRecord.setValue(story.id, "storyID"); @@ -550,6 +555,70 @@ it("site moderator can spam ban commenter", async () => { ); }); +it("site moderator cannot ban another moderator with site privileges", async () => { + await act(async () => { + await createTestRenderer( + { + resolvers: createResolversStub({ + Query: { + user: () => { + return moderators[2]; + }, + settings: () => settingsWithMultisite, + viewer: () => moderators[1], + }, + Mutation: { + banUser: ({ variables }) => { + expectAndFail(variables).toMatchObject({ + userID: thirdComment.author?.id, + rejectExistingComments: true, + siteIDs: ["site-id"], + }); + throw new InvalidRequestError({ + code: ERROR_CODES.CANNOT_BAN_ACCOUNT_WITH_MOD_PRIVILEGES, + param: "input.body", + traceID: "traceID", + }); + }, + rejectComment: ({ variables }) => { + expectAndFail(variables).toMatchObject({ + commentID: thirdComment.id, + commentRevisionID: thirdComment.revision?.id, + }); + return { + comment: pureMerge(thirdComment, { + status: GQLCOMMENT_STATUS.REJECTED, + }), + }; + }, + }, + }), + }, + { muteNetworkErrors: true } + ); + }); + + const comment = screen.getByTestId(`comment-${thirdComment.id}`); + const caretButton = within(comment).getByLabelText("Moderate"); + await act(async () => { + userEvent.click(caretButton); + }); + // Site moderator has Spam ban option + const spamBanButton = await within(comment).findByRole("button", { + name: "Spam ban", + }); + fireEvent.click(spamBanButton); + + const input = screen.getByTestId("userSpamBanConfirmation"); + fireEvent.change(input, { target: { value: "spam" } }); + + const banButtonDialog = screen.getByRole("button", { name: "Ban" }); + fireEvent.click(banButtonDialog); + expect( + await screen.findByText("Cannot ban accounts with moderator privileges") + ).toBeInTheDocument(); +}); + it("can copy comment embed code", async () => { const jsdomPrompt = window.prompt; window.prompt = jest.fn(() => null); diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index a354840b1b..99ca684aff 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -442,6 +442,38 @@ export const baseComment = createFixture({ }, }); +export const moderators = createFixtures( + [ + { + id: "me-as-moderator", + username: "Moderator", + role: GQLUSER_ROLE.MODERATOR, + ignoreable: false, + }, + { + id: "site-moderator", + username: "Site Moderator", + role: GQLUSER_ROLE.MODERATOR, + ignoreable: false, + moderationScopes: { + scoped: true, + sites: [site[0]], + }, + }, + { + id: "site-moderator-2", + username: "Site Moderator 2", + role: GQLUSER_ROLE.MODERATOR, + ignoreable: false, + moderationScopes: { + scoped: true, + sites: [site[0]], + }, + }, + ], + baseUser +); + export const comments = denormalizeComments( createFixtures( [ @@ -472,7 +504,7 @@ export const comments = denormalizeComments( }, { id: "comment-5", - author: commenters[2], + author: moderators[2], body: "Comment Body 5", }, { @@ -735,28 +767,6 @@ export const commentWithDeepestReplies = denormalizeComment( }) ); -export const moderators = createFixtures( - [ - { - id: "me-as-moderator", - username: "Moderator", - role: GQLUSER_ROLE.MODERATOR, - ignoreable: false, - }, - { - id: "site-moderator", - username: "Site Moderator", - role: GQLUSER_ROLE.MODERATOR, - ignoreable: false, - moderationScopes: { - scoped: true, - sites: [site[0]], - }, - }, - ], - baseUser -); - export const commentFromModerator = denormalizeComment( createFixture( { @@ -797,6 +807,7 @@ export const stories = denormalizeStories( edges: [ { node: comments[0], cursor: comments[0].createdAt }, { node: comments[1], cursor: comments[1].createdAt }, + { node: comments[5], cursor: comments[5].createdAt }, ], }, }, From 68884da501de5749550b2e58d729814bebc9d522 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Tue, 11 Jul 2023 11:23:47 -0400 Subject: [PATCH 015/100] use variable for spam confirm text --- .../UserBanPopover/UserBanPopoverContainer.tsx | 16 +++++++++++++--- src/locales/en-US/stream.ftl | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx index ca793d0aad..2a3f771e01 100644 --- a/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/UserBanPopover/UserBanPopoverContainer.tsx @@ -56,6 +56,7 @@ const UserBanPopoverContainer: FunctionComponent = ({ `); const setSpamBanned = useMutation(SetSpamBanned); const siteBan = view === "SITE_BAN"; + const spamConfirmationText = "spam"; const user = comment.author!; const viewerScoped = @@ -258,8 +259,13 @@ const UserBanPopoverContainer: FunctionComponent = ({ Only for use on obvious spam accounts - -
Type in "spam" to confirm
+ +
+ Type in "{spamConfirmationText}" to confirm +
= ({
@@ -265,10 +286,10 @@ const UserBanPopoverContainer: FunctionComponent = ({
- Type in "{spamConfirmationText}" to confirm + Type in "{spamBanConfirmationText}" to confirm
= ({ className={styles.confirmationInput} type="text" placeholder="" - onChange={(e) => setSpamBanConfirmation(e.target.value)} + onChange={onSpamBanConfirmationTextInputChange} /> {banError && (
@@ -304,14 +325,7 @@ const UserBanPopoverContainer: FunctionComponent = ({