]> Untitled Git - lemmy-ui.git/commitdiff
Adding purging of comments, posts, communities, and users. (#459)
authorDessalines <dessalines@users.noreply.github.com>
Thu, 23 Jun 2022 19:44:05 +0000 (15:44 -0400)
committerGitHub <noreply@github.com>
Thu, 23 Jun 2022 19:44:05 +0000 (15:44 -0400)
* Starting on admin purge.

* Updating translations.

* Finishing up item purging.

13 files changed:
lemmy-translations
package.json
src/shared/components/comment/comment-node.tsx
src/shared/components/common/icon.tsx
src/shared/components/community/community.tsx
src/shared/components/community/sidebar.tsx
src/shared/components/home/home.tsx
src/shared/components/modlog.tsx
src/shared/components/person/profile.tsx
src/shared/components/post/post-listing.tsx
src/shared/components/post/post.tsx
src/shared/interfaces.ts
yarn.lock

index 29c689af8d16417c1b84d9491f6bcea888720a87..de5d4f3a758f8e8b41869c90d97e53ab50577f90 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 29c689af8d16417c1b84d9491f6bcea888720a87
+Subproject commit de5d4f3a758f8e8b41869c90d97e53ab50577f90
index 3fa26dbfe2b8c84be21867c3b7c1bbdbaafb648d..70be154236e664e008649b5abbd60359f1e0f07e 100644 (file)
@@ -77,7 +77,7 @@
     "eslint-plugin-prettier": "^4.0.0",
     "husky": "^7.0.4",
     "import-sort-style-module": "^6.0.0",
-    "lemmy-js-client": "0.17.0-rc.32",
+    "lemmy-js-client": "0.17.0-rc.33",
     "lint-staged": "^12.4.1",
     "mini-css-extract-plugin": "^2.6.0",
     "node-fetch": "^2.6.1",
index 356a4820387074f8a023721f09fa0509beb68d20..27e209ee9d2d5ea40f96122b944684cec5dd63ad 100644 (file)
@@ -17,6 +17,8 @@ import {
   MarkPersonMentionAsRead,
   PersonMentionView,
   PersonViewSafe,
+  PurgeComment,
+  PurgePerson,
   RemoveComment,
   SaveComment,
   toUndefined,
@@ -24,7 +26,11 @@ import {
 } from "lemmy-js-client";
 import moment from "moment";
 import { i18n } from "../../i18next";
-import { BanType, CommentNode as CommentNodeI } from "../../interfaces";
+import {
+  BanType,
+  CommentNode as CommentNodeI,
+  PurgeType,
+} from "../../interfaces";
 import { UserService, WebSocketService } from "../../services";
 import {
   amCommunityCreator,
@@ -42,7 +48,7 @@ import {
   showScores,
   wsClient,
 } from "../../utils";
-import { Icon, Spinner } from "../common/icon";
+import { Icon, PurgeWarning, Spinner } from "../common/icon";
 import { MomentTime } from "../common/moment-time";
 import { CommunityLink } from "../community/community-link";
 import { PersonListing } from "../person/person-listing";
@@ -59,6 +65,10 @@ interface CommentNodeState {
   banReason: Option<string>;
   banExpireDays: Option<number>;
   banType: BanType;
+  showPurgeDialog: boolean;
+  purgeReason: Option<string>;
+  purgeType: PurgeType;
+  purgeLoading: boolean;
   showConfirmTransferSite: boolean;
   showConfirmTransferCommunity: boolean;
   showConfirmAppointAsMod: boolean;
@@ -102,6 +112,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     banReason: None,
     banExpireDays: None,
     banType: BanType.Community,
+    showPurgeDialog: false,
+    purgeLoading: false,
+    purgeReason: None,
+    purgeType: PurgeType.Person,
     collapsed: false,
     viewSource: false,
     showAdvanced: false,
@@ -147,6 +161,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     let node = this.props.node;
     let cv = this.props.node.comment_view;
 
+    let purgeTypeText: string;
+    if (this.state.purgeType == PurgeType.Comment) {
+      purgeTypeText = i18n.t("purge_comment");
+    } else if (this.state.purgeType == PurgeType.Person) {
+      purgeTypeText = `${i18n.t("purge")} ${cv.creator.name}`;
+    }
+
     let canMod_ = canMod(
       this.props.moderators,
       this.props.admins,
@@ -645,30 +666,54 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                             {/* Admins can ban from all, and appoint other admins */}
                             {canAdmin_ && (
                               <>
-                                {!isAdmin_ &&
-                                  (!isBanned(cv.creator) ? (
+                                {!isAdmin_ && (
+                                  <>
                                     <button
                                       class="btn btn-link btn-animate text-muted"
                                       onClick={linkEvent(
                                         this,
-                                        this.handleModBanShow
+                                        this.handlePurgePersonShow
                                       )}
-                                      aria-label={i18n.t("ban_from_site")}
+                                      aria-label={i18n.t("purge_user")}
                                     >
-                                      {i18n.t("ban_from_site")}
+                                      {i18n.t("purge_user")}
                                     </button>
-                                  ) : (
                                     <button
                                       class="btn btn-link btn-animate text-muted"
                                       onClick={linkEvent(
                                         this,
-                                        this.handleModBanSubmit
+                                        this.handlePurgeCommentShow
                                       )}
-                                      aria-label={i18n.t("unban_from_site")}
+                                      aria-label={i18n.t("purge_comment")}
                                     >
-                                      {i18n.t("unban_from_site")}
+                                      {i18n.t("purge_comment")}
                                     </button>
-                                  ))}
+
+                                    {!isBanned(cv.creator) ? (
+                                      <button
+                                        class="btn btn-link btn-animate text-muted"
+                                        onClick={linkEvent(
+                                          this,
+                                          this.handleModBanShow
+                                        )}
+                                        aria-label={i18n.t("ban_from_site")}
+                                      >
+                                        {i18n.t("ban_from_site")}
+                                      </button>
+                                    ) : (
+                                      <button
+                                        class="btn btn-link btn-animate text-muted"
+                                        onClick={linkEvent(
+                                          this,
+                                          this.handleModBanSubmit
+                                        )}
+                                        aria-label={i18n.t("unban_from_site")}
+                                      >
+                                        {i18n.t("unban_from_site")}
+                                      </button>
+                                    )}
+                                  </>
+                                )}
                                 {!isBanned(cv.creator) &&
                                   cv.creator.local &&
                                   (!this.state.showConfirmAppointAsAdmin ? (
@@ -848,6 +893,36 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
             </div>
           </form>
         )}
+
+        {this.state.showPurgeDialog && (
+          <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
+            <PurgeWarning />
+            <label class="sr-only" htmlFor="purge-reason">
+              {i18n.t("reason")}
+            </label>
+            <input
+              type="text"
+              id="purge-reason"
+              class="form-control my-3"
+              placeholder={i18n.t("reason")}
+              value={toUndefined(this.state.purgeReason)}
+              onInput={linkEvent(this, this.handlePurgeReasonChange)}
+            />
+            <div class="form-group row col-12">
+              {this.state.purgeLoading ? (
+                <Spinner />
+              ) : (
+                <button
+                  type="submit"
+                  class="btn btn-secondary"
+                  aria-label={purgeTypeText}
+                >
+                  {purgeTypeText}
+                </button>
+              )}
+            </div>
+          </form>
+        )}
         {this.state.showReply && (
           <CommentForm
             node={Left(node)}
@@ -1202,6 +1277,48 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     i.setState(i.state);
   }
 
+  handlePurgePersonShow(i: CommentNode) {
+    i.state.showPurgeDialog = true;
+    i.state.purgeType = PurgeType.Person;
+    i.state.showRemoveDialog = false;
+    i.setState(i.state);
+  }
+
+  handlePurgeCommentShow(i: CommentNode) {
+    i.state.showPurgeDialog = true;
+    i.state.purgeType = PurgeType.Comment;
+    i.state.showRemoveDialog = false;
+    i.setState(i.state);
+  }
+
+  handlePurgeReasonChange(i: CommentNode, event: any) {
+    i.state.purgeReason = Some(event.target.value);
+    i.setState(i.state);
+  }
+
+  handlePurgeSubmit(i: CommentNode, event: any) {
+    event.preventDefault();
+
+    if (i.state.purgeType == PurgeType.Person) {
+      let form = new PurgePerson({
+        person_id: i.props.node.comment_view.creator.id,
+        reason: i.state.purgeReason,
+        auth: auth().unwrap(),
+      });
+      WebSocketService.Instance.send(wsClient.purgePerson(form));
+    } else if (i.state.purgeType == PurgeType.Comment) {
+      let form = new PurgeComment({
+        comment_id: i.props.node.comment_view.comment.id,
+        reason: i.state.purgeReason,
+        auth: auth().unwrap(),
+      });
+      WebSocketService.Instance.send(wsClient.purgeComment(form));
+    }
+
+    i.state.purgeLoading = true;
+    i.setState(i.state);
+  }
+
   handleShowConfirmAppointAsMod(i: CommentNode) {
     i.state.showConfirmAppointAsMod = true;
     i.setState(i.state);
index 5de9a3d1582214034dd1dab961f73e0259c9f644..91271cfe539e45b889f38768ad5223c516851eae 100644 (file)
@@ -1,5 +1,6 @@
 import classNames from "classnames";
 import { Component } from "inferno";
+import { i18n } from "../../i18next";
 
 interface IconProps {
   icon: string;
@@ -48,3 +49,18 @@ export class Spinner extends Component<SpinnerProps, any> {
     );
   }
 }
+
+export class PurgeWarning extends Component<any, any> {
+  constructor(props: any, context: any) {
+    super(props, context);
+  }
+
+  render() {
+    return (
+      <div class="mt-2 alert alert-danger" role="alert">
+        <Icon icon="alert-triangle" classes="icon-inline mr-2" />
+        {i18n.t("purge_warning")}
+      </div>
+    );
+  }
+}
index ab00af14a683af0be8720f24fcd6c9ba07925996..29e3a97d76d37d3c5381bf3d3770b03220cf01db 100644 (file)
@@ -19,6 +19,7 @@ import {
   PostReportResponse,
   PostResponse,
   PostView,
+  PurgeItemResponse,
   SortType,
   toOption,
   UserOperation,
@@ -656,6 +657,12 @@ export class Community extends Component<any, State> {
       if (data) {
         toast(i18n.t("report_created"));
       }
+    } else if (op == UserOperation.PurgeCommunity) {
+      let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
+      if (data.success) {
+        toast(i18n.t("purge_success"));
+        this.context.router.history.push(`/`);
+      }
     }
   }
 }
index 91c7ba8ff5b698faecb1e4df5900622d0b1139df..0f49ca1775e5e7b32b8c9b12b035af6301743d55 100644 (file)
@@ -1,4 +1,4 @@
-import { Option, Some } from "@sniptt/monads";
+import { None, Option, Some } from "@sniptt/monads";
 import { Component, linkEvent } from "inferno";
 import { Link } from "inferno-router";
 import {
@@ -8,6 +8,7 @@ import {
   DeleteCommunity,
   FollowCommunity,
   PersonViewSafe,
+  PurgeCommunity,
   RemoveCommunity,
   SubscribedType,
   toUndefined,
@@ -25,7 +26,7 @@ import {
   wsClient,
 } from "../../utils";
 import { BannerIconHeader } from "../common/banner-icon-header";
-import { Icon } from "../common/icon";
+import { Icon, PurgeWarning, Spinner } from "../common/icon";
 import { CommunityForm } from "../community/community-form";
 import { CommunityLink } from "../community/community-link";
 import { PersonListing } from "../person/person-listing";
@@ -44,6 +45,9 @@ interface SidebarState {
   removeExpires: Option<string>;
   showEdit: boolean;
   showRemoveDialog: boolean;
+  showPurgeDialog: boolean;
+  purgeReason: Option<string>;
+  purgeLoading: boolean;
   showConfirmLeaveModTeam: boolean;
 }
 
@@ -51,8 +55,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
   private emptyState: SidebarState = {
     showEdit: false,
     showRemoveDialog: false,
-    removeReason: null,
-    removeExpires: null,
+    removeReason: None,
+    removeExpires: None,
+    showPurgeDialog: false,
+    purgeReason: None,
+    purgeLoading: false,
     showConfirmLeaveModTeam: false,
   };
 
@@ -403,12 +410,19 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
                   {i18n.t("restore")}
                 </button>
               )}
+              <button
+                class="btn btn-link text-muted d-inline-block"
+                onClick={linkEvent(this, this.handlePurgeCommunityShow)}
+                aria-label={i18n.t("purge_community")}
+              >
+                {i18n.t("purge_community")}
+              </button>
             </li>
           )}
         </ul>
         {this.state.showRemoveDialog && (
           <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
-            <div class="form-group row">
+            <div class="form-group">
               <label class="col-form-label" htmlFor="remove-reason">
                 {i18n.t("reason")}
               </label>
@@ -426,13 +440,46 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
             {/*   <label class="col-form-label">Expires</label> */}
             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
             {/* </div> */}
-            <div class="form-group row">
+            <div class="form-group">
               <button type="submit" class="btn btn-secondary">
                 {i18n.t("remove_community")}
               </button>
             </div>
           </form>
         )}
+        {this.state.showPurgeDialog && (
+          <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
+            <div class="form-group">
+              <PurgeWarning />
+            </div>
+            <div class="form-group">
+              <label class="sr-only" htmlFor="purge-reason">
+                {i18n.t("reason")}
+              </label>
+              <input
+                type="text"
+                id="purge-reason"
+                class="form-control mr-2"
+                placeholder={i18n.t("reason")}
+                value={toUndefined(this.state.purgeReason)}
+                onInput={linkEvent(this, this.handlePurgeReasonChange)}
+              />
+            </div>
+            <div class="form-group">
+              {this.state.purgeLoading ? (
+                <Spinner />
+              ) : (
+                <button
+                  type="submit"
+                  class="btn btn-secondary"
+                  aria-label={i18n.t("purge_community")}
+                >
+                  {i18n.t("purge_community")}
+                </button>
+              )}
+            </div>
+          </form>
+        )}
       </>
     );
   }
@@ -542,13 +589,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
   }
 
   handleModRemoveReasonChange(i: Sidebar, event: any) {
-    i.state.removeReason = event.target.value;
+    i.state.removeReason = Some(event.target.value);
     i.setState(i.state);
   }
 
   handleModRemoveExpiresChange(i: Sidebar, event: any) {
-    console.log(event.target.value);
-    i.state.removeExpires = event.target.value;
+    i.state.removeExpires = Some(event.target.value);
     i.setState(i.state);
   }
 
@@ -566,4 +612,29 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
     i.state.showRemoveDialog = false;
     i.setState(i.state);
   }
+
+  handlePurgeCommunityShow(i: Sidebar) {
+    i.state.showPurgeDialog = true;
+    i.state.showRemoveDialog = false;
+    i.setState(i.state);
+  }
+
+  handlePurgeReasonChange(i: Sidebar, event: any) {
+    i.state.purgeReason = Some(event.target.value);
+    i.setState(i.state);
+  }
+
+  handlePurgeSubmit(i: Sidebar, event: any) {
+    event.preventDefault();
+
+    let form = new PurgeCommunity({
+      community_id: i.props.community_view.community.id,
+      reason: i.state.purgeReason,
+      auth: auth().unwrap(),
+    });
+    WebSocketService.Instance.send(wsClient.purgeCommunity(form));
+
+    i.state.purgeLoading = true;
+    i.setState(i.state);
+  }
 }
index 6baea78326c35acb4b0e59a5139319ff4256922d..1ff06a0044011d35cdb3270ba74aacb97fbf3b96 100644 (file)
@@ -21,6 +21,7 @@ import {
   PostReportResponse,
   PostResponse,
   PostView,
+  PurgeItemResponse,
   SiteResponse,
   SortType,
   UserOperation,
@@ -860,6 +861,17 @@ export class Home extends Component<any, HomeState> {
       if (data) {
         toast(i18n.t("report_created"));
       }
+    } else if (
+      op == UserOperation.PurgePerson ||
+      op == UserOperation.PurgePost ||
+      op == UserOperation.PurgeComment ||
+      op == UserOperation.PurgeCommunity
+    ) {
+      let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
+      if (data.success) {
+        toast(i18n.t("purge_success"));
+        this.context.router.history.push(`/`);
+      }
     }
   }
 }
