]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
ef28816b668147dbb56ab8326ccfe579e150a401
[lemmy-ui.git] / src / shared / components / modlog.tsx
1 import { NoOptionI18nKeys } from "i18next";
2 import { Component, linkEvent } from "inferno";
3 import { T } from "inferno-i18next-dess";
4 import { Link } from "inferno-router";
5 import { RouteComponentProps } from "inferno-router/dist/Route";
6 import {
7   AdminPurgeCommentView,
8   AdminPurgeCommunityView,
9   AdminPurgePersonView,
10   AdminPurgePostView,
11   CommunityModeratorView,
12   GetCommunity,
13   GetCommunityResponse,
14   GetModlog,
15   GetModlogResponse,
16   GetPersonDetails,
17   GetPersonDetailsResponse,
18   ModAddCommunityView,
19   ModAddView,
20   ModBanFromCommunityView,
21   ModBanView,
22   ModFeaturePostView,
23   ModLockPostView,
24   ModRemoveCommentView,
25   ModRemoveCommunityView,
26   ModRemovePostView,
27   ModTransferCommunityView,
28   ModlogActionType,
29   Person,
30   UserOperation,
31   wsJsonToRes,
32   wsUserOp,
33 } from "lemmy-js-client";
34 import moment from "moment";
35 import { Subscription } from "rxjs";
36 import { i18n } from "../i18next";
37 import { InitialFetchRequest } from "../interfaces";
38 import { WebSocketService } from "../services";
39 import {
40   Choice,
41   QueryParams,
42   amAdmin,
43   amMod,
44   debounce,
45   fetchLimit,
46   fetchUsers,
47   getIdFromString,
48   getPageFromString,
49   getQueryParams,
50   getQueryString,
51   getUpdatedSearchId,
52   isBrowser,
53   myAuth,
54   personToChoice,
55   setIsoData,
56   toast,
57   wsClient,
58   wsSubscribe,
59 } from "../utils";
60 import { HtmlTags } from "./common/html-tags";
61 import { Icon, Spinner } from "./common/icon";
62 import { MomentTime } from "./common/moment-time";
63 import { Paginator } from "./common/paginator";
64 import { SearchableSelect } from "./common/searchable-select";
65 import { CommunityLink } from "./community/community-link";
66 import { PersonListing } from "./person/person-listing";
67
68 type FilterType = "mod" | "user";
69
70 type View =
71   | ModRemovePostView
72   | ModLockPostView
73   | ModFeaturePostView
74   | ModRemoveCommentView
75   | ModRemoveCommunityView
76   | ModBanFromCommunityView
77   | ModBanView
78   | ModAddCommunityView
79   | ModTransferCommunityView
80   | ModAddView
81   | AdminPurgePersonView
82   | AdminPurgeCommunityView
83   | AdminPurgePostView
84   | AdminPurgeCommentView;
85
86 interface ModlogType {
87   id: number;
88   type_: ModlogActionType;
89   moderator?: Person;
90   view: View;
91   when_: string;
92 }
93
94 const getModlogQueryParams = () =>
95   getQueryParams<ModlogProps>({
96     actionType: getActionFromString,
97     modId: getIdFromString,
98     userId: getIdFromString,
99     page: getPageFromString,
100   });
101
102 interface ModlogState {
103   res?: GetModlogResponse;
104   communityMods?: CommunityModeratorView[];
105   communityName?: string;
106   loadingModlog: boolean;
107   loadingModSearch: boolean;
108   loadingUserSearch: boolean;
109   modSearchOptions: Choice[];
110   userSearchOptions: Choice[];
111 }
112
113 interface ModlogProps {
114   page: number;
115   userId?: number | null;
116   modId?: number | null;
117   actionType: ModlogActionType;
118 }
119
120 function getActionFromString(action?: string): ModlogActionType {
121   return action !== undefined ? (action as ModlogActionType) : "All";
122 }
123
124 const getModlogActionMapper =
125   (
126     actionType: ModlogActionType,
127     getAction: (view: View) => { id: number; when_: string }
128   ) =>
129   (view: View & { moderator?: Person; admin?: Person }): ModlogType => {
130     const { id, when_ } = getAction(view);
131
132     return {
133       id,
134       type_: actionType,
135       view,
136       when_,
137       moderator: view.moderator ?? view.admin,
138     };
139   };
140
141 function buildCombined({
142   removed_comments,
143   locked_posts,
144   featured_posts,
145   removed_communities,
146   removed_posts,
147   added,
148   added_to_community,
149   admin_purged_comments,
150   admin_purged_communities,
151   admin_purged_persons,
152   admin_purged_posts,
153   banned,
154   banned_from_community,
155   transferred_to_community,
156 }: GetModlogResponse): ModlogType[] {
157   const combined = removed_posts
158     .map(
159       getModlogActionMapper(
160         "ModRemovePost",
161         ({ mod_remove_post }: ModRemovePostView) => mod_remove_post
162       )
163     )
164     .concat(
165       locked_posts.map(
166         getModlogActionMapper(
167           "ModLockPost",
168           ({ mod_lock_post }: ModLockPostView) => mod_lock_post
169         )
170       )
171     )
172     .concat(
173       featured_posts.map(
174         getModlogActionMapper(
175           "ModFeaturePost",
176           ({ mod_feature_post }: ModFeaturePostView) => mod_feature_post
177         )
178       )
179     )
180     .concat(
181       removed_comments.map(
182         getModlogActionMapper(
183           "ModRemoveComment",
184           ({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment
185         )
186       )
187     )
188     .concat(
189       removed_communities.map(
190         getModlogActionMapper(
191           "ModRemoveCommunity",
192           ({ mod_remove_community }: ModRemoveCommunityView) =>
193             mod_remove_community
194         )
195       )
196     )
197     .concat(
198       banned_from_community.map(
199         getModlogActionMapper(
200           "ModBanFromCommunity",
201           ({ mod_ban_from_community }: ModBanFromCommunityView) =>
202             mod_ban_from_community
203         )
204       )
205     )
206     .concat(
207       added_to_community.map(
208         getModlogActionMapper(
209           "ModAddCommunity",
210           ({ mod_add_community }: ModAddCommunityView) => mod_add_community
211         )
212       )
213     )
214     .concat(
215       transferred_to_community.map(
216         getModlogActionMapper(
217           "ModTransferCommunity",
218           ({ mod_transfer_community }: ModTransferCommunityView) =>
219             mod_transfer_community
220         )
221       )
222     )
223     .concat(
224       added.map(
225         getModlogActionMapper("ModAdd", ({ mod_add }: ModAddView) => mod_add)
226       )
227     )
228     .concat(
229       banned.map(
230         getModlogActionMapper("ModBan", ({ mod_ban }: ModBanView) => mod_ban)
231       )
232     )
233     .concat(
234       admin_purged_persons.map(
235         getModlogActionMapper(
236           "AdminPurgePerson",
237           ({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person
238         )
239       )
240     )
241     .concat(
242       admin_purged_communities.map(
243         getModlogActionMapper(
244           "AdminPurgeCommunity",
245           ({ admin_purge_community }: AdminPurgeCommunityView) =>
246             admin_purge_community
247         )
248       )
249     )
250     .concat(
251       admin_purged_posts.map(
252         getModlogActionMapper(
253           "AdminPurgePost",
254           ({ admin_purge_post }: AdminPurgePostView) => admin_purge_post
255         )
256       )
257     )
258     .concat(
259       admin_purged_comments.map(
260         getModlogActionMapper(
261           "AdminPurgeComment",
262           ({ admin_purge_comment }: AdminPurgeCommentView) =>
263             admin_purge_comment
264         )
265       )
266     );
267
268   // Sort them by time
269   combined.sort((a, b) => b.when_.localeCompare(a.when_));
270
271   return combined;
272 }
273
274 function renderModlogType({ type_, view }: ModlogType) {
275   switch (type_) {
276     case "ModRemovePost": {
277       const mrpv = view as ModRemovePostView;
278       const {
279         mod_remove_post: { reason, removed },
280         post: { name, id },
281       } = mrpv;
282
283       return (
284         <>
285           <span>{removed ? "Removed " : "Restored "}</span>
286           <span>
287             Post <Link to={`/post/${id}`}>{name}</Link>
288           </span>
289           {reason && (
290             <span>
291               <div>reason: {reason}</div>
292             </span>
293           )}
294         </>
295       );
296     }
297
298     case "ModLockPost": {
299       const {
300         mod_lock_post: { locked },
301         post: { id, name },
302       } = view as ModLockPostView;
303
304       return (
305         <>
306           <span>{locked ? "Locked " : "Unlocked "}</span>
307           <span>
308             Post <Link to={`/post/${id}`}>{name}</Link>
309           </span>
310         </>
311       );
312     }
313
314     case "ModFeaturePost": {
315       const {
316         mod_feature_post: { featured, is_featured_community },
317         post: { id, name },
318       } = view as ModFeaturePostView;
319
320       return (
321         <>
322           <span>{featured ? "Featured " : "Unfeatured "}</span>
323           <span>
324             Post <Link to={`/post/${id}`}>{name}</Link>
325           </span>
326           <span>{is_featured_community ? " In Community" : " In Local"}</span>
327         </>
328       );
329     }
330     case "ModRemoveComment": {
331       const mrc = view as ModRemoveCommentView;
332       const {
333         mod_remove_comment: { reason, removed },
334         comment: { id, content },
335         commenter,
336       } = mrc;
337
338       return (
339         <>
340           <span>{removed ? "Removed " : "Restored "}</span>
341           <span>
342             Comment <Link to={`/comment/${id}`}>{content}</Link>
343           </span>
344           <span>
345             {" "}
346             by <PersonListing person={commenter} />
347           </span>
348           {reason && (
349             <span>
350               <div>reason: {reason}</div>
351             </span>
352           )}
353         </>
354       );
355     }
356
357     case "ModRemoveCommunity": {
358       const mrco = view as ModRemoveCommunityView;
359       const {
360         mod_remove_community: { reason, expires, removed },
361         community,
362       } = mrco;
363
364       return (
365         <>
366           <span>{removed ? "Removed " : "Restored "}</span>
367           <span>
368             Community <CommunityLink community={community} />
369           </span>
370           {reason && (
371             <span>
372               <div>reason: {reason}</div>
373             </span>
374           )}
375           {expires && (
376             <span>
377               <div>expires: {moment.utc(expires).fromNow()}</div>
378             </span>
379           )}
380         </>
381       );
382     }
383
384     case "ModBanFromCommunity": {
385       const mbfc = view as ModBanFromCommunityView;
386       const {
387         mod_ban_from_community: { reason, expires, banned },
388         banned_person,
389         community,
390       } = mbfc;
391
392       return (
393         <>
394           <span>{banned ? "Banned " : "Unbanned "}</span>
395           <span>
396             <PersonListing person={banned_person} />
397           </span>
398           <span> from the community </span>
399           <span>
400             <CommunityLink community={community} />
401           </span>
402           {reason && (
403             <span>
404               <div>reason: {reason}</div>
405             </span>
406           )}
407           {expires && (
408             <span>
409               <div>expires: {moment.utc(expires).fromNow()}</div>
410             </span>
411           )}
412         </>
413       );
414     }
415
416     case "ModAddCommunity": {
417       const {
418         mod_add_community: { removed },
419         modded_person,
420         community,
421       } = view as ModAddCommunityView;
422
423       return (
424         <>
425           <span>{removed ? "Removed " : "Appointed "}</span>
426           <span>
427             <PersonListing person={modded_person} />
428           </span>
429           <span> as a mod to the community </span>
430           <span>
431             <CommunityLink community={community} />
432           </span>
433         </>
434       );
435     }
436
437     case "ModTransferCommunity": {
438       const { community, modded_person } = view as ModTransferCommunityView;
439
440       return (
441         <>
442           <span>Transferred</span>
443           <span>
444             <CommunityLink community={community} />
445           </span>
446           <span> to </span>
447           <span>
448             <PersonListing person={modded_person} />
449           </span>
450         </>
451       );
452     }
453
454     case "ModBan": {
455       const {
456         mod_ban: { reason, expires, banned },
457         banned_person,
458       } = view as ModBanView;
459
460       return (
461         <>
462           <span>{banned ? "Banned " : "Unbanned "}</span>
463           <span>
464             <PersonListing person={banned_person} />
465           </span>
466           {reason && (
467             <span>
468               <div>reason: {reason}</div>
469             </span>
470           )}
471           {expires && (
472             <span>
473               <div>expires: {moment.utc(expires).fromNow()}</div>
474             </span>
475           )}
476         </>
477       );
478     }
479
480     case "ModAdd": {
481       const {
482         mod_add: { removed },
483         modded_person,
484       } = view as ModAddView;
485
486       return (
487         <>
488           <span>{removed ? "Removed " : "Appointed "}</span>
489           <span>
490             <PersonListing person={modded_person} />
491           </span>
492           <span> as an admin </span>
493         </>
494       );
495     }
496     case "AdminPurgePerson": {
497       const {
498         admin_purge_person: { reason },
499       } = view as AdminPurgePersonView;
500
501       return (
502         <>
503           <span>Purged a Person</span>
504           {reason && (
505             <span>
506               <div>reason: {reason}</div>
507             </span>
508           )}
509         </>
510       );
511     }
512
513     case "AdminPurgeCommunity": {
514       const {
515         admin_purge_community: { reason },
516       } = view as AdminPurgeCommunityView;
517
518       return (
519         <>
520           <span>Purged a Community</span>
521           {reason && (
522             <span>
523               <div>reason: {reason}</div>
524             </span>
525           )}
526         </>
527       );
528     }
529
530     case "AdminPurgePost": {
531       const {
532         admin_purge_post: { reason },
533         community,
534       } = view as AdminPurgePostView;
535
536       return (
537         <>
538           <span>Purged a Post from from </span>
539           <CommunityLink community={community} />
540           {reason && (
541             <span>
542               <div>reason: {reason}</div>
543             </span>
544           )}
545         </>
546       );
547     }
548
549     case "AdminPurgeComment": {
550       const {
551         admin_purge_comment: { reason },
552         post: { id, name },
553       } = view as AdminPurgeCommentView;
554
555       return (
556         <>
557           <span>
558             Purged a Comment from <Link to={`/post/${id}`}>{name}</Link>
559           </span>
560           {reason && (
561             <span>
562               <div>reason: {reason}</div>
563             </span>
564           )}
565         </>
566       );
567     }
568
569     default:
570       return <></>;
571   }
572 }
573
574 const Filter = ({
575   filterType,
576   onChange,
577   value,
578   onSearch,
579   options,
580   loading,
581 }: {
582   filterType: FilterType;
583   onChange: (option: Choice) => void;
584   value?: number | null;
585   onSearch: (text: string) => void;
586   options: Choice[];
587   loading: boolean;
588 }) => (
589   <div className="col-sm-6 form-group">
590     <label className="col-form-label" htmlFor={`filter-${filterType}`}>
591       {i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
592     </label>
593     <SearchableSelect
594       id={`filter-${filterType}`}
595       value={value ?? 0}
596       options={[
597         {
598           label: i18n.t("all"),
599           value: "0",
600         },
601       ].concat(options)}
602       onChange={onChange}
603       onSearch={onSearch}
604       loading={loading}
605     />
606   </div>
607 );
608
609 async function createNewOptions({
610   id,
611   oldOptions,
612   text,
613 }: {
614   id?: number | null;
615   oldOptions: Choice[];
616   text: string;
617 }) {
618   const newOptions: Choice[] = [];
619
620   if (id) {
621     const selectedUser = oldOptions.find(
622       ({ value }) => value === id.toString()
623     );
624
625     if (selectedUser) {
626       newOptions.push(selectedUser);
627     }
628   }
629
630   if (text.length > 0) {
631     newOptions.push(
632       ...(await fetchUsers(text)).users
633         .slice(0, Number(fetchLimit))
634         .map<Choice>(personToChoice)
635     );
636   }
637
638   return newOptions;
639 }
640
641 export class Modlog extends Component<
642   RouteComponentProps<{ communityId?: string }>,
643   ModlogState
644 > {
645   private isoData = setIsoData(this.context);
646   private subscription?: Subscription;
647
648   state: ModlogState = {
649     loadingModlog: true,
650     loadingModSearch: false,
651     loadingUserSearch: false,
652     userSearchOptions: [],
653     modSearchOptions: [],
654   };
655
656   constructor(
657     props: RouteComponentProps<{ communityId?: string }>,
658     context: any
659   ) {
660     super(props, context);
661     this.handlePageChange = this.handlePageChange.bind(this);
662     this.handleUserChange = this.handleUserChange.bind(this);
663     this.handleModChange = this.handleModChange.bind(this);
664
665     this.parseMessage = this.parseMessage.bind(this);
666     this.subscription = wsSubscribe(this.parseMessage);
667
668     // Only fetch the data if coming from another route
669     if (this.isoData.path === this.context.router.route.match.url) {
670       this.state = {
671         ...this.state,
672         res: this.isoData.routeData[0] as GetModlogResponse,
673       };
674
675       const communityRes: GetCommunityResponse | undefined =
676         this.isoData.routeData[1];
677
678       // Getting the moderators
679       this.state = {
680         ...this.state,
681         communityMods: communityRes?.moderators,
682       };
683
684       const filteredModRes: GetPersonDetailsResponse | undefined =
685         this.isoData.routeData[2];
686       if (filteredModRes) {
687         this.state = {
688           ...this.state,
689           modSearchOptions: [personToChoice(filteredModRes.person_view)],
690         };
691       }
692
693       const filteredUserRes: GetPersonDetailsResponse | undefined =
694         this.isoData.routeData[3];
695       if (filteredUserRes) {
696         this.state = {
697           ...this.state,
698           userSearchOptions: [personToChoice(filteredUserRes.person_view)],
699         };
700       }
701
702       this.state = { ...this.state, loadingModlog: false };
703     } else {
704       this.refetch();
705     }
706   }
707
708   componentWillUnmount() {
709     if (isBrowser()) {
710       this.subscription?.unsubscribe();
711     }
712   }
713
714   get combined() {
715     const res = this.state.res;
716     const combined = res ? buildCombined(res) : [];
717
718     return (
719       <tbody>
720         {combined.map(i => (
721           <tr key={i.id}>
722             <td>
723               <MomentTime published={i.when_} />
724             </td>
725             <td>
726               {this.amAdminOrMod && i.moderator ? (
727                 <PersonListing person={i.moderator} />
728               ) : (
729                 <div>{this.modOrAdminText(i.moderator)}</div>
730               )}
731             </td>
732             <td>{renderModlogType(i)}</td>
733           </tr>
734         ))}
735       </tbody>
736     );
737   }
738
739   get amAdminOrMod(): boolean {
740     return amAdmin() || amMod(this.state.communityMods);
741   }
742
743   modOrAdminText(person?: Person): string {
744     return person &&
745       this.isoData.site_res.admins.some(
746         ({ person: { id } }) => id === person.id
747       )
748       ? i18n.t("admin")
749       : i18n.t("mod");
750   }
751
752   get documentTitle(): string {
753     return `Modlog - ${this.isoData.site_res.site_view.site.name}`;
754   }
755
756   render() {
757     const {
758       communityName,
759       loadingModlog,
760       loadingModSearch,
761       loadingUserSearch,
762       userSearchOptions,
763       modSearchOptions,
764     } = this.state;
765     const { actionType, page, modId, userId } = getModlogQueryParams();
766
767     return (
768       <div className="container-lg">
769         <HtmlTags
770           title={this.documentTitle}
771           path={this.context.router.route.match.url}
772         />
773
774         <div>
775           <div
776             className="alert alert-warning text-sm-start text-xs-center"
777             role="alert"
778           >
779             <Icon
780               icon="alert-triangle"
781               inline
782               classes="mr-sm-2 mx-auto d-sm-inline d-block"
783             />
784             <T i18nKey="modlog_content_warning" class="d-inline">
785               #<strong>#</strong>#
786             </T>
787           </div>
788           <h5>
789             {communityName && (
790               <Link className="text-body" to={`/c/${communityName}`}>
791                 /c/{communityName}{" "}
792               </Link>
793             )}
794             <span>{i18n.t("modlog")}</span>
795           </h5>
796           <div className="form-row">
797             <select
798               value={actionType}
799               onChange={linkEvent(this, this.handleFilterActionChange)}
800               className="custom-select col-sm-6"
801               aria-label="action"
802             >
803               <option disabled aria-hidden="true">
804                 {i18n.t("filter_by_action")}
805               </option>
806               <option value={"All"}>{i18n.t("all")}</option>
807               <option value={"ModRemovePost"}>Removing Posts</option>
808               <option value={"ModLockPost"}>Locking Posts</option>
809               <option value={"ModFeaturePost"}>Featuring Posts</option>
810               <option value={"ModRemoveComment"}>Removing Comments</option>
811               <option value={"ModRemoveCommunity"}>Removing Communities</option>
812               <option value={"ModBanFromCommunity"}>
813                 Banning From Communities
814               </option>
815               <option value={"ModAddCommunity"}>Adding Mod to Community</option>
816               <option value={"ModTransferCommunity"}>
817                 Transferring Communities
818               </option>
819               <option value={"ModAdd"}>Adding Mod to Site</option>
820               <option value={"ModBan"}>Banning From Site</option>
821             </select>
822           </div>
823           <div className="form-row mb-2">
824             <Filter
825               filterType="user"
826               onChange={this.handleUserChange}
827               onSearch={this.handleSearchUsers}
828               value={userId}
829               options={userSearchOptions}
830               loading={loadingUserSearch}
831             />
832             {!this.isoData.site_res.site_view.local_site
833               .hide_modlog_mod_names && (
834               <Filter
835                 filterType="mod"
836                 onChange={this.handleModChange}
837                 onSearch={this.handleSearchMods}
838                 value={modId}
839                 options={modSearchOptions}
840                 loading={loadingModSearch}
841               />
842             )}
843           </div>
844           <div className="table-responsive">
845             {loadingModlog ? (
846               <h5>
847                 <Spinner large />
848               </h5>
849             ) : (
850               <table id="modlog_table" className="table table-sm table-hover">
851                 <thead className="pointer">
852                   <tr>
853                     <th> {i18n.t("time")}</th>
854                     <th>{i18n.t("mod")}</th>
855                     <th>{i18n.t("action")}</th>
856                   </tr>
857                 </thead>
858                 {this.combined}
859               </table>
860             )}
861             <Paginator page={page} onChange={this.handlePageChange} />
862           </div>
863         </div>
864       </div>
865     );
866   }
867
868   handleFilterActionChange(i: Modlog, event: any) {
869     i.updateUrl({
870       actionType: event.target.value as ModlogActionType,
871       page: 1,
872     });
873   }
874
875   handlePageChange(page: number) {
876     this.updateUrl({ page });
877   }
878
879   handleUserChange(option: Choice) {
880     this.updateUrl({ userId: getIdFromString(option.value) ?? null, page: 1 });
881   }
882
883   handleModChange(option: Choice) {
884     this.updateUrl({ modId: getIdFromString(option.value) ?? null, page: 1 });
885   }
886
887   handleSearchUsers = debounce(async (text: string) => {
888     const { userId } = getModlogQueryParams();
889     const { userSearchOptions } = this.state;
890     this.setState({ loadingUserSearch: true });
891
892     const newOptions = await createNewOptions({
893       id: userId,
894       text,
895       oldOptions: userSearchOptions,
896     });
897
898     this.setState({
899       userSearchOptions: newOptions,
900       loadingUserSearch: false,
901     });
902   });
903
904   handleSearchMods = debounce(async (text: string) => {
905     const { modId } = getModlogQueryParams();
906     const { modSearchOptions } = this.state;
907     this.setState({ loadingModSearch: true });
908
909     const newOptions = await createNewOptions({
910       id: modId,
911       text,
912       oldOptions: modSearchOptions,
913     });
914
915     this.setState({
916       modSearchOptions: newOptions,
917       loadingModSearch: false,
918     });
919   });
920
921   updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
922     const {
923       page: urlPage,
924       actionType: urlActionType,
925       modId: urlModId,
926       userId: urlUserId,
927     } = getModlogQueryParams();
928
929     const queryParams: QueryParams<ModlogProps> = {
930       page: (page ?? urlPage).toString(),
931       actionType: actionType ?? urlActionType,
932       modId: getUpdatedSearchId(modId, urlModId),
933       userId: getUpdatedSearchId(userId, urlUserId),
934     };
935
936     const communityId = this.props.match.params.communityId;
937
938     this.props.history.push(
939       `/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
940         queryParams
941       )}`
942     );
943
944     this.setState({
945       loadingModlog: true,
946       res: undefined,
947     });
948
949     this.refetch();
950   }
951
952   refetch() {
953     const auth = myAuth(false);
954     const { actionType, page, modId, userId } = getModlogQueryParams();
955     const { communityId: urlCommunityId } = this.props.match.params;
956     const communityId = getIdFromString(urlCommunityId);
957
958     const modlogForm: GetModlog = {
959       community_id: communityId,
960       page,
961       limit: fetchLimit,
962       type_: actionType,
963       other_person_id: userId ?? undefined,
964       mod_person_id: !this.isoData.site_res.site_view.local_site
965         .hide_modlog_mod_names
966         ? modId ?? undefined
967         : undefined,
968       auth,
969     };
970
971     WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
972
973     if (communityId) {
974       const communityForm: GetCommunity = {
975         id: communityId,
976         auth,
977       };
978
979       WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
980     }
981   }
982
983   static fetchInitialData({
984     client,
985     path,
986     query: { modId: urlModId, page, userId: urlUserId, actionType },
987     auth,
988     site,
989   }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<any>[] {
990     const pathSplit = path.split("/");
991     const promises: Promise<any>[] = [];
992     const communityId = getIdFromString(pathSplit[2]);
993     const modId = !site.site_view.local_site.hide_modlog_mod_names
994       ? getIdFromString(urlModId)
995       : undefined;
996     const userId = getIdFromString(urlUserId);
997
998     const modlogForm: GetModlog = {
999       page: getPageFromString(page),
1000       limit: fetchLimit,
1001       community_id: communityId,
1002       type_: getActionFromString(actionType),
1003       mod_person_id: modId,
1004       other_person_id: userId,
1005       auth,
1006     };
1007
1008     promises.push(client.getModlog(modlogForm));
1009
1010     if (communityId) {
1011       const communityForm: GetCommunity = {
1012         id: communityId,
1013         auth,
1014       };
1015       promises.push(client.getCommunity(communityForm));
1016     } else {
1017       promises.push(Promise.resolve());
1018     }
1019
1020     if (modId) {
1021       const getPersonForm: GetPersonDetails = {
1022         person_id: modId,
1023         auth,
1024       };
1025
1026       promises.push(client.getPersonDetails(getPersonForm));
1027     } else {
1028       promises.push(Promise.resolve());
1029     }
1030
1031     if (userId) {
1032       const getPersonForm: GetPersonDetails = {
1033         person_id: userId,
1034         auth,
1035       };
1036
1037       promises.push(client.getPersonDetails(getPersonForm));
1038     } else {
1039       promises.push(Promise.resolve());
1040     }
1041
1042     return promises;
1043   }
1044
1045   parseMessage(msg: any) {
1046     const op = wsUserOp(msg);
1047     console.log(msg);
1048
1049     if (msg.error) {
1050       toast(i18n.t(msg.error), "danger");
1051     } else {
1052       switch (op) {
1053         case UserOperation.GetModlog: {
1054           const res = wsJsonToRes<GetModlogResponse>(msg);
1055           window.scrollTo(0, 0);
1056           this.setState({ res, loadingModlog: false });
1057
1058           break;
1059         }
1060
1061         case UserOperation.GetCommunity: {
1062           const {
1063             moderators,
1064             community_view: {
1065               community: { name },
1066             },
1067           } = wsJsonToRes<GetCommunityResponse>(msg);
1068           this.setState({
1069             communityMods: moderators,
1070             communityName: name,
1071           });
1072
1073           break;
1074         }
1075       }
1076     }
1077   }
1078 }