From c08c857e36da98dfbddb53612769ebf6d831f986 Mon Sep 17 00:00:00 2001 From: kawamataryo Date: Sun, 21 May 2023 22:02:59 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20unfollow=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/content.ts | 46 ++++++++++++++++++++++++++++++------------- src/lib/bskyClient.ts | 8 ++++++-- src/lib/domHelpers.ts | 45 +++++++++++++++++++++++++++++++++++++----- src/style.content.css | 10 +++++++++- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/content.ts b/src/content.ts index e100c5a..7a737af 100644 --- a/src/content.ts +++ b/src/content.ts @@ -15,6 +15,8 @@ let abortController = new AbortController(); const notFoundUserCache = new Set() +const followerUrlMap = new Map() + const initialize = async () => { abortController.abort() abortController = new AbortController() @@ -47,13 +49,24 @@ const searchBskyUsers = async ({ limit: 1, }) + // TODO: Refactor, this is duplicated // first, search by account name if (isSimilarUser(twDisplayName, searchResultByAccountName) || isSimilarUser(twAccountName, searchResultByAccountName)) { insertBskyProfileEl({ dom: userCell, profile: searchResultByAccountName, abortController, - clickAction: async () => { await agent.follow(searchResultByAccountName.did) } + followAction: async () => { + const result = await agent.follow(searchResultByAccountName.did); + followerUrlMap.set(searchResultByAccountName.did, result.uri) + }, + unfollowAction: async () => { + if(searchResultByAccountName?.viewer?.following) { + await agent.unfollow(searchResultByAccountName?.viewer?.following); + } else { + await agent.unfollow(followerUrlMap.get(searchResultByAccountName.did)); + } + }, }) } else { // if not found, search by display name @@ -66,30 +79,35 @@ const searchBskyUsers = async ({ dom: userCell, profile: searchResultByDisplayName, abortController, - clickAction: async () => { await agent.follow(searchResultByAccountName.did) } + followAction: async () => { + const result = await agent.follow(searchResultByDisplayName.did); + followerUrlMap.set(searchResultByDisplayName.did, result.uri) + }, + unfollowAction: async () => { + if(searchResultByDisplayName?.viewer?.following) { + await agent.unfollow(searchResultByDisplayName?.viewer?.following); + } else { + await agent.unfollow(followerUrlMap.get(searchResultByDisplayName.did)); + } + }, }) } else { insertNotFoundEl(userCell) notFoundUserCache.add(twAccountName) } } - if (process.env.NODE_ENV === "development" && index > 100) { + if (process.env.NODE_ENV === "development" && index > 5) { break } } - // if there are more users, insert reload button - const finishedUserCells = getUserCells({ - filterInsertedElement: false - }) - if (finishedUserCells.at(-1) !== userCells.at(-1)) { - insertReloadEl(async () => { - await searchBskyUsers({ - userId, - password, - }) + // TODO: if there are more users, insert reload button + insertReloadEl(async () => { + await searchBskyUsers({ + userId, + password, }) - } + }) } diff --git a/src/lib/bskyClient.ts b/src/lib/bskyClient.ts index 0994f92..4d12554 100644 --- a/src/lib/bskyClient.ts +++ b/src/lib/bskyClient.ts @@ -1,4 +1,4 @@ -import { AppBskyFeedPost, AppBskyRichtextFacet, BskyAgent } from "@atproto/api"; +import { BskyAgent } from "@atproto/api"; export class BskyClient { private service = "https://bsky.social"; @@ -34,6 +34,10 @@ export class BskyClient { }; public follow = async (subjectDid: string) => { - await this.agent.follow(subjectDid); + return await this.agent.follow(subjectDid); + } + + public unfollow = async (followUri: string) => { + return await this.agent.deleteFollow(followUri); } } diff --git a/src/lib/domHelpers.ts b/src/lib/domHelpers.ts index 63fe1eb..4cf8797 100644 --- a/src/lib/domHelpers.ts +++ b/src/lib/domHelpers.ts @@ -46,7 +46,7 @@ export const getAccountNameAndDisplayName = (userCell: Element) => { const twDisplayName = displayNameEl?.textContent return { twAccountName, twDisplayName } } -export const insertBskyProfileEl = ({ dom, profile, abortController, clickAction }: { dom: Element, profile: ProfileView, abortController: AbortController, clickAction: () => void }) => { +export const insertBskyProfileEl = ({ dom, profile, abortController, followAction, unfollowAction }: { dom: Element, profile: ProfileView, abortController: AbortController, followAction: () => void, unfollowAction: () => void }) => { const avatarEl = profile.avatar ? `` : "
" const followButtonEl = profile.viewer?.following ? "" : "" dom.insertAdjacentHTML('afterend', ` @@ -68,20 +68,55 @@ export const insertBskyProfileEl = ({ dom, profile, abortController, clickAction ${profile.description ? `

${profile.description}

` : ""} - `) - dom.nextElementSibling?.addEventListener('click', async (e) => { - // TODO: Add unfollow action + const bskyUserContentDom = dom.nextElementSibling as Element + + // register a click action + bskyUserContentDom?.addEventListener('click', async (e) => { const target = e.target as Element const classList = target.classList + + // follow action if (classList.contains('follow-button') && !classList.contains('follow-button__following')) { target.textContent = "processing..." target.classList.add('follow-button__processing') - await clickAction() + await followAction() target.textContent = "Following on Bluesky" target.classList.remove('follow-button__processing') target.classList.add('follow-button__following') + return + } + + // unfollow action + if (classList.contains('follow-button') && classList.contains('follow-button__following')) { + target.textContent = "processing..." + target.classList.add('follow-button__processing') + await unfollowAction() + target.textContent = "Follow on Bluesky" + target.classList.remove('follow-button__processing') + target.classList.remove('follow-button__following') + return + } + }, { + signal: abortController.signal + }) + + // register a hover action + bskyUserContentDom?.addEventListener('mouseover', async (e) => { + const target = e.target as Element + const classList = target.classList + if (classList.contains('follow-button') && classList.contains('follow-button__following')) { + target.textContent = "Unfollow on Bluesky" + } + }, { + signal: abortController.signal + }) + bskyUserContentDom?.addEventListener('mouseout', async (e) => { + const target = e.target as Element + const classList = target.classList + if (classList.contains('follow-button') && classList.contains('follow-button__following')) { + target.textContent = "Following on Bluesky" } }, { signal: abortController.signal diff --git a/src/style.content.css b/src/style.content.css index 265b1ef..4d181fc 100644 --- a/src/style.content.css +++ b/src/style.content.css @@ -78,12 +78,20 @@ .name-and-controller .follow-button__following { background: transparent; color: #fff; - cursor: auto; + cursor: pointer; +} + +.name-and-controller .follow-button__following:hover { + background: rgba(255, 0, 0, 0.1); + color: red; + border: 1px solid red; + cursor: pointer; } .name-and-controller .follow-button__processing { background: rgb(255,255,255, 0.3);; color: #fff; + border: 1px solid #fff; cursor: auto; }