From 7c13b8dba16452d777dc96494ef90349bc35bf8c Mon Sep 17 00:00:00 2001 From: Jay Sitter Date: Thu, 22 Jun 2023 13:36:38 -0400 Subject: [PATCH 01/18] feat: Move vote buttons to separate component --- src/shared/components/common/vote-buttons.tsx | 207 ++++++++++++++++++ src/shared/components/post/post-listing.tsx | 163 ++++---------- 2 files changed, 244 insertions(+), 126 deletions(-) create mode 100644 src/shared/components/common/vote-buttons.tsx diff --git a/src/shared/components/common/vote-buttons.tsx b/src/shared/components/common/vote-buttons.tsx new file mode 100644 index 00000000..2bbddad6 --- /dev/null +++ b/src/shared/components/common/vote-buttons.tsx @@ -0,0 +1,207 @@ +import { showScores } from "@utils/app"; +import { numToSI } from "@utils/helpers"; +import { Component, linkEvent } from "inferno"; +import { CommentAggregates, PostAggregates } from "lemmy-js-client"; +import { I18NextService } from "../../services"; +import { Icon, Spinner } from "../common/icon"; +import { PostListing } from "../post/post-listing"; + +interface VoteButtonsProps { + postListing: PostListing; + enableDownvotes?: boolean; + upvoteLoading?: boolean; + downvoteLoading?: boolean; + handleUpvote: (i: PostListing) => void; + handleDownvote: (i: PostListing) => void; + counts: CommentAggregates | PostAggregates; + my_vote?: number; +} + +interface VoteButtonsState { + upvoteLoading: boolean; + downvoteLoading: boolean; +} + +export class VoteButtonsCompact extends Component< + VoteButtonsProps, + VoteButtonsState +> { + state: VoteButtonsState = { + upvoteLoading: false, + downvoteLoading: false, + }; + + constructor(props: any, context: any) { + super(props, context); + } + + get pointsTippy(): string { + const points = I18NextService.i18n.t("number_of_points", { + count: Number(this.props.counts.score), + formattedCount: Number(this.props.counts.score), + }); + + const upvotes = I18NextService.i18n.t("number_of_upvotes", { + count: Number(this.props.counts.upvotes), + formattedCount: Number(this.props.counts.upvotes), + }); + + const downvotes = I18NextService.i18n.t("number_of_downvotes", { + count: Number(this.props.counts.downvotes), + formattedCount: Number(this.props.counts.downvotes), + }); + + return `${points} • ${upvotes} • ${downvotes}`; + } + + get tippy() { + return showScores() ? { "data-tippy-content": this.pointsTippy } : {}; + } + + render() { + return ( + <> +
+ + + {numToSI(this.props.counts.score)} + + {this.props.enableDownvotes && ( + + )} +
+ + ); + } +} + +export class VoteButtons extends Component { + state: VotesState = { + upvoteLoading: false, + downvoteLoading: false, + }; + + constructor(props: any, context: any) { + super(props, context); + } + + get pointsTippy(): string { + const points = I18NextService.i18n.t("number_of_points", { + count: Number(this.props.counts.score), + formattedCount: Number(this.props.counts.score), + }); + + const upvotes = I18NextService.i18n.t("number_of_upvotes", { + count: Number(this.props.counts.upvotes), + formattedCount: Number(this.props.counts.upvotes), + }); + + const downvotes = I18NextService.i18n.t("number_of_downvotes", { + count: Number(this.props.counts.downvotes), + formattedCount: Number(this.props.counts.downvotes), + }); + + return `${points} • ${upvotes} • ${downvotes}`; + } + + get tippy() { + return showScores() ? { "data-tippy-content": this.pointsTippy } : {}; + } + + render() { + return ( +
+ + {showScores() ? ( +
+ {numToSI(this.props.counts.score)} +
+ ) : ( +
+ )} + {this.props.enableDownvotes && ( + + )} +
+ ); + } +} diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 4d0951bb..7eed489d 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -1,11 +1,10 @@ -import { myAuthRequired, newVote, showScores } from "@utils/app"; +import { myAuthRequired, newVote } from "@utils/app"; import { canShare, share } from "@utils/browser"; import { getExternalHost, getHttpBase } from "@utils/env"; import { capitalizeFirstLetter, futureDaysToUnixTime, hostname, - numToSI, } from "@utils/helpers"; import { isImage, isVideo } from "@utils/media"; import { @@ -51,6 +50,7 @@ import { setupTippy } from "../../tippy"; import { Icon, PurgeWarning, Spinner } from "../common/icon"; import { MomentTime } from "../common/moment-time"; import { PictrsImage } from "../common/pictrs-image"; +import { VoteButtons, VoteButtonsCompact } from "../common/vote-buttons"; import { CommunityLink } from "../community/community-link"; import { PersonListing } from "../person/person-listing"; import { MetadataCard } from "./metadata-card"; @@ -413,55 +413,6 @@ export class PostListing extends Component { ); } - voteBar() { - return ( -
- - {showScores() ? ( -
- {numToSI(this.postView.counts.score)} -
- ) : ( -
- )} - {this.props.enableDownvotes && ( - - )} -
- ); - } - get postLink() { const post = this.postView.post; return ( @@ -641,7 +592,16 @@ export class PostListing extends Component { )} - {mobile && !this.props.viewOnly && this.mobileVotes} + {mobile && !this.props.viewOnly && ( + + )} {UserService.Instance.myUserInfo && !this.props.viewOnly && this.postActions()} @@ -679,7 +639,6 @@ export class PostListing extends Component { return ( <> {this.saveButton} - {this.crossPostButton} {/** * If there is a URL, or if the post has a body and we were told not to @@ -704,6 +663,11 @@ export class PostListing extends Component {
    +
  • {this.crossPostButton}
  • +
  • +
    +
  • + {!this.myPost ? ( <>
  • {this.reportButton}
  • @@ -770,69 +734,6 @@ export class PostListing extends Component { : pv.unread_comments; } - get mobileVotes() { - // TODO: make nicer - const tippy = showScores() - ? { "data-tippy-content": this.pointsTippy } - : {}; - return ( - <> -
    - - {this.props.enableDownvotes && ( - - )} -
    - - ); - } - get saveButton() { const saved = this.postView.saved; const label = saved @@ -861,7 +762,7 @@ export class PostListing extends Component { get crossPostButton() { return ( { data-tippy-content={I18NextService.i18n.t("cross_post")} aria-label={I18NextService.i18n.t("cross_post")} > - + + {I18NextService.i18n.t("cross_post")} ); } @@ -931,7 +833,6 @@ export class PostListing extends Component { + {this.props.enableDownvotes && ( - - {numToSI(this.props.counts.score)} - - {this.props.enableDownvotes && ( - - )} - - + )} + ); } } -export class VoteButtons extends Component { - state: VotesState = { +export class VoteButtons extends Component { + state: VoteButtonsState = { upvoteLoading: false, downvoteLoading: false, }; From 7af899ee7553c280353937e79da1ce3abda5da71 Mon Sep 17 00:00:00 2001 From: Jay Sitter Date: Thu, 22 Jun 2023 17:06:33 -0400 Subject: [PATCH 03/18] fix: Remove tippy duplicate functions --- src/shared/components/common/vote-buttons.tsx | 71 ++++++------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/src/shared/components/common/vote-buttons.tsx b/src/shared/components/common/vote-buttons.tsx index dce46f5b..20e8da2a 100644 --- a/src/shared/components/common/vote-buttons.tsx +++ b/src/shared/components/common/vote-buttons.tsx @@ -23,6 +23,25 @@ interface VoteButtonsState { downvoteLoading: boolean; } +const tippy = (counts: CommentAggregates | PostAggregates): string => { + const points = I18NextService.i18n.t("number_of_points", { + count: Number(counts.score), + formattedCount: Number(counts.score), + }); + + const upvotes = I18NextService.i18n.t("number_of_upvotes", { + count: Number(counts.upvotes), + formattedCount: Number(counts.upvotes), + }); + + const downvotes = I18NextService.i18n.t("number_of_downvotes", { + count: Number(counts.downvotes), + formattedCount: Number(counts.downvotes), + }); + + return `${points} • ${upvotes} • ${downvotes}`; +}; + export class VoteButtonsCompact extends Component< VoteButtonsProps, VoteButtonsState @@ -36,29 +55,6 @@ export class VoteButtonsCompact extends Component< super(props, context); } - get pointsTippy(): string { - const points = I18NextService.i18n.t("number_of_points", { - count: Number(this.props.counts.score), - formattedCount: Number(this.props.counts.score), - }); - - const upvotes = I18NextService.i18n.t("number_of_upvotes", { - count: Number(this.props.counts.upvotes), - formattedCount: Number(this.props.counts.upvotes), - }); - - const downvotes = I18NextService.i18n.t("number_of_downvotes", { - count: Number(this.props.counts.downvotes), - formattedCount: Number(this.props.counts.downvotes), - }); - - return `${points} • ${upvotes} • ${downvotes}`; - } - - get tippy() { - return showScores() ? { "data-tippy-content": this.pointsTippy } : {}; - } - render() { return (
    @@ -66,7 +62,7 @@ export class VoteButtonsCompact extends Component< className={`btn-animate btn py-0 px-1 ${ this.props.my_vote === 1 ? "text-info" : "text-muted" }`} - {...this.tippy} + data-tippy-content={tippy(this.props.counts)} onClick={linkEvent(this.props.postListing, this.props.handleUpvote)} aria-label={I18NextService.i18n.t("upvote")} aria-pressed={this.props.my_vote === 1} @@ -93,7 +89,7 @@ export class VoteButtonsCompact extends Component< this.props.postListing, this.props.handleDownvote )} - {...this.tippy} + data-tippy-content={tippy(this.props.counts)} aria-label={I18NextService.i18n.t("downvote")} aria-pressed={this.props.my_vote === -1} > @@ -130,29 +126,6 @@ export class VoteButtons extends Component { super(props, context); } - get pointsTippy(): string { - const points = I18NextService.i18n.t("number_of_points", { - count: Number(this.props.counts.score), - formattedCount: Number(this.props.counts.score), - }); - - const upvotes = I18NextService.i18n.t("number_of_upvotes", { - count: Number(this.props.counts.upvotes), - formattedCount: Number(this.props.counts.upvotes), - }); - - const downvotes = I18NextService.i18n.t("number_of_downvotes", { - count: Number(this.props.counts.downvotes), - formattedCount: Number(this.props.counts.downvotes), - }); - - return `${points} • ${upvotes} • ${downvotes}`; - } - - get tippy() { - return showScores() ? { "data-tippy-content": this.pointsTippy } : {}; - } - render() { return (
    @@ -174,7 +147,7 @@ export class VoteButtons extends Component { {showScores() ? (
    {numToSI(this.props.counts.score)}
    From d0fb7e614766b5f5e1021535ac65fac77b219f56 Mon Sep 17 00:00:00 2001 From: Jay Sitter Date: Thu, 22 Jun 2023 17:30:48 -0400 Subject: [PATCH 04/18] fix: Undo some extraneous changes --- src/shared/components/post/post-listing.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index a913f534..15e74168 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -779,7 +779,7 @@ export class PostListing extends Component { get crossPostButton() { return ( { data-tippy-content={I18NextService.i18n.t("cross_post")} aria-label={I18NextService.i18n.t("cross_post")} > - - {I18NextService.i18n.t("cross_post")} + ); } @@ -1361,7 +1360,7 @@ export class PostListing extends Component { ); } - bodyPreview() { + showMobilePreview() { const { body, id } = this.postView.post; return !this.showBody && body ? ( @@ -1385,10 +1384,10 @@ export class PostListing extends Component { {/* If it has a thumbnail, do a right aligned thumbnail */} {this.mobileThumbnail()} -
    - {this.bodyPreview()} - {this.commentsLine(true)} -
    + {/* Show a preview of the post body */} + {this.showMobilePreview()} + + {this.commentsLine(true)} {this.userActionsLine()} {this.duplicatesLine()} {this.removeAndBanDialogs()} @@ -1417,7 +1416,6 @@ export class PostListing extends Component {
    {this.postTitleLine()} {this.createdLine()} - {this.bodyPreview()} {this.commentsLine()} {this.duplicatesLine()} {this.userActionsLine()} From 1ea33e8c7e9acaffa97dd25c6a172ce441f01d91 Mon Sep 17 00:00:00 2001 From: Jay Sitter Date: Thu, 22 Jun 2023 17:32:24 -0400 Subject: [PATCH 05/18] fix: Undo some other extraneous changes --- src/shared/components/post/post-listing.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 15e74168..b4c86eee 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -656,6 +656,7 @@ export class PostListing extends Component { return ( <> {this.saveButton} + {this.crossPostButton} {/** * If there is a URL, or if the post has a body and we were told not to @@ -680,11 +681,6 @@ export class PostListing extends Component {
      -
    • {this.crossPostButton}
    • -
    • -
      -
    • - {!this.myPost ? ( <>
    • {this.reportButton}
    • @@ -779,7 +775,7 @@ export class PostListing extends Component { get crossPostButton() { return ( Date: Thu, 22 Jun 2023 23:48:53 -0400 Subject: [PATCH 06/18] fix: Rework some vote buttons architecture --- src/shared/components/common/vote-buttons.tsx | 52 ++++++++++++------- src/shared/components/post/post-listing.tsx | 36 +++---------- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/shared/components/common/vote-buttons.tsx b/src/shared/components/common/vote-buttons.tsx index 20e8da2a..bba82ba6 100644 --- a/src/shared/components/common/vote-buttons.tsx +++ b/src/shared/components/common/vote-buttons.tsx @@ -1,19 +1,21 @@ -import { showScores } from "@utils/app"; +import { myAuthRequired, newVote, showScores } from "@utils/app"; import { numToSI } from "@utils/helpers"; import classNames from "classnames"; import { Component, linkEvent } from "inferno"; -import { CommentAggregates, PostAggregates } from "lemmy-js-client"; +import { + CommentAggregates, + CreateCommentLike, + CreatePostLike, + PostAggregates, +} from "lemmy-js-client"; +import { VoteType } from "../../interfaces"; import { I18NextService } from "../../services"; import { Icon, Spinner } from "../common/icon"; -import { PostListing } from "../post/post-listing"; interface VoteButtonsProps { - postListing: PostListing; + id: number; + onVote: (i: CreatePostLike | CreateCommentLike) => void; enableDownvotes?: boolean; - upvoteLoading?: boolean; - downvoteLoading?: boolean; - handleUpvote: (i: PostListing) => void; - handleDownvote: (i: PostListing) => void; counts: CommentAggregates | PostAggregates; my_vote?: number; } @@ -42,6 +44,26 @@ const tippy = (counts: CommentAggregates | PostAggregates): string => { return `${points} • ${upvotes} • ${downvotes}`; }; +const handleUpvote = (i: VoteButtons) => { + i.setState({ upvoteLoading: true }); + i.props.onVote({ + post_id: i.props.id, + score: newVote(VoteType.Upvote, i.props.my_vote), + auth: myAuthRequired(), + }); + i.setState({ upvoteLoading: false }); +}; + +const handleDownvote = (i: VoteButtons) => { + i.setState({ downvoteLoading: true }); + i.props.onVote({ + post_id: i.props.id, + score: newVote(VoteType.Downvote, i.props.my_vote), + auth: myAuthRequired(), + }); + i.setState({ downvoteLoading: false }); +}; + export class VoteButtonsCompact extends Component< VoteButtonsProps, VoteButtonsState @@ -63,7 +85,7 @@ export class VoteButtonsCompact extends Component< this.props.my_vote === 1 ? "text-info" : "text-muted" }`} data-tippy-content={tippy(this.props.counts)} - onClick={linkEvent(this.props.postListing, this.props.handleUpvote)} + onClick={linkEvent(this, handleUpvote)} aria-label={I18NextService.i18n.t("upvote")} aria-pressed={this.props.my_vote === 1} > @@ -85,10 +107,7 @@ export class VoteButtonsCompact extends Component< className={`ms-2 btn-animate btn py-0 px-1 ${ this.props.my_vote === -1 ? "text-danger" : "text-muted" }`} - onClick={linkEvent( - this.props.postListing, - this.props.handleDownvote - )} + onClick={linkEvent(this, handleDownvote)} data-tippy-content={tippy(this.props.counts)} aria-label={I18NextService.i18n.t("downvote")} aria-pressed={this.props.my_vote === -1} @@ -133,7 +152,7 @@ export class VoteButtons extends Component { className={`btn-animate btn btn-link p-0 ${ this.props.my_vote == 1 ? "text-info" : "text-muted" }`} - onClick={linkEvent(this.props.postListing, this.props.handleUpvote)} + onClick={linkEvent(this, handleUpvote)} data-tippy-content={I18NextService.i18n.t("upvote")} aria-label={I18NextService.i18n.t("upvote")} aria-pressed={this.props.my_vote === 1} @@ -159,10 +178,7 @@ export class VoteButtons extends Component { className={`btn-animate btn btn-link p-0 ${ this.props.my_vote == -1 ? "text-danger" : "text-muted" }`} - onClick={linkEvent( - this.props.postListing, - this.props.handleDownvote - )} + onClick={linkEvent(this, handleDownvote)} data-tippy-content={I18NextService.i18n.t("downvote")} aria-label={I18NextService.i18n.t("downvote")} aria-pressed={this.props.my_vote === -1} diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 0340cf5e..8e0cc28c 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -1,4 +1,4 @@ -import { myAuthRequired, newVote } from "@utils/app"; +import { myAuthRequired } from "@utils/app"; import { canShare, share } from "@utils/browser"; import { getExternalHost, getHttpBase } from "@utils/env"; import { @@ -43,7 +43,7 @@ import { TransferCommunity, } from "lemmy-js-client"; import { relTags } from "../../config"; -import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces"; +import { BanType, PostFormParams, PurgeType } from "../../interfaces"; import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown"; import { I18NextService, UserService } from "../../services"; import { setupTippy } from "../../tippy"; @@ -78,8 +78,6 @@ interface PostListingState { showBody: boolean; showReportDialog: boolean; reportReason?: string; - upvoteLoading: boolean; - downvoteLoading: boolean; reportLoading: boolean; blockLoading: boolean; lockLoading: boolean; @@ -142,8 +140,6 @@ export class PostListing extends Component { showMoreMobile: false, showBody: false, showReportDialog: false, - upvoteLoading: false, - downvoteLoading: false, purgeLoading: false, reportLoading: false, blockLoading: false, @@ -169,8 +165,6 @@ export class PostListing extends Component { componentWillReceiveProps(nextProps: PostListingProps) { if (this.props !== nextProps) { this.setState({ - upvoteLoading: false, - downvoteLoading: false, purgeLoading: false, reportLoading: false, blockLoading: false, @@ -604,10 +598,10 @@ export class PostListing extends Component { )} {mobile && !this.props.viewOnly && ( @@ -1387,10 +1381,10 @@ export class PostListing extends Component {
      {!this.props.viewOnly && ( @@ -1759,24 +1753,6 @@ export class PostListing extends Component { 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), From e9341e791bfa855683d098f1223bec1938bacf54 Mon Sep 17 00:00:00 2001 From: Jay Sitter Date: Thu, 22 Jun 2023 23:53:48 -0400 Subject: [PATCH 07/18] fix: Remove unused prop --- src/shared/components/post/post-listing.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 8e0cc28c..e8ab95eb 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -600,7 +600,6 @@ export class PostListing extends Component { { Date: Fri, 23 Jun 2023 00:15:24 -0400 Subject: [PATCH 08/18] fix!: Try to get Vote Buttons component working in Comments --- .../components/comment/comment-node.tsx | 113 +++--------------- src/shared/components/common/vote-buttons.tsx | 6 +- 2 files changed, 21 insertions(+), 98 deletions(-) diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index b558d142..d38a7af9 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -3,7 +3,6 @@ import { getCommentParentId, myAuth, myAuthRequired, - newVote, showScores, } from "@utils/app"; import { futureDaysToUnixTime, numToSI } from "@utils/helpers"; @@ -53,13 +52,13 @@ import { CommentNodeI, CommentViewType, PurgeType, - VoteType, } from "../../interfaces"; import { mdToHtml, mdToHtmlNoImages } from "../../markdown"; import { I18NextService, UserService } from "../../services"; import { setupTippy } from "../../tippy"; import { Icon, PurgeWarning, Spinner } from "../common/icon"; import { MomentTime } from "../common/moment-time"; +import { VoteButtonsCompact } from "../common/vote-buttons"; import { CommunityLink } from "../community/community-link"; import { PersonListing } from "../person/person-listing"; import { CommentForm } from "./comment-form"; @@ -280,7 +279,7 @@ export class CommentNode extends Component { node.comment_view.counts.child_count > 0; return ( -
    • +
    • { )} {/* This is an expanding spacer for mobile */}
      + {showScores() && ( <> - - {this.state.upvoteLoading ? ( - - ) : ( - - {numToSI(this.commentView.counts.score)} - - )} - + {numToSI(this.commentView.counts.score)} + )} @@ -448,60 +436,13 @@ export class CommentNode extends Component { )} {UserService.Instance.myUserInfo && !this.props.viewOnly && ( <> - - {this.props.enableDownvotes && ( - - )} + )} {community.removed && ( - + {I18NextService.i18n.t("removed")} )} {community.deleted && ( - + {I18NextService.i18n.t("deleted")} )} {community.nsfw && ( - + {I18NextService.i18n.t("nsfw")} )} @@ -309,7 +309,7 @@ export class Sidebar extends Component { const community_view = this.props.community_view; return ( <> -