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