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