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