]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/modlog.tsx
Add community name to featured post action in Modlog (#1891)
[lemmy-ui.git] / src / shared / components / modlog.tsx
index ef28816b668147dbb56ab8326ccfe579e150a401..ca1cb3326b2cfef3a2ca454d00d071d454e72abc 100644 (file)
@@ -1,3 +1,21 @@
+import {
+  fetchUsers,
+  getUpdatedSearchId,
+  myAuth,
+  personToChoice,
+  setIsoData,
+} from "@utils/app";
+import {
+  debounce,
+  formatPastDate,
+  getIdFromString,
+  getPageFromString,
+  getQueryParams,
+  getQueryString,
+} from "@utils/helpers";
+import { amAdmin, amMod } from "@utils/roles";
+import type { QueryParams } from "@utils/types";
+import { Choice, RouteDataResponse } from "@utils/types";
 import { NoOptionI18nKeys } from "i18next";
 import { Component, linkEvent } from "inferno";
 import { T } from "inferno-i18next-dess";
@@ -8,7 +26,6 @@ import {
   AdminPurgeCommunityView,
   AdminPurgePersonView,
   AdminPurgePostView,
-  CommunityModeratorView,
   GetCommunity,
   GetCommunityResponse,
   GetModlog,
@@ -27,36 +44,11 @@ import {
   ModTransferCommunityView,
   ModlogActionType,
   Person,
-  UserOperation,
-  wsJsonToRes,
-  wsUserOp,
 } from "lemmy-js-client";
-import moment from "moment";
-import { Subscription } from "rxjs";
-import { i18n } from "../i18next";
+import { fetchLimit } from "../config";
 import { InitialFetchRequest } from "../interfaces";
-import { WebSocketService } from "../services";
-import {
-  Choice,
-  QueryParams,
-  amAdmin,
-  amMod,
-  debounce,
-  fetchLimit,
-  fetchUsers,
-  getIdFromString,
-  getPageFromString,
-  getQueryParams,
-  getQueryString,
-  getUpdatedSearchId,
-  isBrowser,
-  myAuth,
-  personToChoice,
-  setIsoData,
-  toast,
-  wsClient,
-  wsSubscribe,
-} from "../utils";
+import { FirstLoadService, I18NextService } from "../services";
+import { HttpService, RequestState } from "../services/HttpService";
 import { HtmlTags } from "./common/html-tags";
 import { Icon, Spinner } from "./common/icon";
 import { MomentTime } from "./common/moment-time";
@@ -83,6 +75,13 @@ type View =
   | AdminPurgePostView
   | AdminPurgeCommentView;
 
+type ModlogData = RouteDataResponse<{
+  res: GetModlogResponse;
+  communityRes: GetCommunityResponse;
+  modUserResponse: GetPersonDetailsResponse;
+  userResponse: GetPersonDetailsResponse;
+}>;
+
 interface ModlogType {
   id: number;
   type_: ModlogActionType;
@@ -100,10 +99,8 @@ const getModlogQueryParams = () =>
   });
 
 interface ModlogState {
-  res?: GetModlogResponse;
-  communityMods?: CommunityModeratorView[];
-  communityName?: string;
-  loadingModlog: boolean;
+  res: RequestState<GetModlogResponse>;
+  communityRes: RequestState<GetCommunityResponse>;
   loadingModSearch: boolean;
   loadingUserSearch: boolean;
   modSearchOptions: Choice[];
@@ -315,6 +312,7 @@ function renderModlogType({ type_, view }: ModlogType) {
       const {
         mod_feature_post: { featured, is_featured_community },
         post: { id, name },
+        community,
       } = view as ModFeaturePostView;
 
       return (
@@ -323,7 +321,12 @@ function renderModlogType({ type_, view }: ModlogType) {
           <span>
             Post <Link to={`/post/${id}`}>{name}</Link>
           </span>
-          <span>{is_featured_community ? " In Community" : " In Local"}</span>
+          <span>
+            {is_featured_community
+              ? " in community "
+              : " in Local, from community "}
+          </span>
+          <CommunityLink community={community} />
         </>
       );
     }
@@ -374,7 +377,7 @@ function renderModlogType({ type_, view }: ModlogType) {
           )}
           {expires && (
             <span>
-              <div>expires: {moment.utc(expires).fromNow()}</div>
+              <div>expires: {formatPastDate(expires)}</div>
             </span>
           )}
         </>
@@ -406,7 +409,7 @@ function renderModlogType({ type_, view }: ModlogType) {
           )}
           {expires && (
             <span>
-              <div>expires: {moment.utc(expires).fromNow()}</div>
+              <div>expires: {formatPastDate(expires)}</div>
             </span>
           )}
         </>
@@ -470,7 +473,7 @@ function renderModlogType({ type_, view }: ModlogType) {
           )}
           {expires && (
             <span>
-              <div>expires: {moment.utc(expires).fromNow()}</div>
+              <div>expires: {formatPastDate(expires)}</div>
             </span>
           )}
         </>