index 0003e54b8d6963bafef3bc61519376458fbfd7d8..c90dd647874bc74afbb9ae8bbcfdec0d92489cd7 100644 (file)
@@ -2,6 +2,10 @@ import { None, Option, Some } from "@sniptt/monads";
 import { Component } from "inferno";
 import { Link } from "inferno-router";
 import {
+  AdminPurgeCommentView,
+  AdminPurgeCommunityView,
+  AdminPurgePersonView,
+  AdminPurgePostView,
   CommunityModeratorView,
   GetCommunity,
   GetCommunityResponse,
@@ -57,11 +61,16 @@ enum ModlogEnum {
   ModTransferCommunity,
   ModAdd,
   ModBan,
+  AdminPurgePerson,
+  AdminPurgeCommunity,
+  AdminPurgePost,
+  AdminPurgeComment,
 }
 
 type ModlogType = {
   id: number;
   type_: ModlogEnum;
+  moderator: PersonSafe;
   view:
     | ModRemovePostView
     | ModLockPostView
@@ -72,7 +81,11 @@ type ModlogType = {
     | ModBanView
     | ModAddCommunityView
     | ModTransferCommunityView
-    | ModAddView;
+    | ModAddView
+    | AdminPurgePersonView
+    | AdminPurgeCommunityView
+    | AdminPurgePostView
+    | AdminPurgeCommentView;
   when_: string;
 };
 
@@ -118,11 +131,13 @@ export class Modlog extends Component<any, ModlogState> {
     if (this.isoData.path == this.context.router.route.match.url) {
       this.state.res = Some(this.isoData.routeData[0] as GetModlogResponse);
 
-      // Getting the moderators
-      let communityRes = Some(
-        this.isoData.routeData[1] as GetCommunityResponse
-      );
-      this.state.communityMods = communityRes.map(c => c.moderators);
+      if (this.isoData.routeData[1]) {
+        // Getting the moderators
+        let communityRes = Some(
+          this.isoData.routeData[1] as GetCommunityResponse
+        );
+        this.state.communityMods = communityRes.map(c => c.moderators);
+      }
 
       this.state.loading = false;
     } else {
@@ -141,6 +156,7 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_remove_post.id,
       type_: ModlogEnum.ModRemovePost,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_remove_post.when_,
     }));
 
@@ -148,6 +164,7 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_lock_post.id,
       type_: ModlogEnum.ModLockPost,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_lock_post.when_,
     }));
 
