feat: more to explore (#360)

This commit is contained in:
Ayaka Rizumu 2022-12-11 18:52:36 +08:00 committed by GitHub
parent a36a26d745
commit 183b1659d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 530 additions and 17 deletions

View file

@ -0,0 +1,61 @@
<script lang="ts" setup>
import type { Account } from 'masto'
const { account, as = 'div' } = $defineProps<{
account: Account
as?: string
}>()
cacheAccount(account)
defineOptions({
inheritAttrs: false,
})
</script>
<template>
<component :is="as" block focus:outline-none focus-visible:ring="2 primary" v-bind="$attrs">
<!-- Banner -->
<div px2 pt2>
<div rounded of-hidden bg="gray-500/20" aspect="3.19">
<img h-full w-full object-cover :src="account.header" :alt="$t('account.profile_description', [account.username])">
</div>
</div>
<div px-4 pb-4 space-y-2>
<!-- User info -->
<div flex sm:flex-row flex-col flex-gap-2>
<div flex items-center justify-between>
<div w-17 h-17 rounded-full border-4 border-bg-base z-2 mt--2 ml--1>
<AccountAvatar :account="account" />
</div>
<a block sm:hidden href="javascript:;" @click.stop>
<AccountFollowButton :account="account" />
</a>
</div>
<div sm:mt-2>
<div>
<ContentRich
font-bold text-lg line-clamp-1 ws-pre-wrap break-all
:content="getDisplayName(account, { rich: true })"
:emojis="account.emojis"
/>
</div>
<AccountHandle text-sm :account="account" />
</div>
</div>
<!-- Note -->
<div v-if="account.note">
<ContentRich
:content="account.note" :emojis="account.emojis"
line-clamp-2
/>
</div>
<!-- Follow info -->
<div flex justify-between items-center>
<AccountPostsFollowers text-sm :account="account" />
<a sm:block hidden href="javascript:;" @click.stop>
<AccountFollowButton :account="account" />
</a>
</div>
</div>
</component>
</template>

View file

@ -0,0 +1,30 @@
<template>
<div>
<!-- Banner -->
<div px2 pt2>
<div rounded of-hidden aspect="3.19" class="flex skeleton-loading-bg" />
<div px-4 pb-4 flex="~ col gap-2">
<!-- User info -->
<div flex sm:flex-row flex-col flex-gap-2>
<div flex items-center justify-between>
<div w-17 h-17 rounded-full border-4 border-bg-base z-2 mt--2 ml--1 of-hidden bg-base>
<div class="flex skeleton-loading-bg" w-full h-full />
</div>
<div block sm:hidden class="skeleton-loading-bg" h-8 w-30 rounded-full />
</div>
<div sm:mt-2 flex="~ col 1 gap-2">
<div flex class="skeleton-loading-bg" h-5 w-20 rounded />
<div flex class="skeleton-loading-bg" h-4 w-40 rounded />
</div>
</div>
<!-- Note -->
<div flex class="skeleton-loading-bg" h-4 my3 w="3/5" rounded />
<!-- Follow info -->
<div flex justify-between items-center>
<div flex class="skeleton-loading-bg" h-4 w="sm:1/2 full" rounded />
<div sm:flex hidden class="skeleton-loading-bg" h-8 w-30 rounded-full />
</div>
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,31 @@
<script lang="ts" setup>
const props = withDefaults(defineProps<{
modelValue?: boolean
}>(), {
modelValue: true,
})
const emits = defineEmits<{
(e: 'update:modelValue', v: boolean): void
(event: 'close'): void
}>()
const visible = useVModel(props, 'modelValue', emits, { passive: true })
function close() {
emits('close')
visible.value = false
}
</script>
<template>
<div
flex="~ gap-2" justify-between items-center
class="border-b border-base text-sm text-secondary px4 py2 sm:py4"
>
<div>
<slot />
</div>
<button text-xl hover:text-primary bg-hover-overflow w-1.4em h-1.4em @click="close()">
<div i-ri:close-line />
</button>
</div>
</template>

View file

@ -0,0 +1,44 @@
<script setup lang="ts">
import type { RouteLocationRaw } from 'vue-router'
const { options, command, replace } = $defineProps<{
options: {
to: RouteLocationRaw
display: string
name?: string
icon?: string
}[]
command?: boolean
replace?: boolean
}>()
const router = useRouter()
useCommands(() => command
? options.map(tab => ({
scope: 'Tabs',
name: tab.display,
icon: tab.icon ?? 'i-ri:file-list-2-line',
onActivate: () => router.replace(tab.to),
}))
: [])
</script>
<template>
<div flex w-full items-center lg:text-lg of-x-auto scrollbar-hide>
<NuxtLink
v-for="(option, index) in options"
:key="option?.name || index"
:to="option.to"
:replace="replace"
relative flex flex-auto cursor-pointer sm:px6 px2 rounded transition-all
tabindex="1"
hover:bg-active transition-100
exact-active-class="children:(font-bold !border-primary !op100)"
@click="$scrollToTop"
>
<span ws-nowrap mxa sm:px2 sm:py3 py2 text-center border-b-3 op50 hover:op70 border-transparent>{{ option.display }}</span>
</NuxtLink>
</div>
</template>

View file

@ -0,0 +1,23 @@
<script lang="ts" setup>
import type { History } from 'masto'
const {
history,
maxDay = 2,
} = $defineProps<{
history: History[]
maxDay?: number
}>()
const ongoingHot = $computed(() => history.slice(0, maxDay))
const people = $computed(() =>
ongoingHot.reduce((total: number, item) => total + (Number(item.accounts) || 0), 0),
)
</script>
<template>
<p>
{{ $t('command.n-people-in-the-past-n-days', [people, maxDay]) }}
</p>
</template>

View file

@ -0,0 +1,28 @@
<script lang="ts" setup>
import type { History } from 'masto'
import sparkline from '@fnando/sparkline'
const {
history,
} = $defineProps<{
history?: History[]
}>()
const historyNum = $computed(() => {
if (!history)
return [1, 1, 1, 1, 1, 1, 1]
return [...history].reverse().map(item => Number(item.accounts) || 0)
})
const sparklineEl = $ref<SVGSVGElement>()
watch([$$(historyNum), $$(sparklineEl)], ([historyNum, sparklineEl]) => {
if (!sparklineEl)
return
sparkline(sparklineEl, historyNum)
})
</script>
<template>
<svg ref="sparklineEl" class="sparkline" width="60" height="40" stroke-width="3" />
</template>

View file

@ -10,7 +10,7 @@ const props = defineProps<{
}>()
const alt = $computed(() => `${props.card.title} - ${props.card.title}`)
const isSquare = $computed(() => props.smallPictureOnly || props.card.width === props.card.height)
const description = $computed(() => props.card.description ? props.card.description : new URL(props.card.url).hostname)
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
// TODO: handle card.type: 'photo' | 'video' | 'rich';
</script>
@ -32,10 +32,9 @@ const description = $computed(() => props.card.description ? props.card.descript
v-if="card.image"
flex flex-col
display-block of-hidden
border="base"
:class="{
'min-w-32 w-32 h-32 border-r': isSquare,
'sm:(min-w-32 w-32 h-32) min-w-22 w-22 h-22 border-r': isSquare,
'w-full aspect-[1.91] border-b': !isSquare,
'rounded-lg': root,
}"
@ -49,19 +48,41 @@ const description = $computed(() => props.card.description ? props.card.descript
w-full h-full object-cover
/>
</div>
<div v-else min-w-32 w-32 h-32 bg="slate-500/10" flex justify-center items-center>
<div
v-else
min-w-22 w-22 h-22 sm="min-w-32 w-32 h-32" bg="slate-500/10" flex justify-center items-center
:class="[
root ? 'rounded-lg' : '',
]"
>
<div i-ri:profile-line w="30%" h="30%" text-secondary />
</div>
<div
p4 max-h-2xl
px3 max-h-2xl
flex flex-col
:class="[
root ? 'flex-gap-1 py1 sm:py3' : 'py3 justify-center sm:justify-start',
]"
>
<p v-if="card.providerName" text-secondary line-clamp-1 text-ellipsis>
{{ card.providerName }}
<p
text-secondary ws-pre-wrap break-all
:class="[
!card.description || root
? 'line-clamp-1'
: 'hidden sm:line-clamp-1',
]"
>
{{ providerName }}
</p>
<strong v-if="card.title" line-clamp-1 text-ellipsis>{{ card.title }}</strong>
<p v-if="description" text-secondary line-clamp-2 text-ellipsis>
{{ description }}
<strong
v-if="card.title" font-normal sm:font-medium line-clamp-1
break-all ws-pre-wrap
>{{ card.title }}</strong>
<p
v-if="card.description"
line-clamp-1 break-all sm:line-clamp-2 sm:break-words text-secondary ws-pre-wrap
>
{{ card.description }}
</p>
</div>
</NuxtLink>

