|
|
@ -40,7 +40,7 @@ type PublicRssListItem = { title?: string | null; canonicalUrl?: string | null; |
|
|
type Payload = { |
|
|
type Payload = { |
|
|
user: { publicSlug: string | null; nickname: string | null; avatar: string | null } |
|
|
user: { publicSlug: string | null; nickname: string | null; avatar: string | null } |
|
|
bio: { markdown: string } | null |
|
|
bio: { markdown: string } | null |
|
|
links: { label: string; url: string; visibility: string }[] |
|
|
links: { label: string; url: string; visibility: string; icon?: string }[] |
|
|
posts: { items: PublicPostListItem[]; total: number } |
|
|
posts: { items: PublicPostListItem[]; total: number } |
|
|
timeline: { items: PublicTimelineItem[]; total: number } |
|
|
timeline: { items: PublicTimelineItem[]; total: number } |
|
|
rssItems: { items: PublicRssListItem[]; total: number } |
|
|
rssItems: { items: PublicRssListItem[]; total: number } |
|
|
@ -137,7 +137,7 @@ const bioHtml = computed(() => { |
|
|
return renderSafeMarkdown(md) |
|
|
return renderSafeMarkdown(md) |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
/** 「查看全文」:正文在去掉 FM 后仍有内容时才显示(避免仅有 desc 时链到空页) */ |
|
|
/** 简介整块可点进 about:去掉 FM 后仍有正文时才包链接(避免仅有 desc 时链到空页) */ |
|
|
const showBioReadMore = computed(() => { |
|
|
const showBioReadMore = computed(() => { |
|
|
const md = data.value?.bio?.markdown |
|
|
const md = data.value?.bio?.markdown |
|
|
if (!md?.trim()) { |
|
|
if (!md?.trim()) { |
|
|
@ -155,200 +155,253 @@ const showBioReadMore = computed(() => { |
|
|
<UAlert color="error" title="无法加载主页" /> |
|
|
<UAlert color="error" title="无法加载主页" /> |
|
|
</UContainer> |
|
|
</UContainer> |
|
|
|
|
|
|
|
|
<!-- 展示:居中窄栏,原版卡片 + 时间轴 --> |
|
|
<!-- 展示:居中窄栏,编辑式层级 + 轻量卡片 --> |
|
|
<UContainer |
|
|
<UContainer |
|
|
v-else-if="data && mode === 'showcase'" |
|
|
v-else-if="data && mode === 'showcase'" |
|
|
class="py-10 space-y-10 max-w-2xl" |
|
|
class="max-w-2xl py-10 sm:py-14" |
|
|
> |
|
|
> |
|
|
<div class="flex flex-col gap-2"> |
|
|
<div class="mx-auto flex w-full max-w-xl flex-col gap-12 sm:gap-14"> |
|
|
<div v-if="data.user.avatar" class="flex justify-center"> |
|
|
<section |
|
|
<img |
|
|
class="w-full rounded-2xl border border-default/80 bg-elevated/25 px-6 py-8 text-center shadow-sm ring-1 ring-black/5 dark:ring-white/10 sm:px-10 sm:py-9" |
|
|
:src="data.user.avatar" |
|
|
aria-label="个人资料" |
|
|
alt="" |
|
|
> |
|
|
class="h-20 w-20 rounded-full object-cover border border-default" |
|
|
<div class="flex flex-col items-center gap-4"> |
|
|
> |
|
|
<div v-if="data.user.avatar" class="flex justify-center"> |
|
|
</div> |
|
|
<img |
|
|
<h1 class="text-2xl font-semibold text-center"> |
|
|
:src="data.user.avatar" |
|
|
{{ data.user.nickname || data.user.publicSlug || slug }} |
|
|
alt="" |
|
|
</h1> |
|
|
class="h-24 w-24 rounded-full border-2 border-default object-cover shadow-md ring-4 ring-primary/10" |
|
|
</div> |
|
|
> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="space-y-1"> |
|
|
|
|
|
<h1 class="text-balance text-2xl font-semibold tracking-tight text-highlighted sm:text-3xl"> |
|
|
|
|
|
{{ data.user.nickname || data.user.publicSlug || slug }} |
|
|
|
|
|
</h1> |
|
|
|
|
|
<p |
|
|
|
|
|
v-if="data.user.nickname && data.user.publicSlug" |
|
|
|
|
|
class="text-sm font-medium tabular-nums text-muted" |
|
|
|
|
|
> |
|
|
|
|
|
@{{ data.user.publicSlug }} |
|
|
|
|
|
</p> |
|
|
|
|
|
</div> |
|
|
|
|
|
<ul |
|
|
|
|
|
v-if="data.links.length" |
|
|
|
|
|
class="flex flex-wrap items-center justify-center gap-2.5" |
|
|
|
|
|
aria-label="社交链接" |
|
|
|
|
|
> |
|
|
|
|
|
<li v-for="(l, i) in data.links" :key="i"> |
|
|
|
|
|
<a |
|
|
|
|
|
:href="l.url" |
|
|
|
|
|
target="_blank" |
|
|
|
|
|
rel="noopener noreferrer" |
|
|
|
|
|
:title="l.label" |
|
|
|
|
|
:aria-label="`${l.label}(新窗口打开)`" |
|
|
|
|
|
class="flex size-10 items-center justify-center rounded-full border border-default/80 bg-default/40 text-primary transition-colors hover:border-primary/35 hover:bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|
|
|
|
|
> |
|
|
|
|
|
<UIcon |
|
|
|
|
|
:name="socialLinkIconName(l)" |
|
|
|
|
|
class="size-5 shrink-0 opacity-90" |
|
|
|
|
|
/> |
|
|
|
|
|
</a> |
|
|
|
|
|
</li> |
|
|
|
|
|
</ul> |
|
|
|
|
|
</div> |
|
|
|
|
|
</section> |
|
|
|
|
|
|
|
|
<div v-if="data.bio?.markdown && bioHtml" class="space-y-3"> |
|
|
<section |
|
|
<div |
|
|
v-if="data.bio?.markdown && bioHtml" |
|
|
class="prose prose-neutral dark:prose-invert max-w-none prose-img:rounded-lg" |
|
|
class="w-full" |
|
|
v-html="bioHtml" |
|
|
> |
|
|
/> |
|
|
|
|
|
<div v-if="showBioReadMore" class="not-prose"> |
|
|
|
|
|
<NuxtLink |
|
|
<NuxtLink |
|
|
|
|
|
v-if="showBioReadMore" |
|
|
:to="`/@${slug}/about`" |
|
|
:to="`/@${slug}/about`" |
|
|
class="text-sm font-medium text-primary hover:underline" |
|
|
class="block rounded-2xl border border-default/70 bg-elevated/20 p-5 transition-colors hover:border-primary/25 hover:bg-elevated/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary sm:p-6" |
|
|
> |
|
|
> |
|
|
查看全文 |
|
|
<div |
|
|
|
|
|
class="prose prose-sm prose-neutral max-w-none dark:prose-invert prose-p:leading-relaxed prose-img:rounded-lg" |
|
|
|
|
|
v-html="bioHtml" |
|
|
|
|
|
/> |
|
|
</NuxtLink> |
|
|
</NuxtLink> |
|
|
</div> |
|
|
<div |
|
|
</div> |
|
|
v-else |
|
|
|
|
|
class="rounded-2xl border border-default/70 bg-elevated/20 p-5 sm:p-6" |
|
|
<div v-if="data.links.length" class="space-y-2"> |
|
|
|
|
|
<h2 class="text-lg font-medium"> |
|
|
|
|
|
链接 |
|
|
|
|
|
</h2> |
|
|
|
|
|
<ul class="space-y-1"> |
|
|
|
|
|
<li v-for="(l, i) in data.links" :key="i"> |
|
|
|
|
|
<a |
|
|
|
|
|
:href="l.url" |
|
|
|
|
|
target="_blank" |
|
|
|
|
|
rel="noopener noreferrer" |
|
|
|
|
|
class="text-primary hover:underline" |
|
|
|
|
|
>{{ l.label }}</a> |
|
|
|
|
|
</li> |
|
|
|
|
|
</ul> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-if="data.posts.total" class="space-y-2"> |
|
|
|
|
|
<div class="flex flex-wrap items-center justify-between gap-2"> |
|
|
|
|
|
<h2 class="text-lg font-medium"> |
|
|
|
|
|
文章 |
|
|
|
|
|
</h2> |
|
|
|
|
|
<UButton |
|
|
|
|
|
v-if="data.posts.total > 5" |
|
|
|
|
|
:to="`/@${slug}/posts`" |
|
|
|
|
|
variant="outline" |
|
|
|
|
|
size="xs" |
|
|
|
|
|
icon="i-lucide-arrow-right" |
|
|
|
|
|
trailing |
|
|
|
|
|
> |
|
|
|
|
|
查看全部(共 {{ data.posts.total }} 条) |
|
|
|
|
|
</UButton> |
|
|
|
|
|
</div> |
|
|
|
|
|
<ul class="space-y-2"> |
|
|
|
|
|
<li |
|
|
|
|
|
v-for="p in data.posts.items" |
|
|
|
|
|
:key="p.slug" |
|
|
|
|
|
class="border border-default rounded-lg overflow-hidden transition-colors hover:bg-elevated/50" |
|
|
|
|
|
> |
|
|
|
|
|
<NuxtLink |
|
|
|
|
|
:to="`/@${slug}/posts/${encodeURIComponent(p.slug)}`" |
|
|
|
|
|
class="group block p-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-lg" |
|
|
|
|
|
> |
|
|
|
|
|
<time |
|
|
|
|
|
v-if="p.publishedAt" |
|
|
|
|
|
class="block text-xs font-medium tabular-nums text-muted" |
|
|
|
|
|
:datetime="occurredOnToIsoAttr(p.publishedAt)" |
|
|
|
|
|
>{{ formatPublishedDateOnly(p.publishedAt) }}</time> |
|
|
|
|
|
<div class="mt-1 font-medium text-highlighted"> |
|
|
|
|
|
{{ p.title }} |
|
|
|
|
|
</div> |
|
|
|
|
|
<div v-if="p.excerpt" class="text-sm text-muted mt-1"> |
|
|
|
|
|
{{ p.excerpt }} |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="mt-1.5 text-sm font-medium text-primary group-hover:underline"> |
|
|
|
|
|
查看全文 |
|
|
|
|
|
</div> |
|
|
|
|
|
</NuxtLink> |
|
|
|
|
|
</li> |
|
|
|
|
|
</ul> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-if="data.timeline.total" class="space-y-3"> |
|
|
|
|
|
<div class="flex flex-wrap items-center justify-between gap-2"> |
|
|
|
|
|
<h2 class="text-lg font-medium text-highlighted"> |
|
|
|
|
|
时光机 |
|
|
|
|
|
</h2> |
|
|
|
|
|
<UButton |
|
|
|
|
|
v-if="data.timeline.total > 5" |
|
|
|
|
|
:to="`/@${slug}/timeline`" |
|
|
|
|
|
variant="outline" |
|
|
|
|
|
size="xs" |
|
|
|
|
|
icon="i-lucide-arrow-right" |
|
|
|
|
|
trailing |
|
|
|
|
|
> |
|
|
|
|
|
查看全部(共 {{ data.timeline.total }} 条) |
|
|
|
|
|
</UButton> |
|
|
|
|
|
</div> |
|
|
|
|
|
<ul class="relative space-y-0"> |
|
|
|
|
|
<li |
|
|
|
|
|
v-for="(e, i) in data.timeline.items" |
|
|
|
|
|
:key="timelineItemKey(e, i)" |
|
|
|
|
|
class="relative flex gap-4 pb-6 pl-1 last:pb-0" |
|
|
|
|
|
> |
|
|
> |
|
|
<div |
|
|
<div |
|
|
v-if="i < data.timeline.items.length - 1" |
|
|
class="prose prose-sm prose-neutral max-w-none dark:prose-invert prose-p:leading-relaxed prose-img:rounded-lg" |
|
|
class="absolute left-[11px] top-5 bottom-0 w-px bg-default" |
|
|
v-html="bioHtml" |
|
|
aria-hidden="true" |
|
|
|
|
|
/> |
|
|
/> |
|
|
<div class="relative z-[1] flex shrink-0 flex-col items-center pt-0.5"> |
|
|
</div> |
|
|
<span class="size-2.5 rounded-full bg-primary ring-4 ring-primary/15" /> |
|
|
</section> |
|
|
</div> |
|
|
|
|
|
<article |
|
|
<section v-if="data.posts.total" class="w-full space-y-4"> |
|
|
class="min-w-0 flex-1 rounded-lg border border-default bg-elevated/40 px-3 py-3 shadow-sm" |
|
|
<div class="flex flex-wrap items-end justify-between gap-3"> |
|
|
|
|
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-muted"> |
|
|
|
|
|
文章 |
|
|
|
|
|
</h2> |
|
|
|
|
|
<UButton |
|
|
|
|
|
v-if="data.posts.total > 5" |
|
|
|
|
|
:to="`/@${slug}/posts`" |
|
|
|
|
|
variant="outline" |
|
|
|
|
|
size="xs" |
|
|
|
|
|
icon="i-lucide-arrow-right" |
|
|
|
|
|
trailing |
|
|
|
|
|
> |
|
|
|
|
|
查看全部(共 {{ data.posts.total }} 条) |
|
|
|
|
|
</UButton> |
|
|
|
|
|
</div> |
|
|
|
|
|
<ul class="divide-y divide-default overflow-hidden rounded-xl border border-default"> |
|
|
|
|
|
<li |
|
|
|
|
|
v-for="p in data.posts.items" |
|
|
|
|
|
:key="p.slug" |
|
|
|
|
|
class="bg-default/30 transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-elevated/40" |
|
|
|
|
|
> |
|
|
|
|
|
<NuxtLink |
|
|
|
|
|
:to="`/@${slug}/posts/${encodeURIComponent(p.slug)}`" |
|
|
|
|
|
class="group flex cursor-pointer gap-4 p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary sm:gap-5" |
|
|
|
|
|
> |
|
|
|
|
|
<div |
|
|
|
|
|
v-if="p.coverUrl" |
|
|
|
|
|
class="relative h-[4.5rem] w-[6.5rem] shrink-0 overflow-hidden rounded-lg border border-default/80" |
|
|
|
|
|
> |
|
|
|
|
|
<img |
|
|
|
|
|
:src="p.coverUrl" |
|
|
|
|
|
alt="" |
|
|
|
|
|
class="size-full object-cover transition-transform duration-300 group-hover:scale-[1.03]" |
|
|
|
|
|
> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="min-w-0 flex-1"> |
|
|
|
|
|
<time |
|
|
|
|
|
v-if="p.publishedAt" |
|
|
|
|
|
class="block text-xs font-medium tabular-nums text-muted" |
|
|
|
|
|
:datetime="occurredOnToIsoAttr(p.publishedAt)" |
|
|
|
|
|
>{{ formatPublishedDateOnly(p.publishedAt) }}</time> |
|
|
|
|
|
<div class="mt-1 text-pretty font-semibold text-highlighted transition-colors group-hover:text-primary"> |
|
|
|
|
|
{{ p.title }} |
|
|
|
|
|
</div> |
|
|
|
|
|
<p |
|
|
|
|
|
v-if="p.excerpt" |
|
|
|
|
|
class="mt-1.5 line-clamp-2 text-sm leading-relaxed text-muted" |
|
|
|
|
|
> |
|
|
|
|
|
{{ p.excerpt }} |
|
|
|
|
|
</p> |
|
|
|
|
|
</div> |
|
|
|
|
|
</NuxtLink> |
|
|
|
|
|
</li> |
|
|
|
|
|
</ul> |
|
|
|
|
|
</section> |
|
|
|
|
|
|
|
|
|
|
|
<section v-if="data.timeline.total" class="w-full space-y-4"> |
|
|
|
|
|
<div class="flex flex-wrap items-end justify-between gap-3"> |
|
|
|
|
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-muted"> |
|
|
|
|
|
时光机 |
|
|
|
|
|
</h2> |
|
|
|
|
|
<UButton |
|
|
|
|
|
v-if="data.timeline.total > 5" |
|
|
|
|
|
:to="`/@${slug}/timeline`" |
|
|
|
|
|
variant="outline" |
|
|
|
|
|
size="xs" |
|
|
|
|
|
icon="i-lucide-arrow-right" |
|
|
|
|
|
trailing |
|
|
|
|
|
> |
|
|
|
|
|
查看全部(共 {{ data.timeline.total }} 条) |
|
|
|
|
|
</UButton> |
|
|
|
|
|
</div> |
|
|
|
|
|
<ul class="relative space-y-0"> |
|
|
|
|
|
<li |
|
|
|
|
|
v-for="(e, i) in data.timeline.items" |
|
|
|
|
|
:key="timelineItemKey(e, i)" |
|
|
|
|
|
class="relative flex gap-4 pb-8 pl-1 last:pb-0 sm:pb-9" |
|
|
> |
|
|
> |
|
|
<time |
|
|
<div |
|
|
class="block text-xs font-medium tabular-nums text-muted" |
|
|
v-if="i < data.timeline.items.length - 1" |
|
|
:datetime="e.occurredOn ? occurredOnToIsoAttr(e.occurredOn) : undefined" |
|
|
class="absolute bottom-0 left-[11px] top-5 w-px bg-default" |
|
|
>{{ formatOccurredOnDisplay(e.occurredOn ?? '') }}</time> |
|
|
aria-hidden="true" |
|
|
<div class="mt-1.5 font-medium text-highlighted"> |
|
|
/> |
|
|
{{ e.title }} |
|
|
<div class="relative z-[1] flex shrink-0 flex-col items-center pt-0.5"> |
|
|
|
|
|
<span class="size-2.5 rounded-full bg-primary shadow-sm ring-[6px] ring-primary/12" /> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<article |
|
|
|
|
|
class="min-w-0 flex-1 rounded-xl border border-default/80 bg-elevated/30 px-4 py-4 shadow-sm sm:px-5 sm:py-4" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between sm:gap-3"> |
|
|
|
|
|
<h3 class="order-2 text-pretty text-base font-semibold text-highlighted sm:order-1 sm:min-w-0 sm:flex-1"> |
|
|
|
|
|
{{ e.title }} |
|
|
|
|
|
</h3> |
|
|
|
|
|
<time |
|
|
|
|
|
class="order-1 shrink-0 text-xs font-medium tabular-nums text-muted sm:order-2 sm:text-right" |
|
|
|
|
|
:datetime="e.occurredOn ? occurredOnToIsoAttr(e.occurredOn) : undefined" |
|
|
|
|
|
>{{ formatOccurredOnDisplay(e.occurredOn ?? '') }}</time> |
|
|
|
|
|
</div> |
|
|
|
|
|
<p |
|
|
|
|
|
v-if="e.bodyMarkdown && e.bodyMarkdown.trim()" |
|
|
|
|
|
class="mt-3 line-clamp-3 border-t border-default/50 pt-3 text-sm leading-relaxed text-muted" |
|
|
|
|
|
> |
|
|
|
|
|
{{ e.bodyMarkdown }} |
|
|
|
|
|
</p> |
|
|
|
|
|
<a |
|
|
|
|
|
v-if="e.linkUrl" |
|
|
|
|
|
:href="e.linkUrl" |
|
|
|
|
|
target="_blank" |
|
|
|
|
|
rel="noopener noreferrer" |
|
|
|
|
|
class="mt-3 inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline" |
|
|
|
|
|
> |
|
|
|
|
|
<span>相关链接</span> |
|
|
|
|
|
<UIcon name="i-lucide-external-link" class="size-3.5 opacity-80" /> |
|
|
|
|
|
</a> |
|
|
|
|
|
</article> |
|
|
|
|
|
</li> |
|
|
|
|
|
</ul> |
|
|
|
|
|
</section> |
|
|
|
|
|
|
|
|
|
|
|
<section v-if="data.rssItems.total" class="w-full space-y-4"> |
|
|
|
|
|
<div class="flex flex-wrap items-end justify-between gap-3"> |
|
|
|
|
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-muted"> |
|
|
|
|
|
阅读 |
|
|
|
|
|
</h2> |
|
|
|
|
|
<UButton |
|
|
|
|
|
v-if="data.rssItems.total > 5" |
|
|
|
|
|
:to="`/@${slug}/reading`" |
|
|
|
|
|
variant="outline" |
|
|
|
|
|
size="xs" |
|
|
|
|
|
icon="i-lucide-arrow-right" |
|
|
|
|
|
trailing |
|
|
|
|
|
> |
|
|
|
|
|
查看全部(共 {{ data.rssItems.total }} 条) |
|
|
|
|
|
</UButton> |
|
|
|
|
|
</div> |
|
|
|
|
|
<ul class="divide-y divide-default overflow-hidden rounded-xl border border-default"> |
|
|
|
|
|
<li |
|
|
|
|
|
v-for="(it, i) in data.rssItems.items" |
|
|
|
|
|
:key="i" |
|
|
|
|
|
class="bg-default/30 transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-elevated/40" |
|
|
|
|
|
> |
|
|
<a |
|
|
<a |
|
|
v-if="e.linkUrl" |
|
|
v-if="rssPublicHref(it)" |
|
|
:href="e.linkUrl" |
|
|
:href="rssPublicHref(it)" |
|
|
target="_blank" |
|
|
target="_blank" |
|
|
rel="noopener noreferrer" |
|
|
rel="noopener noreferrer" |
|
|
class="mt-2 inline-block text-sm text-primary hover:underline" |
|
|
class="group block p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary" |
|
|
>相关链接</a> |
|
|
> |
|
|
</article> |
|
|
<div class="text-pretty font-semibold text-primary transition-colors group-hover:underline"> |
|
|
</li> |
|
|
{{ rssPublicTitle(it) }} |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-1 break-all text-sm text-muted"> |
|
|
|
|
|
{{ rssHostname(rssPublicHref(it)) }} |
|
|
<div v-if="data.rssItems.total" class="space-y-2"> |
|
|
</div> |
|
|
<div class="flex flex-wrap items-center justify-between gap-2"> |
|
|
</a> |
|
|
<h2 class="text-lg font-medium"> |
|
|
<div v-else class="p-4 text-muted"> |
|
|
阅读 |
|
|
|
|
|
</h2> |
|
|
|
|
|
<UButton |
|
|
|
|
|
v-if="data.rssItems.total > 5" |
|
|
|
|
|
:to="`/@${slug}/reading`" |
|
|
|
|
|
variant="outline" |
|
|
|
|
|
size="xs" |
|
|
|
|
|
icon="i-lucide-arrow-right" |
|
|
|
|
|
trailing |
|
|
|
|
|
> |
|
|
|
|
|
查看全部(共 {{ data.rssItems.total }} 条) |
|
|
|
|
|
</UButton> |
|
|
|
|
|
</div> |
|
|
|
|
|
<ul class="space-y-2"> |
|
|
|
|
|
<li |
|
|
|
|
|
v-for="(it, i) in data.rssItems.items" |
|
|
|
|
|
:key="i" |
|
|
|
|
|
class="rounded-lg border border-default transition-colors hover:bg-elevated/40" |
|
|
|
|
|
> |
|
|
|
|
|
<a |
|
|
|
|
|
v-if="rssPublicHref(it)" |
|
|
|
|
|
:href="rssPublicHref(it)" |
|
|
|
|
|
target="_blank" |
|
|
|
|
|
rel="noopener noreferrer" |
|
|
|
|
|
class="block p-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-lg" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="font-medium text-primary"> |
|
|
|
|
|
{{ rssPublicTitle(it) }} |
|
|
{{ rssPublicTitle(it) }} |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-1 text-sm text-muted break-all"> |
|
|
</li> |
|
|
{{ rssHostname(rssPublicHref(it)) }} |
|
|
</ul> |
|
|
</div> |
|
|
</section> |
|
|
</a> |
|
|
|
|
|
<div v-else class="p-3 text-muted"> |
|
|
<UEmpty |
|
|
{{ rssPublicTitle(it) }} |
|
|
v-if="!data.posts.total && !data.timeline.total && !data.rssItems.total && !data.bio && !data.links.length" |
|
|
</div> |
|
|
title="这里还没有公开内容" |
|
|
</li> |
|
|
description="站主尚未发布任何公开文章或动态。" |
|
|
</ul> |
|
|
/> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<UEmpty |
|
|
|
|
|
v-if="!data.posts.total && !data.timeline.total && !data.rssItems.total && !data.bio && !data.links.length" |
|
|
|
|
|
title="这里还没有公开内容" |
|
|
|
|
|
description="站主尚未发布任何公开文章或动态。" |
|
|
|
|
|
/> |
|
|
|
|
|
</UContainer> |
|
|
</UContainer> |
|
|
|
|
|
|
|
|
<!-- 阅读:侧栏 + 与展示一致的预览块 --> |
|
|
<!-- 阅读:侧栏 + 与展示一致的预览块 --> |
|
|
@ -371,30 +424,41 @@ const showBioReadMore = computed(() => { |
|
|
{{ data.user.nickname || data.user.publicSlug || slug }} |
|
|
{{ data.user.nickname || data.user.publicSlug || slug }} |
|
|
</h1> |
|
|
</h1> |
|
|
<div v-if="data.bio?.markdown && bioHtml" class="space-y-2"> |
|
|
<div v-if="data.bio?.markdown && bioHtml" class="space-y-2"> |
|
|
<div |
|
|
|
|
|
class="bio-preview-scroll max-h-36 overflow-y-auto rounded-lg border border-default bg-elevated/30 p-3 text-xs leading-relaxed text-muted prose prose-neutral dark:prose-invert max-w-none prose-p:my-1 prose-img:rounded" |
|
|
|
|
|
v-html="bioHtml" |
|
|
|
|
|
/> |
|
|
|
|
|
<NuxtLink |
|
|
<NuxtLink |
|
|
v-if="showBioReadMore" |
|
|
v-if="showBioReadMore" |
|
|
:to="`/@${slug}/about`" |
|
|
:to="`/@${slug}/about`" |
|
|
class="text-xs font-medium text-primary hover:underline" |
|
|
class="block cursor-pointer rounded-lg transition-colors hover:bg-elevated/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|
|
> |
|
|
> |
|
|
查看全文 |
|
|
<div |
|
|
|
|
|
class="bio-preview-scroll max-h-36 overflow-y-auto rounded-lg border border-default bg-elevated/30 p-3 text-xs leading-relaxed text-muted prose prose-neutral dark:prose-invert max-w-none prose-p:my-1 prose-img:rounded" |
|
|
|
|
|
v-html="bioHtml" |
|
|
|
|
|
/> |
|
|
</NuxtLink> |
|
|
</NuxtLink> |
|
|
|
|
|
<div |
|
|
|
|
|
v-else |
|
|
|
|
|
class="bio-preview-scroll max-h-36 overflow-y-auto rounded-lg border border-default bg-elevated/30 p-3 text-xs leading-relaxed text-muted prose prose-neutral dark:prose-invert max-w-none prose-p:my-1 prose-img:rounded" |
|
|
|
|
|
v-html="bioHtml" |
|
|
|
|
|
/> |
|
|
</div> |
|
|
</div> |
|
|
<div v-if="data.links.length" class="space-y-1.5"> |
|
|
<div v-if="data.links.length" class="space-y-1.5"> |
|
|
<div class="text-xs font-medium uppercase tracking-wide text-muted"> |
|
|
<div class="text-xs font-medium uppercase tracking-wide text-muted"> |
|
|
链接 |
|
|
链接 |
|
|
</div> |
|
|
</div> |
|
|
<ul class="space-y-1 text-sm"> |
|
|
<ul class="flex flex-col gap-1"> |
|
|
<li v-for="(l, i) in data.links" :key="i"> |
|
|
<li v-for="(l, i) in data.links" :key="i"> |
|
|
<a |
|
|
<a |
|
|
:href="l.url" |
|
|
:href="l.url" |
|
|
target="_blank" |
|
|
target="_blank" |
|
|
rel="noopener noreferrer" |
|
|
rel="noopener noreferrer" |
|
|
class="text-primary hover:underline break-all" |
|
|
class="inline-flex max-w-full items-center gap-1.5 rounded py-0.5 text-xs font-medium text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|
|
>{{ l.label }}</a> |
|
|
> |
|
|
|
|
|
<UIcon |
|
|
|
|
|
:name="socialLinkIconName(l)" |
|
|
|
|
|
class="size-3.5 shrink-0 text-primary opacity-90" |
|
|
|
|
|
aria-hidden="true" |
|
|
|
|
|
/> |
|
|
|
|
|
<span class="min-w-0 break-words text-highlighted">{{ l.label }}</span> |
|
|
|
|
|
</a> |
|
|
</li> |
|
|
</li> |
|
|
</ul> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
@ -495,7 +559,7 @@ const showBioReadMore = computed(() => { |
|
|
> |
|
|
> |
|
|
<NuxtLink |
|
|
<NuxtLink |
|
|
:to="`/@${slug}/posts/${encodeURIComponent(p.slug)}`" |
|
|
:to="`/@${slug}/posts/${encodeURIComponent(p.slug)}`" |
|
|
class="group flex flex-col gap-4 py-6 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-default rounded-lg sm:flex-row sm:gap-5" |
|
|
class="group flex cursor-pointer flex-col gap-4 py-6 transition-colors hover:bg-elevated/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-default rounded-lg sm:flex-row sm:gap-5" |
|
|
> |
|
|
> |
|
|
<div |
|
|
<div |
|
|
v-if="p.coverUrl" |
|
|
v-if="p.coverUrl" |
|
|
@ -522,9 +586,6 @@ const showBioReadMore = computed(() => { |
|
|
> |
|
|
> |
|
|
{{ p.excerpt }} |
|
|
{{ p.excerpt }} |
|
|
</p> |
|
|
</p> |
|
|
<p class="mt-2 text-sm font-medium text-primary group-hover:underline"> |
|
|
|
|
|
查看全文 |
|
|
|
|
|
</p> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</NuxtLink> |
|
|
</NuxtLink> |
|
|
</li> |
|
|
</li> |
|
|
|