]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
Add support for filtering mod logs
[lemmy-ui.git] / src / shared / components / modlog.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Link } from "inferno-router";
4 import {
5   AdminPurgeCommentView,
6   AdminPurgeCommunityView,
7   AdminPurgePersonView,
8   AdminPurgePostView,
9   CommunityModeratorView,
10   GetCommunity,
11   GetCommunityResponse,
12   GetModlog,
13   GetModlogResponse,
14   GetSiteResponse,
15   ModAddCommunityView,
16   ModAddView,
17   ModBanFromCommunityView,
18   ModBanView,
19   ModLockPostView,
20   ModlogActionType,
21   ModRemoveCommentView,
22   ModRemoveCommunityView,
23   ModRemovePostView,
24   ModStickyPostView,
25   ModTransferCommunityView,
26   PersonSafe,
27   toUndefined,
28   UserOperation,
29   wsJsonToRes,
30   wsUserOp,
31 } from "lemmy-js-client";
32 import moment from "moment";
33 import { Subscription } from "rxjs";
34 import { i18n } from "../i18next";
35 import { InitialFetchRequest } from "../interfaces";
36 import { WebSocketService } from "../services";
37 import {
38   amAdmin,
39   amMod,
40   auth,
41   choicesConfig,
42   choicesModLogConfig,
43   debounce,
44   fetchLimit,
45   fetchUsers,
46   isBrowser,
47   setIsoData,
48   toast,
49   wsClient,
50   wsSubscribe,
51 } from "../utils";
52 import { HtmlTags } from "./common/html-tags";
53 import { Spinner } from "./common/icon";
54 import { MomentTime } from "./common/moment-time";
55 import { Paginator } from "./common/paginator";
56 import { CommunityLink } from "./community/community-link";
57 import { PersonListing } from "./person/person-listing";
58 type ModlogType = {
59   id: number;
60   type_: ModlogActionType;
61   moderator: Option<PersonSafe>;
62   view:
63   | ModRemovePostView
64   | ModLockPostView
65   | ModStickyPostView
66   | ModRemoveCommentView
67   | ModRemoveCommunityView
68   | ModBanFromCommunityView
69   | ModBanView
70   | ModAddCommunityView
71   | ModTransferCommunityView
72   | ModAddView
73   | AdminPurgePersonView
74   | AdminPurgeCommunityView
75   | AdminPurgePostView
76   | AdminPurgeCommentView;
77   when_: string;
78 };
79 var Choices: any;
80 if (isBrowser()) {
81   Choices = require("choices.js");
82 }
83
84 interface ModlogState {
85   res: Option<GetModlogResponse>;
86   communityId: Option<number>;
87   communityMods: Option<CommunityModeratorView[]>;
88   communityName: Option<string>,
89   page: number;
90   siteRes: GetSiteResponse;
91   loading: boolean;
92   filter_action: ModlogActionType,
93   filter_user: Option<number>,
94   filter_mod: Option<number>,
95 }
96
97 export class Modlog extends Component<any, ModlogState> {
98   private isoData = setIsoData(
99     this.context,
100     GetModlogResponse,
101     GetCommunityResponse
102   );
103   private subscription: Subscription;
104   private userChoices: any;
105   private modChoices: any;
106   private emptyState: ModlogState = {
107     res: None,
108     communityId: None,
109     communityMods: None,
110     communityName: None,
111     page: 1,
112     loading: true,
113     siteRes: this.isoData.site_res,
114     filter_action: ModlogActionType.All,
115     filter_user: None,
116     filter_mod: None,
117   };
118   constructor(props: any, context: any) {
119     super(props, context);
120     this.state = this.emptyState;
121     this.handlePageChange = this.handlePageChange.bind(this);
122
123     this.state.communityId = this.props.match.params.community_id
124       ? Some(Number(this.props.match.params.community_id))
125       : None;
126
127     this.parseMessage = this.parseMessage.bind(this);
128     this.subscription = wsSubscribe(this.parseMessage);
129
130     // Only fetch the data if coming from another route
131     if (this.isoData.path == this.context.router.route.match.url) {
132       this.state.res = Some(this.isoData.routeData[0] as GetModlogResponse);
133
134       if (this.isoData.routeData[1]) {
135         // Getting the moderators
136         let communityRes = Some(
137           this.isoData.routeData[1] as GetCommunityResponse
138         );
139         this.state.communityMods = communityRes.map(c => c.moderators);
140       }
141
142       this.state.loading = false;
143     } else {
144       this.refetch();
145     }
146   }
147
148   componentDidMount() {
149     this.setupUserFilter();
150     this.setupModFilter();
151   }
152
153   componentWillUnmount() {
154     if (isBrowser()) {
155       this.subscription.unsubscribe();
156     }
157   }
158
159   buildCombined(res: GetModlogResponse): ModlogType[] {
160     let removed_posts: ModlogType[] = res.removed_posts.map(r => ({
161       id: r.mod_remove_post.id,
162       type_: ModlogActionType.ModRemovePost,
163       view: r,
164       moderator: r.moderator,
165       when_: r.mod_remove_post.when_,
166     }));
167
168     let locked_posts: ModlogType[] = res.locked_posts.map(r => ({
169       id: r.mod_lock_post.id,
170       type_: ModlogActionType.ModLockPost,
171       view: r,
172       moderator: r.moderator,
173       when_: r.mod_lock_post.when_,
174     }));
175
176     let stickied_posts: ModlogType[] = res.stickied_posts.map(r => ({
177       id: r.mod_sticky_post.id,
178       type_: ModlogActionType.ModStickyPost,
179       view: r,
180       moderator: r.moderator,
181       when_: r.mod_sticky_post.when_,
182     }));
183
184     let removed_comments: ModlogType[] = res.removed_comments.map(r => ({
185       id: r.mod_remove_comment.id,
186       type_: ModlogActionType.ModRemoveComment,
187       view: r,
188       moderator: r.moderator,
189       when_: r.mod_remove_comment.when_,
190     }));
191
192     let removed_communities: ModlogType[] = res.removed_communities.map(r => ({
193       id: r.mod_remove_community.id,
194       type_: ModlogActionType.ModRemoveCommunity,
195       view: r,
196       moderator: r.moderator,
197       when_: r.mod_remove_community.when_,
198     }));
199
200     let banned_from_community: ModlogType[] = res.banned_from_community.map(
201       r => ({
202         id: r.mod_ban_from_community.id,
203         type_: ModlogActionType.ModBanFromCommunity,
204         view: r,
205         moderator: r.moderator,
206         when_: r.mod_ban_from_community.when_,
207       })
208     );
209
210     let added_to_community: ModlogType[] = res.added_to_community.map(r => ({
211       id: r.mod_add_community.id,
212       type_: ModlogActionType.ModAddCommunity,
213       view: r,
214       moderator: r.moderator,
215       when_: r.mod_add_community.when_,
216     }));
217
218     let transferred_to_community: ModlogType[] =
219       res.transferred_to_community.map(r => ({
220         id: r.mod_transfer_community.id,
221         type_: ModlogActionType.ModTransferCommunity,
222         view: r,
223         moderator: r.moderator,
224         when_: r.mod_transfer_community.when_,
225       }));
226
227     let added: ModlogType[] = res.added.map(r => ({
228       id: r.mod_add.id,
229       type_: ModlogActionType.ModAdd,
230       view: r,
231       moderator: r.moderator,
232       when_: r.mod_add.when_,
233     }));
234
235     let banned: ModlogType[] = res.banned.map(r => ({
236       id: r.mod_ban.id,
237       type_: ModlogActionType.ModBan,
238       view: r,
239       moderator: r.moderator,
240       when_: r.mod_ban.when_,
241     }));
242
243     let purged_persons: ModlogType[] = res.admin_purged_persons.map(r => ({
244       id: r.admin_purge_person.id,
245       type_: ModlogActionType.AdminPurgePerson,
246       view: r,
247       moderator: r.admin,
248       when_: r.admin_purge_person.when_,
249     }));
250
251     let purged_communities: ModlogType[] = res.admin_purged_communities.map(
252       r => ({
253         id: r.admin_purge_community.id,
254         type_: ModlogActionType.AdminPurgeCommunity,
255         view: r,
256         moderator: r.admin,
257         when_: r.admin_purge_community.when_,
258       })
259     );
260
261     let purged_posts: ModlogType[] = res.admin_purged_posts.map(r => ({
262       id: r.admin_purge_post.id,
263       type_: ModlogActionType.AdminPurgePost,
264       view: r,
265       moderator: r.admin,
266       when_: r.admin_purge_post.when_,
267     }));
268
269     let purged_comments: ModlogType[] = res.admin_purged_comments.map(r => ({
270       id: r.admin_purge_comment.id,
271       type_: ModlogActionType.AdminPurgeComment,
272       view: r,
273       moderator: r.admin,
274       when_: r.admin_purge_comment.when_,
275     }));
276
277     let combined: ModlogType[] = [];
278
279     combined.push(...removed_posts);
280     combined.push(...locked_posts);
281     combined.push(...stickied_posts);
282     combined.push(...removed_comments);
283     combined.push(...removed_communities);
284     combined.push(...banned_from_community);
285     combined.push(...added_to_community);
286     combined.push(...transferred_to_community);
287     combined.push(...added);
288     combined.push(...banned);
289     combined.push(...purged_persons);
290     combined.push(...purged_communities);
291     combined.push(...purged_posts);
292     combined.push(...purged_comments);
293
294     // Sort them by time
295     combined.sort((a, b) => b.when_.localeCompare(a.when_));
296
297     return combined;
298   }
299
300   renderModlogType(i: ModlogType) {
301     switch (i.type_) {
302       case ModlogActionType.ModRemovePost: {
303         let mrpv = i.view as ModRemovePostView;
304         return [
305           mrpv.mod_remove_post.removed.unwrapOr(false)
306             ? "Removed "
307             : "Restored ",
308           <span>
309             Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link>
310           </span>,
311           mrpv.mod_remove_post.reason.match({
312             some: reason => <div>reason: {reason}</div>,
313             none: <></>,
314           }),
315         ];
316       }
317       case ModlogActionType.ModLockPost: {
318         let mlpv = i.view as ModLockPostView;
319         return [
320           mlpv.mod_lock_post.locked.unwrapOr(false) ? "Locked " : "Unlocked ",
321           <span>
322             Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
323           </span>,
324         ];
325       }
326       case ModlogActionType.ModStickyPost: {
327         let mspv = i.view as ModStickyPostView;
328         return [
329           mspv.mod_sticky_post.stickied.unwrapOr(false)
330             ? "Stickied "
331             : "Unstickied ",
332           <span>
333             Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
334           </span>,
335         ];
336       }
337       case ModlogActionType.ModRemoveComment: {
338         let mrc = i.view as ModRemoveCommentView;
339         return [
340           mrc.mod_remove_comment.removed.unwrapOr(false)
341             ? "Removed "
342             : "Restored ",
343           <span>
344             Comment{" "}
345             <Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
346               {mrc.comment.content}
347             </Link>
348           </span>,
349           <span>
350             {" "}
351             by <PersonListing person={mrc.commenter} />
352           </span>,
353           mrc.mod_remove_comment.reason.match({
354             some: reason => <div>reason: {reason}</div>,
355             none: <></>,
356           }),
357         ];
358       }
359       case ModlogActionType.ModRemoveCommunity: {
360         let mrco = i.view as ModRemoveCommunityView;
361         return [
362           mrco.mod_remove_community.removed.unwrapOr(false)
363             ? "Removed "
364             : "Restored ",
365           <span>
366             Community <CommunityLink community={mrco.community} />
367           </span>,
368           mrco.mod_remove_community.reason.match({
369             some: reason => <div>reason: {reason}</div>,
370             none: <></>,
371           }),
372           mrco.mod_remove_community.expires.match({
373             some: expires => (
374               <div>expires: {moment.utc(expires).fromNow()}</div>
375             ),
376             none: <></>,
377           }),
378         ];
379       }
380       case ModlogActionType.ModBanFromCommunity: {
381         let mbfc = i.view as ModBanFromCommunityView;
382         return [
383           <span>
384             {mbfc.mod_ban_from_community.banned.unwrapOr(false)
385               ? "Banned "
386               : "Unbanned "}{" "}
387           </span>,
388           <span>
389             <PersonListing person={mbfc.banned_person} />
390           </span>,
391           <span> from the community </span>,
392           <span>
393             <CommunityLink community={mbfc.community} />
394           </span>,
395           mbfc.mod_ban_from_community.reason.match({
396             some: reason => <div>reason: {reason}</div>,
397             none: <></>,
398           }),
399           mbfc.mod_ban_from_community.expires.match({
400             some: expires => (
401               <div>expires: {moment.utc(expires).fromNow()}</div>
402             ),
403             none: <></>,
404           }),
405         ];
406       }
407       case ModlogActionType.ModAddCommunity: {
408         let mac = i.view as ModAddCommunityView;
409         return [
410           <span>
411             {mac.mod_add_community.removed.unwrapOr(false)
412               ? "Removed "
413               : "Appointed "}{" "}
414           </span>,
415           <span>
416             <PersonListing person={mac.modded_person} />
417           </span>,
418           <span> as a mod to the community </span>,
419           <span>
420             <CommunityLink community={mac.community} />
421           </span>,
422         ];
423       }
424       case ModlogActionType.ModTransferCommunity: {
425         let mtc = i.view as ModTransferCommunityView;
426         return [
427           <span>
428             {mtc.mod_transfer_community.removed.unwrapOr(false)
429               ? "Removed "
430               : "Transferred "}{" "}
431           </span>,
432           <span>
433             <CommunityLink community={mtc.community} />
434           </span>,
435           <span> to </span>,
436           <span>
437             <PersonListing person={mtc.modded_person} />
438           </span>,
439         ];
440       }
441       case ModlogActionType.ModBan: {
442         let mb = i.view as ModBanView;
443         return [
444           <span>
445             {mb.mod_ban.banned.unwrapOr(false) ? "Banned " : "Unbanned "}{" "}
446           </span>,
447           <span>
448             <PersonListing person={mb.banned_person} />
449           </span>,
450           mb.mod_ban.reason.match({
451             some: reason => <div>reason: {reason}</div>,
452             none: <></>,
453           }),
454           mb.mod_ban.expires.match({
455             some: expires => (
456               <div>expires: {moment.utc(expires).fromNow()}</div>
457             ),
458             none: <></>,
459           }),
460         ];
461       }
462       case ModlogActionType.ModAdd: {
463         let ma = i.view as ModAddView;
464         return [
465           <span>
466             {ma.mod_add.removed.unwrapOr(false) ? "Removed " : "Appointed "}{" "}
467           </span>,
468           <span>
469             <PersonListing person={ma.modded_person} />
470           </span>,
471           <span> as an admin </span>,
472         ];
473       }
474       case ModlogActionType.AdminPurgePerson: {
475         let ap = i.view as AdminPurgePersonView;
476         return [
477           <span>Purged a Person</span>,
478           ap.admin_purge_person.reason.match({
479             some: reason => <div>reason: {reason}</div>,
480             none: <></>,
481           }),
482         ];
483       }
484       case ModlogActionType.AdminPurgeCommunity: {
485         let ap = i.view as AdminPurgeCommunityView;
486         return [
487           <span>Purged a Community</span>,
488           ap.admin_purge_community.reason.match({
489             some: reason => <div>reason: {reason}</div>,
490             none: <></>,
491           }),
492         ];
493       }
494       case ModlogActionType.AdminPurgePost: {
495         let ap = i.view as AdminPurgePostView;
496         return [
497           <span>Purged a Post from from </span>,
498           <CommunityLink community={ap.community} />,
499           ap.admin_purge_post.reason.match({
500             some: reason => <div>reason: {reason}</div>,
501             none: <></>,
502           }),
503         ];
504       }
505       case ModlogActionType.AdminPurgeComment: {
506         let ap = i.view as AdminPurgeCommentView;
507         return [
508           <span>
509             Purged a Comment from{" "}
510             <Link to={`/post/${ap.post.id}`}>{ap.post.name}</Link>
511           </span>,
512           ap.admin_purge_comment.reason.match({
513             some: reason => <div>reason: {reason}</div>,
514             none: <></>,
515           }),
516         ];
517       }
518       default:
519         return <div />;
520     }
521   }
522
523   combined() {
524     let combined = this.state.res.map(this.buildCombined).unwrapOr([]);
525
526     return (
527       <tbody>
528         {combined.map(i => (
529           <tr>
530             <td>
531               <MomentTime published={i.when_} updated={None} />
532             </td>
533             <td>
534               {this.amAdminOrMod ? (
535                 <PersonListing person={i.moderator.unwrap()} />
536               ) : (
537                 <div>{this.modOrAdminText(i.moderator)}</div>
538               )}
539             </td>
540             <td>{this.renderModlogType(i)}</td>
541           </tr>
542         ))}
543       </tbody>
544     );
545   }
546
547   get amAdminOrMod(): boolean {
548     return (
549       amAdmin(Some(this.state.siteRes.admins)) ||
550       amMod(this.state.communityMods)
551     );
552   }
553
554   modOrAdminText(person: Option<PersonSafe>): string {
555     return person.match({
556       some: res => this.isoData.site_res.admins.map(a => a.person.id).includes(res.id) ? i18n.t("admin") : i18n.t("mod"),
557       none: i18n.t("mod"),
558     });
559   }
560
561   get documentTitle(): string {
562     return this.state.siteRes.site_view.match({
563       some: siteView => `Modlog - ${siteView.site.name}`,
564       none: "",
565     });
566   }
567
568   render() {
569     return (
570       <div className="container">
571         <HtmlTags
572           title={this.documentTitle}
573           path={this.context.router.route.match.url}
574           description={None}
575           image={None}
576         />
577         {this.state.loading ? (
578           <h5>
579             <Spinner large />
580           </h5>
581         ) : (
582           <div>
583             <h5>
584               {this.state.communityName.isSome() && (
585                 <Link
586                   className="text-body"
587                   to={`/c/${this.state.communityName}`}
588                 >
589                   /c/{this.state.communityName}{" "}
590                 </Link>
591               )}
592               <span>{i18n.t("modlog")}</span>
593             </h5>
594             <form className="form-inline mr-2">
595               <select
596                 value={this.state.filter_action}
597                 onChange={linkEvent(this, this.handleFilterActionChange)}
598                 className="custom-select col-4 mb-2"
599                 aria-label="action">
600                 <option disabled aria-hidden="true">{i18n.t("filter_by_action")}</option>
601                 <option value={ModlogActionType.All}>{i18n.t("all")}</option>
602                 <option value={ModlogActionType.ModRemovePost}>Removing Posts</option>
603                 <option value={ModlogActionType.ModLockPost}>Locking Posts</option>
604                 <option value={ModlogActionType.ModStickyPost}>Stickying Posts</option>
605                 <option value={ModlogActionType.ModRemoveComment}>Removing Comments</option>
606                 <option value={ModlogActionType.ModRemoveCommunity}>Removing Communities</option>
607                 <option value={ModlogActionType.ModBanFromCommunity}>Banning From Communities</option>
608                 <option value={ModlogActionType.ModAddCommunity}>Adding Mod to Community</option>
609                 <option value={ModlogActionType.ModTransferCommunity}>Transfering Communities</option>
610                 <option value={ModlogActionType.ModAdd}>Adding Mod to Site</option>
611                 <option value={ModlogActionType.ModBan}>Banning From Site</option>
612               </select>
613               {
614                 this.state.siteRes.site_view.match({
615                   some: site_view => !site_view.site.hide_modlog_mod_names.unwrapOr(false) &&  (
616                     <select
617                       id="filter-mod"
618                       value={toUndefined(this.state.filter_mod)}>
619                       <option>{i18n.t("filter_by_mod")}</option>
620                     </select>
621                   ),
622                   none: <></>,
623                 })
624               }
625               <select
626                 id="filter-user"
627                 value={toUndefined(this.state.filter_user)}>
628                 <option>{i18n.t("filter_by_user")}</option>
629               </select> 
630             </form>
631             <div className="table-responsive">
632               <table id="modlog_table" className="table table-sm table-hover">
633                 <thead className="pointer">
634                   <tr>
635                     <th> {i18n.t("time")}</th>
636                     <th>{i18n.t("mod")}</th>
637                     <th>{i18n.t("action")}</th>
638                   </tr>
639                 </thead>
640                 {this.combined()}
641               </table>
642               <Paginator
643                 page={this.state.page}
644                 onChange={this.handlePageChange}
645               />
646             </div>
647           </div>
648         )}
649       </div>
650     );
651   }
652
653   handleFilterActionChange(i: Modlog, event: any) {
654     i.setState({ filter_action: event.target.value });
655     i.refetch();
656   }
657
658   handlePageChange(val: number) {
659     this.setState({ page: val });
660     this.refetch();
661   }
662
663   refetch() {
664     let modlogForm = new GetModlog({
665       community_id: this.state.communityId,
666       page: Some(this.state.page),
667       limit: Some(fetchLimit),
668       auth: auth(false).ok(),
669       type_: this.state.filter_action,
670       other_person_id: this.state.filter_user,
671       mod_person_id: this.state.filter_mod,
672     });
673     WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
674
675     this.state.communityId.match({
676       some: id => {
677         let communityForm = new GetCommunity({
678           id: Some(id),
679           name: None,
680           auth: auth(false).ok(),
681         });
682         WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
683       },
684       none: void 0,
685     });
686   }
687
688   setupUserFilter() {
689     if (isBrowser()) {
690       let selectId: any = document.getElementById("filter-user");
691       if (selectId) {
692         this.userChoices = new Choices(selectId, choicesModLogConfig);
693         this.userChoices.passedElement.element.addEventListener(
694           "choice",
695           (e: any) => {
696             this.state.filter_user = Some(Number(e.detail.choice.value));
697             this.setState(this.state);
698             this.refetch();
699           },
700           false
701         );
702         this.userChoices.passedElement.element.addEventListener(
703           "search",
704           debounce(async (e: any) => {
705             try {
706               let users = (await fetchUsers(e.detail.value)).users;
707               this.userChoices.setChoices(
708                 users.map(u => {return {
709                   value: u.person.id.toString(),
710                   label: u.person.name,
711                 }}),
712                 "value",
713                 "label",
714                 true
715               );
716             } catch (err) {
717               console.log(err);
718             }
719           }),
720           false
721         );
722       }
723     }
724   }
725
726   setupModFilter() {
727     if (isBrowser()) {
728       let selectId: any = document.getElementById("filter-mod");
729       if (selectId) {
730         this.modChoices = new Choices(selectId, choicesModLogConfig);
731         this.modChoices.passedElement.element.addEventListener(
732           "choice",
733           (e: any) => {
734             this.state.filter_mod = Some(Number(e.detail.choice.value));
735             this.setState(this.state);
736             this.refetch();
737           },
738           false
739         );
740         this.modChoices.passedElement.element.addEventListener(
741           "search",
742           debounce(async (e: any) => {
743             try {
744               let mods = (await fetchUsers(e.detail.value)).users;
745               this.modChoices.setChoices(
746                 mods.map(u => {return {
747                   value: u.person.id.toString(),
748                   label: u.person.name,
749                 }}),
750                 "value",
751                 "label",
752                 true
753               );
754             } catch (err) {
755               console.log(err);
756             }
757           }),
758           false
759         );
760       }
761     }
762   }
763
764   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
765     let pathSplit = req.path.split("/");
766     let communityId = Some(pathSplit[3]).map(Number);
767     let promises: Promise<any>[] = [];
768
769     let modlogForm = new GetModlog({
770       page: Some(1),
771       limit: Some(fetchLimit),
772       community_id: communityId,
773       mod_person_id: None,
774       auth: req.auth,
775       type_: ModlogActionType.All,
776       other_person_id: None
777     });
778
779     promises.push(req.client.getModlog(modlogForm));
780
781     if (communityId.isSome()) {
782       let communityForm = new GetCommunity({
783         id: communityId,
784         name: None,
785         auth: req.auth,
786       });
787       promises.push(req.client.getCommunity(communityForm));
788     } else {
789       promises.push(Promise.resolve());
790     }
791     return promises;
792   }
793
794   parseMessage(msg: any) {
795     let op = wsUserOp(msg);
796     console.log(msg);
797     if (msg.error) {
798       toast(i18n.t(msg.error), "danger");
799       return;
800     } else if (op == UserOperation.GetModlog) {
801       let data = wsJsonToRes<GetModlogResponse>(msg, GetModlogResponse);
802       this.state.loading = false;
803       window.scrollTo(0, 0);
804       this.state.res = Some(data);
805       this.setState(this.state);
806     } else if (op == UserOperation.GetCommunity) {
807       let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
808       this.state.communityMods = Some(data.moderators);
809       this.state.communityName = Some(data.community_view.community.name);
810     }
811   }
812 }