View file

@ -0,0 +1,46 @@
<script setup lang="ts">
defineProps<{
/** For the preview image, only the small image mode is displayed */
square?: boolean
/** When it is root card in the list, not appear as a child card */
root?: boolean
}>()
</script>
<template>
<div
of-hidden
:class="{
'flex': square,
'p-4': root,
'rounded-lg border border-base': !root,
}"
>
<div
flex flex-col
display-block of-hidden
border="base"
:class="{
'sm:(min-w-32 w-32 h-32) min-w-22 w-22 h-22 border-r': square,
'w-full aspect-[1.91] border-b': !square,
'rounded-lg': root,
}"
>
<div w-full h-full class="skeleton-loading-bg" />
</div>
<div
px3 max-h-2xl
flex-1 flex flex-col flex-gap-2 sm:flex-gap-3
:class="[
root ? 'py2.5 sm:py3' : 'py3 justify-center sm:justify-start',
]"
>
<div flex class="skeleton-loading-bg" h-4 w-30 rounded :class="root ? '' : 'hidden sm:block'" />
<div flex class="skeleton-loading-bg" h-5 w="4/5" rounded />
<div flex="~ col gap-2">
<div flex class="skeleton-loading-bg" h-4 w-full rounded />
<div sm:flex hidden class="skeleton-loading-bg" h-4 w="2/5" rounded />
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,26 @@
<script lang="ts" setup>
import type { Tag } from 'masto'
const {
tag,
} = $defineProps<{
tag: Tag
}>()
const to = $computed(() => new URL(tag.url).pathname)
</script>
<template>
<NuxtLink :to="to" block p4 hover:bg-active flex justify-between>
<div>
<h4 text-size-base leading-normal font-medium line-clamp-1 break-all ws-pre-wrap>
<span>#</span>
<span hover:underline>{{ tag.name }}</span>
</h4>
<CommonTrending :history="tag.history" text-sm text-secondary line-clamp-1 ws-pre-wrap break-all />
</div>
<div flex items-center>
<CommonTrendingCharts :history="tag.history" />
</div>
</NuxtLink>
</template>

View file

@ -0,0 +1,11 @@
<template>
<div p4 flex justify-between>
<div flex="~ col 1 gap-2">
<div flex class="skeleton-loading-bg" h-5 w-30 rounded />
<div flex class="skeleton-loading-bg" h-4 w-45 rounded />
</div>
<div flex items-center>
<div flex class="skeleton-loading-bg" h-9 w-15 rounded />
</div>
</div>
</template>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
// @ts-expect-error missing types
import { DynamicScrollerItem } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import type { FilterContext, Paginator, Status, WsEvents } from 'masto'
const { paginator, stream } = defineProps<{