enhance(frontend): improve note detail page

This commit is contained in:
syuilo 2023-09-17 10:33:33 +09:00
parent 5c6b7991ef
commit 907d519da3
8 changed files with 152 additions and 235 deletions

View file

@ -11,7 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only
v-hotkey="keymap"
:class="$style.root"
>
<MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/>
<div v-if="appearNote.reply.replyId">
<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"/>
</div>
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
<div v-if="isRenote" :class="$style.renote">
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
@ -125,7 +130,43 @@ SPDX-License-Identifier: AGPL-3.0-only
</button>
</footer>
</article>
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
<div :class="$style.tabs">
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ti ti-arrow-back-up"></i> {{ i18n.ts.replies }}</button>
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ti ti-repeat"></i> {{ i18n.ts.renotes }}</button>
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ti ti-icons"></i> {{ i18n.ts.reactions }}</button>
</div>
<div>
<div v-if="tab === 'replies'" :class="$style.tab_replies">
<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"/>
</div>
<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
<MkPagination :pagination="renotesPagination">
<template #default="{ items }">
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
<MkUserCardMini :user="item.user" :withChart="false"/>
</MkA>
</template>
</MkPagination>
</div>
<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
<div :class="$style.reactionTabs">
<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction">
<MkReactionIcon :reaction="reaction"/>
<span style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
</button>
</div>
<MkPagination :pagination="reactionsPagination">
<template #default="{ items }">
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
<MkUserCardMini :user="item.user" :withChart="false"/>
</MkA>
</template>
</MkPagination>
</div>
</div>
</div>
<div v-else class="_panel" :class="$style.muted" @click="muted = false">
<I18n :src="i18n.ts.userSaysSomething" tag="small">
@ -169,6 +210,10 @@ import { claimAchievement } from '@/scripts/achievements';
import { MenuItem } from '@/types/menu';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue';
const props = defineProps<{
note: Misskey.entities.Note;
@ -224,6 +269,26 @@ const keymap = {
's': () => showContent.value !== showContent.value,
};
let tab = $ref('replies');
let reactionTabType = $ref(null);
const renotesPagination = $computed(() => ({
endpoint: 'notes/renotes',
limit: 10,
params: {
noteId: appearNote.id,
},
}));
const reactionsPagination = $computed(() => ({
endpoint: 'notes/reactions',
limit: 10,
params: {
noteId: appearNote.id,
type: reactionTabType,
},
}));
useNoteCapture({
rootEl: el,
note: $$(appearNote),
@ -426,14 +491,20 @@ function blur() {
el.value.blur();
}
os.api('notes/children', {
noteId: appearNote.id,
limit: 30,
}).then(res => {
replies.value = res;
});
const repliesLoaded = ref(false);
function loadReplies() {
repliesLoaded.value = true;
os.api('notes/children', {
noteId: appearNote.id,
limit: 30,
}).then(res => {
replies.value = res;
});
}
if (appearNote.replyId) {
const conversationLoaded = ref(false);
function loadConversation() {
conversationLoaded.value = true;
os.api('notes/conversation', {
noteId: appearNote.replyId,
}).then(res => {
@ -640,10 +711,52 @@ if (appearNote.replyId) {
}
}
.reply {
.reply:not(:first-child) {
border-top: solid 0.5px var(--divider);
}
.tabs {
border-top: solid 0.5px var(--divider);
border-bottom: solid 0.5px var(--divider);
display: flex;
}
.tab {
flex: 1;
padding: 12px 8px;
border-top: solid 2px transparent;
border-bottom: solid 2px transparent;
}
.tabActive {
border-bottom: solid 2px var(--accent);
}
.tab_renotes {
padding: 16px;
}
.tab_reactions {
padding: 16px;
}
.reactionTabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.reactionTab {
padding: 4px 6px;
border: solid 1px var(--divider);
border-radius: 6px;
}
.reactionTabActive {
border-color: var(--accent);
}
@container (max-width: 500px) {
.root {
font-size: 0.9em;