mirror of
https://github.com/lbr77/blog-astro.git
synced 2026-04-08 16:11:56 +00:00
customize more
This commit is contained in:
@@ -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 && (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
|
||||
@@ -18,6 +18,10 @@ export const NAV_LINKS: SocialLink[] = [
|
||||
href: '/blog',
|
||||
label: 'blog',
|
||||
},
|
||||
{
|
||||
href: '/tags',
|
||||
label: 'tags',
|
||||
},
|
||||
{
|
||||
href: '/about',
|
||||
label: 'about',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -24,6 +24,7 @@ export async function getStaticPaths() {
|
||||
}
|
||||
|
||||
const { tag, posts } = Astro.props
|
||||
export const prerender = false;
|
||||
---
|
||||
|
||||
<Layout class="max-w-3xl">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user