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