From 907d519da36c550dde0a39970057bce22ffdcc5f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Sep 2023 10:33:33 +0900
Subject: enhance(frontend): improve note detail page

---
 .../frontend/src/components/MkNoteDetailed.vue     | 133 +++++++++++++++++++--
 1 file changed, 123 insertions(+), 10 deletions(-)

(limited to 'packages/frontend/src/components/MkNoteDetailed.vue')

diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index f7578dd390..086667127b 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -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;
-- 
cgit v1.2.3-freya

