]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
Merge pull request #288 from LemmyNet/hide_modlog_names
[lemmy-ui.git] / src / shared / components / modlog.tsx
1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
3 import { Subscription } from "rxjs";
4 import {
5   UserOperation,
6   GetModlog,
7   GetModlogResponse,
8   SiteView,
9   ModRemovePostView,
10   ModLockPostView,
11   ModStickyPostView,
12   ModRemoveCommentView,
13   ModRemoveCommunityView,
14   ModBanFromCommunityView,
15   ModBanView,
16   ModAddCommunityView,
17   ModAddView,
18   GetCommunity,
19   GetCommunityResponse,
20   CommunityModeratorView,
21 } from "lemmy-js-client";
22 import { WebSocketService, UserService } from "../services";
23 import {
24   wsJsonToRes,
25   fetchLimit,
26   toast,
27   setIsoData,
28   wsSubscribe,
29   isBrowser,
30   wsUserOp,
31   wsClient,
32 } from "../utils";
33 import { MomentTime } from "./moment-time";
34 import { HtmlTags } from "./html-tags";
35 import moment from "moment";
36 import { i18n } from "../i18next";
37 import { InitialFetchRequest } from "shared/interfaces";
38 import { PersonListing } from "./person-listing";
39 import { CommunityLink } from "./community-link";
40 import { Spinner } from "./icon";
41
42 enum ModlogEnum {
43   ModRemovePost,
44   ModLockPost,
45   ModStickyPost,
46   ModRemoveComment,
47   ModRemoveCommunity,
48   ModBanFromCommunity,
49   ModAddCommunity,
50   ModAdd,
51   ModBan,
52 }
53
54 type ModlogType = {
55   id: number;
56   type_: ModlogEnum;
57   view:
58     | ModRemovePostView
59     | ModLockPostView
60     | ModStickyPostView
61     | ModRemoveCommentView
62     | ModRemoveCommunityView
63     | ModBanFromCommunityView
64     | ModBanView
65     | ModAddCommunityView
66     | ModAddView;
67   when_: string;
68 };
69
70 interface ModlogState {
71   res: GetModlogResponse;
72   communityId?: number;
73   communityName?: string;
74   communityMods?: CommunityModeratorView[];
75   page: number;
76   site_view: SiteView;
77   loading: boolean;
78 }
79
80 export class Modlog extends Component<any, ModlogState> {
81   private isoData = setIsoData(this.context);
82   private subscription: Subscription;
83   private emptyState: ModlogState = {
84     res: {
85       removed_posts: [],
86       locked_posts: [],
87       stickied_posts: [],
88       removed_comments: [],
89       removed_communities: [],
90       banned_from_community: [],
91       banned: [],
92       added_to_community: [],
93       added: [],
94     },
95     page: 1,
96     loading: true,
97     site_view: this.isoData.site_res.site_view,
98   };
99
100   constructor(props: any, context: any) {
101     super(props, context);
102
103     this.state = this.emptyState;
104     this.state.communityId = this.props.match.params.community_id
105       ? Number(this.props.match.params.community_id)
106       : undefined;
107
108     this.parseMessage = this.parseMessage.bind(this);
109     this.subscription = wsSubscribe(this.parseMessage);
110
111     // Only fetch the data if coming from another route
112     if (this.isoData.path == this.context.router.route.match.url) {
113       let data = this.isoData.routeData[0];
114       this.state.res = data;
115       this.state.loading = false;
116
117       // Getting the moderators
118       if (this.isoData.routeData[1]) {
119         this.state.communityMods = this.isoData.routeData[1].moderators;
120       }
121     } else {
122       this.refetch();
123     }
124   }
125
126   componentWillUnmount() {
127     if (isBrowser()) {
128       this.subscription.unsubscribe();
129     }
130   }
131
132   buildCombined(res: GetModlogResponse): ModlogType[] {
133     let removed_posts: ModlogType[] = res.removed_posts.map(r => ({
134       id: r.mod_remove_post.id,
135       type_: ModlogEnum.ModRemovePost,
136       view: r,
137       when_: r.mod_remove_post.when_,
138     }));
139
140     let locked_posts: ModlogType[] = res.locked_posts.map(r => ({
141       id: r.mod_lock_post.id,
142       type_: ModlogEnum.ModLockPost,
143       view: r,
144       when_: r.mod_lock_post.when_,
145     }));
146
147     let stickied_posts: ModlogType[] = res.stickied_posts.map(r => ({
148       id: r.mod_sticky_post.id,
149       type_: ModlogEnum.ModStickyPost,
150       view: r,
151       when_: r.mod_sticky_post.when_,
152     }));
153
154     let removed_comments: ModlogType[] = res.removed_comments.map(r => ({
155       id: r.mod_remove_comment.id,
156       type_: ModlogEnum.ModRemoveComment,
157       view: r,
158       when_: r.mod_remove_comment.when_,
159     }));
160
161     let removed_communities: ModlogType[] = res.removed_communities.map(r => ({
162       id: r.mod_remove_community.id,
163       type_: ModlogEnum.ModRemoveCommunity,
164       view: r,
165       when_: r.mod_remove_community.when_,
166     }));
167
168     let banned_from_community: ModlogType[] = res.banned_from_community.map(
169       r => ({
170         id: r.mod_ban_from_community.id,
171         type_: ModlogEnum.ModBanFromCommunity,
172         view: r,
173         when_: r.mod_ban_from_community.when_,
174       })
175     );
176
177     let added_to_community: ModlogType[] = res.added_to_community.map(r => ({
178       id: r.mod_add_community.id,
179       type_: ModlogEnum.ModAddCommunity,
180       view: r,
181       when_: r.mod_add_community.when_,
182     }));
183
184     let added: ModlogType[] = res.added.map(r => ({
185       id: r.mod_add.id,
186       type_: ModlogEnum.ModAdd,
187       view: r,
188       when_: r.mod_add.when_,
189     }));
190
191     let banned: ModlogType[] = res.banned.map(r => ({
192       id: r.mod_ban.id,
193       type_: ModlogEnum.ModBan,
194       view: r,
195       when_: r.mod_ban.when_,
196     }));
197
198     let combined: ModlogType[] = [];
199
200     combined.push(...removed_posts);
201     combined.push(...locked_posts);
202     combined.push(...stickied_posts);
203     combined.push(...removed_comments);
204     combined.push(...removed_communities);
205     combined.push(...banned_from_community);
206     combined.push(...added_to_community);
207     combined.push(...added);
208     combined.push(...banned);
209
210     if (this.state.communityId && combined.length > 0) {
211       this.state.communityName = (combined[0]
212         .view as ModRemovePostView).community.name;
213     }
214
215     // Sort them by time
216     combined.sort((a, b) => b.when_.localeCompare(a.when_));
217
218     return combined;
219   }
220
221   renderModlogType(i: ModlogType) {
222     switch (i.type_) {
223       case ModlogEnum.ModRemovePost: {
224         let mrpv = i.view as ModRemovePostView;
225         return [
226           mrpv.mod_remove_post.removed ? "Removed " : "Restored ",
227           <span>
228             Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link>
229           </span>,
230           mrpv.mod_remove_post.reason &&
231             ` reason: ${mrpv.mod_remove_post.reason}`,
232         ];
233       }
234       case ModlogEnum.ModLockPost: {
235         let mlpv = i.view as ModLockPostView;
236         return [
237           mlpv.mod_lock_post.locked ? "Locked " : "Unlocked ",
238           <span>
239             Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
240           </span>,
241         ];
242       }
243       case ModlogEnum.ModStickyPost: {
244         let mspv = i.view as ModStickyPostView;
245         return [
246           mspv.mod_sticky_post.stickied ? "Stickied " : "Unstickied ",
247           <span>
248             Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
249           </span>,
250         ];
251       }
252       case ModlogEnum.ModRemoveComment: {
253         let mrc = i.view as ModRemoveCommentView;
254         return [
255           mrc.mod_remove_comment.removed ? "Removed " : "Restored ",
256           <span>
257             Comment{" "}
258             <Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
259               {mrc.comment.content}
260             </Link>
261           </span>,
262           <span>
263             {" "}
264             by <PersonListing person={mrc.commenter} />
265           </span>,
266           mrc.mod_remove_comment.reason &&
267             ` reason: ${mrc.mod_remove_comment.reason}`,
268         ];
269       }
270       case ModlogEnum.ModRemoveCommunity: {
271         let mrco = i.view as ModRemoveCommunityView;
272         return [
273           mrco.mod_remove_community.removed ? "Removed " : "Restored ",
274           <span>
275             Community <CommunityLink community={mrco.community} />
276           </span>,
277           mrco.mod_remove_community.reason &&
278             ` reason: ${mrco.mod_remove_community.reason}`,
279           mrco.mod_remove_community.expires &&
280             ` expires: ${moment
281               .utc(mrco.mod_remove_community.expires)
282               .fromNow()}`,
283         ];
284       }
285       case ModlogEnum.ModBanFromCommunity: {
286         let mbfc = i.view as ModBanFromCommunityView;
287         return [
288           <span>
289             {mbfc.mod_ban_from_community.banned ? "Banned " : "Unbanned "}{" "}
290           </span>,
291           <span>
292             <PersonListing person={mbfc.banned_person} />
293           </span>,
294           <span> from the community </span>,
295           <span>
296             <CommunityLink community={mbfc.community} />
297           </span>,
298           <div>
299             {mbfc.mod_ban_from_community.reason &&
300               ` reason: ${mbfc.mod_ban_from_community.reason}`}
301           </div>,
302           <div>
303             {mbfc.mod_ban_from_community.expires &&
304               ` expires: ${moment
305                 .utc(mbfc.mod_ban_from_community.expires)
306                 .fromNow()}`}
307           </div>,
308         ];
309       }
310       case ModlogEnum.ModAddCommunity: {
311         let mac = i.view as ModAddCommunityView;
312         return [
313           <span>
314             {mac.mod_add_community.removed ? "Removed " : "Appointed "}{" "}
315           </span>,
316           <span>
317             <PersonListing person={mac.modded_person} />
318           </span>,
319           <span> as a mod to the community </span>,
320           <span>
321             <CommunityLink community={mac.community} />
322           </span>,
323         ];
324       }
325       case ModlogEnum.ModBan: {
326         let mb = i.view as ModBanView;
327         return [
328           <span>{mb.mod_ban.banned ? "Banned " : "Unbanned "} </span>,
329           <span>
330             <PersonListing person={mb.banned_person} />
331           </span>,
332           <div>{mb.mod_ban.reason && ` reason: ${mb.mod_ban.reason}`}</div>,
333           <div>
334             {mb.mod_ban.expires &&
335               ` expires: ${moment.utc(mb.mod_ban.expires).fromNow()}`}
336           </div>,
337         ];
338       }
339       case ModlogEnum.ModAdd: {
340         let ma = i.view as ModAddView;
341         return [
342           <span>{ma.mod_add.removed ? "Removed " : "Appointed "} </span>,
343           <span>
344             <PersonListing person={ma.modded_person} />
345           </span>,
346           <span> as an admin </span>,
347         ];
348       }
349       default:
350         return <div />;
351     }
352   }
353
354   combined() {
355     let combined = this.buildCombined(this.state.res);
356
357     return (
358       <tbody>
359         {combined.map(i => (
360           <tr>
361             <td>
362               <MomentTime data={i} />
363             </td>
364             <td>
365               {this.isAdminOrMod ? (
366                 <PersonListing person={i.view.moderator} />
367               ) : (
368                 <div>{i18n.t("mod")}</div>
369               )}
370             </td>
371             <td>{this.renderModlogType(i)}</td>
372           </tr>
373         ))}
374       </tbody>
375     );
376   }
377
378   get isAdminOrMod(): boolean {
379     let isAdmin =
380       UserService.Instance.localUserView &&
381       this.isoData.site_res.admins
382         .map(a => a.person.id)
383         .includes(UserService.Instance.localUserView.person.id);
384     let isMod =
385       UserService.Instance.localUserView &&
386       this.state.communityMods &&
387       this.state.communityMods
388         .map(m => m.moderator.id)
389         .includes(UserService.Instance.localUserView.person.id);
390     return isAdmin || isMod;
391   }
392
393   get documentTitle(): string {
394     return `Modlog - ${this.state.site_view.site.name}`;
395   }
396
397   render() {
398     return (
399       <div class="container">
400         <HtmlTags
401           title={this.documentTitle}
402           path={this.context.router.route.match.url}
403         />
404         {this.state.loading ? (
405           <h5>
406             <Spinner />
407           </h5>
408         ) : (
409           <div>
410             <h5>
411               {this.state.communityName && (
412                 <Link
413                   className="text-body"
414                   to={`/c/${this.state.communityName}`}
415                 >
416                   /c/{this.state.communityName}{" "}
417                 </Link>
418               )}
419               <span>{i18n.t("modlog")}</span>
420             </h5>
421             <div class="table-responsive">
422               <table id="modlog_table" class="table table-sm table-hover">
423                 <thead class="pointer">
424                   <tr>
425                     <th> {i18n.t("time")}</th>
426                     <th>{i18n.t("mod")}</th>
427                     <th>{i18n.t("action")}</th>
428                   </tr>
429                 </thead>
430                 {this.combined()}
431               </table>
432               {this.paginator()}
433             </div>
434           </div>
435         )}
436       </div>
437     );
438   }
439
440   paginator() {
441     return (
442       <div class="mt-2">
443         {this.state.page > 1 && (
444           <button
445             class="btn btn-secondary mr-1"
446             onClick={linkEvent(this, this.prevPage)}
447           >
448             {i18n.t("prev")}
449           </button>
450         )}
451         <button
452           class="btn btn-secondary"
453           onClick={linkEvent(this, this.nextPage)}
454         >
455           {i18n.t("next")}
456         </button>
457       </div>
458     );
459   }
460
461   nextPage(i: Modlog) {
462     i.state.page++;
463     i.setState(i.state);
464     i.refetch();
465   }
466
467   prevPage(i: Modlog) {
468     i.state.page--;
469     i.setState(i.state);
470     i.refetch();
471   }
472
473   refetch() {
474     let modlogForm: GetModlog = {
475       community_id: this.state.communityId,
476       page: this.state.page,
477       limit: fetchLimit,
478     };
479     WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
480
481     if (this.state.communityId) {
482       let communityForm: GetCommunity = {
483         id: this.state.communityId,
484         name: this.state.communityName,
485       };
486       WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
487     }
488   }
489
490   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
491     let pathSplit = req.path.split("/");
492     let communityId = pathSplit[3];
493     let promises: Promise<any>[] = [];
494
495     let modlogForm: GetModlog = {
496       page: 1,
497       limit: fetchLimit,
498     };
499
500     if (communityId) {
501       modlogForm.community_id = Number(communityId);
502     }
503
504     promises.push(req.client.getModlog(modlogForm));
505
506     if (communityId) {
507       let communityForm: GetCommunity = {
508         id: Number(communityId),
509       };
510       promises.push(req.client.getCommunity(communityForm));
511     }
512     return promises;
513   }
514
515   parseMessage(msg: any) {
516     let op = wsUserOp(msg);
517     console.log(msg);
518     if (msg.error) {
519       toast(i18n.t(msg.error), "danger");
520       return;
521     } else if (op == UserOperation.GetModlog) {
522       let data = wsJsonToRes<GetModlogResponse>(msg).data;
523       this.state.loading = false;
524       window.scrollTo(0, 0);
525       this.state.res = data;
526       this.setState(this.state);
527     } else if (op == UserOperation.GetCommunity) {
528       let data = wsJsonToRes<GetCommunityResponse>(msg).data;
529       this.state.communityMods = data.moderators;
530     }
531   }
532 }