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