]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/post/post-listing.tsx
use badge-muted
[lemmy-ui.git] / src / shared / components / post / post-listing.tsx
index f8140c3e990576bda8a15a167377d3746ff8de60..137ded9e80626b20ab5b81d264301ec1161df1af 100644 (file)
@@ -14,8 +14,7 @@ import {
   FeaturePost,
   Language,
   LockPost,
-  PersonViewSafe,
-  PostFeatureType,
+  PersonView,
   PostView,
   PurgePerson,
   PurgePost,
@@ -23,15 +22,17 @@ import {
   SavePost,
   TransferCommunity,
 } from "lemmy-js-client";
-import { externalHost } from "../../env";
+import { getExternalHost, getHttpBase } from "../../env";
 import { i18n } from "../../i18next";
-import { BanType, PurgeType } from "../../interfaces";
+import { BanType, PostFormParams, PurgeType } from "../../interfaces";
 import { UserService, WebSocketService } from "../../services";
 import {
   amAdmin,
   amCommunityCreator,
+  amMod,
   canAdmin,
   canMod,
+  canShare,
   futureDaysToUnixTime,
   hostname,
   isAdmin,
@@ -46,6 +47,7 @@ import {
   numToSI,
   relTags,
   setupTippy,
+  share,
   showScores,
   wsClient,
 } from "../../utils";
@@ -89,7 +91,7 @@ interface PostListingProps {
   post_view: PostView;
   duplicates?: PostView[];
   moderators?: CommunityModeratorView[];
-  admins?: PersonViewSafe[];
+  admins?: PersonView[];
   allLanguages: Language[];
   siteLanguages: number[];
   showCommunity?: boolean;
@@ -146,7 +148,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   }
 
   render() {
-    let post = this.props.post_view.post;
+    const post = this.props.post_view.post;
+
     return (
       <div className="post-listing">
         {!this.state.showEdit ? (
@@ -324,23 +327,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           <PersonListing person={post_view.creator} />
 
           {this.creatorIsMod_ && (
-            <span className="mx-1 badge badge-light">{i18n.t("mod")}</span>
+            <span className="mx-1 badge badge-muted">{i18n.t("mod")}</span>
           )}
           {this.creatorIsAdmin_ && (
-            <span className="mx-1 badge badge-light">{i18n.t("admin")}</span>
+            <span className="mx-1 badge badge-muted">{i18n.t("admin")}</span>
           )}
           {post_view.creator.bot_account && (
-            <span className="mx-1 badge badge-light">
+            <span className="mx-1 badge badge-muted">
               {i18n.t("bot_account").toLowerCase()}
             </span>
           )}
-          {(post_view.creator_banned_from_community ||
-            isBanned(post_view.creator)) && (
-            <span className="mx-1 badge badge-danger">{i18n.t("banned")}</span>
-          )}
-          {post_view.creator_blocked && (
-            <span className="mx-1 badge badge-danger">{"blocked"}</span>
-          )}
           {this.props.showCommunity && (
             <span>
               <span className="mx-1"> {i18n.t("to")} </span>
@@ -348,8 +344,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             </span>
           )}
         </li>
+        {post_view.post.language_id !== 0 && (
+          <span className="mx-1 badge badge-muted">
+            {
+              this.props.allLanguages.find(
+                lang => lang.id === post_view.post.language_id
+              )?.name
+            }
+          </span>
+        )}
         <li className="list-inline-item">•</li>
-        {url && !(hostname(url) == externalHost) && (
+        {url && !(hostname(url) === getExternalHost()) && (
           <>
             <li className="list-inline-item">
               <a
@@ -434,15 +439,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     let post = this.props.post_view.post;
     return (
       <Link
-        className={
+        className={`d-inline-block ${
           !post.featured_community && !post.featured_local
             ? "text-body"
             : "text-primary"
-        }
+        }`}
         to={`/post/${post.id}`}
         title={i18n.t("comments")}
       >
-        <div dangerouslySetInnerHTML={mdToHtmlInline(post.name)} />
+        <div
+          className="d-inline-block"
+          dangerouslySetInnerHTML={mdToHtmlInline(post.name)}
+        />
       </Link>
     );
   }
@@ -457,16 +465,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           {url ? (
             this.props.showBody ? (
               <a
-                className={
+                className={`d-inline-block ${
                   !post.featured_community && !post.featured_local
                     ? "text-body"
                     : "text-primary"
-                }
+                }`}
                 href={url}
                 title={url}
                 rel={relTags}
               >
-                <div dangerouslySetInnerHTML={mdToHtmlInline(post.name)} />
+                <div
+                  className="d-inline-block"
+                  dangerouslySetInnerHTML={mdToHtmlInline(post.name)}
+                />
               </a>
             ) : (
               this.postLink
@@ -477,7 +488,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
           {(url && isImage(url)) ||
             (post.thumbnail_url && (
               <button
-                className="btn btn-link text-monospace text-muted small d-inline-block ml-2"
+                className="btn btn-link text-monospace text-muted small d-inline-block"
                 data-tippy-content={i18n.t("expand_here")}
                 onClick={linkEvent(this, this.handleImageExpandClick)}
               >
@@ -560,9 +571,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
 
   commentsLine(mobile = false) {
     let post = this.props.post_view.post;
+
     return (
       <div className="d-flex justify-content-start flex-wrap text-muted font-weight-bold mb-1">
         {this.commentsButton}
+        {canShare() && (
+          <button
+            className="btn btn-link"
+            onClick={linkEvent(this, this.handleShare)}
+            type="button"
+          >
+            <Icon icon="share" inline />
+          </button>
+        )}
         {!post.local && (
           <a
             className="btn btn-link btn-animate text-muted py-0"
@@ -608,7 +629,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
         {this.state.showAdvanced && (
           <>
             {this.showBody && post_view.post.body && this.viewSourceButton}
-            {this.canModOnSelf_ && (
+            {/* Any mod can do these, not limited to hierarchy*/}
+            {(amMod(this.props.moderators) || amAdmin()) && (
               <>
                 {this.lockButton}
                 {this.featureButton}
@@ -629,15 +651,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
         <Link
           className="text-muted"
           title={i18n.t("number_of_comments", {
-            count: post_view.counts.comments,
-            formattedCount: post_view.counts.comments,
+            count: Number(post_view.counts.comments),
+            formattedCount: Number(post_view.counts.comments),
           })}
           to={`/post/${post_view.post.id}?scrollToComments=true`}
         >
           <Icon icon="message-square" classes="mr-1" inline />
           <span className="mr-2">
             {i18n.t("number_of_comments", {
-              count: post_view.counts.comments,
+              count: Number(post_view.counts.comments),
               formattedCount: numToSI(post_view.counts.comments),
             })}
           </span>
@@ -726,7 +748,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     return (
       <Link
         className="btn btn-link btn-animate text-muted py-0"
-        to={`/create_post${this.crossPostParams}`}
+        to={{
+          /* Empty string properties are required to satisfy type*/
+          pathname: "/create_post",
+          state: { ...this.crossPostParams },
+          hash: "",
+          key: "",
+          search: "",
+        }}
         title={i18n.t("cross_post")}
       >
         <Icon icon="copy" inline />
@@ -842,41 +871,40 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   }
 
   get featureButton() {
-    const featured_community = this.props.post_view.post.featured_community;
-    const label_community = featured_community
+    const featuredCommunity = this.props.post_view.post.featured_community;
+    const labelCommunity = featuredCommunity
       ? i18n.t("unfeature_from_community")
       : i18n.t("feature_in_community");
 
-    const is_admin = amAdmin();
-    const featured_local = this.props.post_view.post.featured_local;
-    const label_local = featured_local
+    const featuredLocal = this.props.post_view.post.featured_local;
+    const labelLocal = featuredLocal
       ? i18n.t("unfeature_from_local")
       : i18n.t("feature_in_local");
     return (
       <span>
         <button
           className="btn btn-link btn-animate text-muted py-0 pl-0"
-          onClick={() => this.handleModFeaturePost(this, true)}
-          data-tippy-content={label_community}
-          aria-label={label_community}
+          onClick={linkEvent(this, this.handleModFeaturePostCommunity)}
+          data-tippy-content={labelCommunity}
+          aria-label={labelCommunity}
         >
           <Icon
             icon="pin"
-            classes={classNames({ "text-success": featured_community })}
+            classes={classNames({ "text-success": featuredCommunity })}
             inline
           />{" "}
           Community
         </button>
-        {is_admin && (
+        {amAdmin() && (
           <button
             className="btn btn-link btn-animate text-muted py-0"
-            onClick={() => this.handleModFeaturePost(this, false)}
-            data-tippy-content={label_local}
-            aria-label={label_local}
+            onClick={linkEvent(this, this.handleModFeaturePostLocal)}
+            data-tippy-content={labelLocal}
+            aria-label={labelLocal}
           >
             <Icon
               icon="pin"
-              classes={classNames({ "text-success": featured_local })}
+              classes={classNames({ "text-success": featuredLocal })}
               inline
             />{" "}
             Local
@@ -921,9 +949,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                       this,
                       this.handleModBanFromCommunityShow
                     )}
-                    aria-label={i18n.t("ban")}
+                    aria-label={i18n.t("ban_from_community")}
                   >
-                    {i18n.t("ban")}
+                    {i18n.t("ban_from_community")}
                   </button>
                 ) : (
                   <button
@@ -1392,6 +1420,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     this.setState({ showEdit: false });
   }
 
+  handleShare(i: PostListing) {
+    const { name, body, id } = i.props.post_view.post;
+    share({
+      title: name,
+      text: body?.slice(0, 50),
+      url: `${getHttpBase()}/post/${id}`,
+    });
+  }
+
   handleShowReportDialog(i: PostListing) {
     i.setState({ showReportDialog: !i.state.showReportDialog });
   }
@@ -1454,18 +1491,22 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     }
   }
 
-  get crossPostParams(): string {
-    let post = this.props.post_view.post;
-    let params = `?title=${encodeURIComponent(post.name)}`;
+  get crossPostParams(): PostFormParams {
+    const queryParams: PostFormParams = {};
+    const { name, url } = this.props.post_view.post;
+
+    queryParams.name = name;
 
-    if (post.url) {
-      params += `&url=${encodeURIComponent(post.url)}`;
+    if (url) {
+      queryParams.url = url;
     }
-    let crossPostBody = this.crossPostBody();
+
+    const crossPostBody = this.crossPostBody();
     if (crossPostBody) {
-      params += `&body=${encodeURIComponent(crossPostBody)}`;
+      queryParams.body = crossPostBody;
     }
-    return params;
+
+    return queryParams;
   }
 
   crossPostBody(): string | undefined {
@@ -1527,20 +1568,26 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     }
   }
 
-  handleModFeaturePost(i: PostListing, is_community: boolean) {
+  handleModFeaturePostLocal(i: PostListing) {
     let auth = myAuth();
     if (auth) {
-      let featured: [PostFeatureType, boolean] = is_community
-        ? [
-            PostFeatureType.Community,
-            !i.props.post_view.post.featured_community,
-          ]
-        : [PostFeatureType.Local, !i.props.post_view.post.featured_local];
+      let form: FeaturePost = {
+        post_id: i.props.post_view.post.id,
+        feature_type: "Local",
+        featured: !i.props.post_view.post.featured_local,
+        auth,
+      };
+      WebSocketService.Instance.send(wsClient.featurePost(form));
+    }
+  }
 
+  handleModFeaturePostCommunity(i: PostListing) {
+    let auth = myAuth();
+    if (auth) {
       let form: FeaturePost = {
         post_id: i.props.post_view.post.id,
-        feature_type: featured[0],
-        featured: featured[1],
+        feature_type: "Community",
+        featured: !i.props.post_view.post.featured_community,
         auth,
       };
       WebSocketService.Instance.send(wsClient.featurePost(form));
@@ -1759,18 +1806,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
 
   get pointsTippy(): string {
     let points = i18n.t("number_of_points", {
-      count: this.state.score,
-      formattedCount: this.state.score,
+      count: Number(this.state.score),
+      formattedCount: Number(this.state.score),
     });
 
     let upvotes = i18n.t("number_of_upvotes", {
-      count: this.state.upvotes,
-      formattedCount: this.state.upvotes,
+      count: Number(this.state.upvotes),
+      formattedCount: Number(this.state.upvotes),
     });
 
     let downvotes = i18n.t("number_of_downvotes", {
-      count: this.state.downvotes,
-      formattedCount: this.state.downvotes,
+      count: Number(this.state.downvotes),
+      formattedCount: Number(this.state.downvotes),
     });
 
     return `${points} • ${upvotes} • ${downvotes}`;