]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
Merge branch 'main' into more_accessibility
[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 { UserListing } from './user-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       case ModlogEnum.ModLockPost:
225         let mlpv = i.view as ModLockPostView;
226         return [
227           mlpv.mod_lock_post.locked ? 'Locked ' : 'Unlocked ',
228           <span>
229             Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
230           </span>,
231         ];
232       case ModlogEnum.ModStickyPost:
233         let mspv = i.view as ModStickyPostView;
234         return [
235           mspv.mod_sticky_post.stickied ? 'Stickied ' : 'Unstickied ',
236           <span>
237             Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
238           </span>,
239         ];
240       case ModlogEnum.ModRemoveComment:
241         let mrc = i.view as ModRemoveCommentView;
242         return [
243           mrc.mod_remove_comment.removed ? 'Removed ' : 'Restored ',
244           <span>
245             Comment{' '}
246             <Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
247               {mrc.comment.content}
248             </Link>
249           </span>,
250           <span>
251             {' '}
252             by <UserListing user={mrc.commenter} />
253           </span>,
254           mrc.mod_remove_comment.reason &&
255             ` reason: ${mrc.mod_remove_comment.reason}`,
256         ];
257       case ModlogEnum.ModRemoveCommunity:
258         let mrco = i.view as ModRemoveCommunityView;
259         return [
260           mrco.mod_remove_community.removed ? 'Removed ' : 'Restored ',
261           <span>
262             Community <CommunityLink community={mrco.community} />
263           </span>,
264           mrco.mod_remove_community.reason &&
265             ` reason: ${mrco.mod_remove_community.reason}`,
266           mrco.mod_remove_community.expires &&
267             ` expires: ${moment
268               .utc(mrco.mod_remove_community.expires)
269               .fromNow()}`,
270         ];
271       case ModlogEnum.ModBanFromCommunity:
272         let mbfc = i.view as ModBanFromCommunityView;
273         return [
274           <span>
275             {mbfc.mod_ban_from_community.banned ? 'Banned ' : 'Unbanned '}{' '}
276           </span>,
277           <span>
278             <UserListing user={mbfc.banned_user} />
279           </span>,
280           <span> from the community </span>,
281           <span>
282             <CommunityLink community={mbfc.community} />
283           </span>,
284           <div>
285             {mbfc.mod_ban_from_community.reason &&
286               ` reason: ${mbfc.mod_ban_from_community.reason}`}
287           </div>,
288           <div>
289             {mbfc.mod_ban_from_community.expires &&
290               ` expires: ${moment
291                 .utc(mbfc.mod_ban_from_community.expires)
292                 .fromNow()}`}
293           </div>,
294         ];
295       case ModlogEnum.ModAddCommunity:
296         let mac = i.view as ModAddCommunityView;
297         return [
298           <span>
299             {mac.mod_add_community.removed ? 'Removed ' : 'Appointed '}{' '}
300           </span>,
301           <span>
302             <UserListing user={mac.modded_user} />
303           </span>,
304           <span> as a mod to the community </span>,
305           <span>
306             <CommunityLink community={mac.community} />
307           </span>,
308         ];
309       case ModlogEnum.ModBan:
310         let mb = i.view as ModBanView;
311         return [
312           <span>{mb.mod_ban.banned ? 'Banned ' : 'Unbanned '} </span>,
313           <span>
314             <UserListing user={mb.banned_user} />
315           </span>,
316           <div>{mb.mod_ban.reason && ` reason: ${mb.mod_ban.reason}`}</div>,
317           <div>
318             {mb.mod_ban.expires &&
319               ` expires: ${moment.utc(mb.mod_ban.expires).fromNow()}`}
320           </div>,
321         ];
322       case ModlogEnum.ModAdd:
323         let ma = i.view as ModAddView;
324         return [
325           <span>{ma.mod_add.removed ? 'Removed ' : 'Appointed '} </span>,
326           <span>
327             <UserListing user={ma.modded_user} />
328           </span>,
329           <span> as an admin </span>,
330         ];
331       default:
332         return <div />;
333     }
334   }
335
336   combined() {
337     let combined = this.buildCombined(this.state.res);
338
339     return (
340       <tbody>
341         {combined.map(i => (
342           <tr>
343             <td>
344               <MomentTime data={i} />
345             </td>
346             <td>
347               <UserListing user={i.view.moderator} />
348             </td>
349             <td>{this.renderModlogType(i)}</td>
350           </tr>
351         ))}
352       </tbody>
353     );
354   }
355
356   get documentTitle(): string {
357     return `Modlog - ${this.state.site_view.site.name}`;
358   }
359
360   render() {
361     return (
362       <div class="container">
363         <HtmlTags
364           title={this.documentTitle}
365           path={this.context.router.route.match.url}
366         />
367         {this.state.loading ? (
368           <h5>
369             <Spinner />
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.send(wsClient.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 }