feat: resolve status paths with router (#258)

zio/stable
Daniel Roe 2022-11-30 17:15:18 +00:00 committed by GitHub
parent 24bbe9135b
commit 4ed1816806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 106 additions and 50 deletions

View File

@ -14,7 +14,7 @@ cacheAccount(account)
<AccountInfo <AccountInfo
:account="account" hover p1 as="router-link" :account="account" hover p1 as="router-link"
:hover-card="hoverCard" :hover-card="hoverCard"
:to="getAccountPath(account)" :to="getAccountRoute(account)"
/> />
<div h-full p1> <div h-full p1>
<AccountFollowButton :account="account" /> <AccountFollowButton :account="account" />

View File

@ -9,7 +9,7 @@ defineProps<{
<template> <template>
<div flex="~ col gap2" rounded min-w-90 max-w-120 z-100 overflow-hidden p-4> <div flex="~ col gap2" rounded min-w-90 max-w-120 z-100 overflow-hidden p-4>
<div flex="~ gap2" items-center> <div flex="~ gap2" items-center>
<NuxtLink :to="getAccountPath(account)" flex-auto rounded-full hover:bg-active transition-100 pr5 mr-a> <NuxtLink :to="getAccountRoute(account)" flex-auto rounded-full hover:bg-active transition-100 pr5 mr-a>
<AccountInfo :account="account" /> <AccountInfo :account="account" />
</NuxtLink> </NuxtLink>
<AccountFollowButton text-sm :account="account" /> <AccountFollowButton text-sm :account="account" />

View File

@ -10,7 +10,7 @@ const { link = true } = defineProps<{
<template> <template>
<AccountHoverWrapper :account="account"> <AccountHoverWrapper :account="account">
<NuxtLink <NuxtLink
:to="link ? getAccountPath(account) : undefined" :to="link ? getAccountRoute(account) : undefined"
:class="link ? 'text-link-rounded ml-0 pl-0' : ''" :class="link ? 'text-link-rounded ml-0 pl-0' : ''"
min-w-0 flex gap-1 items-center min-w-0 flex gap-1 items-center
> >

View File

@ -8,21 +8,21 @@ defineProps<{
<template> <template>
<div flex gap-5> <div flex gap-5>
<NuxtLink :to="getAccountPath(account)" text-secondary exact-active-class="text-primary"> <NuxtLink :to="getAccountRoute(account)" text-secondary exact-active-class="text-primary">
<template #default="{ isExactActive }"> <template #default="{ isExactActive }">
<i18n-t keypath="account.posts_count"> <i18n-t keypath="account.posts_count">
<span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ formattedNumber(account.statusesCount) }}</span> <span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ formattedNumber(account.statusesCount) }}</span>
</i18n-t> </i18n-t>
</template> </template>
</NuxtLink> </NuxtLink>
<NuxtLink :to="`${getAccountPath(account)}/following`" text-secondary exact-active-class="text-primary"> <NuxtLink :to="getAccountFollowingRoute(account)" text-secondary exact-active-class="text-primary">
<template #default="{ isExactActive }"> <template #default="{ isExactActive }">
<i18n-t keypath="account.following_count"> <i18n-t keypath="account.following_count">
<span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ humanReadableNumber(account.followingCount) }}</span> <span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ humanReadableNumber(account.followingCount) }}</span>
</i18n-t> </i18n-t>
</template> </template>
</NuxtLink> </NuxtLink>
<NuxtLink :to="`${getAccountPath(account)}/followers`" text-secondary exact-active-class="text-primary"> <NuxtLink :to="getAccountFollowersRoute(account)" text-secondary exact-active-class="text-primary">
<template #default="{ isExactActive }"> <template #default="{ isExactActive }">
<i18n-t keypath="account.followers_count"> <i18n-t keypath="account.followers_count">
<span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ humanReadableNumber(account.followersCount) }}</span> <span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ humanReadableNumber(account.followersCount) }}</span>

View File

@ -18,7 +18,7 @@ const { t } = useI18n()
<NavSideItem <NavSideItem
v-if="isMediumScreen" v-if="isMediumScreen"
:text="currentUser.account.displayName" :text="currentUser.account.displayName"
:to="getAccountPath(currentUser.account)" :to="getAccountRoute(currentUser.account)"
icon="i-ri:account-circle-line" icon="i-ri:account-circle-line"
> >
<template #icon> <template #icon>