@@ -535,7 +538,7 @@ function renderModlogType({ type_, view }: ModlogType) {
 
       return (
         <>
-          <span>Purged a Post from from </span>
+          <span>Purged a Post from </span>
           <CommunityLink community={community} />
           {reason && (
             <span>
@@ -586,16 +589,16 @@ const Filter = ({
   options: Choice[];
   loading: boolean;
 }) => (
-  <div className="col-sm-6 form-group">
-    <label className="col-form-label" htmlFor={`filter-${filterType}`}>
-      {i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
+  <div className="col-sm-6 mb-3">
+    <label className="mb-2" htmlFor={`filter-${filterType}`}>
+      {I18NextService.i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
     </label>
     <SearchableSelect
       id={`filter-${filterType}`}
       value={value ?? 0}
       options={[
         {
-          label: i18n.t("all"),
+          label: I18NextService.i18n.t("all"),
           value: "0",
         },
       ].concat(options)}
@@ -629,7 +632,7 @@ async function createNewOptions({
 
   if (text.length > 0) {
     newOptions.push(
-      ...(await fetchUsers(text)).users
+      ...(await fetchUsers(text))
         .slice(0, Number(fetchLimit))
         .map<Choice>(personToChoice)
     );
@@ -642,11 +645,11 @@ export class Modlog extends Component<
   RouteComponentProps<{ communityId?: string }>,
   ModlogState
 > {
-  private isoData = setIsoData(this.context);
-  private subscription?: Subscription;
+  private isoData = setIsoData<ModlogData>(this.context);
 
   state: ModlogState = {
-    loadingModlog: true,
+    res: { state: "empty" },
+    communityRes: { state: "empty" },
     loadingModSearch: false,
     loadingUserSearch: false,
     userSearchOptions: [],
@@ -662,58 +665,40 @@ export class Modlog extends Component<
     this.handleUserChange = this.handleUserChange.bind(this);
     this.handleModChange = this.handleModChange.bind(this);
 
-    this.parseMessage = this.parseMessage.bind(this);
-    this.subscription = wsSubscribe(this.parseMessage);
-
     // Only fetch the data if coming from another route
-    if (this.isoData.path === this.context.router.route.match.url) {
-      this.state = {
-        ...this.state,
-        res: this.isoData.routeData[0] as GetModlogResponse,
-      };
-
-      const communityRes: GetCommunityResponse | undefined =
-        this.isoData.routeData[1];
+    if (FirstLoadService.isFirstLoad) {
+      const { res, communityRes, modUserResponse, userResponse } =
+        this.isoData.routeData;
 
-      // Getting the moderators
       this.state = {
         ...this.state,
-        communityMods: communityRes?.moderators,
+        res,
+        communityRes,
       };
 
-      const filteredModRes: GetPersonDetailsResponse | undefined =
-        this.isoData.routeData[2];
-      if (filteredModRes) {
+      if (modUserResponse.state === "success") {
         this.state = {
           ...this.state,
-          modSearchOptions: [personToChoice(filteredModRes.person_view)],
+          modSearchOptions: [personToChoice(modUserResponse.data.person_view)],
         };
       }
 
-      const filteredUserRes: GetPersonDetailsResponse | undefined =
-        this.isoData.routeData[3];
-      if (filteredUserRes) {
+      if (userResponse.state === "success") {
         this.state = {
           ...this.state,
-          userSearchOptions: [personToChoice(filteredUserRes.person_view)],
+          userSearchOptions: [personToChoice(userResponse.data.person_view)],
         };
       }
-
-      this.state = { ...this.state, loadingModlog: false };
-    } else {
-      this.refetch();
     }
   }
 
-  componentWillUnmount() {
-    if (isBrowser()) {
-      this.subscription?.unsubscribe();
-    }
+  async componentDidMount() {
+    await this.refetch();
   }
 
   get combined() {
     const res = this.state.res;
-    const combined = res ? buildCombined(res) : [];
+    const combined = res.state == "success" ? buildCombined(res.data) : [];
 
     return (
       <tbody>
@@ -737,7 +722,10 @@ export class Modlog extends Component<
   }
 
   get amAdminOrMod(): boolean {
-    return amAdmin() || amMod(this.state.communityMods);
+    const amMod_ =
+      this.state.communityRes.state == "success" &&
+      amMod(this.state.communityRes.data.moderators);
+    return amAdmin() || amMod_;
   }
 
   modOrAdminText(person?: Person): string {
@@ -745,8 +733,8 @@ export class Modlog extends Component<
       this.isoData.site_res.admins.some(
         ({ person: { id } }) => id === person.id
       )
-      ? i18n.t("admin")
-      : i18n.t("mod");
+      ? I18NextService.i18n.t("admin")
+      : I18NextService.i18n.t("mod");
   }
 
   get documentTitle(): string {
@@ -755,55 +743,58 @@ export class Modlog extends Component<
 
   render() {
     const {
-      communityName,
-      loadingModlog,
       loadingModSearch,
       loadingUserSearch,
       userSearchOptions,
       modSearchOptions,
     } = this.state;
-    const { actionType, page, modId, userId } = getModlogQueryParams();
+    const { actionType, modId, userId } = getModlogQueryParams();
 
     return (
-      <div className="container-lg">
+      <div className="modlog container-lg">
         <HtmlTags
           title={this.documentTitle}
           path={this.context.router.route.match.url}
         />
 
-        <div>
-          <div
-            className="alert alert-warning text-sm-start text-xs-center"
-            role="alert"
-          >
-            <Icon
-              icon="alert-triangle"
-              inline
-              classes="mr-sm-2 mx-auto d-sm-inline d-block"
-            />
-            <T i18nKey="modlog_content_warning" class="d-inline">
-              #<strong>#</strong>#
-            </T>
-          </div>
+        <h1 className="h4 mb-4">{I18NextService.i18n.t("modlog")}</h1>
+
+        <div
+          className="alert alert-warning text-sm-start text-xs-center"
+          role="alert"
+        >
+          <Icon
+            icon="alert-triangle"
+            inline
+            classes="me-sm-2 mx-auto d-sm-inline d-block"
+          />
+          <T i18nKey="modlog_content_warning" class="d-inline">
+            #<strong>#</strong>#
+          </T>
+        </div>
+        {this.state.communityRes.state === "success" && (
           <h5>
-            {communityName && (
-              <Link className="text-body" to={`/c/${communityName}`}>
-                /c/{communityName}{" "}
-              </Link>
-            )}
-            <span>{i18n.t("modlog")}</span>
+            <Link
+              className="text-body"
+              to={`/c/${this.state.communityRes.data.community_view.community.name}`}
+            >
+              /c/{this.state.communityRes.data.community_view.community.name}{" "}
+            </Link>
+            <span>{I18NextService.i18n.t("modlog")}</span>
           </h5>
-          <div className="form-row">
+        )}
+        <div className="row mb-2">
+          <div className="col-sm-6">
             <select
               value={actionType}
               onChange={linkEvent(this, this.handleFilterActionChange)}
-              className="custom-select col-sm-6"
+              className="form-select"
               aria-label="action"
             >
               <option disabled aria-hidden="true">
-                {i18n.t("filter_by_action")}
+                {I18NextService.i18n.t("filter_by_action")}
               </option>
-              <option value={"All"}>{i18n.t("all")}</option>
+              <option value={"All"}>{I18NextService.i18n.t("all")}</option>
               <option value={"ModRemovePost"}>Removing Posts</option>
               <option value={"ModLockPost"}>Locking Posts</option>
               <option value={"ModFeaturePost"}>Featuring Posts</option>
@@ -820,51 +811,62 @@ export class Modlog extends Component<
               <option value={"ModBan"}>Banning From Site</option>
             </select>
           </div>
-          <div className="form-row mb-2">
+        </div>
+        <div className="row mb-2">
+          <Filter
+            filterType="user"
+            onChange={this.handleUserChange}
+            onSearch={this.handleSearchUsers}
+            value={userId}
+            options={userSearchOptions}
+            loading={loadingUserSearch}
+          />
+          {!this.isoData.site_res.site_view.local_site
+            .hide_modlog_mod_names && (
             <Filter
-              filterType="user"
-              onChange={this.handleUserChange}
-              onSearch={this.handleSearchUsers}
-              value={userId}
-              options={userSearchOptions}
-              loading={loadingUserSearch}
+              filterType="mod"
+              onChange={this.handleModChange}
+              onSearch={this.handleSearchMods}
+              value={modId}
+              options={modSearchOptions}
+              loading={loadingModSearch}
             />
-            {!this.isoData.site_res.site_view.local_site
-              .hide_modlog_mod_names && (
-              <Filter
-                filterType="mod"
-                onChange={this.handleModChange}
-                onSearch={this.handleSearchMods}
-                value={modId}
-                options={modSearchOptions}
-                loading={loadingModSearch}
-              />
-            )}
-          </div>
-          <div className="table-responsive">
-            {loadingModlog ? (
-              <h5>
-                <Spinner large />
-              </h5>
-            ) : (
-              <table id="modlog_table" className="table table-sm table-hover">
-                <thead className="pointer">
-                  <tr>
-                    <th> {i18n.t("time")}</th>
-                    <th>{i18n.t("mod")}</th>
-                    <th>{i18n.t("action")}</th>
-                  </tr>
-                </thead>
-                {this.combined}
-              </table>
-            )}
-            <Paginator page={page} onChange={this.handlePageChange} />
-          </div>
+          )}
         </div>
+        {this.renderModlogTable()}
       </div>
     );
   }
 
+  renderModlogTable() {
+    switch (this.state.res.state) {
+      case "loading":
+        return (
+          <h5>
+            <Spinner large />
+          </h5>
+        );
+      case "success": {
+        const page = getModlogQueryParams().page;
+        return (
+          <div className="table-responsive">
+            <table id="modlog_table" className="table table-sm table-hover">
+              <thead className="pointer">
+                <tr>
+                  <th> {I18NextService.i18n.t("time")}</th>
+                  <th>{I18NextService.i18n.t("mod")}</th>
+                  <th>{I18NextService.i18n.t("action")}</th>
+                </tr>
+              </thead>
+              {this.combined}
+            </table>
+            <Paginator page={page} onChange={this.handlePageChange} />
+          </div>
+        );
+      }
+    }
+  }
+
   handleFilterActionChange(i: Modlog, event: any) {
     i.updateUrl({
       actionType: event.target.value as ModlogActionType,
@@ -918,7 +920,7 @@ export class Modlog extends Component<
     });
   });
 
-  updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
+  async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
     const {
       page: urlPage,
       actionType: urlActionType,
@@ -941,54 +943,50 @@ export class Modlog extends Component<
       )}`
     );
 
-    this.setState({
-      loadingModlog: true,
-      res: undefined,
-    });
-
-    this.refetch();
+    await this.refetch();
   }
 
-  refetch() {
-    const auth = myAuth(false);
+  async refetch() {
+    const auth = myAuth();
     const { actionType, page, modId, userId } = getModlogQueryParams();
     const { communityId: urlCommunityId } = this.props.match.params;
     const communityId = getIdFromString(urlCommunityId);
 
-    const modlogForm: GetModlog = {
-      community_id: communityId,
-      page,
-      limit: fetchLimit,
-      type_: actionType,
-      other_person_id: userId ?? undefined,
-      mod_person_id: !this.isoData.site_res.site_view.local_site
-        .hide_modlog_mod_names
-        ? modId ?? undefined
-        : undefined,
-      auth,
-    };
-
-    WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
-
-    if (communityId) {
-      const communityForm: GetCommunity = {
-        id: communityId,
+    this.setState({ res: { state: "loading" } });
+    this.setState({
+      res: await HttpService.client.getModlog({
+        community_id: communityId,
+        page,
+        limit: fetchLimit,
+        type_: actionType,
+        other_person_id: userId ?? undefined,
+        mod_person_id: !this.isoData.site_res.site_view.local_site
+          .hide_modlog_mod_names
+          ? modId ?? undefined
+          : undefined,
         auth,
-      };
+      }),
+    });
 
-      WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
+    if (communityId) {
+      this.setState({ communityRes: { state: "loading" } });
+      this.setState({
+        communityRes: await HttpService.client.getCommunity({
+          id: communityId,
+          auth,
+        }),
+      });
     }
   }
 
-  static fetchInitialData({
+  static async fetchInitialData({
     client,
     path,
     query: { modId: urlModId, page, userId: urlUserId, actionType },
     auth,
     site,
-  }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<any>[] {
+  }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
     const pathSplit = path.split("/");
-    const promises: Promise<any>[] = [];
     const communityId = getIdFromString(pathSplit[2]);
     const modId = !site.site_view.local_site.hide_modlog_mod_names
       ? getIdFromString(urlModId)
@@ -1005,74 +1003,50 @@ export class Modlog extends Component<
       auth,
     };
 
-    promises.push(client.getModlog(modlogForm));
+    let communityResponse: RequestState<GetCommunityResponse> = {
+      state: "empty",
+    };
 
     if (communityId) {
       const communityForm: GetCommunity = {
         id: communityId,
         auth,
       };
-      promises.push(client.getCommunity(communityForm));
-    } else {
-      promises.push(Promise.resolve());
+
+      communityResponse = await client.getCommunity(communityForm);
     }
 
+    let modUserResponse: RequestState<GetPersonDetailsResponse> = {
+      state: "empty",
+    };
+
     if (modId) {
       const getPersonForm: GetPersonDetails = {
         person_id: modId,
         auth,
       };
 
-      promises.push(client.getPersonDetails(getPersonForm));
-    } else {
-      promises.push(Promise.resolve());
+      modUserResponse = await client.getPersonDetails(getPersonForm);
     }
 
+    let userResponse: RequestState<GetPersonDetailsResponse> = {
+      state: "empty",
+    };
+
     if (userId) {
       const getPersonForm: GetPersonDetails = {
         person_id: userId,
         auth,
       };
 
-      promises.push(client.getPersonDetails(getPersonForm));
-    } else {
-      promises.push(Promise.resolve());
+      userResponse = await client.getPersonDetails(getPersonForm);
     }
 
-    return promises;
-  }
-
-  parseMessage(msg: any) {
-    const op = wsUserOp(msg);
-    console.log(msg);
-
-    if (msg.error) {
-      toast(i18n.t(msg.error), "danger");
-    } else {
-      switch (op) {
-        case UserOperation.GetModlog: {
-          const res = wsJsonToRes<GetModlogResponse>(msg);
-          window.scrollTo(0, 0);
-          this.setState({ res, loadingModlog: false });
-
-          break;
-        }
-
-        case UserOperation.GetCommunity: {
-          const {
-            moderators,
-            community_view: {
-              community: { name },
-            },
-          } = wsJsonToRes<GetCommunityResponse>(msg);
-          this.setState({
-            communityMods: moderators,
-            communityName: name,
-          });
-
-          break;
-        }
-      }
-    }
+    return {
+      res: await client.getModlog(modlogForm),
+      communityRes: communityResponse,
+      modUserResponse,
+      userResponse,
+    };
   }
 }