1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
5 AdminPurgeCommunityView,
8 CommunityModeratorView,
16 ModBanFromCommunityView,
22 ModRemoveCommunityView,
24 ModTransferCommunityView,
29 } from "lemmy-js-client";
30 import moment from "moment";
31 import { Subscription } from "rxjs";
32 import { i18n } from "../i18next";
33 import { InitialFetchRequest } from "../interfaces";
34 import { WebSocketService } from "../services";
49 import { HtmlTags } from "./common/html-tags";
50 import { Spinner } from "./common/icon";
51 import { MomentTime } from "./common/moment-time";
52 import { Paginator } from "./common/paginator";
53 import { CommunityLink } from "./community/community-link";
54 import { PersonListing } from "./person/person-listing";
57 type_: ModlogActionType;
58 moderator?: PersonSafe;
63 | ModRemoveCommentView
64 | ModRemoveCommunityView
65 | ModBanFromCommunityView
68 | ModTransferCommunityView
70 | AdminPurgePersonView
71 | AdminPurgeCommunityView
73 | AdminPurgeCommentView;
78 Choices = require("choices.js");
81 interface ModlogState {
82 res?: GetModlogResponse;
84 communityMods?: CommunityModeratorView[];
85 communityName?: string;
87 siteRes: GetSiteResponse;
89 filter_action: ModlogActionType;
94 export class Modlog extends Component<any, ModlogState> {
95 private isoData = setIsoData(this.context);
96 private subscription?: Subscription;
97 private userChoices: any;
98 private modChoices: any;
99 state: ModlogState = {
102 siteRes: this.isoData.site_res,
103 filter_action: ModlogActionType.All,
106 constructor(props: any, context: any) {
107 super(props, context);
108 this.handlePageChange = this.handlePageChange.bind(this);
110 this.parseMessage = this.parseMessage.bind(this);
111 this.subscription = wsSubscribe(this.parseMessage);
115 communityId: this.props.match.params.community_id
116 ? Number(this.props.match.params.community_id)
120 // Only fetch the data if coming from another route
121 if (this.isoData.path == this.context.router.route.match.url) {
124 res: this.isoData.routeData[0] as GetModlogResponse,
127 let communityRes: GetCommunityResponse | undefined =
128 this.isoData.routeData[1];
130 // Getting the moderators
133 communityMods: communityRes?.moderators,
136 this.state = { ...this.state, loading: false };
142 componentDidMount() {
143 this.setupUserFilter();
144 this.setupModFilter();
147 componentWillUnmount() {
149 this.subscription?.unsubscribe();
153 buildCombined(res: GetModlogResponse): ModlogType[] {
154 let removed_posts: ModlogType[] = res.removed_posts.map(r => ({
155 id: r.mod_remove_post.id,
156 type_: ModlogActionType.ModRemovePost,
158 moderator: r.moderator,
159 when_: r.mod_remove_post.when_,
162 let locked_posts: ModlogType[] = res.locked_posts.map(r => ({
163 id: r.mod_lock_post.id,
164 type_: ModlogActionType.ModLockPost,
166 moderator: r.moderator,
167 when_: r.mod_lock_post.when_,
170 let featured_posts: ModlogType[] = res.featured_posts.map(r => ({
171 id: r.mod_feature_post.id,
172 type_: ModlogActionType.ModFeaturePost,
174 moderator: r.moderator,
175 when_: r.mod_feature_post.when_,
178 let removed_comments: ModlogType[] = res.removed_comments.map(r => ({
179 id: r.mod_remove_comment.id,
180 type_: ModlogActionType.ModRemoveComment,
182 moderator: r.moderator,
183 when_: r.mod_remove_comment.when_,
186 let removed_communities: ModlogType[] = res.removed_communities.map(r => ({
187 id: r.mod_remove_community.id,
188 type_: ModlogActionType.ModRemoveCommunity,
190 moderator: r.moderator,
191 when_: r.mod_remove_community.when_,
194 let banned_from_community: ModlogType[] = res.banned_from_community.map(
196 id: r.mod_ban_from_community.id,
197 type_: ModlogActionType.ModBanFromCommunity,
199 moderator: r.moderator,
200 when_: r.mod_ban_from_community.when_,
204 let added_to_community: ModlogType[] = res.added_to_community.map(r => ({
205 id: r.mod_add_community.id,
206 type_: ModlogActionType.ModAddCommunity,
208 moderator: r.moderator,
209 when_: r.mod_add_community.when_,
212 let transferred_to_community: ModlogType[] =
213 res.transferred_to_community.map(r => ({
214 id: r.mod_transfer_community.id,
215 type_: ModlogActionType.ModTransferCommunity,
217 moderator: r.moderator,
218 when_: r.mod_transfer_community.when_,
221 let added: ModlogType[] = res.added.map(r => ({
223 type_: ModlogActionType.ModAdd,
225 moderator: r.moderator,
226 when_: r.mod_add.when_,
229 let banned: ModlogType[] = res.banned.map(r => ({
231 type_: ModlogActionType.ModBan,
233 moderator: r.moderator,
234 when_: r.mod_ban.when_,
237 let purged_persons: ModlogType[] = res.admin_purged_persons.map(r => ({
238 id: r.admin_purge_person.id,
239 type_: ModlogActionType.AdminPurgePerson,
242 when_: r.admin_purge_person.when_,
245 let purged_communities: ModlogType[] = res.admin_purged_communities.map(
247 id: r.admin_purge_community.id,
248 type_: ModlogActionType.AdminPurgeCommunity,
251 when_: r.admin_purge_community.when_,
255 let purged_posts: ModlogType[] = res.admin_purged_posts.map(r => ({
256 id: r.admin_purge_post.id,
257 type_: ModlogActionType.AdminPurgePost,
260 when_: r.admin_purge_post.when_,
263 let purged_comments: ModlogType[] = res.admin_purged_comments.map(r => ({
264 id: r.admin_purge_comment.id,
265 type_: ModlogActionType.AdminPurgeComment,
268 when_: r.admin_purge_comment.when_,
271 let combined: ModlogType[] = [];
273 combined.push(...removed_posts);
274 combined.push(...locked_posts);
275 combined.push(...featured_posts);
276 combined.push(...removed_comments);
277 combined.push(...removed_communities);
278 combined.push(...banned_from_community);
279 combined.push(...added_to_community);
280 combined.push(...transferred_to_community);
281 combined.push(...added);
282 combined.push(...banned);
283 combined.push(...purged_persons);
284 combined.push(...purged_communities);
285 combined.push(...purged_posts);
286 combined.push(...purged_comments);
289 combined.sort((a, b) => b.when_.localeCompare(a.when_));
294 renderModlogType(i: ModlogType) {
296 case ModlogActionType.ModRemovePost: {
297 let mrpv = i.view as ModRemovePostView;
298 let reason = mrpv.mod_remove_post.reason;
302 {mrpv.mod_remove_post.removed ? "Removed " : "Restored "}
305 Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link>
309 <div>reason: {reason}</div>
315 case ModlogActionType.ModLockPost: {
316 let mlpv = i.view as ModLockPostView;
319 <span>{mlpv.mod_lock_post.locked ? "Locked " : "Unlocked "}</span>
321 Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
326 case ModlogActionType.ModFeaturePost: {
327 let mspv = i.view as ModFeaturePostView;
331 {mspv.mod_feature_post.featured ? "Featured " : "Unfeatured "}
334 Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
337 {mspv.mod_feature_post.is_featured_community
344 case ModlogActionType.ModRemoveComment: {
345 let mrc = i.view as ModRemoveCommentView;
346 let reason = mrc.mod_remove_comment.reason;
350 {mrc.mod_remove_comment.removed ? "Removed " : "Restored "}
354 <Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
355 {mrc.comment.content}
360 by <PersonListing person={mrc.commenter} />
364 <div>reason: {reason}</div>
370 case ModlogActionType.ModRemoveCommunity: {
371 let mrco = i.view as ModRemoveCommunityView;
372 let reason = mrco.mod_remove_community.reason;
373 let expires = mrco.mod_remove_community.expires;
377 {mrco.mod_remove_community.removed ? "Removed " : "Restored "}
380 Community <CommunityLink community={mrco.community} />
384 <div>reason: {reason}</div>
389 <div>expires: {moment.utc(expires).fromNow()}</div>
395 case ModlogActionType.ModBanFromCommunity: {
396 let mbfc = i.view as ModBanFromCommunityView;
397 let reason = mbfc.mod_ban_from_community.reason;
398 let expires = mbfc.mod_ban_from_community.expires;
402 {mbfc.mod_ban_from_community.banned ? "Banned " : "Unbanned "}{" "}
405 <PersonListing person={mbfc.banned_person} />
407 <span> from the community </span>
409 <CommunityLink community={mbfc.community} />
413 <div>reason: {reason}</div>
418 <div>expires: {moment.utc(expires).fromNow()}</div>
424 case ModlogActionType.ModAddCommunity: {
425 let mac = i.view as ModAddCommunityView;
429 {mac.mod_add_community.removed ? "Removed " : "Appointed "}{" "}
432 <PersonListing person={mac.modded_person} />
434 <span> as a mod to the community </span>
436 <CommunityLink community={mac.community} />
441 case ModlogActionType.ModTransferCommunity: {
442 let mtc = i.view as ModTransferCommunityView;
446 {mtc.mod_transfer_community.removed ? "Removed " : "Transferred "}{" "}
449 <CommunityLink community={mtc.community} />
453 <PersonListing person={mtc.modded_person} />
458 case ModlogActionType.ModBan: {
459 let mb = i.view as ModBanView;
460 let reason = mb.mod_ban.reason;
461 let expires = mb.mod_ban.expires;
464 <span>{mb.mod_ban.banned ? "Banned " : "Unbanned "} </span>
466 <PersonListing person={mb.banned_person} />
470 <div>reason: {reason}</div>
475 <div>expires: {moment.utc(expires).fromNow()}</div>
481 case ModlogActionType.ModAdd: {
482 let ma = i.view as ModAddView;
485 <span>{ma.mod_add.removed ? "Removed " : "Appointed "} </span>
487 <PersonListing person={ma.modded_person} />
489 <span> as an admin </span>
493 case ModlogActionType.AdminPurgePerson: {
494 let ap = i.view as AdminPurgePersonView;
495 let reason = ap.admin_purge_person.reason;
498 <span>Purged a Person</span>
501 <div>reason: {reason}</div>
507 case ModlogActionType.AdminPurgeCommunity: {
508 let ap = i.view as AdminPurgeCommunityView;
509 let reason = ap.admin_purge_community.reason;
512 <span>Purged a Community</span>
515 <div>reason: {reason}</div>
521 case ModlogActionType.AdminPurgePost: {
522 let ap = i.view as AdminPurgePostView;
523 let reason = ap.admin_purge_post.reason;
526 <span>Purged a Post from from </span>
527 <CommunityLink community={ap.community} />
530 <div>reason: {reason}</div>
536 case ModlogActionType.AdminPurgeComment: {
537 let ap = i.view as AdminPurgeCommentView;
538 let reason = ap.admin_purge_comment.reason;
542 Purged a Comment from{" "}
543 <Link to={`/post/${ap.post.id}`}>{ap.post.name}</Link>
547 <div>reason: {reason}</div>
559 let res = this.state.res;
560 let combined = res ? this.buildCombined(res) : [];
567 <MomentTime published={i.when_} />
570 {this.amAdminOrMod && i.moderator ? (
571 <PersonListing person={i.moderator} />
573 <div>{this.modOrAdminText(i.moderator)}</div>
576 <td>{this.renderModlogType(i)}</td>
583 get amAdminOrMod(): boolean {
584 return amAdmin() || amMod(this.state.communityMods);
587 modOrAdminText(person?: PersonSafe): string {
589 ? this.isoData.site_res.admins.map(a => a.person.id).includes(person.id)
595 get documentTitle(): string {
596 return `Modlog - ${this.state.siteRes.site_view.site.name}`;
600 let communityName = this.state.communityName;
602 <div className="container-lg">
604 title={this.documentTitle}
605 path={this.context.router.route.match.url}
607 {this.state.loading ? (
615 <Link className="text-body" to={`/c/${communityName}`}>
616 /c/{communityName}{" "}
619 <span>{i18n.t("modlog")}</span>
621 <div className="form-row">
622 <div className="form-group col-sm-6">
624 value={this.state.filter_action}
625 onChange={linkEvent(this, this.handleFilterActionChange)}
626 className="custom-select mb-2"
629 <option disabled aria-hidden="true">
630 {i18n.t("filter_by_action")}
632 <option value={ModlogActionType.All}>{i18n.t("all")}</option>
633 <option value={ModlogActionType.ModRemovePost}>
636 <option value={ModlogActionType.ModLockPost}>
639 <option value={ModlogActionType.ModFeaturePost}>
642 <option value={ModlogActionType.ModRemoveComment}>
645 <option value={ModlogActionType.ModRemoveCommunity}>
648 <option value={ModlogActionType.ModBanFromCommunity}>
649 Banning From Communities
651 <option value={ModlogActionType.ModAddCommunity}>
652 Adding Mod to Community
654 <option value={ModlogActionType.ModTransferCommunity}>
655 Transfering Communities
657 <option value={ModlogActionType.ModAdd}>
660 <option value={ModlogActionType.ModBan}>
665 {!this.state.siteRes.site_view.local_site
666 .hide_modlog_mod_names && (
667 <div className="form-group col-sm-6">
670 className="form-control"
671 value={this.state.filter_mod}
673 <option>{i18n.t("filter_by_mod")}</option>
677 <div className="form-group col-sm-6">
680 className="form-control"
681 value={this.state.filter_user}
683 <option>{i18n.t("filter_by_user")}</option>
687 <div className="table-responsive">
688 <table id="modlog_table" className="table table-sm table-hover">
689 <thead className="pointer">
691 <th> {i18n.t("time")}</th>
692 <th>{i18n.t("mod")}</th>
693 <th>{i18n.t("action")}</th>
699 page={this.state.page}
700 onChange={this.handlePageChange}
709 handleFilterActionChange(i: Modlog, event: any) {
710 i.setState({ filter_action: event.target.value });
714 handlePageChange(val: number) {
715 this.setState({ page: val });
720 let auth = myAuth(false);
721 let modlogForm: GetModlog = {
722 community_id: this.state.communityId,
723 page: this.state.page,
725 type_: this.state.filter_action,
726 other_person_id: this.state.filter_user,
727 mod_person_id: this.state.filter_mod,
730 WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
732 let communityId = this.state.communityId;
734 let communityForm: GetCommunity = {
738 WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
744 let selectId: any = document.getElementById("filter-user");
746 this.userChoices = new Choices(selectId, choicesConfig);
747 this.userChoices.passedElement.element.addEventListener(
750 this.setState({ filter_user: Number(e.detail.choice.value) });
755 this.userChoices.passedElement.element.addEventListener(
757 debounce(async (e: any) => {
759 let users = (await fetchUsers(e.detail.value)).users;
760 this.userChoices.setChoices(
763 value: u.person.id.toString(),
764 label: u.person.name,
783 let selectId: any = document.getElementById("filter-mod");
785 this.modChoices = new Choices(selectId, choicesConfig);
786 this.modChoices.passedElement.element.addEventListener(
789 this.setState({ filter_mod: Number(e.detail.choice.value) });
794 this.modChoices.passedElement.element.addEventListener(
796 debounce(async (e: any) => {
798 let mods = (await fetchUsers(e.detail.value)).users;
799 this.modChoices.setChoices(
802 value: u.person.id.toString(),
803 label: u.person.name,
820 static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
821 let pathSplit = req.path.split("/");
822 let communityId = pathSplit[3] ? Number(pathSplit[3]) : undefined;
824 let promises: Promise<any>[] = [];
826 let modlogForm: GetModlog = {
829 community_id: communityId,
830 type_: ModlogActionType.All,
834 promises.push(req.client.getModlog(modlogForm));
837 let communityForm: GetCommunity = {
841 promises.push(req.client.getCommunity(communityForm));
843 promises.push(Promise.resolve());
848 parseMessage(msg: any) {
849 let op = wsUserOp(msg);
852 toast(i18n.t(msg.error), "danger");
854 } else if (op == UserOperation.GetModlog) {
855 let data = wsJsonToRes<GetModlogResponse>(msg);
856 window.scrollTo(0, 0);
857 this.setState({ res: data, loading: false });
858 this.setupUserFilter();
859 this.setupModFilter();
860 } else if (op == UserOperation.GetCommunity) {
861 let data = wsJsonToRes<GetCommunityResponse>(msg);
863 communityMods: data.moderators,
864 communityName: data.community_view.community.name,