@@ -155,6 +172,7 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_sticky_post.id,
       type_: ModlogEnum.ModStickyPost,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_sticky_post.when_,
     }));
 
@@ -162,6 +180,7 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_remove_comment.id,
       type_: ModlogEnum.ModRemoveComment,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_remove_comment.when_,
     }));
 
@@ -169,6 +188,7 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_remove_community.id,
       type_: ModlogEnum.ModRemoveCommunity,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_remove_community.when_,
     }));
 
@@ -177,6 +197,7 @@ export class Modlog extends Component<any, ModlogState> {
         id: r.mod_ban_from_community.id,
         type_: ModlogEnum.ModBanFromCommunity,
         view: r,
+        moderator: r.moderator,
         when_: r.mod_ban_from_community.when_,
       })
     );
@@ -185,6 +206,7 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_add_community.id,
       type_: ModlogEnum.ModAddCommunity,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_add_community.when_,
     }));
 
@@ -193,6 +215,7 @@ export class Modlog extends Component<any, ModlogState> {
         id: r.mod_transfer_community.id,
         type_: ModlogEnum.ModTransferCommunity,
         view: r,
+        moderator: r.moderator,
         when_: r.mod_transfer_community.when_,
       }));
 
@@ -200,6 +223,7 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_add.id,
       type_: ModlogEnum.ModAdd,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_add.when_,
     }));
 
