From 50a64f97df5c526a2f09e8e84e02347082b4954f Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Thu, 29 May 2025 16:56:24 -0400
Subject: allow unauthenticated (logged-out) users to translate notes

---
 packages/frontend/src/components/SkNoteSub.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 775436cb0f..70128e5f47 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -70,7 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.noteFooterButton" class="_button" @click.stop="clip()">
 					<i class="ti ti-paperclip"></i>
 				</button>
-				<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
+				<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
 					<i class="ti ti-language-hiragana"></i>
 				</button>
 				<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="menu()">
@@ -108,7 +108,7 @@ import * as os from '@/os.js';
 import * as sound from '@/utility/sound.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/i.js';
+import { $i, policies } from '@/i.js';
 import { userPage } from '@/filters/user.js';
 import { checkMutes } from '@/utility/check-word-mute.js';
 import { pleaseLogin } from '@/utility/please-login.js';
-- 
cgit v1.2.3-freya


From 5818a89ba035339360dedb4698d7bc3313ade5c4 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Fri, 30 May 2025 12:35:26 -0400
Subject: fix circular reference between i.ts, instance.ts, and misskeyApi.ts

---
 packages/frontend/src/components/MkNote.vue         | 4 ++--
 packages/frontend/src/components/MkNoteDetailed.vue | 4 ++--
 packages/frontend/src/components/MkNoteSub.vue      | 4 ++--
 packages/frontend/src/components/SkNote.vue         | 4 ++--
 packages/frontend/src/components/SkNoteDetailed.vue | 4 ++--
 packages/frontend/src/components/SkNoteSub.vue      | 4 ++--
 packages/frontend/src/i.ts                          | 5 -----
 packages/frontend/src/instance.ts                   | 8 +++++++-
 packages/frontend/src/utility/get-note-menu.ts      | 4 ++--
 9 files changed, 21 insertions(+), 20 deletions(-)

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

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 641f75b922..34c33270bb 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -214,7 +214,7 @@ import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
 import { reactionPicker } from '@/utility/reaction-picker.js';
 import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
 import { checkAnimationFromMfm } from '@/utility/check-animated-mfm.js';
-import { $i, policies } from '@/i.js';
+import { $i } from '@/i.js';
 import { i18n } from '@/i18n.js';
 import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu, translateNote } from '@/utility/get-note-menu.js';
 import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
@@ -226,7 +226,7 @@ import { getNoteSummary } from '@/utility/get-note-summary.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/utility/show-moved-dialog.js';
 import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
-import { instance, isEnabledUrlPreview } from '@/instance.js';
+import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
 import { focusPrev, focusNext } from '@/utility/focus.js';
 import { getAppearNote } from '@/utility/get-appear-note.js';
 import { prefer } from '@/preferences.js';
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 0050ce26d5..b2f237a82e 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -262,7 +262,7 @@ import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
 import * as sound from '@/utility/sound.js';
 import { reactionPicker } from '@/utility/reaction-picker.js';
 import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
-import { $i, policies } from '@/i.js';
+import { $i } from '@/i.js';
 import { i18n } from '@/i18n.js';
 import { getNoteClipMenu, getNoteMenu, getRenoteMenu, translateNote } from '@/utility/get-note-menu.js';
 import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
@@ -278,7 +278,7 @@ import MkPagination from '@/components/MkPagination.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import MkButton from '@/components/MkButton.vue';
 import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
-import { instance, isEnabledUrlPreview } from '@/instance.js';
+import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
 import { getAppearNote } from '@/utility/get-appear-note.js';
 import { prefer } from '@/preferences.js';
 import { getPluginHandlers } from '@/plugin.js';
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 4190ece4fb..e6c0e83a8e 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -100,7 +100,7 @@ import * as os from '@/os.js';
 import * as sound from '@/utility/sound.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { $i, policies } from '@/i.js';
+import { $i } from '@/i.js';
 import { userPage } from '@/filters/user.js';
 import { checkMutes } from '@/utility/check-word-mute.js';
 import { pleaseLogin } from '@/utility/please-login.js';
@@ -113,7 +113,7 @@ import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
 import { prefer } from '@/preferences.js';
 import { useNoteCapture } from '@/use/use-note-capture.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
-import { instance } from '@/instance';
+import { instance, policies } from '@/instance';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 9f85531b05..c4580e9156 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -214,7 +214,7 @@ import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
 import { reactionPicker } from '@/utility/reaction-picker.js';
 import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
 import { checkAnimationFromMfm } from '@/utility/check-animated-mfm.js';
