diff --git a/locales/en-US.yml b/locales/en-US.yml index 7a452e1c52..a231f22716 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -464,6 +464,8 @@ enable: "Enable" next: "Next" retype: "Enter again" noteOf: "Note by {user}" +expandAllCws: "Show content for all replies" +collapseAllCws: "Hide content for all replies" quoteAttached: "Quote" quoteQuestion: "Append as quote?" noMessagesYet: "No messages yet" diff --git a/locales/index.d.ts b/locales/index.d.ts index 90825836cb..fd9a2cb7e0 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -462,6 +462,8 @@ export interface Locale { "next": string; "retype": string; "noteOf": string; + "expandAllCws": string; + "collapseAllCws": string; "quoteAttached": string; "quoteQuestion": string; "noMessagesYet": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8433313cad..deab375c1a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -459,6 +459,8 @@ enable: "有効にする" next: "次" retype: "再入力" noteOf: "{user}のノート" +expandAllCws: "すべての返信の内容を表示する" +collapseAllCws: "すべての返信の内容を隠す" quoteAttached: "引用付き" quoteQuestion: "引用として添付しますか?" noMessagesYet: "まだチャットはありません" diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index e39c2ad156..5f204fe3c4 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -15,9 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="!conversationLoaded" style="padding: 16px"> <MkButton style="margin: 0 auto;" primary rounded @click="loadConversation">{{ i18n.ts.loadConversation }}</MkButton> </div> - <MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/> + <MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note" :expandAllCws="props.expandAllCws"/> </div> - <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/> + <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws"/> <div v-if="isRenote" :class="$style.renote"> <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> <i class="ph-rocket-launch ph-bold ph-lg" style="margin-right: 4px;"></i> @@ -88,7 +88,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/> <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/> - <div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> + <div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote" :expandAllCws="props.expandAllCws"/></div> </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> @@ -159,7 +159,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="!repliesLoaded" style="padding: 16px"> <MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton> </div> - <MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/> + <MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws"/> </div> <div v-else-if="tab === 'renotes'" :class="$style.tab_renotes"> <MkPagination :pagination="renotesPagination" :disableAutoLoad="true"> @@ -176,7 +176,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="!quotesLoaded" style="padding: 16px"> <MkButton style="margin: 0 auto;" primary rounded @click="loadQuotes">{{ i18n.ts.loadReplies }}</MkButton> </div> - <MkNoteSub v-for="note in quotes" :key="note.id" :note="note" :class="$style.reply" :detail="true"/> + <MkNoteSub v-for="note in quotes" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws"/> </div> <div v-else-if="tab === 'reactions'" :class="$style.tab_reactions"> <div :class="$style.reactionTabs"> @@ -209,7 +209,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, inject, onMounted, ref, shallowRef } from 'vue'; +import { computed, inject, onMounted, ref, shallowRef, watch } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import MkNoteSub from '@/components/MkNoteSub.vue'; @@ -246,6 +246,7 @@ import MkButton from '@/components/MkButton.vue'; const props = defineProps<{ note: Misskey.entities.Note; + expandAllCws?: boolean; }>(); const inChannel = inject('inChannel', null); @@ -297,6 +298,10 @@ const replies = ref<Misskey.entities.Note[]>([]); const quotes = ref<Misskey.entities.Note[]>([]); const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id); +watch(() => props.expandAllCws, (expandAllCws) => { + if (expandAllCws !== showContent.value) showContent.value = expandAllCws; +}); + if ($i) { os.api("notes/renotes", { noteId: appearNote.id, @@ -305,7 +310,7 @@ if ($i) { }).then((res) => { renoted.value = res.length > 0; }); - + os.api("notes/renotes", { noteId: appearNote.id, userId: $i.id, diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index b7da548e9d..db5e26e5da 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { } from 'vue'; +import { watch } from 'vue'; import * as Misskey from 'misskey-js'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; @@ -31,9 +31,14 @@ import { $i } from '@/account.js'; const props = defineProps<{ note: Misskey.entities.Note; + expandAllCws?: boolean; }>(); -const showContent = $ref(false); +let showContent = $ref(false); + +watch(() => props.expandAllCws, (expandAllCws) => { + if (expandAllCws !== showContent) showContent = expandAllCws; +}); </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 5b01b022da..83c91e34ed 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <template v-if="depth < 5"> - <MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="$style.reply" :detail="true" :depth="depth + 1"/> + <MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="$style.reply" :detail="true" :depth="depth + 1" :expandAllCws="props.expandAllCws"/> </template> <div v-else :class="$style.more"> <MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ph-caret-double-right ph-bold ph-lg"></i></MkA> @@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, ref, shallowRef } from 'vue'; +import { computed, ref, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; @@ -111,6 +111,7 @@ const canRenote = computed(() => ['public', 'home'].includes(props.note.visibili const props = withDefaults(defineProps<{ note: Misskey.entities.Note; detail?: boolean; + expandAllCws?: boolean; // how many notes are in between this one and the note being viewed in detail depth?: number; @@ -271,6 +272,11 @@ function undoQuote() : void { } let showContent = $ref(false); + +watch(() => props.expandAllCws, (expandAllCws) => { + if (expandAllCws !== showContent) showContent = expandAllCws; +}); + let replies: Misskey.entities.Note[] = $ref([]); function renote() { @@ -448,7 +454,7 @@ if (props.detail) { color: var(--fgHighlighted); } } - + @container (max-width: 400px) { .noteFooterButton { &:not(:last-child) { diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 1de610e282..7880e3361b 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton v-if="!showNext" :class="$style.loadNext" @click="showNext = true"><i class="ph-caret-up ph-bold ph-lg"></i></MkButton> <div class="_margin _gaps_s"> <MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/> - <MkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note"/> + <MkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note" :expandAllCws="expandAllCws"/> </div> <div v-if="clips && clips.length > 0" class="_margin"> <div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div> @@ -65,6 +65,7 @@ let note = $ref<null | Misskey.entities.Note>(); let clips = $ref(); let showPrev = $ref(false); let showNext = $ref(false); +let expandAllCws = $ref(false); let error = $ref(); const prevPagination = { @@ -111,7 +112,13 @@ watch(() => props.noteId, fetchNote, { immediate: true, }); -const headerActions = $computed(() => []); +const headerActions = $computed(() => note ? [ + { + icon: `${expandAllCws ? 'ph-eye' : 'ph-eye-slash'} ph-bold ph-lg`, + text: expandAllCws ? i18n.ts.collapseAllCws : i18n.ts.expandAllCws, + handler: () => { expandAllCws = !expandAllCws; }, + }, +] : []); const headerTabs = $computed(() => []);