feat: resolve status paths with router (#258)
parent
24bbe9135b
commit
4ed1816806
|
@ -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" />
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
>
|
>
|
||||||
|
|
|
@ -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}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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(() => [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue