From be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Sun, 9 Mar 2025 14:28:01 +0900
Subject: refactor(frontend): scripts -> utility

---
 packages/frontend/src/components/MkInviteCode.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'packages/frontend/src/components/MkInviteCode.vue')

diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
index 1a71f6574f..08eddfc03c 100644
--- a/packages/frontend/src/components/MkInviteCode.vue
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -64,7 +64,7 @@ import { computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkFolder from '@/components/MkFolder.vue';
 import MkButton from '@/components/MkButton.vue';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 
-- 
cgit v1.2.3-freya


From c2940fd77cba7b0cf912db9fda92e66dbce9110f Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Sun, 16 Mar 2025 10:58:06 +0900
Subject: enhance(frontend): improve usability on touch device

---
 locales/index.d.ts                                      |  8 ++++++++
 locales/ja-JP.yml                                       |  2 ++
 packages/frontend/src/boot/common.ts                    |  4 ++++
 packages/frontend/src/components/MkDialog.vue           |  4 ++--
 packages/frontend/src/components/MkInfo.vue             |  2 +-
 packages/frontend/src/components/MkInput.vue            |  8 ++++----
 packages/frontend/src/components/MkInviteCode.vue       |  2 +-
 packages/frontend/src/components/MkKeyValue.vue         |  2 +-
 packages/frontend/src/components/MkNote.vue             |  3 ++-
 packages/frontend/src/components/MkNoteDetailed.vue     |  3 ++-
 packages/frontend/src/components/MkTextarea.vue         |  4 ++--
 packages/frontend/src/pages/settings/accessibility.vue  | 11 +++++++++++
 packages/frontend/src/preferences/def.ts                |  3 +++
 packages/frontend/src/style.scss                        | 17 +++++++++++++++++
 .../src/utility/autogen/settings-search-index.ts        | 13 +++++++++----
 15 files changed, 69 insertions(+), 17 deletions(-)

(limited to 'packages/frontend/src/components/MkInviteCode.vue')

diff --git a/locales/index.d.ts b/locales/index.d.ts
index 00d275ee43..7e4892eb39 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -5433,6 +5433,14 @@ export interface Locale extends ILocale {
          * タイムラインとノート
          */
         "timelineAndNote": string;
+        /**
+         * 全てのテキスト要素を選択可能にする
+         */
+        "makeEveryTextElementsSelectable": string;
+        /**
+         * 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
+         */
+        "makeEveryTextElementsSelectable_description": string;
     };
     "_preferencesProfile": {
         /**
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 8e787fa02f..f17c5a1278 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1357,6 +1357,8 @@ _settings:
   appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
   soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
   timelineAndNote: "タイムラインとノート"
+  makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
+  makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
 
 _preferencesProfile:
   profileName: "プロファイル名"
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 73c4256c4b..10bcddbde7 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -234,6 +234,10 @@ export async function common(createVue: () => App<Element>) {
 			});
 	}
 
+	if (prefer.s.makeEveryTextElementsSelectable) {
+		document.documentElement.classList.add('forceSelectableAll');
+	}
+
 	//#region Fetch user
 	if ($i && $i.token) {
 		if (_DEV_) {
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 6c9fa3167a..312c7ab58f 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i>
 			<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
 		</div>
-		<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
-		<div v-if="text" :class="$style.text"><Mfm :text="text"/></div>
+		<header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header>
+		<div v-if="text" :class="$style.text" class="_selectable"><Mfm :text="text"/></div>
 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
 			<template #caption>
diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue
index 90ca1b5a9d..f114ec8a71 100644
--- a/packages/frontend/src/components/MkInfo.vue
+++ b/packages/frontend/src/components/MkInfo.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div :class="[$style.root, { [$style.warn]: warn }]">
+<div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable">
 	<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i>
 	<i v-else class="ti ti-info-circle" :class="$style.i"></i>
 	<div><slot></slot></div>
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index aff9a67020..a66e64311a 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div>
+<div class="_selectable">
 	<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
 	<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]">
 		<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
@@ -45,13 +45,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
-import type { InputHTMLAttributes } from 'vue';
 import { debounce } from 'throttle-debounce';
-import MkButton from '@/components/MkButton.vue';
 import { useInterval } from '@@/js/use-interval.js';
+import type { InputHTMLAttributes } from 'vue';
+import type { SuggestionType } from '@/utility/autocomplete.js';
+import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import { Autocomplete } from '@/utility/autocomplete.js';
-import type { SuggestionType } from '@/utility/autocomplete.js';
 
 const props = defineProps<{
 	modelValue: string | number | null;
diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
index 08eddfc03c..0f53d1573d 100644
--- a/packages/frontend/src/components/MkInviteCode.vue
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.items">
 			<div>
 				<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
-				<div>{{ invite.code }}</div>
+				<div class="_selectableAtomic">{{ invite.code }}</div>
 			</div>
 			<div v-if="moderator">
 				<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue
index 6a4b298523..275b153e4a 100644
--- a/packages/frontend/src/components/MkKeyValue.vue
+++ b/packages/frontend/src/components/MkKeyValue.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div :class="$style.key">
 		<slot name="key"></slot>
 	</div>
-	<div :class="$style.value">
+	<div :class="$style.value" class="_selectable">
 		<slot name="value"></slot>
 		<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button>
 	</div>
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index e08f5e6163..1f18b173a0 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -76,12 +76,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 							:emojiUrls="appearNote.emojis"
 							:enableEmojiMenu="true"
 							:enableEmojiMenuReaction="true"
+							class="_selectable"
 						/>
 						<div v-if="translating || translation" :class="$style.translation">
 							<MkLoading v-if="translating" mini/>
 							<div v-else-if="translation">
 								<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
-								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
+								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
 							</div>
 						</div>
 					</div>
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index dd8d3567b2..6af8acb2df 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -97,13 +97,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 					:emojiUrls="appearNote.emojis"
 					:enableEmojiMenu="true"
 					:enableEmojiMenuReaction="true"
+					class="_selectable"
 				/>
 				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
 				<div v-if="translating || translation" :class="$style.translation">
 					<MkLoading v-if="translating" mini/>
 					<div v-else-if="translation">
 						<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
-						<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
+						<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
 					</div>
 				</div>
 				<div v-if="appearNote.files && appearNote.files.length > 0">
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index eb47a8b858..c9da226734 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div>
+<div class="_selectable">
 	<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
 	<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;">
 		<textarea
@@ -38,10 +38,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
 import { debounce } from 'throttle-debounce';
+import type { SuggestionType } from '@/utility/autocomplete.js';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import { Autocomplete } from '@/utility/autocomplete.js';
-import type { SuggestionType } from '@/utility/autocomplete.js';
 
 const props = defineProps<{
 	modelValue: string | null;
diff --git a/packages/frontend/src/pages/settings/accessibility.vue b/packages/frontend/src/pages/settings/accessibility.vue
index f7b1e7d2a0..e8268719f5 100644
--- a/packages/frontend/src/pages/settings/accessibility.vue
+++ b/packages/frontend/src/pages/settings/accessibility.vue
@@ -58,6 +58,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</MkSwitch>
 				</MkPreferenceContainer>
 			</SearchMarker>
+
+			<SearchMarker :keywords="['text', 'selectable']">
+				<MkPreferenceContainer k="makeEveryTextElementsSelectable">
+					<MkSwitch v-model="makeEveryTextElementsSelectable">
+						<template #label><SearchLabel>{{ i18n.ts._settings.makeEveryTextElementsSelectable }}</SearchLabel></template>
+						<template #caption>{{ i18n.ts._settings.makeEveryTextElementsSelectable_description }}</template>
+					</MkSwitch>
+				</MkPreferenceContainer>
+			</SearchMarker>
 		</div>
 
 		<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
@@ -122,6 +131,7 @@ const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
 const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
 const contextMenu = prefer.model('contextMenu');
 const menuStyle = prefer.model('menuStyle');
+const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable');
 
 const fontSize = ref(miLocalStorage.getItem('fontSize'));
 const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
@@ -147,6 +157,7 @@ watch([
 	contextMenu,
 	fontSize,
 	useSystemFont,
+	makeEveryTextElementsSelectable,
 ], async () => {
 	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
 });
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index e460359acd..048392ef49 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -313,6 +313,9 @@ export const PREF_DEF = {
 	defaultFollowWithReplies: {
 		default: false,
 	},
+	makeEveryTextElementsSelectable: {
+		default: DEFAULT_DEVICE_KIND === 'desktop',
+	},
 	plugins: {
 		default: [] as Plugin[],
 	},
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index 5308c312bb..43aff3012e 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -81,6 +81,11 @@ html {
 	&.useSystemFont {
 		font-family: system-ui;
 	}
+
+	&:not(.forceSelectableAll) {
+		user-select: none;
+		-webkit-user-select: none;
+	}
 }
 
 html._themeChanging_ {
@@ -120,6 +125,8 @@ a {
 textarea, input {
 	tap-highlight-color: transparent;
 	-webkit-tap-highlight-color: transparent;
+	user-select: text;
+	-webkit-user-select: text;
 }
 
 optgroup, option {
@@ -184,6 +191,16 @@ rt {
 	padding: 0.3em 0.5em;
 }
 
+._selectable {
+	user-select: text;
+	-webkit-user-select: text;
+}
+
+._selectableAtomic {
+	user-select: all;
+	-webkit-user-select: all;
+}
+
 ._noSelect {
 	user-select: none;
 	-webkit-user-select: none;
diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts
index ab26d671a2..2a0a4c1857 100644
--- a/packages/frontend/src/utility/autogen/settings-search-index.ts
+++ b/packages/frontend/src/utility/autogen/settings-search-index.ts
@@ -897,22 +897,27 @@ export const searchIndexes: SearchIndexItem[] = [
 				keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
 			},
 			{
-				id: '1fV9WINCQ',
+				id: 'b1GYEEJeh',
+				label: i18n.ts._settings.makeEveryTextElementsSelectable,
+				keywords: ['text', 'selectable'],
+			},
+			{
+				id: 'vVLxwINTJ',
 				label: i18n.ts.menuStyle,
 				keywords: ['menu', 'style', 'popup', 'drawer'],
 			},
 			{
-				id: 'mLQzlKUNu',
+				id: '14cMhMLHL',
 				label: i18n.ts._contextMenu.title,
 				keywords: ['contextmenu', 'system', 'native'],
 			},
 			{
-				id: 'yP96aA3j9',
+				id: 'oSo4LXMX9',
 				label: i18n.ts.fontSize,
 				keywords: ['font', 'size'],
 			},
 			{
-				id: 'jQeiMopFE',
+				id: '7LQSAThST',
 				label: i18n.ts.useSystemFont,
 				keywords: ['font', 'system', 'native'],
 			},
-- 
cgit v1.2.3-freya


From 81a0cbd294bd6485fc47200b1bf63dbf2cbc8507 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Sun, 16 Mar 2025 15:04:38 +0900
Subject: chore(frontend): use toast to show message when call copyToClipboard

---
 locales/index.d.ts                                        | 4 ++++
 locales/ja-JP.yml                                         | 1 +
 packages/frontend/src/components/MkCode.vue               | 1 -
 packages/frontend/src/components/MkEmbedCodeGenDialog.vue | 1 -
 packages/frontend/src/components/MkInviteCode.vue         | 1 -
 packages/frontend/src/components/MkKeyValue.vue           | 1 -
 packages/frontend/src/components/global/MkCustomEmoji.vue | 1 -
 packages/frontend/src/components/global/MkEmoji.vue       | 1 -
 packages/frontend/src/os.ts                               | 1 -
 packages/frontend/src/pages/channel.vue                   | 1 -
 packages/frontend/src/pages/clip.vue                      | 1 -
 packages/frontend/src/pages/drop-and-fusion.game.vue      | 1 -
 packages/frontend/src/pages/emojis.emoji.vue              | 3 +--
 packages/frontend/src/pages/flash/flash.vue               | 1 -
 packages/frontend/src/pages/gallery/post.vue              | 1 -
 packages/frontend/src/pages/page.vue                      | 1 -
 packages/frontend/src/pages/settings/theme.manage.vue     | 3 +--
 packages/frontend/src/utility/copy-to-clipboard.ts        | 8 +++++++-
 packages/frontend/src/utility/get-drive-file-menu.ts      | 1 -
 packages/frontend/src/utility/get-embed-code.ts           | 5 ++---
 packages/frontend/src/utility/get-note-menu.ts            | 5 -----
 21 files changed, 16 insertions(+), 27 deletions(-)

(limited to 'packages/frontend/src/components/MkInviteCode.vue')

diff --git a/locales/index.d.ts b/locales/index.d.ts
index 3b56563149..0c6e73aac4 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -2810,6 +2810,10 @@ export interface Locale extends ILocale {
      * コピー
      */
     "copy": string;
+    /**
+     * クリップボードにコピーされました
+     */
+    "copiedToClipboard": string;
     /**
      * メトリクス
      */
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index ad96549160..d6af72ab57 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし
 makeActive: "アクティブにする"
 display: "表示"
 copy: "コピー"
+copiedToClipboard: "クリップボードにコピーされました"
 metrics: "メトリクス"
 overview: "概要"
 logs: "ログ"
diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue
index 9708b78a30..d2d9f320ee 100644
--- a/packages/frontend/src/components/MkCode.vue
+++ b/packages/frontend/src/components/MkCode.vue
@@ -48,7 +48,6 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
 
 function copy() {
 	copyToClipboard(props.code);
-	os.success();
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
index a53c9c7904..e038b84ba7 100644
--- a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
+++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
@@ -194,7 +194,6 @@ function generate() {
 
 function doCopy() {
 	copyToClipboard(result.value);
-	os.success();
 }
 //#endregion
 
diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
index 0f53d1573d..ab797459cc 100644
--- a/packages/frontend/src/components/MkInviteCode.vue
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -90,7 +90,6 @@ function deleteCode() {
 
 function copyInviteCode() {
 	copyToClipboard(props.invite.code);
-	os.success();
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue
index 275b153e4a..b4185d2d0a 100644
--- a/packages/frontend/src/components/MkKeyValue.vue
+++ b/packages/frontend/src/components/MkKeyValue.vue
@@ -31,7 +31,6 @@ const props = withDefaults(defineProps<{
 
 const copy_ = () => {
 	copyToClipboard(props.copy);
-	os.success();
 };
 </script>
 
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index 65d6e9a3f6..af8f1d035e 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -100,7 +100,6 @@ function onClick(ev: MouseEvent) {
 			icon: 'ti ti-copy',
 			action: () => {
 				copyToClipboard(`:${props.name}:`);
-				os.success();
 			},
 		});
 
diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue
index 37df945233..ca67a28b70 100644
--- a/packages/frontend/src/components/global/MkEmoji.vue
+++ b/packages/frontend/src/components/global/MkEmoji.vue
@@ -50,7 +50,6 @@ function onClick(ev: MouseEvent) {
 			icon: 'ti ti-copy',
 			action: () => {
 				copyToClipboard(props.emoji);
-				os.success();
 			},
 		});
 
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index a009a3f3d1..8e4c97e59f 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -63,7 +63,6 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Miss
 			});
 			if (result === 'copy') {
 				copyToClipboard(`Endpoint: ${endpoint}\nInfo: ${JSON.stringify(err.info)}\nDate: ${date}`);
-				success();
 			}
 			return;
 		} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index c5951f0e13..1245561169 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -210,7 +210,6 @@ const headerActions = computed(() => {
 					return;
 				}
 				copyToClipboard(`${url}/channels/${channel.value.id}`);
-				os.success();
 			},
 		});
 
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 590a506a55..772ed6bd11 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -148,7 +148,6 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
 			text: i18n.ts.copyUrl,
 			action: () => {
 				copyToClipboard(`${url}/clips/${clip.value!.id}`);
-				os.success();
 			},
 		}, {
 			icon: 'ti ti-code',
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 364006e9ad..e30e381ce1 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -849,7 +849,6 @@ function exportLog() {
 		l: DropAndFusionGame.serializeLogs(logs),
 	});
 	copyToClipboard(data);
-	os.success();
 }
 
 function updateSettings<
diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index bedb0b64f9..d5570eb20a 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -25,7 +25,7 @@ import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialo
 import { $i } from '@/i.js';
 
 const props = defineProps<{
-  emoji: Misskey.entities.EmojiSimple;
+	emoji: Misskey.entities.EmojiSimple;
 }>();
 
 function menu(ev) {
@@ -38,7 +38,6 @@ function menu(ev) {
 		icon: 'ti ti-copy',
 		action: () => {
 			copyToClipboard(`:${props.emoji.name}:`);
-			os.success();
 		},
 	}, {
 		text: i18n.ts.info,
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 08ac913958..96e43b0205 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -129,7 +129,6 @@ function copyLink() {
 	if (!flash.value) return;
 
 	copyToClipboard(`${url}/play/${flash.value.id}`);
-	os.success();
 }
 
 function shareWithNavigator() {
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index eb01aadbcc..6a9737e30f 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -111,7 +111,6 @@ function fetchPost() {
 
 function copyLink() {
 	copyToClipboard(`${url}/gallery/${post.value.id}`);
-	os.success();
 }
 
 function share() {
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 00c664d2a0..523d443359 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -190,7 +190,6 @@ function copyLink() {
 	if (!page.value) return;
 
 	copyToClipboard(`${url}/@${page.value.user.username}/pages/${page.value.name}`);
-	os.success();
 }
 
 function shareWithNote() {
diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue
index e2b48ea232..c68c04bb44 100644
--- a/packages/frontend/src/pages/settings/theme.manage.vue
+++ b/packages/frontend/src/pages/settings/theme.manage.vue
@@ -33,12 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, ref } from 'vue';
 import JSON5 from 'json5';
+import type { Theme } from '@/theme.js';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import { getBuiltinThemesRef } from '@/theme.js';
-import type { Theme } from '@/theme.js';
 import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
 import * as os from '@/os.js';
 import { getThemes, removeTheme } from '@/theme-store.js';
@@ -63,7 +63,6 @@ const selectedThemeCode = computed(() => {
 
 function copyThemeCode() {
 	copyToClipboard(selectedThemeCode.value);
-	os.success();
 }
 
 function uninstall() {
diff --git a/packages/frontend/src/utility/copy-to-clipboard.ts b/packages/frontend/src/utility/copy-to-clipboard.ts
index 7e0bb25606..08a759588e 100644
--- a/packages/frontend/src/utility/copy-to-clipboard.ts
+++ b/packages/frontend/src/utility/copy-to-clipboard.ts
@@ -3,9 +3,15 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+
 /**
  * Clipboardに値をコピー(TODO: 文字列以外も対応)
  */
 export function copyToClipboard(input: string | null) {
-	if (input) navigator.clipboard.writeText(input);
+	if (input) {
+		navigator.clipboard.writeText(input);
+		os.toast(i18n.ts.copiedToClipboard);
+	}
 };
diff --git a/packages/frontend/src/utility/get-drive-file-menu.ts b/packages/frontend/src/utility/get-drive-file-menu.ts
index 0212a051ce..3c6cbba002 100644
--- a/packages/frontend/src/utility/get-drive-file-menu.ts
+++ b/packages/frontend/src/utility/get-drive-file-menu.ts
@@ -65,7 +65,6 @@ function toggleSensitive(file: Misskey.entities.DriveFile) {
 
 function copyUrl(file: Misskey.entities.DriveFile) {
 	copyToClipboard(file.url);
-	os.success();
 }
 
 /*
diff --git a/packages/frontend/src/utility/get-embed-code.ts b/packages/frontend/src/utility/get-embed-code.ts
index 9021520da8..d458e64f19 100644
--- a/packages/frontend/src/utility/get-embed-code.ts
+++ b/packages/frontend/src/utility/get-embed-code.ts
@@ -4,11 +4,11 @@
  */
 import { defineAsyncComponent } from 'vue';
 import { v4 as uuid } from 'uuid';
-import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
 import { url } from '@@/js/config.js';
+import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
+import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
 import * as os from '@/os.js';
 import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
-import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
 
 const MOBILE_THRESHOLD = 500;
 
@@ -74,7 +74,6 @@ export function genEmbedCode(entity: EmbeddableEntity, id: string, params?: Embe
 	// PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー
 	if (window.innerWidth < MOBILE_THRESHOLD) {
 		copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params));
-		os.success();
 	} else {
 		const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), {
 			entity,
diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts
index dea9bc39ad..9e1ae7e9ac 100644
--- a/packages/frontend/src/utility/get-note-menu.ts
+++ b/packages/frontend/src/utility/get-note-menu.ts
@@ -157,7 +157,6 @@ export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string):
 		text,
 		action: (): void => {
 			copyToClipboard(`${url}/notes/${note.id}`);
-			os.success();
 		},
 	};
 }
@@ -237,7 +236,6 @@ export function getNoteMenu(props: {
 
 	function copyContent(): void {
 		copyToClipboard(appearNote.text);
-		os.success();
 	}
 
 	function togglePin(pin: boolean): void {
@@ -324,7 +322,6 @@ export function getNoteMenu(props: {
 				text: i18n.ts.copyRemoteLink,
 				action: () => {
 					copyToClipboard(appearNote.url ?? appearNote.uri);
-					os.success();
 				},
 			}, {
 				icon: 'ti ti-external-link',
@@ -483,7 +480,6 @@ export function getNoteMenu(props: {
 				text: i18n.ts.copyRemoteLink,
 				action: () => {
 					copyToClipboard(appearNote.url ?? appearNote.uri);
-					os.success();
 				},
 			}, {
 				icon: 'ti ti-external-link',
@@ -516,7 +512,6 @@ export function getNoteMenu(props: {
 			text: i18n.ts.copyNoteId,
 			action: () => {
 				copyToClipboard(appearNote.id);
-				os.success();
 			},
 		});
 	}
-- 
cgit v1.2.3-freya

