enhance(frontend): improve note detail page
This commit is contained in:
parent
5c6b7991ef
commit
907d519da3
|
@ -32,6 +32,10 @@
|
||||||
- ローカリゼーションの更新
|
- ローカリゼーションの更新
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
- ノート詳細ページを改修
|
||||||
|
- 読み込み時のパフォーマンスが向上しました
|
||||||
|
- リノート一覧、リアクション一覧がタブとして追加されました
|
||||||
|
- ノートのメニューからは当該項目は消えました
|
||||||
- プロフィールにその人が作ったPlayの一覧出せるように
|
- プロフィールにその人が作ったPlayの一覧出せるように
|
||||||
- メニューのスイッチの動作を改善
|
- メニューのスイッチの動作を改善
|
||||||
- 絵文字ピッカーの検索の表示件数を100件に増加
|
- 絵文字ピッカーの検索の表示件数を100件に増加
|
||||||
|
@ -48,7 +52,6 @@
|
||||||
- `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました
|
- `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました
|
||||||
- Playの操作を行うAPI TokenをAPIコンソールから発行できるように
|
- Playの操作を行うAPI TokenをAPIコンソールから発行できるように
|
||||||
- リアクションの表示サイズをより大きくできるように
|
- リアクションの表示サイズをより大きくできるように
|
||||||
- ノート詳細ページ読み込み時のパフォーマンスを改善
|
|
||||||
- タイムラインでリスト/アンテナ選択時のパフォーマンスを改善
|
- タイムラインでリスト/アンテナ選択時のパフォーマンスを改善
|
||||||
- 「Moderation note」、「Add moderation note」をローカライズできるように
|
- 「Moderation note」、「Add moderation note」をローカライズできるように
|
||||||
- 新しい実績を追加
|
- 新しい実績を追加
|
||||||
|
|
|
@ -1110,6 +1110,10 @@ export interface Locale {
|
||||||
"pastAnnouncements": string;
|
"pastAnnouncements": string;
|
||||||
"youHaveUnreadAnnouncements": string;
|
"youHaveUnreadAnnouncements": string;
|
||||||
"useSecurityKey": string;
|
"useSecurityKey": string;
|
||||||
|
"replies": string;
|
||||||
|
"renotes": string;
|
||||||
|
"loadReplies": string;
|
||||||
|
"loadConversation": string;
|
||||||
"_announcement": {
|
"_announcement": {
|
||||||
"forExistingUsers": string;
|
"forExistingUsers": string;
|
||||||
"forExistingUsersDescription": string;
|
"forExistingUsersDescription": string;
|
||||||
|
|
|
@ -15,7 +15,7 @@ gotIt: "わかった"
|
||||||
cancel: "キャンセル"
|
cancel: "キャンセル"
|
||||||
noThankYou: "やめておく"
|
noThankYou: "やめておく"
|
||||||
enterUsername: "ユーザー名を入力"
|
enterUsername: "ユーザー名を入力"
|
||||||
renotedBy: "{user}がRenote"
|
renotedBy: "{user}がリノート"
|
||||||
noNotes: "ノートはありません"
|
noNotes: "ノートはありません"
|
||||||
noNotifications: "通知はありません"
|
noNotifications: "通知はありません"
|
||||||
instance: "サーバー"
|
instance: "サーバー"
|
||||||
|
@ -45,10 +45,10 @@ pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
copyContent: "内容をコピー"
|
copyContent: "内容をコピー"
|
||||||
copyLink: "リンクをコピー"
|
copyLink: "リンクをコピー"
|
||||||
copyLinkRenote: "Renoteのリンクをコピー"
|
copyLinkRenote: "リノートのリンクをコピー"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
deleteAndEdit: "削除して編集"
|
deleteAndEdit: "削除して編集"
|
||||||
deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、Renote、返信も全て削除されます。"
|
deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、リノート、返信も全て削除されます。"
|
||||||
addToList: "リストに追加"
|
addToList: "リストに追加"
|
||||||
addToAntenna: "アンテナに追加"
|
addToAntenna: "アンテナに追加"
|
||||||
sendMessage: "メッセージを送信"
|
sendMessage: "メッセージを送信"
|
||||||
|
@ -105,13 +105,13 @@ followRequests: "フォロー申請"
|
||||||
unfollow: "フォロー解除"
|
unfollow: "フォロー解除"
|
||||||
followRequestPending: "フォロー許可待ち"
|
followRequestPending: "フォロー許可待ち"
|
||||||
enterEmoji: "絵文字を入力"
|
enterEmoji: "絵文字を入力"
|
||||||
renote: "Renote"
|
renote: "リノート"
|
||||||
unrenote: "Renote解除"
|
unrenote: "リノート解除"
|
||||||
renoted: "Renoteしました。"
|
renoted: "リノートしました。"
|
||||||
cantRenote: "この投稿はRenoteできません。"
|
cantRenote: "この投稿はリノートできません。"
|
||||||
cantReRenote: "RenoteをRenoteすることはできません。"
|
cantReRenote: "リノートをリノートすることはできません。"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
inChannelRenote: "チャンネル内Renote"
|
inChannelRenote: "チャンネル内リノート"
|
||||||
inChannelQuote: "チャンネル内引用"
|
inChannelQuote: "チャンネル内引用"
|
||||||
pinnedNote: "ピン留めされたノート"
|
pinnedNote: "ピン留めされたノート"
|
||||||
pinned: "ピン留め"
|
pinned: "ピン留め"
|
||||||
|
@ -657,7 +657,7 @@ behavior: "動作"
|
||||||
sample: "サンプル"
|
sample: "サンプル"
|
||||||
abuseReports: "通報"
|
abuseReports: "通報"
|
||||||
reportAbuse: "通報"
|
reportAbuse: "通報"
|
||||||
reportAbuseRenote: "Renoteを通報"
|
reportAbuseRenote: "リノートを通報"
|
||||||
reportAbuseOf: "{name}を通報する"
|
reportAbuseOf: "{name}を通報する"
|
||||||
fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。"
|
fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。"
|
||||||
abuseReported: "内容が送信されました。ご報告ありがとうございました。"
|
abuseReported: "内容が送信されました。ご報告ありがとうございました。"
|
||||||
|
@ -691,9 +691,9 @@ manageAccessTokens: "アクセストークンの管理"
|
||||||
accountInfo: "アカウント情報"
|
accountInfo: "アカウント情報"
|
||||||
notesCount: "ノートの数"
|
notesCount: "ノートの数"
|
||||||
repliesCount: "返信した数"
|
repliesCount: "返信した数"
|
||||||
renotesCount: "Renoteした数"
|
renotesCount: "リノートした数"
|
||||||
repliedCount: "返信された数"
|
repliedCount: "返信された数"
|
||||||
renotedCount: "Renoteされた数"
|
renotedCount: "リノートされた数"
|
||||||
followingCount: "フォロー数"
|
followingCount: "フォロー数"
|
||||||
followersCount: "フォロワー数"
|
followersCount: "フォロワー数"
|
||||||
sentReactionsCount: "リアクションした数"
|
sentReactionsCount: "リアクションした数"
|
||||||
|
@ -989,7 +989,7 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります
|
||||||
thisPostMayBeAnnoyingHome: "ホームに投稿"
|
thisPostMayBeAnnoyingHome: "ホームに投稿"
|
||||||
thisPostMayBeAnnoyingCancel: "やめる"
|
thisPostMayBeAnnoyingCancel: "やめる"
|
||||||
thisPostMayBeAnnoyingIgnore: "このまま投稿"
|
thisPostMayBeAnnoyingIgnore: "このまま投稿"
|
||||||
collapseRenotes: "見たことのあるRenoteを省略して表示"
|
collapseRenotes: "見たことのあるリノートを省略して表示"
|
||||||
internalServerError: "サーバー内部エラー"
|
internalServerError: "サーバー内部エラー"
|
||||||
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
|
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
|
||||||
copyErrorInfo: "エラー情報をコピー"
|
copyErrorInfo: "エラー情報をコピー"
|
||||||
|
@ -1037,7 +1037,7 @@ forceShowAds: "常に広告を表示する"
|
||||||
addMemo: "メモを追加"
|
addMemo: "メモを追加"
|
||||||
editMemo: "メモを編集"
|
editMemo: "メモを編集"
|
||||||
reactionsList: "リアクション一覧"
|
reactionsList: "リアクション一覧"
|
||||||
renotesList: "Renote一覧"
|
renotesList: "リノート一覧"
|
||||||
notificationDisplay: "通知の表示"
|
notificationDisplay: "通知の表示"
|
||||||
leftTop: "左上"
|
leftTop: "左上"
|
||||||
rightTop: "右上"
|
rightTop: "右上"
|
||||||
|
@ -1107,6 +1107,10 @@ currentAnnouncements: "現在のお知らせ"
|
||||||
pastAnnouncements: "過去のお知らせ"
|
pastAnnouncements: "過去のお知らせ"
|
||||||
youHaveUnreadAnnouncements: "未読のお知らせがあります。"
|
youHaveUnreadAnnouncements: "未読のお知らせがあります。"
|
||||||
useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。"
|
useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。"
|
||||||
|
replies: "返信"
|
||||||
|
renotes: "リノート"
|
||||||
|
loadReplies: "返信を見る"
|
||||||
|
loadConversation: "会話を見る"
|
||||||
|
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "既存ユーザーのみ"
|
forExistingUsers: "既存ユーザーのみ"
|
||||||
|
|
|
@ -86,9 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<MkReactionsViewer :note="appearNote" :maxNumber="16">
|
<MkReactionsViewer :note="appearNote" :maxNumber="16">
|
||||||
<template #more>
|
<template #more>
|
||||||
<button class="_button" :class="$style.reactionDetailsButton" @click="showReactions">
|
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
|
||||||
{{ i18n.ts.more }}
|
|
||||||
</button>
|
|
||||||
</template>
|
</template>
|
||||||
</MkReactionsViewer>
|
</MkReactionsViewer>
|
||||||
<footer :class="$style.footer">
|
<footer :class="$style.footer">
|
||||||
|
@ -457,7 +455,7 @@ function showRenoteMenu(viaKeyboard = false): void {
|
||||||
} else {
|
} else {
|
||||||
os.popupMenu([
|
os.popupMenu([
|
||||||
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
|
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
|
||||||
null,
|
null,
|
||||||
getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote),
|
getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote),
|
||||||
$i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
|
$i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
|
||||||
], renoteTime.value, {
|
], renoteTime.value, {
|
||||||
|
@ -488,12 +486,6 @@ function readPromo() {
|
||||||
});
|
});
|
||||||
isDeleted.value = true;
|
isDeleted.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showReactions(): void {
|
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/MkReactedUsersDialog.vue')), {
|
|
||||||
noteId: appearNote.id,
|
|
||||||
}, {}, 'closed');
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -941,7 +933,7 @@ function showReactions(): void {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reactionDetailsButton {
|
.reactionOmitted {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
|
@ -950,9 +942,5 @@ function showReactions(): void {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--X5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,7 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
v-hotkey="keymap"
|
v-hotkey="keymap"
|
||||||
:class="$style.root"
|
: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"/>
|
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
|
||||||
<div v-if="isRenote" :class="$style.renote">
|
<div v-if="isRenote" :class="$style.renote">
|
||||||
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
|
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
|
||||||
|
@ -125,7 +130,43 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</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>
|
||||||
<div v-else class="_panel" :class="$style.muted" @click="muted = false">
|
<div v-else class="_panel" :class="$style.muted" @click="muted = false">
|
||||||
<I18n :src="i18n.ts.userSaysSomething" tag="small">
|
<I18n :src="i18n.ts.userSaysSomething" tag="small">
|
||||||
|
@ -169,6 +210,10 @@ import { claimAchievement } from '@/scripts/achievements';
|
||||||
import { MenuItem } from '@/types/menu';
|
import { MenuItem } from '@/types/menu';
|
||||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||||
import { showMovedDialog } from '@/scripts/show-moved-dialog';
|
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<{
|
const props = defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
@ -224,6 +269,26 @@ const keymap = {
|
||||||
's': () => showContent.value !== showContent.value,
|
'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({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note: $$(appearNote),
|
note: $$(appearNote),
|
||||||
|
@ -426,14 +491,20 @@ function blur() {
|
||||||
el.value.blur();
|
el.value.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
os.api('notes/children', {
|
const repliesLoaded = ref(false);
|
||||||
noteId: appearNote.id,
|
function loadReplies() {
|
||||||
limit: 30,
|
repliesLoaded.value = true;
|
||||||
}).then(res => {
|
os.api('notes/children', {
|
||||||
replies.value = res;
|
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', {
|
os.api('notes/conversation', {
|
||||||
noteId: appearNote.replyId,
|
noteId: appearNote.replyId,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
@ -640,10 +711,52 @@ if (appearNote.replyId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply {
|
.reply:not(:first-child) {
|
||||||
border-top: solid 0.5px var(--divider);
|
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) {
|
@container (max-width: 500px) {
|
||||||
.root {
|
.root {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MkModalWindow
|
|
||||||
ref="dialog"
|
|
||||||
:width="400"
|
|
||||||
:height="450"
|
|
||||||
@close="dialog.close()"
|
|
||||||
@closed="emit('closed')"
|
|
||||||
>
|
|
||||||
<template #header>{{ i18n.ts.reactionsList }}</template>
|
|
||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
|
||||||
<div v-if="note" class="_gaps">
|
|
||||||
<div v-if="reactions.length === 0" class="_fullinfo">
|
|
||||||
<img :src="infoImageUrl" class="_ghost"/>
|
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div :class="$style.tabs">
|
|
||||||
<button v-for="reaction in reactions" :key="reaction" :class="[$style.tab, { [$style.tabActive]: tab === reaction }]" class="_button" @click="tab = reaction">
|
|
||||||
<MkReactionIcon :reaction="reaction"/>
|
|
||||||
<span style="margin-left: 4px;">{{ note.reactions[reaction] }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()">
|
|
||||||
<MkUserCardMini :user="user" :withChart="false"/>
|
|
||||||
</MkA>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<MkLoading/>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkModalWindow>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, watch } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
|
||||||
import { userPage } from '@/filters/user';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { infoImageUrl } from '@/instance';
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'closed'): void,
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
noteId: Misskey.entities.Note['id'];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
|
|
||||||
|
|
||||||
let note = $ref<Misskey.entities.Note>();
|
|
||||||
let tab = $ref<string>();
|
|
||||||
let reactions = $ref<string[]>();
|
|
||||||
let users = $ref();
|
|
||||||
|
|
||||||
watch($$(tab), async () => {
|
|
||||||
const res = await os.api('notes/reactions', {
|
|
||||||
noteId: props.noteId,
|
|
||||||
type: tab,
|
|
||||||
limit: 30,
|
|
||||||
});
|
|
||||||
|
|
||||||
users = res.map(x => x.user);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
os.api('notes/show', {
|
|
||||||
noteId: props.noteId,
|
|
||||||
}).then((res) => {
|
|
||||||
reactions = Object.keys(res.reactions);
|
|
||||||
tab = reactions[0];
|
|
||||||
note = res;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
padding: 4px 6px;
|
|
||||||
border: solid 1px var(--divider);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabActive {
|
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,71 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MkModalWindow
|
|
||||||
ref="dialog"
|
|
||||||
:width="400"
|
|
||||||
:height="450"
|
|
||||||
@close="dialog.close()"
|
|
||||||
@closed="emit('closed')"
|
|
||||||
>
|
|
||||||
<template #header>{{ i18n.ts.renotesList }}</template>
|
|
||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
|
||||||
<div v-if="renotes" class="_gaps">
|
|
||||||
<div v-if="renotes.length === 0" class="_fullinfo">
|
|
||||||
<img :src="infoImageUrl" class="_ghost"/>
|
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()">
|
|
||||||
<MkUserCardMini :user="user" :withChart="false"/>
|
|
||||||
</MkA>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<MkLoading/>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkModalWindow>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
|
||||||
import { userPage } from '@/filters/user';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { infoImageUrl } from '@/instance';
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'closed'): void,
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
noteId: Misskey.entities.Note['id'];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
|
|
||||||
|
|
||||||
let note = $ref<Misskey.entities.Note>();
|
|
||||||
let renotes = $ref();
|
|
||||||
let users = $ref();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const res = await os.api('notes/renotes', {
|
|
||||||
noteId: props.noteId,
|
|
||||||
limit: 30,
|
|
||||||
});
|
|
||||||
|
|
||||||
renotes = res;
|
|
||||||
users = res.map(x => x.user);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
</style>
|
|
|
@ -238,18 +238,6 @@ export function getNoteMenu(props: {
|
||||||
os.pageWindow(`/notes/${appearNote.id}`);
|
os.pageWindow(`/notes/${appearNote.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showReactions(): void {
|
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/MkReactedUsersDialog.vue')), {
|
|
||||||
noteId: appearNote.id,
|
|
||||||
}, {}, 'closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showRenotes(): void {
|
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/MkRenotedUsersDialog.vue')), {
|
|
||||||
noteId: appearNote.id,
|
|
||||||
}, {}, 'closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function translate(): Promise<void> {
|
async function translate(): Promise<void> {
|
||||||
if (props.translation.value != null) return;
|
if (props.translation.value != null) return;
|
||||||
props.translating.value = true;
|
props.translating.value = true;
|
||||||
|
@ -279,14 +267,6 @@ export function getNoteMenu(props: {
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
text: i18n.ts.details,
|
text: i18n.ts.details,
|
||||||
action: openDetail,
|
action: openDetail,
|
||||||
}, {
|
|
||||||
icon: 'ti ti-repeat',
|
|
||||||
text: i18n.ts.renotesList,
|
|
||||||
action: showRenotes,
|
|
||||||
}, {
|
|
||||||
icon: 'ti ti-icons',
|
|
||||||
text: i18n.ts.reactionsList,
|
|
||||||
action: showReactions,
|
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-copy',
|
icon: 'ti ti-copy',
|
||||||
text: i18n.ts.copyContent,
|
text: i18n.ts.copyContent,
|
||||||
|
|
Loading…
Reference in New Issue