Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310import { html } from "/js/lib/lit-html.js";import { noop, classnames, formatFullTimestamp, formatLargeNumber,} from "/js/utils.js";import { isBlockedPost, isNotFoundPost, isUnavailablePost, doHideAuthorOnUnauthenticated,} from "/js/dataHelpers.js";import "/js/components/app-icon.js";import { avatarTemplate } from "/js/templates/avatar.template.js";import "/js/components/plugin-rich-text.js";import { postEmbedTemplate } from "/js/templates/postEmbed.template.js";import { postActionBarTemplate } from "/js/templates/postActionBar.template.js";import { postHeaderTextTemplate } from "/js/templates/postHeaderText.template.js";import { authorBadgesTemplate } from "/js/templates/labelBadges.template.js";import { blockedPostTemplate } from "/js/templates/blockedPost.template.js";import { whoCanReplyBadgeTemplate } from "/js/templates/whoCanReplyBadge.template.js";import { WhoCanReplyModal } from "/js/modals/whoCanReply.modal.js";import { notFoundPostTemplate } from "/js/templates/notFoundPost.template.js";import { unavailablePostTemplate } from "/js/templates/unavailablePost.template.js";import { linkToPostLikes, linkToPostQuotes, linkToPostReposts,} from "/js/navigation.js";import "/js/components/lightbox-image-group.js";
function mutedWarningTemplate({ post, children }) { if (post.viewer?.hasMutedWord) { return html`<moderation-warning label="Post hidden by muted word" icon-style="closed-eye" >${children}</moderation-warning > `; } else if (post.viewer?.isHidden) { return html`<moderation-warning label="Post hidden by you" icon-style="closed-eye" >${children}</moderation-warning > `; } return children;}
function followButtonTemplate({ profile, isFollowPending, onClick }) { const isFollowing = !!profile.viewer?.following; const isFollowedBy = !!profile.viewer?.followedBy; return html`<button @click=${() => onClick(profile, !isFollowing)} class=${classnames( "rounded-button profile-following-button large-post-follow-button", { "rounded-button-secondary-inverted": !isFollowing, }, )} data-testid="follow-button" data-teststate=${isFollowing ? "following" : isFollowedBy ? "follow-back" : "follow"} ?disabled=${isFollowPending} > ${isFollowing ? "Following" : isFollowedBy ? html`<app-icon icon="plus-line"></app-icon> Follow back` : html`<app-icon icon="plus-line"></app-icon> Follow`} </button>`;}
function postActionCountsTemplate({ repostCount, likeCount, quoteCount, bookmarkCount, post,}) { if (!repostCount && !likeCount && !quoteCount && !bookmarkCount) { return null; } return html` <div class="post-action-counts"> ${repostCount > 0 ? html`<a href="${linkToPostReposts(post)}" class="post-action-count"> <span class="post-action-count-num" >${formatLargeNumber(repostCount)}</span > <span class="post-action-count-text" >${repostCount === 1 ? "repost" : "reposts"}</span > </a>` : ""} ${quoteCount > 0 ? html`<a href="${linkToPostQuotes(post)}" class="post-action-count"> <span class="post-action-count-num" >${formatLargeNumber(quoteCount)}</span > <span class="post-action-count-text" >${quoteCount === 1 ? "quote" : "quotes"}</span > </a>` : ""} ${likeCount > 0 ? html`<a href="${linkToPostLikes(post)}" class="post-action-count"> <span class="post-action-count-num" >${formatLargeNumber(likeCount)}</span > <span class="post-action-count-text" >${likeCount === 1 ? "like" : "likes"}</span > </a>` : ""} ${bookmarkCount > 0 ? html`<div class="post-action-count"> <span class="post-action-count-num" >${formatLargeNumber(bookmarkCount)}</span > <span class="post-action-count-text" >${bookmarkCount === 1 ? "save" : "saves"}</span > </div>` : ""} </div> `;}
export function largePostTemplate({ post, currentUser, isAuthenticated, isUserPost, postInteractionHandler, onClickReply = noop, replyContext, showActions = true, afterDelete = null, afterHide = null, afterBlock = null, showFollowButton = false, isFollowPending = false, onClickFollow = noop, pluginService,}) { if (isBlockedPost(post)) { return blockedPostTemplate(); } else if (isNotFoundPost(post)) { return notFoundPostTemplate(); } else if (isUnavailablePost(post)) { return unavailablePostTemplate(); } else if ( !isAuthenticated && post.author && doHideAuthorOnUnauthenticated(post.author) ) { return unavailablePostTemplate(); } const embed = post.embed ?? post.record?.embed; const postText = post.record.text || ""; const isRootPost = !post.record?.reply; const canEditInteractionSettings = isUserPost && isRootPost; const badgeLabels = post.badgeLabels ?? []; const contentLabel = post.contentLabel; // Instead of hiding, add the content label to the badge labels if (contentLabel && contentLabel.visibility !== "ignore") { badgeLabels.push(contentLabel); } return html`<div class="post large-post" data-testid="large-post"> <div class="post-content"> <div class="post-content-top"> <div> ${replyContext === "parent" || replyContext === "reply" ? html`<div class="reply-context-line-in"></div>` : ""} <div>${avatarTemplate({ author: post.author })}</div> </div> <div class="large-post-header-text"> ${postHeaderTextTemplate({ author: post.author, timestamp: post.indexedAt, includeTime: false, })} </div> ${showFollowButton && post.author ? followButtonTemplate({ profile: post.author, isFollowPending, onClick: (profile, doFollow) => onClickFollow(profile, doFollow), }) : ""} </div> ${authorBadgesTemplate({ badgeLabels, did: post.author?.did, pluginService, })} <div class="post-content-bottom"> ${mutedWarningTemplate({ post, children: html`<div class="post-body"> ${postText.length > 0 ? html`<div class="post-text"> <plugin-rich-text .pluginService=${pluginService} .text=${postText} .facets=${post.record.facets} .transformContext=${{ surface: "largePost", uri: post.uri, did: post.author?.did ?? null, }} truncate-urls ></plugin-rich-text> </div>` : ""} ${embed ? html`<div class="post-embed"> ${postEmbedTemplate({ embed, mediaLabel: post.mediaLabel, isAuthenticated, pluginService, })} </div>` : null} </div>`, })} <div class="post-full-timestamp"> ${formatFullTimestamp(post.indexedAt)} ${isRootPost ? whoCanReplyBadgeTemplate({ post, linkStyle: canEditInteractionSettings, onClick: (post) => { if (canEditInteractionSettings) { postInteractionHandler.handleEditInteractionSettings(post); } else { WhoCanReplyModal.open({ post }); } }, }) : null} </div> ${showActions ? html` ${postActionCountsTemplate({ repostCount: post.repostCount, quoteCount: post.quoteCount, likeCount: post.likeCount, bookmarkCount: post.bookmarkCount, post, })} ${postActionBarTemplate({ post, isUserPost, isAuthenticated, currentUser, onClickReply, onClickLike: (post, doLike) => postInteractionHandler.handleLike(post, doLike), onClickRepost: (post, doRepost) => postInteractionHandler.handleRepost(post, doRepost), onClickQuotePost: (post) => postInteractionHandler.handleQuotePost(post), onClickBookmark: (post, doBookmark) => postInteractionHandler.handleBookmark(post, doBookmark), onClickHidePost: async (post) => { await postInteractionHandler.handleHidePost(post); if (afterHide) { afterHide(post); } }, onClickMute: (profile, doMute) => postInteractionHandler.handleMuteAuthor(profile, doMute), onClickBlock: async (profile, doBlock) => { const success = await postInteractionHandler.handleBlockAuthor( profile, doBlock, ); if (success && doBlock && afterBlock) { afterBlock(post); } }, onClickDelete: async (post) => { await postInteractionHandler.handleDeletePost(post); if (afterDelete) { afterDelete(post); } }, onClickPin: (post, doPin) => postInteractionHandler.handlePinPost(post, doPin), onClickEditInteractionSettings: (post) => postInteractionHandler.handleEditInteractionSettings(post), onClickReport: (post) => postInteractionHandler.handleReport(post), pluginService, })} ` : ""} </div> </div> </div>`;}