-import { $i, policies } from '@/i.js';
+import { $i } from '@/i.js';
 import { i18n } from '@/i18n.js';
 import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, translateNote } from '@/utility/get-note-menu.js';
 import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
@@ -226,7 +226,7 @@ import { getNoteSummary } from '@/utility/get-note-summary.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/utility/show-moved-dialog.js';
 import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
-import { instance, isEnabledUrlPreview } from '@/instance.js';
+import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
 import { focusPrev, focusNext } from '@/utility/focus.js';
 import { getAppearNote } from '@/utility/get-appear-note.js';
 import { prefer } from '@/preferences.js';
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index 188f13277d..8b495fb873 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -267,7 +267,7 @@ import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
 import * as sound from '@/utility/sound.js';
 import { reactionPicker } from '@/utility/reaction-picker.js';
 import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
-import { $i, policies } from '@/i.js';
+import { $i } from '@/i.js';
 import { i18n } from '@/i18n.js';
 import { getNoteClipMenu, getNoteMenu, getRenoteMenu, translateNote } from '@/utility/get-note-menu.js';
 import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
@@ -283,7 +283,7 @@ import MkPagination from '@/components/MkPagination.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import MkButton from '@/components/MkButton.vue';
 import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
-import { instance, isEnabledUrlPreview } from '@/instance.js';
+import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
 import { getAppearNote } from '@/utility/get-appear-note.js';
 import { prefer } from '@/preferences.js';
 import { getPluginHandlers } from '@/plugin.js';
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 70128e5f47..9fa0c11d48 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -108,7 +108,7 @@ import * as os from '@/os.js';
 import * as sound from '@/utility/sound.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { $i, policies } from '@/i.js';
+import { $i } from '@/i.js';
 import { userPage } from '@/filters/user.js';
 import { checkMutes } from '@/utility/check-word-mute.js';
 import { pleaseLogin } from '@/utility/please-login.js';
@@ -121,7 +121,7 @@ import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
 import { prefer } from '@/preferences.js';
 import { useNoteCapture } from '@/use/use-note-capture.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
-import { instance } from '@/instance';
+import { instance, policies } from '@/instance';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
diff --git a/packages/frontend/src/i.ts b/packages/frontend/src/i.ts
index 996cec8b54..0021298d63 100644
--- a/packages/frontend/src/i.ts
+++ b/packages/frontend/src/i.ts
@@ -6,7 +6,6 @@
 import { computed, reactive } from 'vue';
 import * as Misskey from 'misskey-js';
 import { miLocalStorage } from '@/local-storage.js';
-import { fetchInstance } from '@/instance';
 
 // TODO: 他のタブと永続化されたstateを同期
 
@@ -30,10 +29,6 @@ export function incNotesCount() {
 	notesCount++;
 }
 
-// instance export can be empty sometimes, which causes problems.
-const instance = await fetchInstance();
-export const policies = computed<Misskey.entities.RolePolicies>(() => $i?.policies ?? instance.policies);
-
 if (_DEV_) {
 	(window as any).$i = $i;
 }
diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts
index e75e3dfd34..956fc9e120 100644
--- a/packages/frontend/src/instance.ts
+++ b/packages/frontend/src/instance.ts
@@ -3,11 +3,12 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { computed, reactive } from 'vue';
+import { computed, nextTick, reactive } from 'vue';
 import * as Misskey from 'misskey-js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@@/js/const.js';
+import { $i } from '@/i';
 
 // TODO: 他のタブと永続化されたstateを同期
 
@@ -38,6 +39,8 @@ export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFA
 
 export const isEnabledUrlPreview = computed(() => instance.enableUrlPreview ?? true);
 
