feat: add account tab pages (#422)
This commit is contained in:
parent
8f8e65e9db
commit
089890677f
41
components/account/AccountTabs.vue
Normal file
41
components/account/AccountTabs.vue
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const server = $(computedEager(() => route.params.server as string))
|
||||||
|
const account = $(computedEager(() => route.params.account as string))
|
||||||
|
|
||||||
|
const tabs = $computed(() => [
|
||||||
|
{
|
||||||
|
name: 'account-index',
|
||||||
|
to: {
|
||||||
|
name: 'account-index',
|
||||||
|
params: { server, account },
|
||||||
|
},
|
||||||
|
display: t('tab.posts'),
|
||||||
|
icon: 'i-ri:file-list-2-line',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'account-replies',
|
||||||
|
to: {
|
||||||
|
name: 'account-replies',
|
||||||
|
params: { server, account },
|
||||||
|
},
|
||||||
|
display: t('tab.posts_with_replies'),
|
||||||
|
icon: 'i-ri:chat-3-line',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'account-media',
|
||||||
|
to: {
|
||||||
|
name: 'account-media',
|
||||||
|
params: { server, account },
|
||||||
|
},
|
||||||
|
display: t('tab.media'),
|
||||||
|
icon: 'i-ri:camera-2-line',
|
||||||
|
},
|
||||||
|
] as const)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CommonRouteTabs force :options="tabs" prevent-scroll-top command />
|
||||||
|
</template>
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { RouteLocationRaw } from 'vue-router'
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
|
|
||||||
const { options, command, replace } = $defineProps<{
|
const { options, command, replace, preventScrollTop = false } = $defineProps<{
|
||||||
options: {
|
options: {
|
||||||
to: RouteLocationRaw
|
to: RouteLocationRaw
|
||||||
display: string
|
display: string
|
||||||
|
@ -10,6 +10,7 @@ const { options, command, replace } = $defineProps<{
|
||||||
}[]
|
}[]
|
||||||
command?: boolean
|
command?: boolean
|
||||||
replace?: boolean
|
replace?: boolean
|
||||||
|
preventScrollTop?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -36,7 +37,7 @@ useCommands(() => command
|
||||||
tabindex="1"
|
tabindex="1"
|
||||||
hover:bg-active transition-100
|
hover:bg-active transition-100
|
||||||
exact-active-class="children:(font-bold !border-primary !op100)"
|
exact-active-class="children:(font-bold !border-primary !op100)"
|
||||||
@click="$scrollToTop"
|
@click="!preventScrollTop && $scrollToTop()"
|
||||||
>
|
>
|
||||||
<span ws-nowrap mxa sm:px2 sm:py3 py2 text-center border-b-3 op50 hover:op70 border-transparent>{{ option.display }}</span>
|
<span ws-nowrap mxa sm:px2 sm:py3 py2 text-center border-b-3 op50 hover:op70 border-transparent>{{ option.display }}</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"follow": "Follow",
|
"follow": "Follow",
|
||||||
"follow_back": "Follow back",
|
"follow_back": "Follow back",
|
||||||
"follow_requested": "Requested",
|
"follow_requested": "Requested",
|
||||||
|
"followers": "Followers",
|
||||||
"followers_count": "{0} Followers",
|
"followers_count": "{0} Followers",
|
||||||
"following": "Following",
|
"following": "Following",
|
||||||
"following_count": "{0} Following",
|
"following_count": "{0} Following",
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
"muted_users": "Muted users",
|
"muted_users": "Muted users",
|
||||||
"mutuals": "Mutuals",
|
"mutuals": "Mutuals",
|
||||||
"pinned": "Pinned",
|
"pinned": "Pinned",
|
||||||
|
"posts": "Posts",
|
||||||
"posts_count": "{0} Posts",
|
"posts_count": "{0} Posts",
|
||||||
"profile_description": "{0}'s profile header",
|
"profile_description": "{0}'s profile header",
|
||||||
"profile_unavailable": "Profile unavailable",
|
"profile_unavailable": "Profile unavailable",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"follow": "Seguir",
|
"follow": "Seguir",
|
||||||
"follow_back": "Seguir de vuelta",
|
"follow_back": "Seguir de vuelta",
|
||||||
"follow_requested": "Enviado",
|
"follow_requested": "Enviado",
|
||||||
|
"followers": "Seguidores",
|
||||||
"followers_count": "{0} Seguidores|{0} Seguidor|{0} Seguidores",
|
"followers_count": "{0} Seguidores|{0} Seguidor|{0} Seguidores",
|
||||||
"following": "Siguiendo",
|
"following": "Siguiendo",
|
||||||
"following_count": "{0} Siguiendo",
|
"following_count": "{0} Siguiendo",
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
"muted_users": "Usuarios silenciados",
|
"muted_users": "Usuarios silenciados",
|
||||||
"mutuals": "Mutuo",
|
"mutuals": "Mutuo",
|
||||||
"pinned": "Publicaciones fijadas",
|
"pinned": "Publicaciones fijadas",
|
||||||
|
"posts": "Publicaciones",
|
||||||
"posts_count": "{0} publicaciones|{0} publicación|{0} publicaciones",
|
"posts_count": "{0} publicaciones|{0} publicación|{0} publicaciones",
|
||||||
"profile_description": "Encabezado del perfil de {0}",
|
"profile_description": "Encabezado del perfil de {0}",
|
||||||
"profile_unavailable": "Perfil no disponible",
|
"profile_unavailable": "Perfil no disponible",
|
||||||
|
@ -172,7 +174,7 @@
|
||||||
"tab": {
|
"tab": {
|
||||||
"for_you": "Para tí",
|
"for_you": "Para tí",
|
||||||
"hashtags": "Etiquetas",
|
"hashtags": "Etiquetas",
|
||||||
"media": "Medios de comunicación",
|
"media": "Multimedia",
|
||||||
"news": "Noticias",
|
"news": "Noticias",
|
||||||
"notifications_all": "Todas",
|
"notifications_all": "Todas",
|
||||||
"notifications_mention": "Menciones",
|
"notifications_mention": "Menciones",
|
||||||
|
|
|
@ -11,12 +11,6 @@ const { t } = useI18n()
|
||||||
const { data: account, refresh } = $(await useAsyncData(() => fetchAccountByHandle(accountName).catch(() => null)))
|
const { data: account, refresh } = $(await useAsyncData(() => fetchAccountByHandle(accountName).catch(() => null)))
|
||||||
const relationship = $computed(() => account ? useRelationship(account).value : undefined)
|
const relationship = $computed(() => account ? useRelationship(account).value : undefined)
|
||||||
|
|
||||||
if (account) {
|
|
||||||
useHeadFixed({
|
|
||||||
title: () => `${getDisplayName(account)} (@${account.acct})`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onReactivated(() => {
|
onReactivated(() => {
|
||||||
// Silently update data when reentering the page
|
// Silently update data when reentering the page
|
||||||
// The user will see the previous content first, and any changes will be updated to the UI when the request is completed
|
// The user will see the previous content first, and any changes will be updated to the UI when the request is completed
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = $(computedEager(() => params.account as string))
|
const handle = $(computedEager(() => params.account as string))
|
||||||
|
|
||||||
|
@ -6,6 +7,12 @@ definePageMeta({ name: 'account-followers' })
|
||||||
|
|
||||||
const account = await fetchAccountByHandle(handle)
|
const account = await fetchAccountByHandle(handle)
|
||||||
const paginator = account ? useMasto().accounts.iterateFollowers(account.id, {}) : null
|
const paginator = account ? useMasto().accounts.iterateFollowers(account.id, {}) : null
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
useHeadFixed({
|
||||||
|
title: () => `${t('account.followers')} | ${getDisplayName(account)} (@${account})`,
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = $(computedEager(() => params.account as string))
|
const handle = $(computedEager(() => params.account as string))
|
||||||
|
|
||||||
|
@ -6,6 +7,12 @@ definePageMeta({ name: 'account-following' })
|
||||||
|
|
||||||
const account = await fetchAccountByHandle(handle)
|
const account = await fetchAccountByHandle(handle)
|
||||||
const paginator = account ? useMasto().accounts.iterateFollowing(account.id, {}) : null
|
const paginator = account ? useMasto().accounts.iterateFollowing(account.id, {}) : null
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
useHeadFixed({
|
||||||
|
title: () => `${t('account.following')} | ${getDisplayName(account)} (@${account})`,
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,52 +1,31 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Account } from 'masto'
|
import type { Account } from 'masto'
|
||||||
|
import AccountTabs from '~/components/account/AccountTabs.vue'
|
||||||
|
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = $(computedEager(() => params.account as string))
|
const handle = $(computedEager(() => params.account as string))
|
||||||
|
|
||||||
definePageMeta({ name: 'account-index' })
|
definePageMeta({ name: 'account-index' })
|
||||||
|
|
||||||
const { data: account } = await useAsyncData(`account:${handle}`, async () => (
|
|
||||||
window.history.state?.account as Account | undefined)
|
|
||||||
?? await fetchAccountByHandle(handle),
|
|
||||||
)
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const paginatorPosts = useMasto().accounts.iterateStatuses(account.value!.id, { excludeReplies: true })
|
const { data: account } = await useAsyncData(`account:${handle}`, async () => (
|
||||||
const paginatorPostsWithReply = useMasto().accounts.iterateStatuses(account.value!.id, { excludeReplies: false })
|
window.history.state?.account as Account | undefined)
|
||||||
const paginatorMedia = useMasto().accounts.iterateStatuses(account.value!.id, { onlyMedia: true, excludeReplies: false })
|
?? await fetchAccountByHandle(handle),
|
||||||
|
)
|
||||||
|
|
||||||
const tabs = $computed(() => [
|
const paginator = useMasto().accounts.iterateStatuses(account.value!.id, { excludeReplies: true })
|
||||||
{
|
|
||||||
name: 'posts',
|
|
||||||
display: t('tab.posts'),
|
|
||||||
icon: 'i-ri:file-list-2-line',
|
|
||||||
paginator: paginatorPosts,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'relies',
|
|
||||||
display: t('tab.posts_with_replies'),
|
|
||||||
icon: 'i-ri:chat-3-line',
|
|
||||||
paginator: paginatorPostsWithReply,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'media',
|
|
||||||
display: t('tab.media'),
|
|
||||||
icon: 'i-ri:camera-2-line',
|
|
||||||
paginator: paginatorMedia,
|
|
||||||
},
|
|
||||||
] as const)
|
|
||||||
|
|
||||||
// Don't use local storage because it is better to default to Posts every time you visit a user's profile.
|
if (account) {
|
||||||
const tab = $ref(tabs[0].name)
|
useHeadFixed({
|
||||||
const paginator = $computed(() => tabs.find(t => t.name === tab)!.paginator)
|
title: () => `${t('account.posts')} | ${getDisplayName(account.value!)} (@${account.value!.acct})`,
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<CommonTabs v-model="tab" :options="tabs" command />
|
<AccountTabs />
|
||||||
<KeepAlive>
|
<TimelinePaginator :paginator="paginator" context="account" />
|
||||||
<TimelinePaginator :key="tab" :paginator="paginator" context="account" />
|
|
||||||
</KeepAlive>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
29
pages/[[server]]/@[account]/index/media.vue
Normal file
29
pages/[[server]]/@[account]/index/media.vue
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Account } from 'masto'
|
||||||
|
|
||||||
|
definePageMeta({ name: 'account-media' })
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const params = useRoute().params
|
||||||
|
const handle = $(computedEager(() => params.account as string))
|
||||||
|
|
||||||
|
const { data: account } = await useAsyncData(`account:${handle}`, async () => (
|
||||||
|
window.history.state?.account as Account | undefined)
|
||||||
|
?? await fetchAccountByHandle(handle),
|
||||||
|
)
|
||||||
|
|
||||||
|
const paginator = useMasto().accounts.iterateStatuses(account.value!.id, { onlyMedia: true, excludeReplies: false })
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
useHeadFixed({
|
||||||
|
title: () => `${t('tab.media')} | ${getDisplayName(account.value!)} (@${account.value!.acct})`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AccountTabs />
|
||||||
|
<TimelinePaginator :paginator="paginator" context="account" />
|
||||||
|
</div>
|
||||||
|
</template>
|
29
pages/[[server]]/@[account]/index/with_replies.vue
Normal file
29
pages/[[server]]/@[account]/index/with_replies.vue
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Account } from 'masto'
|
||||||
|
|
||||||
|
definePageMeta({ name: 'account-replies' })
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const params = useRoute().params
|
||||||
|
const handle = $(computedEager(() => params.account as string))
|
||||||
|
|
||||||
|
const { data: account } = await useAsyncData(`account:${handle}`, async () => (
|
||||||
|
window.history.state?.account as Account | undefined)
|
||||||
|
?? await fetchAccountByHandle(handle),
|
||||||
|
)
|
||||||
|
|
||||||
|
const paginator = useMasto().accounts.iterateStatuses(account.value!.id, { excludeReplies: false })
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
useHeadFixed({
|
||||||
|
title: () => `${t('tab.posts_with_replies')} | ${getDisplayName(account.value!)} (@${account.value!.acct})`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AccountTabs />
|
||||||
|
<TimelinePaginator :paginator="paginator" context="account" />
|
||||||
|
</div>
|
||||||
|
</template>
|
Loading…
Reference in a new issue