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