]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
a22c20b8f61415c533337b6c80bc53a747efff03
[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             {
585               this.state.communityName.match({
586                 some: name => (
587                   <Link
588                     className="text-body"
589                     to={`/c/${name}`}
590                   >
591                     /c/{name}{" "}
592                   </Link>
593                 ),
594                 none: <></>,
595               })
596             }
597               <span>{i18n.t("modlog")}</span>
598             </h5>
599             <form className="form-inline mr-2">
600               <select
601                 value={this.state.filter_action}
602                 onChange={linkEvent(this, this.handleFilterActionChange)}
603                 className="custom-select col-4 mb-2"
604                 aria-label="action">
605                 <option disabled aria-hidden="true">{i18n.t("filter_by_action")}</option>
606                 <option value={ModlogActionType.All}>{i18n.t("all")}</option>
607                 <option value={ModlogActionType.ModRemovePost}>Removing Posts</option>
608                 <option value={ModlogActionType.ModLockPost}>Locking Posts</option>
609                 <option value={ModlogActionType.ModStickyPost}>Stickying Posts</option>
610                 <option value={ModlogActionType.ModRemoveComment}>Removing Comments</option>
611                 <option value={ModlogActionType.ModRemoveCommunity}>Removing Communities</option>
612                 <option value={ModlogActionType.ModBanFromCommunity}>Banning From Communities</option>
613                 <option value={ModlogActionType.ModAddCommunity}>Adding Mod to Community</option>
614                 <option value={ModlogActionType.ModTransferCommunity}>Transfering Communities</option>
615                 <option value={ModlogActionType.ModAdd}>Adding Mod to Site</option>
616                 <option value={ModlogActionType.ModBan}>Banning From Site</option>
617               </select>
618               {
619                 this.state.siteRes.site_view.match({
620                   some: site_view => !site_view.site.hide_modlog_mod_names.unwrapOr(false) &&  (
621                     <select
622                       id="filter-mod"
623                       value={toUndefined(this.state.filter_mod)}>
624                       <option>{i18n.t("filter_by_mod")}</option>
625                     </select>
626                   ),
627                   none: <></>,
628                 })
629               }
630               <select
631                 id="filter-user"
632                 value={toUndefined(this.state.filter_user)}>
633                 <option>{i18n.t("filter_by_user")}</option>
634               </select> 
635             </form>
636             <div className="table-responsive">
637               <table id="modlog_table" className="table table-sm table-hover">
638                 <thead className="pointer">
639                   <tr>
640                     <th> {i18n.t("time")}</th>
641                     <th>{i18n.t("mod")}</th>
642                     <th>{i18n.t("action")}</th>
643                   </tr>
644                 </thead>
645                 {this.combined()}
646               </table>
647               <Paginator
648                 page={this.state.page}
649                 onChange={this.handlePageChange}
650               />
651             </div>
652           </div>
653         )}
654       </div>
655     );
656   }
657
658   handleFilterActionChange(i: Modlog, event: any) {
659     i.setState({ filter_action: event.target.value });
660     i.refetch();
661   }
662
663   handlePageChange(val: number) {
664     this.setState({ page: val });
665     this.refetch();
666   }
667
668   refetch() {
669     let modlogForm = new GetModlog({
670       community_id: this.state.communityId,
671       page: Some(this.state.page),
672       limit: Some(fetchLimit),
673       auth: auth(false).ok(),
674       type_: this.state.filter_action,
675       other_person_id: this.state.filter_user,
676       mod_person_id: this.state.filter_mod,
677     });
678     WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
679
680     this.state.communityId.match({
681       some: id => {
682         let communityForm = new GetCommunity({
683           id: Some(id),
684           name: None,
685           auth: auth(false).ok(),
686         });
687         WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
688       },
689       none: void 0,
690     });
691   }
692
693   setupUserFilter() {
694     if (isBrowser()) {
695       let selectId: any = document.getElementById("filter-user");
696       if (selectId) {
697         this.userChoices = new Choices(selectId, choicesModLogConfig);
698         this.userChoices.passedElement.element.addEventListener(
699           "choice",
700           (e: any) => {
701             this.state.filter_user = Some(Number(e.detail.choice.value));
702             this.setState(this.state);
703             this.refetch();
704           },
705           false
706         );
707         this.userChoices.passedElement.element.addEventListener(
708           "search",
709           debounce(async (e: any) => {
710             try {
711               let users = (await fetchUsers(e.detail.value)).users;
712               this.userChoices.setChoices(
713                 users.map(u => {return {
714                   value: u.person.id.toString(),
715                   label: u.person.name,
716                 }}),
717                 "value",
718                 "label",
719                 true
720               );
721             } catch (err) {
722               console.log(err);
723             }
724           }),
725           false
726         );
727       }
728     }
729   }
730
731   setupModFilter() {
732     if (isBrowser()) {
733       let selectId: any = document.getElementById("filter-mod");
734       if (selectId) {
735         this.modChoices = new Choices(selectId, choicesModLogConfig);
736         this.modChoices.passedElement.element.addEventListener(
737           "choice",
738           (e: any) => {
739             this.state.filter_mod = Some(Number(e.detail.choice.value));
740             this.setState(this.state);
741             this.refetch();
742           },
743           false
744         );
745         this.modChoices.passedElement.element.addEventListener(
746           "search",
747           debounce(async (e: any) => {
748             try {
749               let mods = (await fetchUsers(e.detail.value)).users;
750               this.modChoices.setChoices(
751                 mods.map(u => {return {
752                   value: u.person.id.toString(),
753                   label: u.person.name,
754                 }}),
755                 "value",
756                 "label",
757                 true
758               );
759             } catch (err) {
760               console.log(err);
761             }
762           }),
763           false
764         );
765       }
766     }
767   }
768
769   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
770     let pathSplit = req.path.split("/");
771     let communityId = Some(pathSplit[3]).map(Number);
772     let promises: Promise<any>[] = [];
773
774     let modlogForm = new GetModlog({
775       page: Some(1),
776       limit: Some(fetchLimit),
777       community_id: communityId,
778       mod_person_id: None,
779       auth: req.auth,
780       type_: ModlogActionType.All,
781       other_person_id: None
782     });
783
784     promises.push(req.client.getModlog(modlogForm));
785
786     if (communityId.isSome()) {
787       let communityForm = new GetCommunity({
788         id: communityId,
789         name: None,
790         auth: req.auth,
791       });
792       promises.push(req.client.getCommunity(communityForm));
793     } else {
794       promises.push(Promise.resolve());
795     }
796     return promises;
797   }
798
799   parseMessage(msg: any) {
800     let op = wsUserOp(msg);
801     console.log(msg);
802     if (msg.error) {
803       toast(i18n.t(msg.error), "danger");
804       return;
805     } else if (op == UserOperation.GetModlog) {
806       let data = wsJsonToRes<GetModlogResponse>(msg, GetModlogResponse);
807       this.state.loading = false;
808       window.scrollTo(0, 0);
809       this.state.res = Some(data);
810       this.setState(this.state);
811     } else if (op == UserOperation.GetCommunity) {
812       let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
813       this.state.communityMods = Some(data.moderators);
814       this.state.communityName = Some(data.community_view.community.name);
815     }
816   }
817 }