customize more

This commit is contained in:
2025-11-28 16:02:10 +08:00
parent 9133d23a15
commit 8b9feeeb10
13 changed files with 70 additions and 112 deletions

View File

@@ -73,8 +73,6 @@ const subpostCount = !isSubpost(entry.id) ? await getSubpostCount(entry.id) : 0
)
}
<span>{formattedDate}</span>
<Separator orientation="vertical" className="h-4!" />
<span>{readTime}</span>
{
subpostCount > 0 && (
<>

View File

@@ -5,7 +5,6 @@ import {
getParentId,
getParentPost,
getPostById,
getPostReadingTime,
getSubpostsForParent,
isSubpost,
} from '@/lib/data-utils'
@@ -23,17 +22,9 @@ const parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null
const activePost = parentPost || currentPost
const isActivePost = activePost?.id === currentPostId
const activePostReadingTime = activePost
? await getPostReadingTime(activePost.id)
: null
const activePostCombinedReadingTime =
activePost && subposts.length > 0
? await getCombinedReadingTime(activePost.id)
: null
const subpostsWithReadingTime = await Promise.all(
subposts.map(async (subpost) => ({
...subpost,
readingTime: await getPostReadingTime(subpost.id),
})),
)
@@ -97,7 +88,7 @@ const currentSubpostDetails = isCurrentSubpost
<span class="line-clamp-2">
{activePost.data.title}
</span>
{activePostReadingTime && (
{/* {activePostReadingTime && (
<span class="text-muted-foreground/80 text-xs">
{activePostReadingTime}
{activePostCombinedReadingTime &&
@@ -109,7 +100,7 @@ const currentSubpostDetails = isCurrentSubpost
</span>
)}
</span>
)}
)} */}
</div>
</div>
) : (
@@ -126,19 +117,6 @@ const currentSubpostDetails = isCurrentSubpost
<span class="line-clamp-2">
{activePost.data.title}
</span>
{activePostReadingTime && (
<span class="text-muted-foreground/80 hover:text-foreground/80 text-xs">
{activePostReadingTime}
{activePostCombinedReadingTime &&
activePostCombinedReadingTime !==
activePostReadingTime && (
<span>
{' '}
({activePostCombinedReadingTime} total)
</span>
)}
</span>
)}
</div>
</a>
)}
@@ -157,9 +135,6 @@ const currentSubpostDetails = isCurrentSubpost
/>
<div class="flex flex-col">
<span class="line-clamp-2">{subpost.data.title}</span>
<span class="text-muted-foreground/80 text-xs">
{subpost.readingTime}
</span>
</div>
</div>
) : (
@@ -174,9 +149,6 @@ const currentSubpostDetails = isCurrentSubpost
/>
<div class="flex flex-col">
<span class="line-clamp-2">{subpost.data.title}</span>
<span class="text-muted-foreground/80 hover:text-foreground/80 text-xs">
{subpost.readingTime}
</span>
</div>
</a>
),

View File

@@ -6,7 +6,6 @@ import {
getParentId,
getParentPost,
getPostById,
getPostReadingTime,
getSubpostsForParent,
isSubpost,
} from '@/lib/data-utils'
@@ -24,17 +23,9 @@ const parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null
const activePost = parentPost || currentPost
const isActivePost = activePost?.id === currentPostId
const activePostReadingTime = activePost
? await getPostReadingTime(activePost.id)
: null
const activePostCombinedReadingTime =
activePost && subposts.length > 0
? await getCombinedReadingTime(activePost.id)
: null
const subpostsWithReadingTime = await Promise.all(
subposts.map(async (subpost) => ({
...subpost,
readingTime: await getPostReadingTime(subpost.id),
})),
)
---
@@ -64,19 +55,6 @@ const subpostsWithReadingTime = await Promise.all(
<span class="line-clamp-2 text-pretty">
{activePost.data.title}
</span>
{activePostReadingTime && (
<span class="text-muted-foreground/80 text-xs">
{activePostReadingTime}
{activePostCombinedReadingTime &&
activePostCombinedReadingTime !==
activePostReadingTime && (
<span>
{' '}
({activePostCombinedReadingTime} total)
</span>
)}
</span>
)}
</div>
</div>
) : (
@@ -93,19 +71,6 @@ const subpostsWithReadingTime = await Promise.all(
<span class="line-clamp-2 text-pretty">
{activePost.data.title}
</span>
{activePostReadingTime && (
<span class="text-muted-foreground/80 text-xs">
{activePostReadingTime}
{activePostCombinedReadingTime &&
activePostCombinedReadingTime !==
activePostReadingTime && (
<span>
{' '}
({activePostCombinedReadingTime} total)
</span>
)}
</span>
)}
</div>
</Link>
)}
@@ -128,9 +93,6 @@ const subpostsWithReadingTime = await Promise.all(
<span class="line-clamp-2 text-pretty">
{subpost.data.title}
</span>
<span class="text-muted-foreground/80 text-xs">
{subpost.readingTime}
</span>
</div>
</div>
) : (
@@ -147,9 +109,6 @@ const subpostsWithReadingTime = await Promise.all(
<span class="line-clamp-2 text-pretty">
{subpost.data.title}
</span>
<span class="text-muted-foreground/80 text-xs">
{subpost.readingTime}
</span>
</div>
</Link>
),

View File

@@ -18,6 +18,10 @@ export const NAV_LINKS: SocialLink[] = [
href: '/blog',
label: 'blog',
},
{
href: '/tags',
label: 'tags',
},
{
href: '/about',
label: 'about',

View File

@@ -9,6 +9,17 @@ import {
} from './blog-helpers'
const DEFAULT_AUTHOR_ID = 'libr'
type ContentCollection = 'blog' | 'authors' | 'projects'
async function getCollectionSafe<T extends ContentCollection>(
name: T,
): Promise<CollectionEntry<T>[]> {
try {
return await getCollection(name)
} catch {
return []
}
}
type NotionPost = {
id: string
@@ -56,14 +67,14 @@ export interface LinkEntry {
}
export async function getAllAuthors(): Promise<CollectionEntry<'authors'>[]> {
return await getCollection('authors')
return getCollectionSafe('authors')
}
export const POSTS_API_URL = 'https://notion-api.nvme0n1p.dev/v2/posts'
export async function getAllPosts(): Promise<CollectionEntry<'blog'>[]> {
const fallback = async () => {
const posts = await getCollection('blog')
const posts = await getCollectionSafe('blog')
return posts
.filter((post) => !post.data.draft && !isSubpost(post.id))
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
@@ -91,6 +102,7 @@ export async function getAllPosts(): Promise<CollectionEntry<'blog'>[]> {
const date = new Date(dateString)
const id = post.slug || post.id
const banner = (post as any).banner || null
const image = banner
return {
id,
@@ -103,6 +115,7 @@ export async function getAllPosts(): Promise<CollectionEntry<'blog'>[]> {
draft: !(post.Published ?? true),
authors: [DEFAULT_AUTHOR_ID],
banner,
image,
},
body: '',
}
@@ -125,7 +138,7 @@ export async function getAllPostsAndSubposts(): Promise<
}
export async function getAllProjects(): Promise<CollectionEntry<'projects'>[]> {
const projects = await getCollection('projects')
const projects = await getCollectionSafe('projects')
return projects.sort((a, b) => {
const dateA = a.data.startDate?.getTime() || 0
const dateB = b.data.startDate?.getTime() || 0
@@ -155,7 +168,7 @@ export async function getAdjacentPosts(currentId: string): Promise<{
const allPosts = await getAllPosts()
const parent = allPosts.find((post) => post.id === parentId) || null
const posts = await getCollection('blog')
const posts = await getCollectionSafe('blog')
const subposts = posts
.filter(
(post) =>
@@ -242,7 +255,7 @@ export function getParentId(subpostId: string): string {
export async function getSubpostsForParent(
parentId: string,
): Promise<CollectionEntry<'blog'>[]> {
const posts = await getCollection('blog')
const posts = await getCollectionSafe('blog')
return posts
.filter(
(post) =>
@@ -305,6 +318,12 @@ export async function fetchRemotePostContent(
if (!res.ok) throw new Error(`Failed to fetch post content: ${res.status}`)
const data = (await res.json()) as Partial<RemotePostPayload>
if (!data.post || !data.blockMap) return null
const post = data.post as any
if (post?.banner && !post?.image) {
post.image = post.banner
}
return data as RemotePostPayload
} catch (error) {
console.error(`fetchRemotePostContent error for slug "${slug}":`, error)

View File

@@ -1,5 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import type { ImageMetadata } from 'astro:assets'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
@@ -35,3 +36,19 @@ export function getHeadingMargin(depth: number): string {
}
return margins[depth] || ''
}
export type ImageSource = string | ImageMetadata
export function normalizeImageSource(value: unknown): ImageSource | null {
if (!value) return null
if (typeof value === 'string') return value
if (
typeof value === 'object' &&
value !== null &&
'src' in value &&
typeof (value as ImageMetadata).src === 'string'
) {
return value as ImageMetadata
}
return null
}

View File

@@ -14,10 +14,8 @@ import Layout from '@/layouts/Layout.astro'
import {
getAdjacentPosts,
getAllPostsAndSubposts,
getCombinedReadingTime,
getParentId,
getParentPost,
getPostReadingTime,
getSubpostCount,
getTOCSections,
fetchRemotePostContent,
@@ -27,7 +25,7 @@ import {
parseAuthors,
} from '@/lib/data-utils'
import type { TOCSection } from '@/lib/data-utils'
import { formatDate, readingTime } from '@/lib/utils'
import { formatDate } from '@/lib/utils'
import { Icon } from 'astro-icon/components'
import { Image } from 'astro:assets'
import { render } from 'astro:content'
@@ -68,13 +66,6 @@ const hasChildPosts = await hasSubposts(currentPostId)
const subpostCount = !isCurrentSubpost
? await getSubpostCount(currentPostId)
: 0
const postReadingTime = remoteContent
? readingTime(remoteContent.wordCount)
: await getPostReadingTime(currentPostId)
const combinedReadingTime =
!remoteContent && hasChildPosts && !isCurrentSubpost
? await getCombinedReadingTime(currentPostId)
: null
const tocSections: TOCSection[] = remoteContent
? remoteContent.headings.length > 0
@@ -87,12 +78,8 @@ const tocSections: TOCSection[] = remoteContent
]
: []
: await getTOCSections(currentPostId)
const heroImage =
post.data.banner && typeof post.data.banner === 'object' && 'src' in post.data.banner
? post.data.banner
: post.data.image && typeof post.data.image === 'object' && 'src' in post.data.image
? post.data.image
: null
const heroImage = post.data.banner
export const prerender = false;
---
<Layout>
@@ -204,23 +191,6 @@ const heroImage =
<span>{formatDate(post.data.date)}</span>
</div>
<div
class="flex w-full items-center justify-center gap-2 py-2 sm:w-fit sm:px-2 sm:py-0 first:sm:pl-0 last:sm:pr-0"
>
<span>
{postReadingTime}
{
combinedReadingTime &&
combinedReadingTime !== postReadingTime && (
<span class="text-muted-foreground">
{' '}
({combinedReadingTime} total)
</span>
)
}
</span>
</div>
{
subpostCount > 0 && (
<div class="flex w-full items-center justify-center gap-1 py-2 sm:w-fit sm:px-2 sm:py-0 first:sm:pl-0 last:sm:pr-0">

View File

@@ -21,6 +21,7 @@ const { page } = Astro.props
const postsByYear = groupPostsByYear(page.data)
const years = Object.keys(postsByYear).sort((a, b) => parseInt(b) - parseInt(a))
export const prerender = false;
---
<Layout class="max-w-3xl">

View File

@@ -24,6 +24,7 @@ export async function getStaticPaths() {
}
const { tag, posts } = Astro.props
export const prerender = false;
---
<Layout class="max-w-3xl">

View File

@@ -8,6 +8,7 @@ import { getSortedTags } from '@/lib/data-utils'
import { Icon } from 'astro-icon/components'
const sortedTags = await getSortedTags()
export const prerender = false;
---
<Layout class="max-w-3xl">