View File

@ -2,7 +2,7 @@
const props = defineProps<{ const props = defineProps<{
text?: string text?: string
icon: string icon: string
to: string to: string | Record<string, string>
}>() }>()
defineSlots<{ defineSlots<{
@ -15,7 +15,7 @@ const router = useRouter()
useCommand({ useCommand({
scope: 'Navigation', scope: 'Navigation',
name: () => props.text ?? props.to, name: () => props.text ?? typeof props.to === 'string' ? props.to as string : props.to.name,
icon: () => props.icon, icon: () => props.icon,
onActivate() { onActivate() {

View File

@ -29,7 +29,7 @@ const isExpanded = ref(false)
:key="item.id" :key="item.id"
:account="item.account" :account="item.account"
> >
<NuxtLink :to="getAccountPath(item.account)"> <NuxtLink :to="getAccountRoute(item.account)">
<AccountAvatar :account="item.account" w-8 h-8 /> <AccountAvatar :account="item.account" w-8 h-8 />
</NuxtLink> </NuxtLink>
</AccountHoverWrapper> </AccountHoverWrapper>

View File

@ -157,7 +157,7 @@ const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
</template> </template>
<div p4 flex gap-4> <div p4 flex gap-4>
<NuxtLink w-12 h-12 :to="getAccountPath(currentUser.account)"> <NuxtLink w-12 h-12 :to="getAccountRoute(currentUser.account)">
<AccountAvatar :account="currentUser.account" w-12 h-12 /> <AccountAvatar :account="currentUser.account" w-12 h-12 />
</NuxtLink> </NuxtLink>
<div <div

View File

@ -9,7 +9,7 @@ const { account, link = true } = defineProps<{
<template> <template>
<NuxtLink <NuxtLink
:to="link ? getAccountPath(account) : undefined" :to="link ? getAccountRoute(account) : undefined"
flex="~ col" min-w-0 md:flex="~ row gap-2" md:items-center flex="~ col" min-w-0 md:flex="~ row gap-2" md:items-center
text-link-rounded text-link-rounded
> >

View File

@ -77,7 +77,7 @@ const toggleTranslation = async () => {
} }
const copyLink = async (status: Status) => { const copyLink = async (status: Status) => {
const url = getStatusPermalink(status) const url = getStatusPermalinkRoute(status)
if (url) if (url)
await clipboard.copy(`${location.origin}${url}`) await clipboard.copy(`${location.origin}${url}`)
} }

View File

@ -30,13 +30,13 @@ function onclick(evt: MouseEvent | KeyboardEvent) {
} }
function go(evt: MouseEvent | KeyboardEvent) { function go(evt: MouseEvent | KeyboardEvent) {
const path = getStatusPath(status) const route = getStatusRoute(status)
if (evt.metaKey || evt.ctrlKey) { if (evt.metaKey || evt.ctrlKey) {
window.open(path) window.open(route.href)
} }
else { else {
cacheStatus(status) cacheStatus(status)
router.push(path) router.push(route)
} }
} }
@ -57,7 +57,7 @@ const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
<div flex gap-4> <div flex gap-4>
<div> <div>
<AccountHoverWrapper :account="status.account"> <AccountHoverWrapper :account="status.account">
<NuxtLink :to="getAccountPath(status.account)" rounded-full> <NuxtLink :to="getAccountRoute(status.account)" rounded-full>
<AccountAvatar w-12 h-12 :account="status.account" /> <AccountAvatar w-12 h-12 :account="status.account" />
</NuxtLink> </NuxtLink>
</AccountHoverWrapper> </AccountHoverWrapper>
@ -70,7 +70,7 @@ const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
<div flex-auto /> <div flex-auto />
<div text-sm text-secondary flex="~ row nowrap" hover:underline> <div text-sm text-secondary flex="~ row nowrap" hover:underline>
<CommonTooltip :content="createdAt"> <CommonTooltip :content="createdAt">
<a :title="status.createdAt" :href="getStatusPath(status)" @click.prevent="go($event)"> <a :title="status.createdAt" :href="getStatusRoute(status).href" @click.prevent="go($event)">
<time text-sm hover:underline :datetime="status.createdAt"> <time text-sm hover:underline :datetime="status.createdAt">
{{ timeago }} {{ timeago }}
</time> </time>

View File

@ -19,7 +19,7 @@ const visibility = $computed(() => STATUS_VISIBILITIES.find(v => v.value === sta
<template> <template>
<div :id="`status-${status.id}`" flex flex-col gap-2 py3 px-4> <div :id="`status-${status.id}`" flex flex-col gap-2 py3 px-4>
<NuxtLink :to="getAccountPath(status.account)" rounded-full hover:bg-active transition-100 pr5 mr-a> <NuxtLink :to="getAccountRoute(status.account)" rounded-full hover:bg-active transition-100 pr5 mr-a>
<AccountHoverWrapper :account="status.account"> <AccountHoverWrapper :account="status.account">
<AccountInfo :account="status.account" /> <AccountInfo :account="status.account" />
</AccountHoverWrapper> </AccountHoverWrapper>

View File

@ -12,7 +12,7 @@ const account = useAccountById(status.inReplyToAccountId!)
<NuxtLink <NuxtLink
v-if="status.inReplyToId" v-if="status.inReplyToId"
flex="~ wrap" items-center text-sm text-secondary flex="~ wrap" items-center text-sm text-secondary
:to="getStatusInReplyToPath(status)" :to="getStatusInReplyToRoute(status)"
:title="account ? `Replying to ${getDisplayName(account)}` : 'Replying to someone'" :title="account ? `Replying to ${getDisplayName(account)}` : 'Replying to someone'"
> >
<div i-ri:reply-fill rotate-180 text-secondary-light class="mr-1.5" /> <div i-ri:reply-fill rotate-180 text-secondary-light class="mr-1.5" />

View File

@ -13,7 +13,7 @@ const sorted = computed(() => {
const router = useRouter() const router = useRouter()
const switchUser = (user: UserLogin) => { const switchUser = (user: UserLogin) => {
if (user.account.id === currentUser.value?.account.id) if (user.account.id === currentUser.value?.account.id)
router.push(getAccountPath(user.account)) router.push(getAccountRoute(user.account))
else else
loginTo(user) loginTo(user)
} }

View File

@ -56,20 +56,69 @@ export function toShortHandle(fullHandle: string) {
return fullHandle return fullHandle
} }
export function getAccountPath(account: Account) { export function getAccountRoute(account: Account) {
return `/${getFullHandle(account)}` return useRouter().resolve({
name: 'account-index',
params: {
account: getFullHandle(account).slice(1),
},
state: {
account: account as any,
},
})
}
export function getAccountFollowingRoute(account: Account) {
return useRouter().resolve({
name: 'account-following',
params: {
account: getFullHandle(account).slice(1),
},
state: {
account: account as any,
},
})
}
export function getAccountFollowersRoute(account: Account) {
return useRouter().resolve({
name: 'account-followers',
params: {
account: getFullHandle(account).slice(1),
},
state: {
account: account as any,
},
})
} }
export function getStatusPath(status: Status) { export function getStatusRoute(status: Status) {
return `/${getFullHandle(status.account)}/${status.id}` return useRouter().resolve({
name: 'status',
params: {
account: getFullHandle(status.account).slice(1),
status: status.id,
},
state: {
status: status as any,
},
})
} }
export function getStatusPermalink(status: Status) { export function getStatusPermalinkRouteRoute(status: Status) {
return status.url ? `/${withoutProtocol(status.url)}` : null return status.url
? useRouter().resolve({
name: 'permalink',
params: { permalink: withoutProtocol(status.url) },
})
: null
} }
export function getStatusInReplyToPath(status: Status) { export function getStatusInReplyToRoute(status: Status) {
return `/status/${status.inReplyToId}` return useRouter().resolve({
name: 'status-by-id',
params: {
status: status.inReplyToId,
},
})
} }
export function useAccountHandle(account: Account, fullServer = true) { export function useAccountHandle(account: Account, fullServer = true) {

View File

@ -25,7 +25,7 @@
<NuxtLink <NuxtLink
p2 rounded-full text-start w-full p2 rounded-full text-start w-full
hover:bg-active cursor-pointer transition-100 hover:bg-active cursor-pointer transition-100
:to="getAccountPath(currentUser.account)" :to="getAccountRoute(currentUser.account)"
> >
<AccountInfo :account="currentUser.account" md:break-words /> <AccountInfo :account="currentUser.account" md:break-words />
</NuxtLink> </NuxtLink>

View File

@ -2,12 +2,16 @@
import type { Status } from 'masto' import type { Status } from 'masto'
import type { ComponentPublicInstance } from 'vue' import type { ComponentPublicInstance } from 'vue'
definePageMeta({
name: 'status',
})
const route = useRoute() const route = useRoute()
const id = $(computedEager(() => route.params.status as string)) const id = $(computedEager(() => route.params.status as string))
const main = ref<ComponentPublicInstance | null>(null) const main = ref<ComponentPublicInstance | null>(null)
let bottomSpace = $ref(0) let bottomSpace = $ref(0)
const { data: status, pending, refresh: refreshStatus } = useAsyncData(async () => ( const { data: status, pending, refresh: refreshStatus } = useAsyncData(`status:${id}`, async () => (
window.history.state?.status as Status | undefined) window.history.state?.status as Status | undefined)
?? await fetchStatus(id), ?? await fetchStatus(id),
) )

View File

@ -2,6 +2,8 @@
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-followers' })
const account = await fetchAccountByHandle(handle) const account = await fetchAccountByHandle(handle)
const paginator = account ? useMasto().accounts.getFollowersIterable(account.id, {}) : null const paginator = account ? useMasto().accounts.getFollowersIterable(account.id, {}) : null
</script> </script>

View File

@ -2,6 +2,8 @@
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-following' })
const account = await fetchAccountByHandle(handle) const account = await fetchAccountByHandle(handle)
const paginator = account ? useMasto().accounts.getFollowingIterable(account.id, {}) : null const paginator = account ? useMasto().accounts.getFollowingIterable(account.id, {}) : null
</script> </script>

View File

@ -1,13 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Account } from 'masto'
const params = useRoute().params const params = useRoute().params
const handle = $(computedEager(() => params.account as string)) const handle = $(computedEager(() => params.account as string))
const account = await fetchAccountByHandle(handle) 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.getStatusesIterable(account.id, { excludeReplies: true }) const paginatorPosts = useMasto().accounts.getStatusesIterable(account.value!.id, { excludeReplies: true })
const paginatorPostsWithReply = useMasto().accounts.getStatusesIterable(account.id, { excludeReplies: false }) const paginatorPostsWithReply = useMasto().accounts.getStatusesIterable(account.value!.id, { excludeReplies: false })
const paginatorMedia = useMasto().accounts.getStatusesIterable(account.id, { onlyMedia: true, excludeReplies: false }) const paginatorMedia = useMasto().accounts.getStatusesIterable(account.value!.id, { onlyMedia: true, excludeReplies: false })
const tabs = $computed(() => [ const tabs = $computed(() => [
{ {

View File

@ -2,6 +2,7 @@
import { parseURL } from 'ufo' import { parseURL } from 'ufo'
definePageMeta({ definePageMeta({
name: 'permalink',
middleware: async (to) => { middleware: async (to) => {
const HANDLED_MASTO_URL = /^(https?:\/\/)?(\w+\.)+\w+\/(@[@\w\d\.]+)(\/\d+)?$/ const HANDLED_MASTO_URL = /^(https?:\/\/)?(\w+\.)+\w+\/(@[@\w\d\.]+)(\/\d+)?$/
try { try {
@ -24,16 +25,11 @@ definePageMeta({
const { value } = await useMasto().search({ q: permalink, resolve: true, limit: 1 }).next() const { value } = await useMasto().search({ q: permalink, resolve: true, limit: 1 }).next()
const { accounts, statuses } = value const { accounts, statuses } = value
if (statuses[0]) { if (statuses[0])
return { return getStatusRoute(statuses[0])
path: getStatusPath(statuses[0]),
state: {
status: statuses[0] as any,
},
}
}
if (accounts[0]) if (accounts[0])
return getAccountPath(accounts[0]) return getAccountRoute(accounts[0])
} }
catch {} catch {}

View File

@ -1,15 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
name: 'status-by-id',
middleware: async (to) => { middleware: async (to) => {
const params = to.params const params = to.params
const id = params.status as string const id = params.status as string
const status = await fetchStatus(id) const status = await fetchStatus(id)
return { return getStatusRoute(status)
path: getStatusPath(status),
state: {
status: status as any,
},
}
}, },
}) })
</script> </script>