@@ -207,9 +231,44 @@ export class Modlog extends Component<any, ModlogState> {
       id: r.mod_ban.id,
       type_: ModlogEnum.ModBan,
       view: r,
+      moderator: r.moderator,
       when_: r.mod_ban.when_,
     }));
 
+    let purged_persons: ModlogType[] = res.admin_purged_persons.map(r => ({
+      id: r.admin_purge_person.id,
+      type_: ModlogEnum.AdminPurgePerson,
+      view: r,
+      moderator: r.admin,
+      when_: r.admin_purge_person.when_,
+    }));
+
+    let purged_communities: ModlogType[] = res.admin_purged_communities.map(
+      r => ({
+        id: r.admin_purge_community.id,
+        type_: ModlogEnum.AdminPurgeCommunity,
+        view: r,
+        moderator: r.admin,
+        when_: r.admin_purge_community.when_,
+      })
+    );
+
+    let purged_posts: ModlogType[] = res.admin_purged_posts.map(r => ({
+      id: r.admin_purge_post.id,
+      type_: ModlogEnum.AdminPurgePost,
+      view: r,
+      moderator: r.admin,
+      when_: r.admin_purge_post.when_,
+    }));
+
+    let purged_comments: ModlogType[] = res.admin_purged_comments.map(r => ({
+      id: r.admin_purge_comment.id,
+      type_: ModlogEnum.AdminPurgeComment,
+      view: r,
+      moderator: r.admin,
+      when_: r.admin_purge_comment.when_,
+    }));
+
     let combined: ModlogType[] = [];
 
     combined.push(...removed_posts);
