diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue index 80d6ac369e1ccf6ba54b7419adb9b94eace7165a..685e0a4b7e096c24b283c1a89239d0ffbd0e126a 100644 --- a/packages/frontend/src/components/SkNoteDetailed.vue +++ b/packages/frontend/src/components/SkNoteDetailed.vue @@ -178,7 +178,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> - <SkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" :isReply="true"/> + <SkNoteSub v-for="note in sortedReplies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" :isReply="true"/> </div> <div v-else-if="tab === 'renotes'" :class="$style.tab_renotes"> <MkPagination :pagination="renotesPagination" :disableAutoLoad="true"> @@ -266,6 +266,7 @@ import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkButton from '@/components/MkButton.vue'; import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; +import { sortReplies } from '@/scripts/reply-sorting.js'; const props = defineProps<{ note: Misskey.entities.Note; @@ -327,6 +328,7 @@ const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.anima const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); const conversation = ref<Misskey.entities.Note[]>([]); const replies = ref<Misskey.entities.Note[]>([]); +const sortedReplies = computed(() => sortReplies(props.note, replies.value)); const quotes = ref<Misskey.entities.Note[]>([]); const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id)); const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue index 1486742f42ee39c25dd289e9376068e7999754aa..6d806e4fb8cb502a7822c8e50d79f42b95aee7d7 100644 --- a/packages/frontend/src/components/SkNoteSub.vue +++ b/packages/frontend/src/components/SkNoteSub.vue @@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <template v-if="depth < numberOfReplies"> - <SkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="[$style.reply, { [$style.single]: replies.length === 1 }]" :detail="true" :depth="depth + 1" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" :isReply="props.isReply"/> + <SkNoteSub v-for="reply in sortedReplies" :key="reply.id" :note="reply" :class="[$style.reply, { [$style.single]: replies.length === 1 }]" :detail="true" :depth="depth + 1" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" :isReply="props.isReply"/> </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> @@ -114,6 +114,7 @@ import { claimAchievement } from '@/scripts/achievements.js'; import { getNoteMenu } from '@/scripts/get-note-menu.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js'; import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; +import { sortReplies } from '@/scripts/reply-sorting.js'; const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); const hideLine = computed(() => { return props.detail ? true : false; }); @@ -151,6 +152,7 @@ const likeButton = shallowRef<HTMLElement>(); let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note); const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); const replies = ref<Misskey.entities.Note[]>([]); +const sortedReplies = computed(() => sortReplies(props.note, replies.value)); const isRenote = ( props.note.renote != null && diff --git a/packages/frontend/src/scripts/reply-sorting.ts b/packages/frontend/src/scripts/reply-sorting.ts new file mode 100644 index 0000000000000000000000000000000000000000..43485ee19c605af6b68275cb15a7ab75fab8729a --- /dev/null +++ b/packages/frontend/src/scripts/reply-sorting.ts @@ -0,0 +1,17 @@ +import * as Misskey from 'misskey-js'; + +// sorts replies to self before other replies to make threads easier to read +export function sortReplies(root: Misskey.entities.Note, replies: Misskey.entities.Note[]): Misskey.entities.Note[] { + const result: Misskey.entities.Note[] = []; + + for (const reply of replies) { + if (root.userId === reply.userId) { + result.unshift(reply); + } else { + result.push(reply); + } + } + + return result; +} +