-import { myAuthRequired, newVote } from "@utils/app";
+import { myAuthRequired } from "@utils/app";
import { canShare, share } from "@utils/browser";
import { getExternalHost, getHttpBase } from "@utils/env";
import {
TransferCommunity,
} from "lemmy-js-client";
import { relTags } from "../../config";
-import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
+import {
+ BanType,
+ PostFormParams,
+ PurgeType,
+ VoteContentType,
+} from "../../interfaces";
import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy";
showBody: boolean;
showReportDialog: boolean;
reportReason?: string;
- upvoteLoading: boolean;
- downvoteLoading: boolean;
reportLoading: boolean;
blockLoading: boolean;
lockLoading: boolean;
showMoreMobile: false,
showBody: false,
showReportDialog: false,
- upvoteLoading: false,
- downvoteLoading: false,
purgeLoading: false,
reportLoading: false,
blockLoading: false,
componentWillReceiveProps(nextProps: PostListingProps) {
if (this.props !== nextProps) {
this.setState({
- upvoteLoading: false,
- downvoteLoading: false,
purgeLoading: false,
reportLoading: false,
blockLoading: false,
</a>
</div>
<div className="my-2 d-block d-sm-none">
- <a
- className="d-inline-block"
+ <button
+ type="button"
+ className="p-0 border-0 bg-transparent d-inline-block"
onClick={linkEvent(this, this.handleImageExpandClick)}
>
<PictrsImage src={this.imageSrc} />
- </a>
+ </button>
</div>
</>
);
const { post } = this.postView;
const { url } = post;
+ // if direct video link
if (url && isVideo(url)) {
return (
<div className="embed-responsive mt-3">
);
}
+ // if embedded video link
+ if (url && post.embed_video_url) {
+ return (
+ <div className="ratio ratio-16x9">
+ <iframe
+ allowFullScreen
+ className="post-metadata-iframe"
+ src={post.embed_video_url}
+ title={post.embed_title}
+ ></iframe>
+ </div>
+ );
+ }
+
return <></>;
}
</a>
);
} else if (url) {
- if (!this.props.hideImage && isVideo(url)) {
+ if ((!this.props.hideImage && isVideo(url)) || post.embed_video_url) {
return (
<a
className="text-body"
createdLine() {
const post_view = this.postView;
return (
- <ul className="list-inline mb-1 text-muted small mt-2">
- <li className="list-inline-item">
- <PersonListing person={post_view.creator} />
-
- {this.creatorIsMod_ && (
- <span className="mx-1 badge text-bg-light">
- {I18NextService.i18n.t("mod")}
- </span>
- )}
- {this.creatorIsAdmin_ && (
- <span className="mx-1 badge text-bg-light">
- {I18NextService.i18n.t("admin")}
- </span>
- )}
- {post_view.creator.bot_account && (
- <span className="mx-1 badge text-bg-light">
- {I18NextService.i18n.t("bot_account").toLowerCase()}
- </span>
- )}
- {this.props.showCommunity && (
- <>
- {" "}
- {I18NextService.i18n.t("to")}{" "}
- <CommunityLink community={post_view.community} />
- </>
- )}
- </li>
+ <span className="small">
+ <PersonListing person={post_view.creator} muted={true} />
+ {this.creatorIsMod_ && (
+ <span className="mx-1 badge text-bg-light">
+ {I18NextService.i18n.t("mod")}
+ </span>
+ )}
+ {this.creatorIsAdmin_ && (
+ <span className="mx-1 badge text-bg-light">
+ {I18NextService.i18n.t("admin")}
+ </span>
+ )}
+ {post_view.creator.bot_account && (
+ <span className="mx-1 badge text-bg-light">
+ {I18NextService.i18n.t("bot_account").toLowerCase()}
+ </span>
+ )}
+ {this.props.showCommunity && (
+ <>
+ {" "}
+ {I18NextService.i18n.t("to")}{" "}
+ <CommunityLink community={post_view.community} />
+ </>
+ )}
{post_view.post.language_id !== 0 && (
<span className="mx-1 badge text-bg-light">
{
)?.name
}
</span>
- )}
- <li className="list-inline-item">•</li>
- <li className="list-inline-item">
- <span>
- <MomentTime
- published={post_view.post.published}
- updated={post_view.post.updated}
- />
- </span>
- </li>
- </ul>
+ )}{" "}
+ •{" "}
+ <MomentTime
+ published={post_view.post.published}
+ updated={post_view.post.updated}
+ />
+ </span>
);
}
<Link
className={`d-inline ${
!post.featured_community && !post.featured_local
- ? "text-body"
- : "text-primary"
+ ? "link-dark"
+ : "link-primary"
}`}
to={`/post/${post.id}`}
title={I18NextService.i18n.t("comments")}
<a
className={
!post.featured_community && !post.featured_local
- ? "text-body"
- : "text-primary"
+ ? "link-dark"
+ : "link-primary"
}
href={url}
title={url}
)}
{post.deleted && (
<small
- className="unselectable pointer ms-2 text-muted font-italic"
+ className="unselectable pointer ms-2 text-muted fst-italic"
data-tippy-content={I18NextService.i18n.t("deleted")}
>
<Icon icon="trash" classes="icon-inline text-danger" />
)}
{post.locked && (
<small
- className="unselectable pointer ms-2 text-muted font-italic"
+ className="unselectable pointer ms-2 text-muted fst-italic"
data-tippy-content={I18NextService.i18n.t("locked")}
>
<Icon icon="lock" classes="icon-inline text-danger" />
)}
{post.featured_community && (
<small
- className="unselectable pointer ms-2 text-muted font-italic"
+ className="unselectable pointer ms-2 text-muted fst-italic"
data-tippy-content={I18NextService.i18n.t(
"featured_in_community"
)}
)}
{post.featured_local && (
<small
- className="unselectable pointer ms-2 text-muted font-italic"
+ className="unselectable pointer ms-2 text-muted fst-italic"
data-tippy-content={I18NextService.i18n.t("featured_in_local")}
aria-label={I18NextService.i18n.t("featured_in_local")}
>
const url = post.url;
return (
- <p className="d-flex text-muted align-items-center gap-1 small m-0">
+ <p className="small m-0">
{url && !(hostname(url) === getExternalHost()) && (
<a
- className="text-muted font-italic"
+ className="fst-italic link-dark link-opacity-75 link-opacity-100-hover"
href={url}
title={url}
rel={relTags}
)}
{mobile && !this.props.viewOnly && (
<VoteButtonsCompact
- postListing={this}
+ voteContentType={VoteContentType.Post}
+ id={this.postView.post.id}
+ onVote={this.props.onPostVote}
enableDownvotes={this.props.enableDownvotes}
- handleUpvote={this.handleUpvote}
- handleDownvote={this.handleDownvote}
counts={this.postView.counts}
my_vote={this.postView.my_vote}
/>
return (
<>
{this.saveButton}
+ {this.crossPostButton}
{/**
* If there is a URL, or if the post has a body and we were told not to
data-tippy-content={I18NextService.i18n.t("more")}
data-bs-toggle="dropdown"
aria-expanded="false"
- aria-controls="advancedButtonsDropdown"
+ aria-controls={`advancedButtonsDropdown${post.id}`}
aria-label={I18NextService.i18n.t("more")}
>
<Icon icon="more-vertical" inline />
</button>
- <ul className="dropdown-menu" id="advancedButtonsDropdown">
- <li>{this.crossPostButton}</li>
- <li>
- <hr className="dropdown-divider" />
- </li>
-
+ <ul
+ className="dropdown-menu"
+ id={`advancedButtonsDropdown${post.id}`}
+ >
{!this.myPost ? (
<>
<li>{this.reportButton}</li>
to={`/post/${post_view.post.id}?scrollToComments=true`}
data-tippy-content={title}
>
- <span className="me-1">
- <Icon icon="message-square" classes="me-1" inline />
- {post_view.counts.comments}
- </span>
+ <Icon icon="message-square" classes="me-1" inline />
+ {post_view.counts.comments}
{this.unreadCount && (
- <span className="text-muted fst-italic">
- ({this.unreadCount} {I18NextService.i18n.t("new")})
- </span>
+ <>
+ {" "}
+ <span className="fst-italic">
+ ({this.unreadCount} {I18NextService.i18n.t("new")})
+ </span>
+ </>
)}
</Link>
);
get crossPostButton() {
return (
<Link
- className="btn-animate text-muted py-0"
+ className="btn btn-sm btn-animate text-muted py-0"
to={{
/* Empty string properties are required to satisfy type*/
pathname: "/create_post",
const post_view = this.postView;
return (
this.state.showAdvanced && (
- <>
+ <div className="mt-3">
{this.canMod_ && (
<>
{!this.creatorIsMod_ &&
)}
</>
)}
- </>
+ </div>
)
);
}
value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
- <label className="col-form-label" htmlFor={`mod-ban-expires`}>
+ <label className="col-form-label" htmlFor="mod-ban-expires">
{I18NextService.i18n.t("expires")}
</label>
<input
type="number"
- id={`mod-ban-expires`}
+ id="mod-ban-expires"
className="form-control me-2"
placeholder={I18NextService.i18n.t("number_of_days")}
value={this.state.banExpireDays}
);
}
- showMobilePreview() {
+ showBodyPreview() {
const { body, id } = this.postView.post;
return !this.showBody && body ? (
- <Link className="text-body" to={`/post/${id}`}>
+ <Link className="text-body mt-2 d-block" to={`/post/${id}`}>
<div className="md-div mb-1 preview-lines">{body}</div>
</Link>
) : (
{this.mobileThumbnail()}
{/* Show a preview of the post body */}
- {this.showMobilePreview()}
+ {this.showBodyPreview()}
{this.commentsLine(true)}
{this.userActionsLine()}
<div className="d-none d-sm-block">
<article className="row post-container">
{!this.props.viewOnly && (
- <VoteButtons
- postListing={this}
- enableDownvotes={this.props.enableDownvotes}
- handleUpvote={this.handleUpvote}
- handleDownvote={this.handleDownvote}
- counts={this.postView.counts}
- my_vote={this.postView.my_vote}
- />
+ <div className="col flex-grow-0">
+ <VoteButtons
+ voteContentType={VoteContentType.Post}
+ id={this.postView.post.id}
+ onVote={this.props.onPostVote}
+ enableDownvotes={this.props.enableDownvotes}
+ counts={this.postView.counts}
+ my_vote={this.postView.my_vote}
+ />
+ </div>
)}
- <div className="col-sm-2 pe-0 post-media">
- <div className="">{this.thumbnail()}</div>
- </div>
- <div className="col-12 col-sm-9">
+ <div className="col flex-grow-1">
<div className="row">
- <div className="col-12">
+ <div className="col-sm-3 col-lg-2 pe-0 post-media">
+ <div className="">{this.thumbnail()}</div>
+ </div>
+ <div className="col-12 col-sm-9 col-lg-10">
{this.postTitleLine()}
{this.createdLine()}
+ {this.showBodyPreview()}
{this.commentsLine()}
{this.duplicatesLine()}
{this.userActionsLine()}
setupTippy();
}
- handleUpvote(i: PostListing) {
- i.setState({ upvoteLoading: true });
- i.props.onPostVote({
- post_id: i.postView.post.id,
- score: newVote(VoteType.Upvote, i.props.post_view.my_vote),
- auth: myAuthRequired(),
- });
- }
-
- handleDownvote(i: PostListing) {
- i.setState({ downvoteLoading: true });
- i.props.onPostVote({
- post_id: i.postView.post.id,
- score: newVote(VoteType.Downvote, i.props.post_view.my_vote),
- auth: myAuthRequired(),
- });
- }
-
get pointsTippy(): string {
const points = I18NextService.i18n.t("number_of_points", {
count: Number(this.postView.counts.score),