From 9a402611b1cd38ce2cbb4788c69fb4c46e131c85 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Sun, 11 Feb 2024 02:29:32 +0900
Subject: [PATCH] feat (client): ability to hide misclickable follow buttons

---
 docs/changelog.md                                 |  1 +
 locales/en-US.yml                                 |  2 ++
 locales/ja-JP.yml                                 |  2 ++
 locales/zh-TW.yml                                 |  2 ++
 packages/client/src/components/MkFollowButton.vue |  5 ++++-
 packages/client/src/components/MkNotification.vue |  3 ++-
 packages/client/src/pages/settings/general.vue    | 13 +++++++++++++
 packages/client/src/pages/user/home.vue           |  2 ++
 packages/client/src/store.ts                      |  4 ++++
 9 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/docs/changelog.md b/docs/changelog.md
index 416a0241c9..6167f0e1f8 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,3 +1,4 @@
+- add a toggleable setting to hide follow buttons in a misclickable position
 - add a toggleable setting to show preview in posting form by default
 
 # v20240210
diff --git a/locales/en-US.yml b/locales/en-US.yml
index b3df9dbc99..e6d59a0862 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1161,6 +1161,8 @@ noLanguage: "No language"
 useEmojiCdn: "Get Twemoji from CDN"
 useEmojiCdnDescription: "Use Twemoji from the JSDelivr CDN instead of the server's assets."
 showPreviewByDefault: "Show preview in posting form by default"
+preventMisclick: "Accidental click prevention"
+hideFollowButtons: "Hide follow buttons in a misclickable position"
 
 _sensitiveMediaDetection:
   description: "Reduces the effort of server moderation through automatically recognizing
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b587051e63..bc0c92900d 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -2010,3 +2010,5 @@ noLanguage: "言語なし"
 moreUrls: "固定するページ"
 moreUrlsDescription: "左下のヘルプメニューに固定したいページを以下の形式で、改行区切りで入力してください:\n\"表示名\": https://example.com/"
 showPreviewByDefault: "投稿画面でプレビュー表示をデフォルトでオンにする"
+preventMisclick: "誤タップ防止"
+hideFollowButtons: "誤タップしやすい位置にあるフォローボタンを隠す"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index b4ba8ce1a5..bfc34ce014 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -2010,3 +2010,5 @@ useEmojiCdnDescription: 使用JSDelivr CDN提供的Twemoji,而不使用儲存
 moreUrls: 置頂的頁面
 moreUrlsDescription: "請以下列形式輸入欲釘選在左下角幫助選單的頁面,一行一個:\n\"顯示名稱\": https://example.com/"
 showPreviewByDefault: "自動開啟發文介面中的預覽顯示"
+preventMisclick: "預防誤觸"
+hideFollowButtons: "隱藏會誤觸的追隨按鈕"
diff --git a/packages/client/src/components/MkFollowButton.vue b/packages/client/src/components/MkFollowButton.vue
index 321d7ac3bf..d9a367c49a 100644
--- a/packages/client/src/components/MkFollowButton.vue
+++ b/packages/client/src/components/MkFollowButton.vue
@@ -8,7 +8,7 @@
 		<i :class="icon('ph-dots-three-outline')"></i>
 	</button>
 	<button
-		v-if="isSignedIn && $i.id != user.id"
+		v-if="!hideFollowButton && isSignedIn && $i.id != user.id"
 		v-tooltip="full ? null : `${state} ${user.name || user.username}`"
 		class="kpoogebi _button follow-button"
 		:class="{
@@ -81,6 +81,7 @@ const props = withDefaults(
 		full?: boolean;
 		large?: boolean;
 		hideMenu?: boolean;
+		hideFollowButton?: boolean;
 	}>(),
 	{
 		full: false,
@@ -99,6 +100,8 @@ const hasPendingFollowRequestFromYou = ref(
 const wait = ref(false);
 const connection = stream.useChannel("main");
 
+const hideFollowButton = props.hideFollowButton ?? false;
+
 if (props.user.isFollowing == null) {
 	os.api("users/show", {
 		userId: props.user.id,
diff --git a/packages/client/src/components/MkNotification.vue b/packages/client/src/components/MkNotification.vue
index 668c3dfff0..e196129828 100644
--- a/packages/client/src/components/MkNotification.vue
+++ b/packages/client/src/components/MkNotification.vue
@@ -215,7 +215,7 @@
 				class="text"
 				style="opacity: 0.7"
 				>{{ i18n.ts.youGotNewFollower }}
-				<div v-if="full">
+				<div v-if="full && !hideFollowButton">
 					<MkFollowButton
 						:user="notification.user"
 						:full="true"
@@ -304,6 +304,7 @@ const props = withDefaults(
 const elRef = ref<HTMLElement>(null);
 const reactionRef = ref(null);
 
+const hideFollowButton = defaultStore.state.hideFollowButtons;
 const showEmojiReactions =
 	defaultStore.state.enableEmojiReactions ||
 	defaultStore.state.showEmojisInReactionNotifications;
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index 21c236ca8b..7892cbd5e1 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -285,6 +285,16 @@
 			</FormSelect>
 		</FormSection>
 
+		<FormSection>
+			<template #label>{{ i18n.ts.preventMisclick }}</template>
+			<FormSwitch v-model="hideFollowButtons" class="_formBlock"
+				>{{ i18n.ts.hideFollowButtons
+				}}<span class="_beta">{{
+					i18n.ts.originalFeature
+				}}</span></FormSwitch
+			>
+		</FormSection>
+
 		<FormRange
 			v-model="numberOfPageCache"
 			:min="1"
@@ -430,6 +440,9 @@ const showPreviewByDefault = computed(
 const showTimelineReplies = computed(
 	defaultStore.makeGetterSetter("showTimelineReplies"),
 );
+const hideFollowButtons = computed(
+	defaultStore.makeGetterSetter("hideFollowButtons"),
+);
 const detectPostLanguage = computed(
 	defaultStore.makeGetterSetter("detectPostLanguage"),
 );
diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue
index 429eedd0a3..442ad6dc6c 100644
--- a/packages/client/src/pages/user/home.vue
+++ b/packages/client/src/pages/user/home.vue
@@ -400,6 +400,8 @@ import icon from "@/scripts/icon";
 const XPhotos = defineAsyncComponent(() => import("./index.photos.vue"));
 const XActivity = defineAsyncComponent(() => import("./index.activity.vue"));
 
+const hideFollowButton = defaultStore.state.hideFollowButtons;
+
 const emit = defineEmits(["refresh"]);
 const props = withDefaults(
 	defineProps<{
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index 9c77c1aa37..c3406d0b98 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -381,6 +381,10 @@ export const defaultStore = markRaw(
 			where: "deviceAccount",
 			default: false,
 		},
+		hideFollowButtons: {
+			where: "device",
+			default: true,
+		},
 	}),
 );