+import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
+import { RouteComponentProps } from "inferno-router/dist/Route";
import {
AdminPurgeCommentView,
AdminPurgeCommunityView,
GetCommunityResponse,
GetModlog,
GetModlogResponse,
- GetSiteResponse,
+ GetPersonDetails,
+ GetPersonDetailsResponse,
ModAddCommunityView,
ModAddView,
ModBanFromCommunityView,
import {
amAdmin,
amMod,
- choicesConfig,
+ Choice,
debounce,
fetchLimit,
fetchUsers,
+ getIdFromString,
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+ getUpdatedSearchId,
isBrowser,
myAuth,
+ personToChoice,
+ QueryParams,
setIsoData,
toast,
wsClient,
import { Spinner } from "./common/icon";
import { MomentTime } from "./common/moment-time";
import { Paginator } from "./common/paginator";
+import { SearchableSelect } from "./common/searchable-select";
import { CommunityLink } from "./community/community-link";
import { PersonListing } from "./person/person-listing";
-type ModlogType = {
+
+type FilterType = "mod" | "user";
+
+type View =
+ | ModRemovePostView
+ | ModLockPostView
+ | ModFeaturePostView
+ | ModRemoveCommentView
+ | ModRemoveCommunityView
+ | ModBanFromCommunityView
+ | ModBanView
+ | ModAddCommunityView
+ | ModTransferCommunityView
+ | ModAddView
+ | AdminPurgePersonView
+ | AdminPurgeCommunityView
+ | AdminPurgePostView
+ | AdminPurgeCommentView;
+
+interface ModlogType {
id: number;
type_: ModlogActionType;
moderator?: PersonSafe;
- view:
- | ModRemovePostView
- | ModLockPostView
- | ModFeaturePostView
- | ModRemoveCommentView
- | ModRemoveCommunityView
- | ModBanFromCommunityView
- | ModBanView
- | ModAddCommunityView
- | ModTransferCommunityView
- | ModAddView
- | AdminPurgePersonView
- | AdminPurgeCommunityView
- | AdminPurgePostView
- | AdminPurgeCommentView;
+ view: View;
when_: string;
-};
-var Choices: any;
-if (isBrowser()) {
- Choices = require("choices.js");
}
+const getModlogQueryParams = () =>
+ getQueryParams<ModlogProps>({
+ actionType: getActionFromString,
+ modId: getIdFromString,
+ userId: getIdFromString,
+ page: getPageFromString,
+ });
+
interface ModlogState {
res?: GetModlogResponse;
- communityId?: number;
communityMods?: CommunityModeratorView[];
communityName?: string;
- page: number;
- siteRes: GetSiteResponse;
- loading: boolean;
- filter_action: ModlogActionType;
- filter_user?: number;
- filter_mod?: number;
+ loadingModlog: boolean;
+ loadingModSearch: boolean;
+ loadingUserSearch: boolean;
+ modSearchOptions: Choice[];
+ userSearchOptions: Choice[];
}
-export class Modlog extends Component<any, ModlogState> {
- private isoData = setIsoData(this.context);
- private subscription?: Subscription;
- private userChoices: any;
- private modChoices: any;
- state: ModlogState = {
- page: 1,
- loading: true,
- siteRes: this.isoData.site_res,
- filter_action: ModlogActionType.All,
- };
+interface ModlogProps {
+ page: number;
+ userId?: number | null;
+ modId?: number | null;
+ actionType: ModlogActionType;
+}
- constructor(props: any, context: any) {
- super(props, context);
- this.handlePageChange = this.handlePageChange.bind(this);
+const getActionFromString = (action?: string) =>
+ action
+ ? ModlogActionType[action] ?? ModlogActionType.All
+ : ModlogActionType.All;
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+const getModlogActionMapper =
+ (
+ actionType: ModlogActionType,
+ getAction: (view: View) => { id: number; when_: string }
+ ) =>
+ (view: View & { moderator?: PersonSafe; admin?: PersonSafe }): ModlogType => {
+ const { id, when_ } = getAction(view);
- this.state = {
- ...this.state,
- communityId: this.props.match.params.community_id
- ? Number(this.props.match.params.community_id)
- : undefined,
+ return {
+ id,
+ type_: actionType,
+ view,
+ when_,
+ moderator: view.moderator ?? view.admin,
};
+ };
- // Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
- this.state = {
- ...this.state,
- res: this.isoData.routeData[0] as GetModlogResponse,
- };
+function buildCombined({
+ removed_comments,
+ locked_posts,
+ featured_posts,
+ removed_communities,
+ removed_posts,
+ added,
+ added_to_community,
+ admin_purged_comments,
+ admin_purged_communities,
+ admin_purged_persons,
+ admin_purged_posts,
+ banned,
+ banned_from_community,
+ transferred_to_community,
+}: GetModlogResponse): ModlogType[] {
+ const combined = removed_posts
+ .map(
+ getModlogActionMapper(
+ ModlogActionType.ModRemovePost,
+ ({ mod_remove_post }: ModRemovePostView) => mod_remove_post
+ )
+ )
+ .concat(
+ locked_posts.map(
+ getModlogActionMapper(
+ ModlogActionType.ModLockPost,
+ ({ mod_lock_post }: ModLockPostView) => mod_lock_post
+ )
+ )
+ )
+ .concat(
+ featured_posts.map(
+ getModlogActionMapper(
+ ModlogActionType.ModFeaturePost,
+ ({ mod_feature_post }: ModFeaturePostView) => mod_feature_post
+ )
+ )
+ )
+ .concat(
+ removed_comments.map(
+ getModlogActionMapper(
+ ModlogActionType.ModRemoveComment,
+ ({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment
+ )
+ )
+ )
+ .concat(
+ removed_communities.map(
+ getModlogActionMapper(
+ ModlogActionType.ModRemoveCommunity,
+ ({ mod_remove_community }: ModRemoveCommunityView) =>
+ mod_remove_community
+ )
+ )
+ )
+ .concat(
+ banned_from_community.map(
+ getModlogActionMapper(
+ ModlogActionType.ModBanFromCommunity,
+ ({ mod_ban_from_community }: ModBanFromCommunityView) =>
+ mod_ban_from_community
+ )
+ )
+ )
+ .concat(
+ added_to_community.map(
+ getModlogActionMapper(
+ ModlogActionType.ModAddCommunity,
+ ({ mod_add_community }: ModAddCommunityView) => mod_add_community
+ )
+ )
+ )
+ .concat(
+ transferred_to_community.map(
+ getModlogActionMapper(
+ ModlogActionType.ModTransferCommunity,
+ ({ mod_transfer_community }: ModTransferCommunityView) =>
+ mod_transfer_community
+ )
+ )
+ )
+ .concat(
+ added.map(
+ getModlogActionMapper(
+ ModlogActionType.ModAdd,
+ ({ mod_add }: ModAddView) => mod_add
+ )
+ )
+ )
+ .concat(
+ banned.map(
+ getModlogActionMapper(
+ ModlogActionType.ModBan,
+ ({ mod_ban }: ModBanView) => mod_ban
+ )
+ )
+ )
+ .concat(
+ admin_purged_persons.map(
+ getModlogActionMapper(
+ ModlogActionType.AdminPurgePerson,
+ ({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person
+ )
+ )
+ )
+ .concat(
+ admin_purged_communities.map(
+ getModlogActionMapper(
+ ModlogActionType.AdminPurgeCommunity,
+ ({ admin_purge_community }: AdminPurgeCommunityView) =>
+ admin_purge_community
+ )
+ )
+ )
+ .concat(
+ admin_purged_posts.map(
+ getModlogActionMapper(
+ ModlogActionType.AdminPurgePost,
+ ({ admin_purge_post }: AdminPurgePostView) => admin_purge_post
+ )
+ )
+ )
+ .concat(
+ admin_purged_comments.map(
+ getModlogActionMapper(
+ ModlogActionType.AdminPurgeComment,
+ ({ admin_purge_comment }: AdminPurgeCommentView) =>
+ admin_purge_comment
+ )
+ )
+ );
- let communityRes: GetCommunityResponse | undefined =
- this.isoData.routeData[1];
+ // Sort them by time
+ combined.sort((a, b) => b.when_.localeCompare(a.when_));
- // Getting the moderators
- this.state = {
- ...this.state,
- communityMods: communityRes?.moderators,
- };
+ return combined;
+}
- this.state = { ...this.state, loading: false };
- } else {
- this.refetch();
+function renderModlogType({ type_, view }: ModlogType) {
+ switch (type_) {
+ case ModlogActionType.ModRemovePost: {
+ const mrpv = view as ModRemovePostView;
+ const {
+ mod_remove_post: { reason, removed },
+ post: { name, id },
+ } = mrpv;
+
+ return (
+ <>
+ <span>{removed ? "Removed " : "Restored "}</span>
+ <span>
+ Post <Link to={`/post/${id}`}>{name}</Link>
+ </span>
+ {reason && (
+ <span>
+ <div>reason: {reason}</div>
+ </span>
+ )}
+ </>
+ );
}
- }
- componentDidMount() {
- this.setupUserFilter();
- this.setupModFilter();
- }
+ case ModlogActionType.ModLockPost: {
+ const {
+ mod_lock_post: { locked },
+ post: { id, name },
+ } = view as ModLockPostView;
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ return (
+ <>
+ <span>{locked ? "Locked " : "Unlocked "}</span>
+ <span>
+ Post <Link to={`/post/${id}`}>{name}</Link>
+ </span>
+ </>
+ );
}
- }
-
- buildCombined(res: GetModlogResponse): ModlogType[] {
- let removed_posts: ModlogType[] = res.removed_posts.map(r => ({
- id: r.mod_remove_post.id,
- type_: ModlogActionType.ModRemovePost,
- view: r,
- moderator: r.moderator,
- when_: r.mod_remove_post.when_,
- }));
-
- let locked_posts: ModlogType[] = res.locked_posts.map(r => ({
- id: r.mod_lock_post.id,
- type_: ModlogActionType.ModLockPost,
- view: r,
- moderator: r.moderator,
- when_: r.mod_lock_post.when_,
- }));
-
- let featured_posts: ModlogType[] = res.featured_posts.map(r => ({
- id: r.mod_feature_post.id,
- type_: ModlogActionType.ModFeaturePost,
- view: r,
- moderator: r.moderator,
- when_: r.mod_feature_post.when_,
- }));
-
- let removed_comments: ModlogType[] = res.removed_comments.map(r => ({
- id: r.mod_remove_comment.id,
- type_: ModlogActionType.ModRemoveComment,
- view: r,
- moderator: r.moderator,
- when_: r.mod_remove_comment.when_,
- }));
-
- let removed_communities: ModlogType[] = res.removed_communities.map(r => ({
- id: r.mod_remove_community.id,
- type_: ModlogActionType.ModRemoveCommunity,
- view: r,
- moderator: r.moderator,
- when_: r.mod_remove_community.when_,
- }));
-
- let banned_from_community: ModlogType[] = res.banned_from_community.map(
- r => ({
- id: r.mod_ban_from_community.id,
- type_: ModlogActionType.ModBanFromCommunity,
- view: r,
- moderator: r.moderator,
- when_: r.mod_ban_from_community.when_,
- })
- );
- let added_to_community: ModlogType[] = res.added_to_community.map(r => ({
- id: r.mod_add_community.id,
- type_: ModlogActionType.ModAddCommunity,
- view: r,
- moderator: r.moderator,
- when_: r.mod_add_community.when_,
- }));
-
- let transferred_to_community: ModlogType[] =
- res.transferred_to_community.map(r => ({
- id: r.mod_transfer_community.id,
- type_: ModlogActionType.ModTransferCommunity,
- view: r,
- moderator: r.moderator,
- when_: r.mod_transfer_community.when_,
- }));
-
- let added: ModlogType[] = res.added.map(r => ({
- id: r.mod_add.id,
- type_: ModlogActionType.ModAdd,
- view: r,
- moderator: r.moderator,
- when_: r.mod_add.when_,
- }));
-
- let banned: ModlogType[] = res.banned.map(r => ({
- id: r.mod_ban.id,
- type_: ModlogActionType.ModBan,
- view: r,
- moderator: r.moderator,
- when_: r.mod_ban.when_,
- }));
-
- let purged_persons: ModlogType[] = res.admin_purged_persons.map(r => ({
- id: r.admin_purge_person.id,
- type_: ModlogActionType.AdminPurgePerson,
- view: r,
- moderator: r.admin,
- when_: r.admin_purge_person.when_,
- }));
-
- let purged_communities: ModlogType[] = res.admin_purged_communities.map(
- r => ({
- id: r.admin_purge_community.id,
- type_: ModlogActionType.AdminPurgeCommunity,
- view: r,
- moderator: r.admin,
- when_: r.admin_purge_community.when_,
- })
- );
+ case ModlogActionType.ModFeaturePost: {
+ const {
+ mod_feature_post: { featured, is_featured_community },
+ post: { id, name },
+ } = view as ModFeaturePostView;
- let purged_posts: ModlogType[] = res.admin_purged_posts.map(r => ({
- id: r.admin_purge_post.id,
- type_: ModlogActionType.AdminPurgePost,
- view: r,
- moderator: r.admin,
- when_: r.admin_purge_post.when_,
- }));
-
- let purged_comments: ModlogType[] = res.admin_purged_comments.map(r => ({
- id: r.admin_purge_comment.id,
- type_: ModlogActionType.AdminPurgeComment,
- view: r,
- moderator: r.admin,
- when_: r.admin_purge_comment.when_,
- }));
-
- let combined: ModlogType[] = [];
-
- combined.push(...removed_posts);
- combined.push(...locked_posts);
- combined.push(...featured_posts);
- combined.push(...removed_comments);
- combined.push(...removed_communities);
- combined.push(...banned_from_community);
- combined.push(...added_to_community);
- combined.push(...transferred_to_community);
- combined.push(...added);
- combined.push(...banned);
- combined.push(...purged_persons);
- combined.push(...purged_communities);
- combined.push(...purged_posts);
- combined.push(...purged_comments);
-
- // Sort them by time
- combined.sort((a, b) => b.when_.localeCompare(a.when_));
-
- return combined;
- }
+ return (
+ <>
+ <span>{featured ? "Featured " : "Unfeatured "}</span>
+ <span>
+ Post <Link to={`/post/${id}`}>{name}</Link>
+ </span>
+ <span>{is_featured_community ? " In Community" : " In Local"}</span>
+ </>
+ );
+ }
+ case ModlogActionType.ModRemoveComment: {
+ const mrc = view as ModRemoveCommentView;
+ const {
+ mod_remove_comment: { reason, removed },
+ comment: { id, content },
+ commenter,
+ } = mrc;
- renderModlogType(i: ModlogType) {
- switch (i.type_) {
- case ModlogActionType.ModRemovePost: {
- let mrpv = i.view as ModRemovePostView;
- let reason = mrpv.mod_remove_post.reason;
- return (
- <>
- <span>
- {mrpv.mod_remove_post.removed ? "Removed " : "Restored "}
- </span>
- <span>
- Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link>
- </span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- </>
- );
- }
- case ModlogActionType.ModLockPost: {
- let mlpv = i.view as ModLockPostView;
- return (
- <>
- <span>{mlpv.mod_lock_post.locked ? "Locked " : "Unlocked "}</span>
- <span>
- Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
- </span>
- </>
- );
- }
- case ModlogActionType.ModFeaturePost: {
- let mspv = i.view as ModFeaturePostView;
- return (
- <>
- <span>
- {mspv.mod_feature_post.featured ? "Featured " : "Unfeatured "}
- </span>
- <span>
- Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
- </span>
- <span>
- {mspv.mod_feature_post.is_featured_community
- ? " In Community"
- : " In Local"}
- </span>
- </>
- );
- }
- case ModlogActionType.ModRemoveComment: {
- let mrc = i.view as ModRemoveCommentView;
- let reason = mrc.mod_remove_comment.reason;
- return (
- <>
+ return (
+ <>
+ <span>{removed ? "Removed " : "Restored "}</span>
+ <span>
+ Comment <Link to={`/comment/${id}`}>{content}</Link>
+ </span>
+ <span>
+ {" "}
+ by <PersonListing person={commenter} />
+ </span>
+ {reason && (
<span>
- {mrc.mod_remove_comment.removed ? "Removed " : "Restored "}
- </span>
- <span>
- Comment{" "}
- <Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
- {mrc.comment.content}
- </Link>
- </span>
- <span>
- {" "}
- by <PersonListing person={mrc.commenter} />
- </span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- </>
- );
- }
- case ModlogActionType.ModRemoveCommunity: {
- let mrco = i.view as ModRemoveCommunityView;
- let reason = mrco.mod_remove_community.reason;
- let expires = mrco.mod_remove_community.expires;
- return (
- <>
- <span>
- {mrco.mod_remove_community.removed ? "Removed " : "Restored "}
- </span>
- <span>
- Community <CommunityLink community={mrco.community} />
- </span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- {expires && (
- <span>
- <div>expires: {moment.utc(expires).fromNow()}</div>
- </span>
- )}
- </>
- );
- }
- case ModlogActionType.ModBanFromCommunity: {
- let mbfc = i.view as ModBanFromCommunityView;
- let reason = mbfc.mod_ban_from_community.reason;
- let expires = mbfc.mod_ban_from_community.expires;
- return (
- <>
- <span>
- {mbfc.mod_ban_from_community.banned ? "Banned " : "Unbanned "}{" "}
+ <div>reason: {reason}</div>
</span>
+ )}
+ </>
+ );
+ }
+
+ case ModlogActionType.ModRemoveCommunity: {
+ const mrco = view as ModRemoveCommunityView;
+ const {
+ mod_remove_community: { reason, expires, removed },
+ community,
+ } = mrco;
+
+ return (
+ <>
+ <span>{removed ? "Removed " : "Restored "}</span>
+ <span>
+ Community <CommunityLink community={community} />
+ </span>
+ {reason && (
<span>
- <PersonListing person={mbfc.banned_person} />
+ <div>reason: {reason}</div>
</span>
- <span> from the community </span>
+ )}
+ {expires && (
<span>
- <CommunityLink community={mbfc.community} />
+ <div>expires: {moment.utc(expires).fromNow()}</div>
</span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- {expires && (
- <span>
- <div>expires: {moment.utc(expires).fromNow()}</div>
- </span>
- )}
- </>
- );
- }
- case ModlogActionType.ModAddCommunity: {
- let mac = i.view as ModAddCommunityView;
- return (
- <>
+ )}
+ </>
+ );
+ }
+
+ case ModlogActionType.ModBanFromCommunity: {
+ const mbfc = view as ModBanFromCommunityView;
+ const {
+ mod_ban_from_community: { reason, expires, banned },
+ banned_person,
+ community,
+ } = mbfc;
+
+ return (
+ <>
+ <span>{banned ? "Banned " : "Unbanned "}</span>
+ <span>
+ <PersonListing person={banned_person} />
+ </span>
+ <span> from the community </span>
+ <span>
+ <CommunityLink community={community} />
+ </span>
+ {reason && (
<span>
- {mac.mod_add_community.removed ? "Removed " : "Appointed "}{" "}
+ <div>reason: {reason}</div>
</span>
+ )}
+ {expires && (
<span>
- <PersonListing person={mac.modded_person} />
+ <div>expires: {moment.utc(expires).fromNow()}</div>
</span>
- <span> as a mod to the community </span>
+ )}
+ </>
+ );
+ }
+
+ case ModlogActionType.ModAddCommunity: {
+ const {
+ mod_add_community: { removed },
+ modded_person,
+ community,
+ } = view as ModAddCommunityView;
+
+ return (
+ <>
+ <span>{removed ? "Removed " : "Appointed "}</span>
+ <span>
+ <PersonListing person={modded_person} />
+ </span>
+ <span> as a mod to the community </span>
+ <span>
+ <CommunityLink community={community} />
+ </span>
+ </>
+ );
+ }
+
+ case ModlogActionType.ModTransferCommunity: {
+ const {
+ mod_transfer_community: { removed },
+ community,
+ modded_person,
+ } = view as ModTransferCommunityView;
+
+ return (
+ <>
+ <span>{removed ? "Removed " : "Transferred "}</span>
+ <span>
+ <CommunityLink community={community} />
+ </span>
+ <span> to </span>
+ <span>
+ <PersonListing person={modded_person} />
+ </span>
+ </>
+ );
+ }
+
+ case ModlogActionType.ModBan: {
+ const {
+ mod_ban: { reason, expires, banned },
+ banned_person,
+ } = view as ModBanView;
+
+ return (
+ <>
+ <span>{banned ? "Banned " : "Unbanned "}</span>
+ <span>
+ <PersonListing person={banned_person} />
+ </span>
+ {reason && (
<span>
- <CommunityLink community={mac.community} />
+ <div>reason: {reason}</div>
</span>
- </>
- );
- }
- case ModlogActionType.ModTransferCommunity: {
- let mtc = i.view as ModTransferCommunityView;
- return (
- <>
+ )}
+ {expires && (
<span>
- {mtc.mod_transfer_community.removed ? "Removed " : "Transferred "}{" "}
+ <div>expires: {moment.utc(expires).fromNow()}</div>
</span>
+ )}
+ </>
+ );
+ }
+
+ case ModlogActionType.ModAdd: {
+ const {
+ mod_add: { removed },
+ modded_person,
+ } = view as ModAddView;
+
+ return (
+ <>
+ <span>{removed ? "Removed " : "Appointed "}</span>
+ <span>
+ <PersonListing person={modded_person} />
+ </span>
+ <span> as an admin </span>
+ </>
+ );
+ }
+ case ModlogActionType.AdminPurgePerson: {
+ const {
+ admin_purge_person: { reason },
+ } = view as AdminPurgePersonView;
+
+ return (
+ <>
+ <span>Purged a Person</span>
+ {reason && (
<span>
- <CommunityLink community={mtc.community} />
+ <div>reason: {reason}</div>
</span>
- <span> to </span>
+ )}
+ </>
+ );
+ }
+
+ case ModlogActionType.AdminPurgeCommunity: {
+ const {
+ admin_purge_community: { reason },
+ } = view as AdminPurgeCommunityView;
+
+ return (
+ <>
+ <span>Purged a Community</span>
+ {reason && (
<span>
- <PersonListing person={mtc.modded_person} />
+ <div>reason: {reason}</div>
</span>
- </>
- );
- }
- case ModlogActionType.ModBan: {
- let mb = i.view as ModBanView;
- let reason = mb.mod_ban.reason;
- let expires = mb.mod_ban.expires;
- return (
- <>
- <span>{mb.mod_ban.banned ? "Banned " : "Unbanned "} </span>
+ )}
+ </>
+ );
+ }
+
+ case ModlogActionType.AdminPurgePost: {
+ const {
+ admin_purge_post: { reason },
+ community,
+ } = view as AdminPurgePostView;
+
+ return (
+ <>
+ <span>Purged a Post from from </span>
+ <CommunityLink community={community} />
+ {reason && (
<span>
- <PersonListing person={mb.banned_person} />
+ <div>reason: {reason}</div>
</span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- {expires && (
- <span>
- <div>expires: {moment.utc(expires).fromNow()}</div>
- </span>
- )}
- </>
- );
- }
- case ModlogActionType.ModAdd: {
- let ma = i.view as ModAddView;
- return (
- <>
- <span>{ma.mod_add.removed ? "Removed " : "Appointed "} </span>
+ )}
+ </>
+ );
+ }
+
+ case ModlogActionType.AdminPurgeComment: {
+ const {
+ admin_purge_comment: { reason },
+ post: { id, name },
+ } = view as AdminPurgeCommentView;
+
+ return (
+ <>
+ <span>
+ Purged a Comment from <Link to={`/post/${id}`}>{name}</Link>
+ </span>
+ {reason && (
<span>
- <PersonListing person={ma.modded_person} />
+ <div>reason: {reason}</div>
</span>
- <span> as an admin </span>
- </>
- );
- }
- case ModlogActionType.AdminPurgePerson: {
- let ap = i.view as AdminPurgePersonView;
- let reason = ap.admin_purge_person.reason;
- return (
- <>
- <span>Purged a Person</span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- </>
- );
- }
- case ModlogActionType.AdminPurgeCommunity: {
- let ap = i.view as AdminPurgeCommunityView;
- let reason = ap.admin_purge_community.reason;
- return (
- <>
- <span>Purged a Community</span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- </>
- );
- }
- case ModlogActionType.AdminPurgePost: {
- let ap = i.view as AdminPurgePostView;
- let reason = ap.admin_purge_post.reason;
- return (
- <>
- <span>Purged a Post from from </span>
- <CommunityLink community={ap.community} />
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- </>
- );
+ )}
+ </>
+ );
+ }
+
+ default:
+ return <></>;
+ }
+}
+
+const Filter = ({
+ filterType,
+ onChange,
+ value,
+ onSearch,
+ options,
+ loading,
+}: {
+ filterType: FilterType;
+ onChange: (option: Choice) => void;
+ value?: number | null;
+ onSearch: (text: string) => void;
+ options: Choice[];
+ loading: boolean;
+}) => (
+ <div className="col-sm-6 form-group">
+ <label className="col-form-label" htmlFor={`filter-${filterType}`}>
+ {i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
+ </label>
+ <SearchableSelect
+ id={`filter-${filterType}`}
+ value={value ?? 0}
+ options={[
+ {
+ label: i18n.t("all"),
+ value: "0",
+ },
+ ].concat(options)}
+ onChange={onChange}
+ onSearch={onSearch}
+ loading={loading}
+ />
+ </div>
+);
+
+async function createNewOptions({
+ id,
+ oldOptions,
+ text,
+}: {
+ id?: number | null;
+ oldOptions: Choice[];
+ text: string;
+}) {
+ const newOptions: Choice[] = [];
+
+ if (id) {
+ const selectedUser = oldOptions.find(
+ ({ value }) => value === id.toString()
+ );
+
+ if (selectedUser) {
+ newOptions.push(selectedUser);
+ }
+ }
+
+ if (text.length > 0) {
+ newOptions.push(
+ ...(await fetchUsers(text)).users
+ .slice(0, fetchLimit)
+ .map<Choice>(personToChoice)
+ );
+ }
+
+ return newOptions;
+}
+
+export class Modlog extends Component<
+ RouteComponentProps<{ communityId?: string }>,
+ ModlogState
+> {
+ private isoData = setIsoData(this.context);
+ private subscription?: Subscription;
+
+ state: ModlogState = {
+ loadingModlog: true,
+ loadingModSearch: false,
+ loadingUserSearch: false,
+ userSearchOptions: [],
+ modSearchOptions: [],
+ };
+
+ constructor(
+ props: RouteComponentProps<{ communityId?: string }>,
+ context: any
+ ) {
+ super(props, context);
+ this.handlePageChange = this.handlePageChange.bind(this);
+ this.handleUserChange = this.handleUserChange.bind(this);
+ this.handleModChange = this.handleModChange.bind(this);
+
+ this.parseMessage = this.parseMessage.bind(this);
+ this.subscription = wsSubscribe(this.parseMessage);
+
+ // Only fetch the data if coming from another route
+ if (this.isoData.path === this.context.router.route.match.url) {
+ this.state = {
+ ...this.state,
+ res: this.isoData.routeData[0] as GetModlogResponse,
+ };
+
+ const communityRes: GetCommunityResponse | undefined =
+ this.isoData.routeData[1];
+
+ // Getting the moderators
+ this.state = {
+ ...this.state,
+ communityMods: communityRes?.moderators,
+ };
+
+ const filteredModRes: GetPersonDetailsResponse | undefined =
+ this.isoData.routeData[2];
+ if (filteredModRes) {
+ this.state = {
+ ...this.state,
+ modSearchOptions: [personToChoice(filteredModRes.person_view)],
+ };
}
- case ModlogActionType.AdminPurgeComment: {
- let ap = i.view as AdminPurgeCommentView;
- let reason = ap.admin_purge_comment.reason;
- return (
- <>
- <span>
- Purged a Comment from{" "}
- <Link to={`/post/${ap.post.id}`}>{ap.post.name}</Link>
- </span>
- {reason && (
- <span>
- <div>reason: {reason}</div>
- </span>
- )}
- </>
- );
+
+ const filteredUserRes: GetPersonDetailsResponse | undefined =
+ this.isoData.routeData[3];
+ if (filteredUserRes) {
+ this.state = {
+ ...this.state,
+ userSearchOptions: [personToChoice(filteredUserRes.person_view)],
+ };
}
- default:
- return <div />;
+
+ this.state = { ...this.state, loadingModlog: false };
+ } else {
+ this.refetch();
+ }
+ }
+
+ componentWillUnmount() {
+ if (isBrowser()) {
+ this.subscription?.unsubscribe();
}
}
- combined() {
- let res = this.state.res;
- let combined = res ? this.buildCombined(res) : [];
+ get combined() {
+ const res = this.state.res;
+ const combined = res ? buildCombined(res) : [];
return (
<tbody>
<div>{this.modOrAdminText(i.moderator)}</div>
)}
</td>
- <td>{this.renderModlogType(i)}</td>
+ <td>{renderModlogType(i)}</td>
</tr>
))}
</tbody>
}
modOrAdminText(person?: PersonSafe): string {
- return person
- ? this.isoData.site_res.admins.map(a => a.person.id).includes(person.id)
- ? i18n.t("admin")
- : i18n.t("mod")
+ return person &&
+ this.isoData.site_res.admins.some(
+ ({ person: { id } }) => id === person.id
+ )
+ ? i18n.t("admin")
: i18n.t("mod");
}
get documentTitle(): string {
- return `Modlog - ${this.state.siteRes.site_view.site.name}`;
+ return `Modlog - ${this.isoData.site_res.site_view.site.name}`;
}
render() {
- let communityName = this.state.communityName;
+ const {
+ communityName,
+ loadingModlog,
+ loadingModSearch,
+ loadingUserSearch,
+ userSearchOptions,
+ modSearchOptions,
+ } = this.state;
+ const { actionType, page, modId, userId } = getModlogQueryParams();
+
return (
<div className="container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
- {this.state.loading ? (
+
+ <div>
<h5>
- <Spinner large />
+ {communityName && (
+ <Link className="text-body" to={`/c/${communityName}`}>
+ /c/{communityName}{" "}
+ </Link>
+ )}
+ <span>{i18n.t("modlog")}</span>
</h5>
- ) : (
- <div>
- <h5>
- {communityName && (
- <Link className="text-body" to={`/c/${communityName}`}>
- /c/{communityName}{" "}
- </Link>
- )}
- <span>{i18n.t("modlog")}</span>
- </h5>
- <div className="form-row">
- <div className="form-group col-sm-6">
- <select
- value={this.state.filter_action}
- onChange={linkEvent(this, this.handleFilterActionChange)}
- className="custom-select mb-2"
- aria-label="action"
- >
- <option disabled aria-hidden="true">
- {i18n.t("filter_by_action")}
- </option>
- <option value={ModlogActionType.All}>{i18n.t("all")}</option>
- <option value={ModlogActionType.ModRemovePost}>
- Removing Posts
- </option>
- <option value={ModlogActionType.ModLockPost}>
- Locking Posts
- </option>
- <option value={ModlogActionType.ModFeaturePost}>
- Featuring Posts
- </option>
- <option value={ModlogActionType.ModRemoveComment}>
- Removing Comments
- </option>
- <option value={ModlogActionType.ModRemoveCommunity}>
- Removing Communities
- </option>
- <option value={ModlogActionType.ModBanFromCommunity}>
- Banning From Communities
- </option>
- <option value={ModlogActionType.ModAddCommunity}>
- Adding Mod to Community
- </option>
- <option value={ModlogActionType.ModTransferCommunity}>
- Transfering Communities
- </option>
- <option value={ModlogActionType.ModAdd}>
- Adding Mod to Site
- </option>
- <option value={ModlogActionType.ModBan}>
- Banning From Site
- </option>
- </select>
- </div>
- {!this.state.siteRes.site_view.local_site
- .hide_modlog_mod_names && (
- <div className="form-group col-sm-6">
- <select
- id="filter-mod"
- className="form-control"
- value={this.state.filter_mod}
- >
- <option>{i18n.t("filter_by_mod")}</option>
- </select>
- </div>
- )}
- <div className="form-group col-sm-6">
- <select
- id="filter-user"
- className="form-control"
- value={this.state.filter_user}
- >
- <option>{i18n.t("filter_by_user")}</option>
- </select>
- </div>
- </div>
- <div className="table-responsive">
+ <div className="form-row">
+ <select
+ value={actionType}
+ onChange={linkEvent(this, this.handleFilterActionChange)}
+ className="custom-select col-sm-6"
+ aria-label="action"
+ >
+ <option disabled aria-hidden="true">
+ {i18n.t("filter_by_action")}
+ </option>
+ <option value={ModlogActionType.All}>{i18n.t("all")}</option>
+ <option value={ModlogActionType.ModRemovePost}>
+ Removing Posts
+ </option>
+ <option value={ModlogActionType.ModLockPost}>
+ Locking Posts
+ </option>
+ <option value={ModlogActionType.ModFeaturePost}>
+ Featuring Posts
+ </option>
+ <option value={ModlogActionType.ModRemoveComment}>
+ Removing Comments
+ </option>
+ <option value={ModlogActionType.ModRemoveCommunity}>
+ Removing Communities
+ </option>
+ <option value={ModlogActionType.ModBanFromCommunity}>
+ Banning From Communities
+ </option>
+ <option value={ModlogActionType.ModAddCommunity}>
+ Adding Mod to Community
+ </option>
+ <option value={ModlogActionType.ModTransferCommunity}>
+ Transferring Communities
+ </option>
+ <option value={ModlogActionType.ModAdd}>
+ Adding Mod to Site
+ </option>
+ <option value={ModlogActionType.ModBan}>Banning From Site</option>
+ </select>
+ </div>
+ <div className="form-row mb-2">
+ <Filter
+ filterType="user"
+ onChange={this.handleUserChange}
+ onSearch={this.handleSearchUsers}
+ value={userId}
+ options={userSearchOptions}
+ loading={loadingUserSearch}
+ />
+ {!this.isoData.site_res.site_view.local_site
+ .hide_modlog_mod_names && (
+ <Filter
+ filterType="mod"
+ onChange={this.handleModChange}
+ onSearch={this.handleSearchMods}
+ value={modId}
+ options={modSearchOptions}
+ loading={loadingModSearch}
+ />
+ )}
+ </div>
+ <div className="table-responsive">
+ {loadingModlog ? (
+ <h5>
+ <Spinner large />
+ </h5>
+ ) : (
<table id="modlog_table" className="table table-sm table-hover">
<thead className="pointer">
<tr>
<th>{i18n.t("action")}</th>
</tr>
</thead>
- {this.combined()}
+ {this.combined}
</table>
- <Paginator
- page={this.state.page}
- onChange={this.handlePageChange}
- />
- </div>
+ )}
+ <Paginator page={page} onChange={this.handlePageChange} />
</div>
- )}
+ </div>
</div>
);
}
handleFilterActionChange(i: Modlog, event: any) {
- i.setState({ filter_action: event.target.value });
- i.refetch();
+ i.updateUrl({
+ actionType: ModlogActionType[event.target.value],
+ page: 1,
+ });
+ }
+
+ handlePageChange(page: number) {
+ this.updateUrl({ page });
+ }
+
+ handleUserChange(option: Choice) {
+ this.updateUrl({ userId: getIdFromString(option.value) ?? null, page: 1 });
+ }
+
+ handleModChange(option: Choice) {
+ this.updateUrl({ modId: getIdFromString(option.value) ?? null, page: 1 });
}
- handlePageChange(val: number) {
- this.setState({ page: val });
+ handleSearchUsers = debounce(async (text: string) => {
+ const { userId } = getModlogQueryParams();
+ const { userSearchOptions } = this.state;
+ this.setState({ loadingUserSearch: true });
+
+ const newOptions = await createNewOptions({
+ id: userId,
+ text,
+ oldOptions: userSearchOptions,
+ });
+
+ this.setState({
+ userSearchOptions: newOptions,
+ loadingUserSearch: false,
+ });
+ });
+
+ handleSearchMods = debounce(async (text: string) => {
+ const { modId } = getModlogQueryParams();
+ const { modSearchOptions } = this.state;
+ this.setState({ loadingModSearch: true });
+
+ const newOptions = await createNewOptions({
+ id: modId,
+ text,
+ oldOptions: modSearchOptions,
+ });
+
+ this.setState({
+ modSearchOptions: newOptions,
+ loadingModSearch: false,
+ });
+ });
+
+ updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
+ const {
+ page: urlPage,
+ actionType: urlActionType,
+ modId: urlModId,
+ userId: urlUserId,
+ } = getModlogQueryParams();
+
+ const queryParams: QueryParams<ModlogProps> = {
+ page: (page ?? urlPage).toString(),
+ actionType: actionType ?? urlActionType,
+ modId: getUpdatedSearchId(modId, urlModId),
+ userId: getUpdatedSearchId(userId, urlUserId),
+ };
+
+ const communityId = this.props.match.params.communityId;
+
+ this.props.history.push(
+ `/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
+ queryParams
+ )}`
+ );
+
+ this.setState({
+ loadingModlog: true,
+ res: undefined,
+ });
+
this.refetch();
}
refetch() {
- let auth = myAuth(false);
- let modlogForm: GetModlog = {
- community_id: this.state.communityId,
- page: this.state.page,
+ const auth = myAuth(false);
+ const { actionType, page, modId, userId } = getModlogQueryParams();
+ const { communityId: urlCommunityId } = this.props.match.params;
+ const communityId = getIdFromString(urlCommunityId);
+
+ const modlogForm: GetModlog = {
+ community_id: communityId,
+ page,
limit: fetchLimit,
- type_: this.state.filter_action,
- other_person_id: this.state.filter_user,
- mod_person_id: this.state.filter_mod,
+ type_: actionType,
+ other_person_id: userId ?? undefined,
+ mod_person_id: !this.isoData.site_res.site_view.local_site
+ .hide_modlog_mod_names
+ ? modId ?? undefined
+ : undefined,
auth,
};
+
WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
- let communityId = this.state.communityId;
if (communityId) {
- let communityForm: GetCommunity = {
+ const communityForm: GetCommunity = {
id: communityId,
auth,
};
- WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
- }
- }
- setupUserFilter() {
- if (isBrowser()) {
- let selectId: any = document.getElementById("filter-user");
- if (selectId) {
- this.userChoices = new Choices(selectId, choicesConfig);
- this.userChoices.passedElement.element.addEventListener(
- "choice",
- (e: any) => {
- this.setState({ filter_user: Number(e.detail.choice.value) });
- this.refetch();
- },
- false
- );
- this.userChoices.passedElement.element.addEventListener(
- "search",
- debounce(async (e: any) => {
- try {
- let users = (await fetchUsers(e.detail.value)).users;
- this.userChoices.setChoices(
- users.map(u => {
- return {
- value: u.person.id.toString(),
- label: u.person.name,
- };
- }),
- "value",
- "label",
- true
- );
- } catch (err) {
- console.log(err);
- }
- }),
- false
- );
- }
- }
- }
-
- setupModFilter() {
- if (isBrowser()) {
- let selectId: any = document.getElementById("filter-mod");
- if (selectId) {
- this.modChoices = new Choices(selectId, choicesConfig);
- this.modChoices.passedElement.element.addEventListener(
- "choice",
- (e: any) => {
- this.setState({ filter_mod: Number(e.detail.choice.value) });
- this.refetch();
- },
- false
- );
- this.modChoices.passedElement.element.addEventListener(
- "search",
- debounce(async (e: any) => {
- try {
- let mods = (await fetchUsers(e.detail.value)).users;
- this.modChoices.setChoices(
- mods.map(u => {
- return {
- value: u.person.id.toString(),
- label: u.person.name,
- };
- }),
- "value",
- "label",
- true
- );
- } catch (err) {
- console.log(err);
- }
- }),
- false
- );
- }
+ WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
}
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let pathSplit = req.path.split("/");
- let communityId = pathSplit[3] ? Number(pathSplit[3]) : undefined;
- let auth = req.auth;
- let promises: Promise<any>[] = [];
+ static fetchInitialData({
+ client,
+ path,
+ query: { modId: urlModId, page, userId: urlUserId, actionType },
+ auth,
+ site,
+ }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<any>[] {
+ const pathSplit = path.split("/");
+ const promises: Promise<any>[] = [];
+ const communityId = getIdFromString(pathSplit[2]);
+ const modId = !site.site_view.local_site.hide_modlog_mod_names
+ ? getIdFromString(urlModId)
+ : undefined;
+ const userId = getIdFromString(urlUserId);
- let modlogForm: GetModlog = {
- page: 1,
+ const modlogForm: GetModlog = {
+ page: getPageFromString(page),
limit: fetchLimit,
community_id: communityId,
- type_: ModlogActionType.All,
+ type_: getActionFromString(actionType),
+ mod_person_id: modId,
+ other_person_id: userId,
auth,
};
- promises.push(req.client.getModlog(modlogForm));
+ promises.push(client.getModlog(modlogForm));
if (communityId) {
- let communityForm: GetCommunity = {
+ const communityForm: GetCommunity = {
id: communityId,
- auth: req.auth,
+ auth,
};
- promises.push(req.client.getCommunity(communityForm));
+ promises.push(client.getCommunity(communityForm));
} else {
promises.push(Promise.resolve());
}
+
+ if (modId) {
+ const getPersonForm: GetPersonDetails = {
+ person_id: modId,
+ auth,
+ };
+
+ promises.push(client.getPersonDetails(getPersonForm));
+ } else {
+ promises.push(Promise.resolve());
+ }
+
+ if (userId) {
+ const getPersonForm: GetPersonDetails = {
+ person_id: userId,
+ auth,
+ };
+
+ promises.push(client.getPersonDetails(getPersonForm));
+ } else {
+ promises.push(Promise.resolve());
+ }
+
return promises;
}
parseMessage(msg: any) {
- let op = wsUserOp(msg);
+ const op = wsUserOp(msg);
console.log(msg);
+
if (msg.error) {
toast(i18n.t(msg.error), "danger");
- return;
- } else if (op == UserOperation.GetModlog) {
- let data = wsJsonToRes<GetModlogResponse>(msg);
- window.scrollTo(0, 0);
- this.setState({ res: data, loading: false });
- this.setupUserFilter();
- this.setupModFilter();
- } else if (op == UserOperation.GetCommunity) {
- let data = wsJsonToRes<GetCommunityResponse>(msg);
- this.setState({
- communityMods: data.moderators,
- communityName: data.community_view.community.name,
- });
+ } else {
+ switch (op) {
+ case UserOperation.GetModlog: {
+ const res = wsJsonToRes<GetModlogResponse>(msg);
+ window.scrollTo(0, 0);
+ this.setState({ res, loadingModlog: false });
+
+ break;
+ }
+
+ case UserOperation.GetCommunity: {
+ const {
+ moderators,
+ community_view: {
+ community: { name },
+ },
+ } = wsJsonToRes<GetCommunityResponse>(msg);
+ this.setState({
+ communityMods: moderators,
+ communityName: name,
+ });
+
+ break;
+ }
+ }
}
}
}