@@ -222,6 +281,10 @@ export class Modlog extends Component<any, ModlogState> {
     combined.push(...transferred_to_community);
     combined.push(...added);
     combined.push(...banned);
+    combined.push(...purged_persons);
+    combined.push(...purged_communities);
+    combined.push(...purged_posts);
+    combined.push(...purged_comments);
 
     // Sort them by time
     combined.sort((a, b) => b.when_.localeCompare(a.when_));
@@ -234,18 +297,22 @@ export class Modlog extends Component<any, ModlogState> {
       case ModlogEnum.ModRemovePost: {
         let mrpv = i.view as ModRemovePostView;
         return [
-          mrpv.mod_remove_post.removed ? "Removed " : "Restored ",
+          mrpv.mod_remove_post.removed.unwrapOr(false)
+            ? "Removed "
+            : "Restored ",
           <span>
             Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link>
           </span>,
-          mrpv.mod_remove_post.reason &&
-            ` reason: ${mrpv.mod_remove_post.reason}`,
+          mrpv.mod_remove_post.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
         ];
       }
       case ModlogEnum.ModLockPost: {
         let mlpv = i.view as ModLockPostView;
         return [
-          mlpv.mod_lock_post.locked ? "Locked " : "Unlocked ",
+          mlpv.mod_lock_post.locked.unwrapOr(false) ? "Locked " : "Unlocked ",
           <span>
             Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
           </span>,
@@ -254,7 +321,9 @@ export class Modlog extends Component<any, ModlogState> {
       case ModlogEnum.ModStickyPost: {
         let mspv = i.view as ModStickyPostView;
         return [
-          mspv.mod_sticky_post.stickied ? "Stickied " : "Unstickied ",
+          mspv.mod_sticky_post.stickied.unwrapOr(false)
+            ? "Stickied "
+            : "Unstickied ",
           <span>
             Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
           </span>,
@@ -263,7 +332,9 @@ export class Modlog extends Component<any, ModlogState> {
       case ModlogEnum.ModRemoveComment: {
         let mrc = i.view as ModRemoveCommentView;
         return [
-          mrc.mod_remove_comment.removed ? "Removed " : "Restored ",
+          mrc.mod_remove_comment.removed.unwrapOr(false)
+            ? "Removed "
+            : "Restored ",
           <span>
             Comment{" "}
             <Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
@@ -274,30 +345,40 @@ export class Modlog extends Component<any, ModlogState> {
             {" "}
             by <PersonListing person={mrc.commenter} />
           </span>,
-          mrc.mod_remove_comment.reason &&
-            ` reason: ${mrc.mod_remove_comment.reason}`,
+          mrc.mod_remove_comment.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
         ];
       }
       case ModlogEnum.ModRemoveCommunity: {
         let mrco = i.view as ModRemoveCommunityView;
         return [
-          mrco.mod_remove_community.removed ? "Removed " : "Restored ",
+          mrco.mod_remove_community.removed.unwrapOr(false)
+            ? "Removed "
+            : "Restored ",
           <span>
             Community <CommunityLink community={mrco.community} />
           </span>,
-          mrco.mod_remove_community.reason.isSome() &&
-            ` reason: ${mrco.mod_remove_community.reason.unwrap()}`,
-          mrco.mod_remove_community.expires.isSome() &&
-            ` expires: ${moment
-              .utc(mrco.mod_remove_community.expires.unwrap())
-              .fromNow()}`,
+          mrco.mod_remove_community.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
+          mrco.mod_remove_community.expires.match({
+            some: expires => (
+              <div>expires: {moment.utc(expires).fromNow()}</div>
+            ),
+            none: <></>,
+          }),
         ];
       }
       case ModlogEnum.ModBanFromCommunity: {
         let mbfc = i.view as ModBanFromCommunityView;
         return [
           <span>
-            {mbfc.mod_ban_from_community.banned ? "Banned " : "Unbanned "}{" "}
+            {mbfc.mod_ban_from_community.banned.unwrapOr(false)
+              ? "Banned "
+              : "Unbanned "}{" "}
           </span>,
           <span>
             <PersonListing person={mbfc.banned_person} />
@@ -306,23 +387,25 @@ export class Modlog extends Component<any, ModlogState> {
           <span>
             <CommunityLink community={mbfc.community} />
           </span>,
-          <div>
-            {mbfc.mod_ban_from_community.reason.isSome() &&
-              ` reason: ${mbfc.mod_ban_from_community.reason.unwrap()}`}
-          </div>,
-          <div>
-            {mbfc.mod_ban_from_community.expires.isSome() &&
-              ` expires: ${moment
-                .utc(mbfc.mod_ban_from_community.expires.unwrap())
-                .fromNow()}`}
-          </div>,
+          mbfc.mod_ban_from_community.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
+          mbfc.mod_ban_from_community.expires.match({
+            some: expires => (
+              <div>expires: {moment.utc(expires).fromNow()}</div>
+            ),
+            none: <></>,
+          }),
         ];
       }
       case ModlogEnum.ModAddCommunity: {
         let mac = i.view as ModAddCommunityView;
         return [
           <span>
-            {mac.mod_add_community.removed ? "Removed " : "Appointed "}{" "}
+            {mac.mod_add_community.removed.unwrapOr(false)
+              ? "Removed "
+              : "Appointed "}{" "}
           </span>,
           <span>
             <PersonListing person={mac.modded_person} />
@@ -337,7 +420,9 @@ export class Modlog extends Component<any, ModlogState> {
         let mtc = i.view as ModTransferCommunityView;
         return [
           <span>
-            {mtc.mod_transfer_community.removed ? "Removed " : "Transferred "}{" "}
+            {mtc.mod_transfer_community.removed.unwrapOr(false)
+              ? "Removed "
+              : "Transferred "}{" "}
           </span>,
           <span>
             <CommunityLink community={mtc.community} />
@@ -351,27 +436,29 @@ export class Modlog extends Component<any, ModlogState> {
       case ModlogEnum.ModBan: {
         let mb = i.view as ModBanView;
         return [
-          <span>{mb.mod_ban.banned ? "Banned " : "Unbanned "} </span>,
+          <span>
+            {mb.mod_ban.banned.unwrapOr(false) ? "Banned " : "Unbanned "}{" "}
+          </span>,
           <span>
             <PersonListing person={mb.banned_person} />
           </span>,
-          <div>
-            {mb.mod_ban.reason.isSome() &&
-              ` reason: ${mb.mod_ban.reason.unwrap()}`}
-          </div>,
-          <div>
-            {mb.mod_ban.expires.isSome() &&
-              ` expires: ${moment.utc(mb.mod_ban.expires.unwrap()).fromNow()}`}
-          </div>,
+          mb.mod_ban.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
+          mb.mod_ban.expires.match({
+            some: expires => (
+              <div>expires: {moment.utc(expires).fromNow()}</div>
+            ),
+            none: <></>,
+          }),
         ];
       }
       case ModlogEnum.ModAdd: {
         let ma = i.view as ModAddView;
         return [
           <span>
-            {ma.mod_add.removed.isSome() && ma.mod_add.removed.unwrap()
-              ? "Removed "
-              : "Appointed "}{" "}
+            {ma.mod_add.removed.unwrapOr(false) ? "Removed " : "Appointed "}{" "}
           </span>,
           <span>
             <PersonListing person={ma.modded_person} />
@@ -379,6 +466,50 @@ export class Modlog extends Component<any, ModlogState> {
           <span> as an admin </span>,
         ];
       }
+      case ModlogEnum.AdminPurgePerson: {
+        let ap = i.view as AdminPurgePersonView;
+        return [
+          <span>Purged a Person</span>,
+          ap.admin_purge_person.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
+        ];
+      }
+      case ModlogEnum.AdminPurgeCommunity: {
+        let ap = i.view as AdminPurgeCommunityView;
+        return [
+          <span>Purged a Community</span>,
+          ap.admin_purge_community.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
+        ];
+      }
+      case ModlogEnum.AdminPurgePost: {
+        let ap = i.view as AdminPurgePostView;
+        return [
+          <span>Purged a Post from from </span>,
+          <CommunityLink community={ap.community} />,
+          ap.admin_purge_post.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
+        ];
+      }
+      case ModlogEnum.AdminPurgeComment: {
+        let ap = i.view as AdminPurgeCommentView;
+        return [
+          <span>
+            Purged a Comment from{" "}
+            <Link to={`/post/${ap.post.id}`}>{ap.post.name}</Link>
+          </span>,
+          ap.admin_purge_comment.reason.match({
+            some: reason => <div>reason: {reason}</div>,
+            none: <></>,
+          }),
+        ];
+      }
       default:
         return <div />;
     }
@@ -396,9 +527,9 @@ export class Modlog extends Component<any, ModlogState> {
             </td>
             <td>
               {this.amAdminOrMod ? (
-                <PersonListing person={i.view.moderator} />
+                <PersonListing person={i.moderator} />
               ) : (
-                <div>{this.modOrAdminText(i.view.moderator)}</div>
+                <div>{this.modOrAdminText(i.moderator)}</div>
               )}
             </td>
             <td>{this.renderModlogType(i)}</td>
@@ -415,7 +546,7 @@ export class Modlog extends Component<any, ModlogState> {
     );
   }
 
-  modOrAdminText(person: PersonSafe): Text {
+  modOrAdminText(person: PersonSafe): string {
     if (
       this.isoData.site_res.admins.map(a => a.person.id).includes(person.id)
     ) {
index 053e4b27b754402375168483221042fd5b0c1ffb..9de6d0b967abe06c9380d90eb3f3ef81697d5b8b 100644 (file)
@@ -12,6 +12,7 @@ import {
   GetPersonDetailsResponse,
   GetSiteResponse,
   PostResponse,
+  PurgeItemResponse,
   SortType,
   toUndefined,
   UserOperation,
@@ -897,6 +898,17 @@ export class Profile extends Component<any, ProfileState> {
       updatePersonBlock(data);
       this.setPersonBlock();
       this.setState(this.state);
+    } else if (
+      op == UserOperation.PurgePerson ||
+      op == UserOperation.PurgePost ||
+      op == UserOperation.PurgeComment ||
+      op == UserOperation.PurgeCommunity
+    ) {
+      let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
+      if (data.success) {
+        toast(i18n.t("purge_success"));
+        this.context.router.history.push(`/`);
+      }
     }
   }
 }
index eadf48a030722495747b8977016f9bf480e324b5..cbe84d91fcbb1ce7261f12e81cfaf4677e27a414 100644 (file)
@@ -15,6 +15,8 @@ import {
   LockPost,
   PersonViewSafe,
   PostView,
+  PurgePerson,
+  PurgePost,
   RemovePost,
   SavePost,
   StickyPost,
@@ -23,7 +25,7 @@ import {
 } from "lemmy-js-client";
 import { externalHost } from "../../env";
 import { i18n } from "../../i18next";
-import { BanType } from "../../interfaces";
+import { BanType, PurgeType } from "../../interfaces";
 import { UserService, WebSocketService } from "../../services";
 import {
   amCommunityCreator,
@@ -45,7 +47,7 @@ import {
   showScores,
   wsClient,
 } from "../../utils";
-import { Icon } from "../common/icon";
+import { Icon, PurgeWarning, Spinner } from "../common/icon";
 import { MomentTime } from "../common/moment-time";
 import { PictrsImage } from "../common/pictrs-image";
 import { CommunityLink } from "../community/community-link";
@@ -56,6 +58,10 @@ import { PostForm } from "./post-form";
 interface PostListingState {
   showEdit: boolean;
   showRemoveDialog: boolean;
+  showPurgeDialog: boolean;
+  purgeReason: Option<string>;
+  purgeType: PurgeType;
+  purgeLoading: boolean;
   removeReason: Option<string>;
   showBanDialog: boolean;
   banReason: Option<string>;
@@ -93,6 +99,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   private emptyState: PostListingState = {
     showEdit: false,
     showRemoveDialog: false,
+    showPurgeDialog: false,
+    purgeReason: None,
+    purgeType: PurgeType.Person,
+    purgeLoading: false,
     removeReason: None,
     showBanDialog: false,
     banReason: None,
@@ -943,24 +953,41 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           {/* Admins can ban from all, and appoint other admins */}
           {this.canAdmin_ && (
             <>
-              {!this.creatorIsAdmin_ &&
-                (!isBanned(post_view.creator) ? (
+              {!this.creatorIsAdmin_ && (
+                <>
+                  {!isBanned(post_view.creator) ? (
+                    <button
+                      class="btn btn-link btn-animate text-muted py-0"
+                      onClick={linkEvent(this, this.handleModBanShow)}
+                      aria-label={i18n.t("ban_from_site")}
+                    >
+                      {i18n.t("ban_from_site")}
+                    </button>
+                  ) : (
+                    <button
+                      class="btn btn-link btn-animate text-muted py-0"
+                      onClick={linkEvent(this, this.handleModBanSubmit)}
+                      aria-label={i18n.t("unban_from_site")}
+                    >
+                      {i18n.t("unban_from_site")}
+                    </button>
+                  )}
                   <button
                     class="btn btn-link btn-animate text-muted py-0"
-                    onClick={linkEvent(this, this.handleModBanShow)}
-                    aria-label={i18n.t("ban_from_site")}
+                    onClick={linkEvent(this, this.handlePurgePersonShow)}
+                    aria-label={i18n.t("purge_user")}
                   >
-                    {i18n.t("ban_from_site")}
+                    {i18n.t("purge_user")}
                   </button>
-                ) : (
                   <button
                     class="btn btn-link btn-animate text-muted py-0"
-                    onClick={linkEvent(this, this.handleModBanSubmit)}
-                    aria-label={i18n.t("unban_from_site")}
+                    onClick={linkEvent(this, this.handlePurgePostShow)}
+                    aria-label={i18n.t("purge_post")}
                   >
-                    {i18n.t("unban_from_site")}
+                    {i18n.t("purge_post")}
                   </button>
-                ))}
+                </>
+              )}
               {!isBanned(post_view.creator) && post_view.creator.local && (
                 <button
                   class="btn btn-link btn-animate text-muted py-0"
@@ -985,6 +1012,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
 
   removeAndBanDialogs() {
     let post = this.props.post_view;
+    let purgeTypeText: string;
+    if (this.state.purgeType == PurgeType.Post) {
+      purgeTypeText = i18n.t("purge_post");
+    } else if (this.state.purgeType == PurgeType.Person) {
+      purgeTypeText = `${i18n.t("purge")} ${post.creator.name}`;
+    }
     return (
       <>
         {this.state.showRemoveDialog && (
@@ -1098,6 +1131,36 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             </button>
           </form>
         )}
+        {this.state.showPurgeDialog && (
+          <form
+            class="form-inline"
+            onSubmit={linkEvent(this, this.handlePurgeSubmit)}
+          >
+            <PurgeWarning />
+            <label class="sr-only" htmlFor="purge-reason">
+              {i18n.t("reason")}
+            </label>
+            <input
+              type="text"
+              id="purge-reason"
+              class="form-control mr-2"
+              placeholder={i18n.t("reason")}
+              value={toUndefined(this.state.purgeReason)}
+              onInput={linkEvent(this, this.handlePurgeReasonChange)}
+            />
+            {this.state.purgeLoading ? (
+              <Spinner />
+            ) : (
+              <button
+                type="submit"
+                class="btn btn-secondary"
+                aria-label={purgeTypeText}
+              >
+                {purgeTypeText}
+              </button>
+            )}
+          </form>
+        )}
       </>
     );
   }
@@ -1411,6 +1474,48 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     i.setState(i.state);
   }
 
+  handlePurgePersonShow(i: PostListing) {
+    i.state.showPurgeDialog = true;
+    i.state.purgeType = PurgeType.Person;
+    i.state.showRemoveDialog = false;
+    i.setState(i.state);
+  }
+
+  handlePurgePostShow(i: PostListing) {
+    i.state.showPurgeDialog = true;
+    i.state.purgeType = PurgeType.Post;
+    i.state.showRemoveDialog = false;
+    i.setState(i.state);
+  }
+
+  handlePurgeReasonChange(i: PostListing, event: any) {
+    i.state.purgeReason = Some(event.target.value);
+    i.setState(i.state);
+  }
+
+  handlePurgeSubmit(i: PostListing, event: any) {
+    event.preventDefault();
+
+    if (i.state.purgeType == PurgeType.Person) {
+      let form = new PurgePerson({
+        person_id: i.props.post_view.creator.id,
+        reason: i.state.purgeReason,
+        auth: auth().unwrap(),
+      });
+      WebSocketService.Instance.send(wsClient.purgePerson(form));
+    } else if (i.state.purgeType == PurgeType.Post) {
+      let form = new PurgePost({
+        post_id: i.props.post_view.post.id,
+        reason: i.state.purgeReason,
+        auth: auth().unwrap(),
+      });
+      WebSocketService.Instance.send(wsClient.purgePost(form));
+    }
+
+    i.state.purgeLoading = true;
+    i.setState(i.state);
+  }
+
   handleModBanReasonChange(i: PostListing, event: any) {
     i.state.banReason = Some(event.target.value);
     i.setState(i.state);
index 9b42282d67f1e5c987b0510133391105cc99bf18..9702c3d18460d445c335879856f5c062b998dc21 100644 (file)
@@ -19,6 +19,7 @@ import {
   PostReportResponse,
   PostResponse,
   PostView,
+  PurgeItemResponse,
   Search,
   SearchResponse,
   SearchType,
@@ -760,6 +761,17 @@ export class Post extends Component<any, PostState> {
       if (data) {
         toast(i18n.t("report_created"));
       }
+    } else if (
+      op == UserOperation.PurgePerson ||
+      op == UserOperation.PurgePost ||
+      op == UserOperation.PurgeComment ||
+      op == UserOperation.PurgeCommunity
+    ) {
+      let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
+      if (data.success) {
+        toast(i18n.t("purge_success"));
+        this.context.router.history.push(`/`);
+      }
     }
   }
 }
index 92fabb8eb2a0470eff8a9fc383c9d513e94f08e0..0a157b57596a53e6821b365823af285458ae7cd4 100644 (file)
@@ -73,3 +73,10 @@ export enum PersonDetailsView {
   Posts,
   Saved,
 }
+
+export enum PurgeType {
+  Person,
+  Community,
+  Post,
+  Comment,
+}
index a5a342a86aab29a35053e78ebfd9abc24c6b1489..41141c9885bbd7fd9f5515bf6a10e9c78989f809 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -4948,10 +4948,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-lemmy-js-client@0.17.0-rc.32:
-  version "0.17.0-rc.32"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.32.tgz#d67f432f1fffc54c267f915278fe260ec554b018"
-  integrity sha512-qPLybaesu3GVr1DMStsyCYanW4maxHrqX71UHadFMeuh+aUK8taC3zfsLRK9dlIlSDRS283xd8IZkI6ZlcOVEQ==
+lemmy-js-client@0.17.0-rc.33:
+  version "0.17.0-rc.33"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.33.tgz#e05cc88213da3c0c21c7ea53c29054041d619150"
+  integrity sha512-rG0yCc9AAc5/B+muDfWB7bKizBG7r/xSzHeEw5ms50xF4dN+KOqvRcHTf0+15uAYehBF5B54nyxdlKPRKL9GxQ==
 
 levn@^0.4.1:
   version "0.4.1"