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": { /**