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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379import { hapticsImpactMedium } from "/js/haptics.js";import { showToast } from "/js/toasts.js";import { confirmModal } from "/js/modals/confirm.modal.js";import { html } from "/js/lib/lit-html.js";import { buildPostgateEmbeddingRules, MAX_DETACHED_EMBEDDING_URIS, MAX_HIDDEN_REPLIES,} from "/js/dataHelpers.js";import { DetachedQuoteLimitError, HiddenReplyLimitError,} from "/js/dataLayer/mutations.js";import "/js/components/post-interaction-settings-dialog.js";import "/js/components/app-icon.js";
export class PostInteractionHandler { constructor(dataLayer, postComposerService, reportService) { this.dataLayer = dataLayer; this.postComposerService = postComposerService; this.reportService = reportService; this._interactionSettingsDialog = null; }
handleEditInteractionSettings(post) { if (this._interactionSettingsDialog !== null) { return; } const dialog = document.createElement("post-interaction-settings-dialog"); this._interactionSettingsDialog = dialog; const rootUri = post.record?.reply?.root?.uri ?? post.uri; dialog.dataLayer = this.dataLayer; if (rootUri !== post.uri) { dialog.threadRootUri = rootUri; } dialog.threadgateAllow = post.threadgate?.record?.allow ?? null; dialog.postgateEmbeddingRules = buildPostgateEmbeddingRules( !post.viewer?.embeddingDisabled, ); dialog.hydratedLists = post.threadgate?.lists ?? []; dialog.addEventListener("save-interaction-settings", async (event) => { const { threadgateAllow, threadgateDirty, postgateEmbeddingRules, postgateDirty, successCallback, errorCallback, } = event.detail; try { const writes = []; if (threadgateDirty) { writes.push( this.dataLayer.mutations.updateThreadgateAllow( { uri: rootUri }, threadgateAllow, ), ); } if (postgateDirty) { writes.push( this.dataLayer.mutations.updatePostgateEmbeddingRules( post, postgateEmbeddingRules, ), ); } await Promise.all(writes); successCallback(); } catch (error) { console.error(error); showToast("Failed to update interaction settings", { style: "error", }); errorCallback(error.message || "An unexpected error occurred."); } }); dialog.addEventListener("dialog-closed", () => { dialog.remove(); this._interactionSettingsDialog = null; }); document.body.appendChild(dialog); dialog.open(); }
async handleLike(post, doLike) { if (doLike) { try { hapticsImpactMedium(); await this.dataLayer.mutations.addLike(post); } catch (error) { console.error(error); showToast("Failed to like post", { style: "error" }); } } else { try { await this.dataLayer.mutations.removeLike(post); } catch (error) { console.error(error); showToast("Failed to unlike post", { style: "error" }); } } }
async handleRepost(post, doRepost) { if (doRepost) { try { hapticsImpactMedium(); await this.dataLayer.mutations.createRepost(post); } catch (error) { console.error(error); showToast("Failed to repost post", { style: "error" }); } } else { try { await this.dataLayer.mutations.deleteRepost(post); } catch (error) { console.error(error); showToast("Failed to delete repost", { style: "error" }); } } }
async handleBookmark(post, doBookmark) { if (doBookmark) { try { hapticsImpactMedium(); await this.dataLayer.mutations.addBookmark(post); showToast("Post saved", { style: "success" }); } catch (error) { console.error(error); showToast("Failed to bookmark post", { style: "error" }); } } else { try { await this.dataLayer.mutations.removeBookmark(post); showToast("Removed from saved posts", { iconTemplate: () => html`<app-icon icon="delete-bin-line"></app-icon>`, }); } catch (error) { console.error(error); showToast("Failed to remove bookmark", { style: "error" }); } } }
async handleDeletePost(post) { if ( !(await confirmModal( "If you remove this post, you won't be able to recover it.", { title: "Delete this post?", confirmButtonStyle: "danger", confirmButtonText: "Delete", }, )) ) { return; } try { await this.dataLayer.mutations.deletePost(post); showToast("Post deleted"); } catch (error) { console.error(error); showToast("Failed to delete post", { style: "error" }); } }
async handlePinPost(post, doPin) { if (doPin) { try { hapticsImpactMedium(); await this.dataLayer.mutations.pinPost(post); showToast("Post pinned to your profile", { style: "success" }); } catch (error) { console.error(error); showToast("Failed to pin post", { style: "error" }); } } else { try { await this.dataLayer.mutations.unpinPost(post); showToast("Post unpinned"); } catch (error) { console.error(error); showToast("Failed to unpin post", { style: "error" }); } } }
async handleToggleQuoteDetachment(post, quotedPostUri, doDetach) { if ( doDetach && !(await confirmModal( "This will remove your post from this quote post for all users, and replace it with a placeholder.", { title: "Detach quote post?", confirmButtonText: "Detach", }, )) ) { return; } try { await this.dataLayer.mutations.toggleQuoteDetachment( post, quotedPostUri, doDetach, ); showToast(doDetach ? "Quote detached" : "Quote re-attached", { style: "success", }); } catch (error) { if (error instanceof DetachedQuoteLimitError) { console.warn(error); showToast( `You can detach at most ${MAX_DETACHED_EMBEDDING_URIS} quotes from a post`, { style: "error" }, ); return; } console.error(error); showToast( doDetach ? "Failed to detach quote" : "Failed to re-attach quote", { style: "error" }, ); } }
async handleToggleReplyHiddenForEveryone(post, doHide) { if ( doHide && !(await confirmModal( "This reply will be hidden for everyone in your thread, and you won't be notified of any further replies to it.", { title: "Hide reply for everyone?", confirmButtonText: "Hide", }, )) ) { return; } try { await this.dataLayer.mutations.updateThreadgateHiddenReply(post, doHide); showToast( doHide ? "Reply hidden for everyone" : "Reply shown for everyone", { style: "success" }, ); } catch (error) { if (error instanceof HiddenReplyLimitError) { console.warn(error); showToast( `You can hide at most ${MAX_HIDDEN_REPLIES} replies in a thread`, { style: "error" }, ); return; } console.error(error); showToast(doHide ? "Failed to hide reply" : "Failed to show reply", { style: "error", }); } }
async handleHidePost(post) { if ( !(await confirmModal("This post will be hidden from feeds and threads.", { title: "Hide this post?", confirmButtonText: "Hide", })) ) { return; } try { await this.dataLayer.mutations.hidePost(post); showToast("Post hidden"); } catch (error) { console.error(error); showToast("Failed to hide post", { style: "error" }); } }
async handleMuteAuthor(profile, doMute) { if (doMute) { try { await this.dataLayer.mutations.muteProfile(profile); showToast("Account muted"); } catch (error) { console.error(error); showToast("Failed to mute account", { style: "error" }); } } else { try { await this.dataLayer.mutations.unmuteProfile(profile); showToast("Account unmuted"); } catch (error) { console.error(error); showToast("Failed to unmute account", { style: "error" }); } } }
async handleMuteThread(post, doMute) { try { await this.dataLayer.mutations.setThreadMuted(post, doMute); showToast( doMute ? "You will no longer receive notifications for this thread" : "You will now receive notifications for this thread", ); } catch (error) { console.error(error); showToast(doMute ? "Failed to mute thread" : "Failed to unmute thread", { style: "error", }); } }
async handleBlockAuthor(profile, doBlock) { if (doBlock) { const confirmed = await confirmModal( "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.", { title: "Block Account?", confirmButtonText: "Block", confirmButtonStyle: "danger", }, ); if (!confirmed) return false; try { await this.dataLayer.mutations.blockProfile(profile); showToast("Account blocked"); return true; } catch (error) { console.error(error); showToast("Failed to block account", { style: "error" }); return false; } } else { try { await this.dataLayer.mutations.unblockProfile(profile); showToast("Account unblocked"); return true; } catch (error) { console.error(error); showToast("Failed to unblock account", { style: "error" }); return false; } } }
async handleQuotePost(post) { const currentUser = this.dataLayer.derived.$currentUser.get(); if (!currentUser) { console.warn("handleQuotePost: No current user"); return; } try { await this.postComposerService.composePost({ currentUser, quotedPost: post, }); } catch (error) { console.error(error); } }
async handleReport(post) { try { await this.reportService.openReportDialog({ subject: post, subjectType: "post", }); } catch (error) { console.error(error); } }}