+export const policies = computed<Misskey.entities.RolePolicies>(() => $i?.policies ?? instance.policies);
+
 export async function fetchInstance(force = false): Promise<Misskey.entities.MetaDetailed> {
 	if (!force) {
 		const cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;
@@ -60,3 +63,6 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
 
 	return instance;
 }
+
+// instance export can be empty sometimes, which causes problems.
+nextTick(() => fetchInstance());
diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts
index 6c8e98fd37..2f9b613725 100644
--- a/packages/frontend/src/utility/get-note-menu.ts
+++ b/packages/frontend/src/utility/get-note-menu.ts
@@ -9,9 +9,9 @@ import { url } from '@@/js/config.js';
 import { claimAchievement } from './achievements.js';
 import type { Ref, ShallowRef } from 'vue';
 import type { MenuItem } from '@/types/menu.js';
-import { $i, policies } from '@/i.js';
+import { $i } from '@/i.js';
 import { i18n } from '@/i18n.js';
-import { instance } from '@/instance.js';
+import { instance, policies } from '@/instance.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
-- 
cgit v1.2.3-freya


From 4d024d116509564d7a7424aaee773c9256529b87 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Wed, 4 Jun 2025 14:51:49 -0400
Subject: use responsive gaps between note toolbar items

---
 packages/frontend/src/components/MkNote.vue        | 24 +-------------
 .../frontend/src/components/MkNoteDetailed.vue     | 32 ++++--------------
 packages/frontend/src/components/MkNoteSub.vue     | 26 ++++-----------
 packages/frontend/src/components/SkNote.vue        | 38 +---------------------
 .../frontend/src/components/SkNoteDetailed.vue     | 20 +-----------
 packages/frontend/src/components/SkNoteSub.vue     | 10 +-----
 .../frontend/src/components/SkOldNoteWindow.vue    | 20 +-----------
 packages/frontend/src/style.scss                   |  8 +++++
 8 files changed, 27 insertions(+), 151 deletions(-)

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

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 34c33270bb..862dccd0d3 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -113,7 +113,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
 				</template>
 			</MkReactionsViewer>
-			<footer :class="$style.footer">
+			<footer :class="$style.footer" class="_gaps _h_gaps">
 				<button :class="$style.footerButton" class="_button" @click.stop @click="reply()">
 					<i class="ti ti-arrow-back-up"></i>
 					<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
@@ -1203,10 +1203,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 	padding: 8px;
 	opacity: 0.7;
 
-	&:not(:last-child) {
-		margin-right: 1.5em;
-	}
-
 	&:hover {
 		color: var(--MI_THEME-fgHighlighted);
 	}
@@ -1290,25 +1286,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	}
 }
 
-@container (max-width: 400px) {
-	.root:not(.showActionsOnlyHover) {
-		.footerButton {
-			&:not(:last-child) {
-				margin-right: 0.2em;
-			}
-		}
-	}
-}
-
 @container (max-width: 350px) {
-	.root:not(.showActionsOnlyHover) {
-		.footerButton {
-			&:not(:last-child) {
-				margin-right: 0.1em;
-			}
-		}
-	}
-
 	.colorBar {
 		top: 6px;
 		left: 6px;
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index b2f237a82e..4a8c51402c 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -118,7 +118,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
 		</div>
-		<footer :class="$style.footer">
+		<footer :class="$style.footer" class="_gaps _h_gaps">
 			<div :class="$style.noteFooterInfo">
 				<div v-if="appearNote.updatedAt">
 					{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
@@ -886,12 +886,12 @@ function animatedMFM() {
 }
 
 .footer {
-		position: relative;
-		z-index: 1;
-		margin-top: 0.4em;
-		width: max-content;
-		min-width: min-content;
-		max-width: fit-content;
+	position: relative;
+	z-index: 1;
+	margin-top: 0.4em;
+	width: max-content;
+	min-width: min-content;
+	max-width: fit-content;
 }
 
 .replyTo {
@@ -1083,10 +1083,6 @@ function animatedMFM() {
 	padding: 8px;
 	opacity: 0.7;
 
-	&:not(:last-child) {
-		margin-right: 1.5em;
-	}
-
 	&:hover {
 		color: var(--MI_THEME-fgHighlighted);
 	}
@@ -1169,14 +1165,6 @@ function animatedMFM() {
 	}
 }
 
-@container (max-width: 350px) {
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.1em;
-		}
-	}
-}
-
 @container (max-width: 300px) {
 	.root {
 		font-size: 0.825em;
@@ -1186,12 +1174,6 @@ function animatedMFM() {
 		width: 50px;
 		height: 50px;
 	}
-
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.1em;
-		}
-	}
 }
 
 .muted {
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index e6c0e83a8e..09c3ed67fa 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation" :expandAllCws="props.expandAllCws"/>
 				</div>
 			</div>
-			<footer :class="$style.footer">
+			<footer :class="$style.footer" class="_gaps _h_gaps">
 				<MkReactionsViewer ref="reactionsViewer" :note="note"/>
 				<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 					<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
@@ -419,12 +419,12 @@ if (props.detail) {
 }
 
 .footer {
-		position: relative;
-		z-index: 1;
-		margin-top: 0.4em;
-		width: max-content;
-		min-width: min-content;
-		max-width: fit-content;
+	position: relative;
+	z-index: 1;
+	margin-top: 0.4em;
+	width: max-content;
+	min-width: min-content;
+	max-width: fit-content;
 }
 
 .main {
@@ -469,23 +469,11 @@ if (props.detail) {
 	padding-top: 10px;
 	opacity: 0.7;
 
-	&:not(:last-child) {
-		margin-right: 1.5em;
-	}
-
 	&:hover {
 		color: var(--MI_THEME-fgHighlighted);
 	}
 }
 
-@container (max-width: 400px) {
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.7em;
-		}
-	}
-}
-
 .noteFooterButtonCount {
 	display: inline;
 	margin: 0 0 0 8px;
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index c4580e9156..a3645a6a64 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -114,7 +114,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
 				</template>
 			</MkReactionsViewer>
-			<footer :class="$style.footer">
+			<footer :class="$style.footer" class="_gaps _h_gaps">
 				<button :class="$style.footerButton" class="_button" @click.stop @click="reply()">
 					<i class="ti ti-arrow-back-up"></i>
 					<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
@@ -947,10 +947,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 
 		.footerButton {
 			font-size: 90%;
-
-			&:not(:last-child) {
-				margin-right: 0;
-			}
 		}
 	}
 
@@ -1238,10 +1234,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 	padding: 8px;
 	opacity: 0.7;
 
-	&:not(:last-child) {
-		margin-right: 1.5em;
-	}
-
 	&:hover {
 		color: var(--MI_THEME-fgHighlighted);
 	}
@@ -1358,25 +1350,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	}
 }
 
