From 0b7b59f1e2c2d6782c5dfddb86f3037815d73c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 5 Apr 2026 17:22:17 +0900 Subject: [PATCH] =?UTF-8?q?enhance(frontend):=20=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=B3=E3=83=8D=E3=83=AB=E6=8C=87=E5=AE=9A=E3=83=AA=E3=83=8E?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=A7=E3=83=AA=E3=83=8E=E3=83=BC=E3=83=88?= =?UTF-8?q?=E5=85=88=E3=81=AE=E3=83=81=E3=83=A3=E3=83=B3=E3=83=8D=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E7=A7=BB=E5=8B=95=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20(#17280)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): チャンネル指定リノートでリノート先のチャンネルに移動できるように * Update Changelog * fix condition * refactor --- CHANGELOG.md | 2 +- locales/ja-JP.yml | 1 + packages/frontend/src/components/MkNote.vue | 22 ++++++--- .../src/components/MkNoteDetailed.vue | 45 +++++++++++++------ .../components/MkStreamingNotesTimeline.vue | 3 +- packages/frontend/src/di.ts | 3 +- packages/i18n/src/autogen/locale.ts | 4 ++ 7 files changed, 59 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b6c057d3..b5d712db13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ### Client -- +- Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように ### Server - Fix: `/api-doc` にアクセスできない問題を修正 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4af17dd39e..93679aa24b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1408,6 +1408,7 @@ frame: "フレーム" presets: "プリセット" zeroPadding: "ゼロ埋め" nothingToConfigure: "設定項目はありません" +viewRenotedChannel: "リノート先のチャンネルを見る" _imageEditing: _vars: diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index c78cc44425..ba68971034 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -263,7 +263,7 @@ const emit = defineEmits<{ const inTimeline = inject('inTimeline', false); const tl_withSensitive = inject>('tl_withSensitive', ref(true)); -const inChannel = inject('inChannel', null); +const inChannel = inject(DI.inChannel, null); const currentClip = inject | null>('currentClip', null); let note = deepClone(props.note); @@ -650,23 +650,35 @@ async function showRenoteMenu() { }; } - const renoteDetailsMenu: MenuItem = { + const renoteDetailsMenu: MenuItem[] = [{ type: 'link', text: i18n.ts.renoteDetails, icon: 'ti ti-info-circle', to: notePage(note), - }; + }]; + + if ( + props.note.channelId != null && + (inChannel == null || props.note.channelId !== inChannel.value) + ) { + renoteDetailsMenu.push({ + type: 'link', + text: i18n.ts.viewRenotedChannel, + icon: 'ti ti-device-tv', + to: `/channels/${props.note.channelId}`, + }); + } if (isMyRenote) { os.popupMenu([ - renoteDetailsMenu, + ...renoteDetailsMenu, getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote), { type: 'divider' }, getUnrenote(), ], renoteTime.value); } else { os.popupMenu([ - renoteDetailsMenu, + ...renoteDetailsMenu, getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote), { type: 'divider' }, getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote), diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 083e3e5da0..114edc6204 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -238,6 +238,7 @@ import { isLink } from '@@/js/is-link.js'; import { host } from '@@/js/config.js'; import type { OpenOnRemoteOptions } from '@/utility/please-login.js'; import type { Keymap } from '@/utility/hotkey.js'; +import type { MenuItem } from '@/types/menu.js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; @@ -286,7 +287,7 @@ const props = withDefaults(defineProps<{ initialTab: 'replies', }); -const inChannel = inject('inChannel', null); +const inChannel = inject(DI.inChannel, null); let note = deepClone(props.note); @@ -581,18 +582,36 @@ async function showRenoteMenu() { const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value }); if (!isLoggedIn) return; - os.popupMenu([{ - text: i18n.ts.unrenote, - icon: 'ti ti-trash', - danger: true, - action: () => { - misskeyApi('notes/delete', { - noteId: note.id, - }).then(() => { - globalEvents.emit('noteDeleted', note.id); - }); - }, - }], renoteTime.value); + const menu: MenuItem[] = []; + + if (isMyRenote) { + menu.push({ + text: i18n.ts.unrenote, + icon: 'ti ti-trash', + danger: true, + action: () => { + misskeyApi('notes/delete', { + noteId: note.id, + }).then(() => { + globalEvents.emit('noteDeleted', note.id); + }); + }, + }); + } + + if ( + props.note.channelId != null && + (inChannel == null || props.note.channelId !== inChannel.value) + ) { + menu.push({ + type: 'link', + text: i18n.ts.viewRenotedChannel, + icon: 'ti ti-device-tv', + to: `/channels/${props.note.channelId}`, + }); + } + + os.popupMenu(menu, renoteTime.value); } function focus() { diff --git a/packages/frontend/src/components/MkStreamingNotesTimeline.vue b/packages/frontend/src/components/MkStreamingNotesTimeline.vue index 9784d8e017..00fd778a5e 100644 --- a/packages/frontend/src/components/MkStreamingNotesTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotesTimeline.vue @@ -74,6 +74,7 @@ import { store } from '@/store.js'; import MkNote from '@/components/MkNote.vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; +import { DI } from '@/di.js'; import { globalEvents, useGlobalEvent } from '@/events.js'; import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js'; import { Paginator } from '@/utility/paginator.js'; @@ -101,7 +102,7 @@ const props = withDefaults(defineProps<{ provide('inTimeline', true); provide('tl_withSensitive', computed(() => props.withSensitive)); -provide('inChannel', computed(() => props.src === 'channel')); +provide(DI.inChannel, computed(() => props.src === 'channel' ? props.channel ?? null : null)); let paginator: IPaginator; diff --git a/packages/frontend/src/di.ts b/packages/frontend/src/di.ts index f09782ea38..eddb0dcb35 100644 --- a/packages/frontend/src/di.ts +++ b/packages/frontend/src/di.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { InjectionKey, Ref } from 'vue'; +import type { InjectionKey, Ref, ComputedRef } from 'vue'; import type { PageMetadata } from '@/page.js'; import type { Router } from '@/router.js'; @@ -18,4 +18,5 @@ export const DI = { mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>, inModal: Symbol() as InjectionKey, inAppSearchMarkerId: Symbol() as InjectionKey>, + inChannel: Symbol() as InjectionKey | null>, // 現在開いているチャンネルのID }; diff --git a/packages/i18n/src/autogen/locale.ts b/packages/i18n/src/autogen/locale.ts index 05a2195374..69e7346a59 100644 --- a/packages/i18n/src/autogen/locale.ts +++ b/packages/i18n/src/autogen/locale.ts @@ -5647,6 +5647,10 @@ export interface Locale extends ILocale { * 設定項目はありません */ "nothingToConfigure": string; + /** + * リノート先のチャンネルを見る + */ + "viewRenotedChannel": string; "_imageEditing": { "_vars": { /**