-@container (max-width: 400px) {
-	.root:not(.showActionsOnlyHover) {
-		.footerButton {
-			&:not(:last-child) {
-				margin-right: 0.2em;
-			}
-		}
-	}
-}
-
 @container (max-width: 350px) {
-	.root:not(.showActionsOnlyHover) {
-		.footerButton {
-			&:not(:last-child) {
-				margin-right: 0.1em;
-			}
-		}
-	}
-
 	.colorBar {
 		top: 6px;
 		left: 6px;
@@ -1385,16 +1359,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 	}
 }
 
-@container (max-width: 300px) {
-	.root:not(.showActionsOnlyHover) {
-		.footerButton {
-			&:not(:last-child) {
-				margin-right: 0.1em;
-			}
-		}
-	}
-}
-
 @container (max-width: 250px) {
 	.quoteNote {
 		padding: 12px;
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index 8b495fb873..9501b909bb 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -132,7 +132,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 		</div>
 		<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" style="margin-top: 6px;" :note="appearNote"/>
-		<footer :class="$style.footer">
+		<footer :class="$style.footer" class="_gaps _h_gaps">
 			<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 				<i class="ti ti-arrow-back-up"></i>
 				<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
@@ -1141,10 +1141,6 @@ onUnmounted(() => {
 	padding: 8px;
 	opacity: 0.7;
 
-	&:not(:last-child) {
-		margin-right: 1.5em;
-	}
-
 	&:hover {
 		color: var(--MI_THEME-fgHighlighted);
 	}
@@ -1234,14 +1230,6 @@ onUnmounted(() => {
 	}
 }
 
-@container (max-width: 350px) {
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.1em;
-		}
-	}
-}
-
 @container (max-width: 300px) {
 	.root {
 		font-size: 0.825em;
@@ -1251,12 +1239,6 @@ onUnmounted(() => {
 		width: 50px;
 		height: 50px;
 	}
-
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.1em;
-		}
-	}
 }
 
 .avatar {
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 9fa0c11d48..5f371f8bd7 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</div>
 			<MkReactionsViewer ref="reactionsViewer" :note="note"/>
-			<footer :class="$style.footer">
+			<footer :class="$style.footer" class="_gaps _h_gaps">
 				<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 					<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
 					<p v-if="note.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ note.repliesCount }}</p>
@@ -559,14 +559,6 @@ if (props.detail) {
 	}
 }
 
-@container (max-width: 400px) {
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.7em;
-		}
-	}
-}
-
 .noteFooterButtonCount {
 	display: inline;
 	margin: 0 0 0 8px;
diff --git a/packages/frontend/src/components/SkOldNoteWindow.vue b/packages/frontend/src/components/SkOldNoteWindow.vue
index b6dbec81c5..d4a4dec1cb 100644
--- a/packages/frontend/src/components/SkOldNoteWindow.vue
+++ b/packages/frontend/src/components/SkOldNoteWindow.vue
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 				<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA>
 			</div>
-			<footer :class="$style.footer">
+			<footer :class="$style.footer" class="_gaps _h_gaps">
 				<div :class="$style.noteFooterInfo">
 					<MkTime :time="appearNote.createdAt" mode="detail"/>
 				</div>
@@ -280,23 +280,11 @@ const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceT
 	padding: 8px;
 	opacity: 0.7;
 
-	&:not(:last-child) {
-		margin-right: 1.5em;
-	}
-
 	&:hover {
 		color: var(--MI_THEME-fgHighlighted);
 	}
 }
 
-@container (max-width: 350px) {
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.1em;
-		}
-	}
-}
-
 @container (max-width: 500px) {
 	.root {
 		font-size: 0.9em;
@@ -323,11 +311,5 @@ const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceT
 		width: 50px;
 		height: 50px;
 	}
-
-	.noteFooterButton {
-		&:not(:last-child) {
-			margin-right: 0.1em;
-		}
-	}
 }
 </style>
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index 7b2638903b..d5abe9166f 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -428,6 +428,14 @@ rt {
 	gap: var(--MI-margin);
 }
 
+/**
+ * Use with _gaps, _gaps_m, or _gaps_s.
+ * Place the other class *first*!
+ */
+._h_gaps {
+	flex-direction: row;
+}
+
 ._buttons {
 	display: flex;
 	gap: 8px;
-- 
cgit v1.2.3-freya


From 25174e837affc0b4eaaa2fe3a68b940bc498027d Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Wed, 4 Jun 2025 15:03:01 -0400
Subject: allow note toolbar to scroll

https://developer.mozilla.org/en-US/docs/Web/CSS/overflow#accessibility
---
 locales/index.d.ts                                   |  4 ++++
 packages/frontend/src/components/MkNote.vue          |  6 +++---
 packages/frontend/src/components/MkNoteDetailed.vue  |  6 ++----
 packages/frontend/src/components/MkNoteSub.vue       |  6 ++----
 packages/frontend/src/components/SkNote.vue          |  6 +++---
 packages/frontend/src/components/SkNoteDetailed.vue  | 16 ++++++++--------
 packages/frontend/src/components/SkNoteSub.vue       |  6 +++---
 packages/frontend/src/components/SkOldNoteWindow.vue | 13 +++++++------
 sharkey-locales/en-US.yml                            |  2 ++
 9 files changed, 34 insertions(+), 31 deletions(-)

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

diff --git a/locales/index.d.ts b/locales/index.d.ts
index 182cf03284..6217a08425 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -13233,6 +13233,10 @@ export interface Locale extends ILocale {
      * Enabled (copy CW and prepend "RE:")
      */
     "keepCwPrependRe": string;
+    /**
+     * Note controls
+     */
+    "noteFooterLabel": string;
 }
 declare const locales: {
     [lang: string]: Locale;
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 862dccd0d3..16aed7898e 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -113,7 +113,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
 				</template>
 			</MkReactionsViewer>
-			<footer :class="$style.footer" class="_gaps _h_gaps">
+			<footer :class="$style.footer" class="_gaps _h_gaps" tabindex="0" role="group" :aria-label="i18n.ts.noteFooterLabel">
 				<button :class="$style.footerButton" class="_button" @click.stop @click="reply()">
 					<i class="ti ti-arrow-back-up"></i>
 					<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
@@ -913,11 +913,11 @@ function emitUpdReaction(emoji: string, delta: number) {
 	.footer {
 		display: flex;
 		align-items: center;
-		justify-content: space-between;
+		justify-content: flex-start;
 		position: relative;
 		z-index: 1;
 		margin-top: 0.4em;
-		max-width: 400px;
+		overflow-x: auto;
 	}
 
 	&:hover > .article > .main > .footer > .footerButton {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 4a8c51402c..be0676dc5f 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -118,7 +118,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
 		</div>
-		<footer :class="$style.footer" class="_gaps _h_gaps">
+		<footer :class="$style.footer" class="_gaps _h_gaps" tabindex="0" role="group" :aria-label="i18n.ts.noteFooterLabel">
 			<div :class="$style.noteFooterInfo">
 				<div v-if="appearNote.updatedAt">
 					{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
@@ -889,9 +889,7 @@ function animatedMFM() {
 	position: relative;
 	z-index: 1;
 	margin-top: 0.4em;
-	width: max-content;
-	min-width: min-content;
-	max-width: fit-content;
+	overflow-x: auto;
 }
 
 .replyTo {
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 09c3ed67fa..6b70fddecf 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation" :expandAllCws="props.expandAllCws"/>
 				</div>
 			</div>
-			<footer :class="$style.footer" class="_gaps _h_gaps">
+			<footer :class="$style.footer" class="_gaps _h_gaps" tabindex="0" role="group" :aria-label="i18n.ts.noteFooterLabel">
 				<MkReactionsViewer ref="reactionsViewer" :note="note"/>
 				<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 					<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
@@ -422,9 +422,7 @@ if (props.detail) {
 	position: relative;
 	z-index: 1;
 	margin-top: 0.4em;
-	width: max-content;
-	min-width: min-content;
-	max-width: fit-content;
+	overflow-x: auto;
 }
 
 .main {
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index a3645a6a64..42b2e2a957 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -114,7 +114,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
 				</template>
 			</MkReactionsViewer>
-			<footer :class="$style.footer" class="_gaps _h_gaps">
+			<footer :class="$style.footer" class="_gaps _h_gaps" tabindex="0" role="group" :aria-label="i18n.ts.noteFooterLabel">
 				<button :class="$style.footerButton" class="_button" @click.stop @click="reply()">
 					<i class="ti ti-arrow-back-up"></i>
 					<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
@@ -921,11 +921,11 @@ function emitUpdReaction(emoji: string, delta: number) {
 	.footer {
 		display: flex;
 		align-items: center;
-		justify-content: space-between;
+		justify-content: flex-start;
 		position: relative;
 		z-index: 1;
 		margin-top: 0.4em;
-		max-width: 400px;
+		overflow-x: auto;
 	}
 
 	&:hover > .article > .main > .footer > .footerButton {
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index 9501b909bb..d57a90628e 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -132,7 +132,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 		</div>
 		<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" style="margin-top: 6px;" :note="appearNote"/>
-		<footer :class="$style.footer" class="_gaps _h_gaps">
+		<footer :class="$style.footer" class="_gaps _h_gaps" tabindex="0" role="group" :aria-label="i18n.ts.noteFooterLabel">
 			<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 				<i class="ti ti-arrow-back-up"></i>
 				<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
@@ -918,13 +918,13 @@ onUnmounted(() => {
 }
 
 .footer {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		position: relative;
-		z-index: 1;
-		margin-top: 0.4em;
-		max-width: 400px;
+	display: flex;
+	align-items: center;
+	justify-content: flex-start;
+	position: relative;
+	z-index: 1;
+	margin-top: 0.4em;
+	overflow-x: auto;
 }
 
 .replyTo {
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 5f371f8bd7..a520c9744e 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</div>
 			<MkReactionsViewer ref="reactionsViewer" :note="note"/>
-			<footer :class="$style.footer" class="_gaps _h_gaps">
+			<footer :class="$style.footer" class="_gaps _h_gaps" tabindex="0" role="group" :aria-label="i18n.ts.noteFooterLabel">
 				<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 					<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
 					<p v-if="note.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ note.repliesCount }}</p>
@@ -449,11 +449,11 @@ if (props.detail) {
 .footer {
 	display: flex;
 	align-items: center;
-	justify-content: space-between;
+	justify-content: flex-start;
 	position: relative;
 	z-index: 1;
 	margin-top: 0.4em;
-	max-width: 400px;
+	overflow-x: auto;
 }
 
 .main {
diff --git a/packages/frontend/src/components/SkOldNoteWindow.vue b/packages/frontend/src/components/SkOldNoteWindow.vue
index d4a4dec1cb..778a776167 100644
--- a/packages/frontend/src/components/SkOldNoteWindow.vue
+++ b/packages/frontend/src/components/SkOldNoteWindow.vue
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 				<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA>
 			</div>
-			<footer :class="$style.footer" class="_gaps _h_gaps">
+			<footer :class="$style.footer" class="_gaps _h_gaps" tabindex="0" role="group" :aria-label="i18n.ts.noteFooterLabel">
 				<div :class="$style.noteFooterInfo">
 					<MkTime :time="appearNote.createdAt" mode="detail"/>
 				</div>
@@ -163,11 +163,12 @@ const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceT
 }
 
 .footer {
-		position: relative;
-		z-index: 1;
-		margin-top: 0.4em;
-		width: max-content;
-		min-width: max-content;
+	position: relative;
+	z-index: 1;
+	margin-top: 0.4em;
+	width: max-content;
+	min-width: max-content;
+	overflow-x: auto;
 }
 
 .note {
diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml
index f9463f8221..bb9471a7ef 100644
--- a/sharkey-locales/en-US.yml
+++ b/sharkey-locales/en-US.yml
@@ -619,3 +619,5 @@ keepCwDescription: "When replying to a post with a Content Warning, automaticall
 keepCwDisabled: "Disabled (do not copy CWs)"
 keepCwEnabled: "Enabled (copy CWs verbatim)"
 keepCwPrependRe: "Enabled (copy CW and prepend \"RE:\")"
+
+noteFooterLabel: "Note controls"
-- 
cgit v1.2.3-freya


From b97e505709a305917e71e9b0c56f481754e9b9fe Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Wed, 18 Jun 2025 10:35:38 -0400
Subject: fix usage of appearNote in frontend components

---
 packages/frontend/src/components/MkNote.vue    |  2 +-
 packages/frontend/src/components/MkNoteSub.vue | 37 +++++++++++---------------
 packages/frontend/src/components/SkNote.vue    |  2 +-
 packages/frontend/src/components/SkNoteSub.vue | 37 +++++++++++---------------
 4 files changed, 34 insertions(+), 44 deletions(-)

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

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 133ccababf..56bfa5de94 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -306,7 +306,7 @@ const galleryEl = useTemplateRef('galleryEl');
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(prefer.s.uncollapseCW);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
-const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : []);
+const urls = computed(() => parsed.value ? extractPreviewUrls(appearNote.value, parsed.value) : []);
 const selfNoteIds = computed(() => getSelfNoteIds(props.note));
 const isLong = shouldCollapsed(appearNote.value, urls.value);
 const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 6b70fddecf..58de5bd5a7 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -114,6 +114,7 @@ import { prefer } from '@/preferences.js';
 import { useNoteCapture } from '@/use/use-note-capture.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
 import { instance, policies } from '@/instance';
+import { getAppearNote } from '@/utility/get-appear-note';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -128,7 +129,9 @@ const props = withDefaults(defineProps<{
 	onDeleteCallback: undefined,
 });
 
-const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
+const appearNote = computed(() => getAppearNote(props.note));
+
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
 
 const el = shallowRef<HTMLElement>();
 const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
@@ -144,19 +147,11 @@ const likeButton = shallowRef<HTMLElement>();
 
 const renoteTooltip = computeRenoteTooltip(renoted);
 
-const appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
 const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
 const replies = ref<Misskey.entities.Note[]>([]);
 
 const mergedCW = computed(() => computeMergedCw(appearNote.value));
 
-const isRenote = (
-	props.note.renote != null &&
-	props.note.text == null &&
-	props.note.fileIds && props.note.fileIds.length === 0 &&
-	props.note.poll == null
-);
-
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
 	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
@@ -206,8 +201,8 @@ async function reply(viaKeyboard = false): Promise<void> {
 	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 	await os.post({
-		reply: props.note,
-		channel: props.note.channel ?? undefined,
+		reply: appearNote.value,
+		channel: appearNote.value.channel ?? undefined,
 		animation: !viaKeyboard,
 	});
 	focus();
@@ -217,9 +212,9 @@ function react(): void {
 	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 	sound.playMisskeySfx('reaction');
-	if (props.note.reactionAcceptance === 'likeOnly') {
+	if (appearNote.value.reactionAcceptance === 'likeOnly') {
 		misskeyApi('notes/like', {
-			noteId: props.note.id,
+			noteId: appearNote.value.id,
 			override: defaultLike.value,
 		});
 		const el = reactButton.value as HTMLElement | null | undefined;
@@ -233,12 +228,12 @@ function react(): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
+		reactionPicker.show(reactButton.value ?? null, appearNote.value, reaction => {
 			misskeyApi('notes/reactions/create', {
-				noteId: props.note.id,
+				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
-			if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
+			if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
 				claimAchievement('reactWithoutRead');
 			}
 		}, () => {
@@ -252,7 +247,7 @@ function like(): void {
 	showMovedDialog();
 	sound.playMisskeySfx('reaction');
 	misskeyApi('notes/like', {
-		noteId: props.note.id,
+		noteId: appearNote.value.id,
 		override: defaultLike.value,
 	});
 	const el = likeButton.value as HTMLElement | null | undefined;
@@ -361,7 +356,7 @@ function quote() {
 	}).then((cancelled) => {
 		if (cancelled) return;
 		misskeyApi('notes/renotes', {
-			noteId: props.note.id,
+			noteId: appearNote.value.id,
 			userId: $i?.id,
 			limit: 1,
 			quote: true,
@@ -383,12 +378,12 @@ function quote() {
 }
 
 function menu(): void {
-	const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
+	const { menu, cleanup } = getNoteMenu({ note: appearNote.value, translating, translation, isDeleted });
 	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 async function clip(): Promise<void> {
-	os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
+	os.popupMenu(await getNoteClipMenu({ note: appearNote.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
 }
 
 async function translate() {
@@ -397,7 +392,7 @@ async function translate() {
 
 if (props.detail) {
 	misskeyApi('notes/children', {
-		noteId: props.note.id,
+		noteId: appearNote.value.id,
 		limit: prefer.s.numberOfReplies,
 		showQuotes: false,
 	}).then(res => {
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index c554319c30..4d6d080ddf 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -305,7 +305,7 @@ const galleryEl = useTemplateRef('galleryEl');
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(prefer.s.uncollapseCW);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
-const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : []);
+const urls = computed(() => parsed.value ? extractPreviewUrls(appearNote.value, parsed.value) : []);
 const selfNoteIds = computed(() => getSelfNoteIds(props.note));
 const isLong = shouldCollapsed(appearNote.value, urls.value);
 const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index a520c9744e..4e8a3147ad 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -122,6 +122,7 @@ import { prefer } from '@/preferences.js';
 import { useNoteCapture } from '@/use/use-note-capture.js';
 import SkMutedNote from '@/components/SkMutedNote.vue';
 import { instance, policies } from '@/instance';
+import { getAppearNote } from '@/utility/get-appear-note';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -141,7 +142,9 @@ const props = withDefaults(defineProps<{
 	onDeleteCallback: undefined,
 });
 
-const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
+const appearNote = computed(() => getAppearNote(props.note));
+
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
 const hideLine = computed(() => props.detail);
 
 const el = shallowRef<HTMLElement>();
@@ -158,19 +161,11 @@ const likeButton = shallowRef<HTMLElement>();
 
 const renoteTooltip = computeRenoteTooltip(renoted);
 
-let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
 const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
 const replies = ref<Misskey.entities.Note[]>([]);
 
 const mergedCW = computed(() => computeMergedCw(appearNote.value));
 
-const isRenote = (
-	props.note.renote != null &&
-	props.note.text == null &&
-	props.note.fileIds && props.note.fileIds.length === 0 &&
-	props.note.poll == null
-);
-
 const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
 	type: 'lookup',
 	url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
@@ -220,8 +215,8 @@ async function reply(viaKeyboard = false): Promise<void> {
 	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 	await os.post({
-		reply: props.note,
-		channel: props.note.channel ?? undefined,
+		reply: appearNote.value,
+		channel: appearNote.value.channel ?? undefined,
 		animation: !viaKeyboard,
 	});
 	focus();
@@ -231,9 +226,9 @@ function react(): void {
 	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 	sound.playMisskeySfx('reaction');
-	if (props.note.reactionAcceptance === 'likeOnly') {
+	if (appearNote.value.reactionAcceptance === 'likeOnly') {
 		misskeyApi('notes/like', {
-			noteId: props.note.id,
+			noteId: appearNote.value.id,
 			override: defaultLike.value,
 		});
 		const el = reactButton.value as HTMLElement | null | undefined;
@@ -247,12 +242,12 @@ function react(): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
+		reactionPicker.show(reactButton.value ?? null, appearNote.value, reaction => {
 			misskeyApi('notes/reactions/create', {
-				noteId: props.note.id,
+				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
-			if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
+			if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
 				claimAchievement('reactWithoutRead');
 			}
 		}, () => {
@@ -266,7 +261,7 @@ function like(): void {
 	showMovedDialog();
 	sound.playMisskeySfx('reaction');
 	misskeyApi('notes/like', {
-		noteId: props.note.id,
+		noteId: appearNote.value.id,
 		override: defaultLike.value,
 	});
 	const el = likeButton.value as HTMLElement | null | undefined;
@@ -375,7 +370,7 @@ function quote() {
 	}).then((cancelled) => {
 		if (cancelled) return;
 		misskeyApi('notes/renotes', {
-			noteId: props.note.id,
+			noteId: appearNote.value.id,
 			userId: $i?.id,
 			limit: 1,
 			quote: true,
@@ -397,12 +392,12 @@ function quote() {
 }
 
 function menu(): void {
-	const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
+	const { menu, cleanup } = getNoteMenu({ note: appearNote.value, translating, translation, isDeleted });
 	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 async function clip(): Promise<void> {
-	os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
+	os.popupMenu(await getNoteClipMenu({ note: appearNote.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
 }
 
 async function translate() {
@@ -411,7 +406,7 @@ async function translate() {
 
 if (props.detail) {
 	misskeyApi('notes/children', {
-		noteId: props.note.id,
+		noteId: appearNote.value.id,
 		limit: prefer.s.numberOfReplies,
 		showQuotes: false,
 	}).then(res => {
-- 
cgit v1.2.3-freya

