mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 03:32:51 +02:00
feat: add custom slug validation and handling for posts
- Enhance content configuration with slug validation to ensure only valid characters are used - Update post routing to support custom slugs with fallback to default slug - Implement slug duplication check to prevent conflicts across different languages - Modify various page components to use custom or default slugs in URL generation
This commit is contained in:
parent
ee35006f7c
commit
a26031d490
10 changed files with 67 additions and 11 deletions
|
@ -11,7 +11,19 @@ const postsCollection = defineCollection({
|
|||
image: z.string().optional().default(''),
|
||||
// Extended Settings
|
||||
lang: z.string().optional().default(''),
|
||||
slug: z.string().optional().default(''),
|
||||
slug: z.string()
|
||||
.optional()
|
||||
.default('')
|
||||
.refine(
|
||||
(slug) => {
|
||||
if (!slug)
|
||||
return true
|
||||
return /^[\w\-]*$/.test(slug)
|
||||
},
|
||||
{
|
||||
message: 'Slug can only contain letters, numbers, hyphens and underscores',
|
||||
},
|
||||
),
|
||||
toc: z.boolean().optional().default(false),
|
||||
pin: z.boolean().optional().default(false),
|
||||
draft: z.boolean().optional().default(false),
|
||||
|
|
|
@ -21,7 +21,7 @@ const posts = await getPosts()
|
|||
<ul>
|
||||
{pinnedPosts.map(post => (
|
||||
<li>
|
||||
<a href={`/${lang}/posts/${post.slug}/`}>
|
||||
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>
|
||||
{post.data.title}
|
||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
||||
</a>
|
||||
|
@ -35,7 +35,7 @@ const posts = await getPosts()
|
|||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
<a href={`/${lang}/posts/${post.slug}/`}>
|
||||
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>
|
||||
{post.data.title}
|
||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
||||
</a>
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
---
|
||||
import { themeConfig } from '@/config'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { checkSlugDuplication } from '@/utils/content.config'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('posts')
|
||||
|
||||
const duplicates = await checkSlugDuplication(posts)
|
||||
if (duplicates.length > 0) {
|
||||
throw new Error(`Slug conflicts found:\n${duplicates.join('\n')}`)
|
||||
}
|
||||
|
||||
return themeConfig.global.moreLocale.flatMap(lang =>
|
||||
posts.map(post => ({
|
||||
params: { lang, slug: post.slug },
|
||||
params: {
|
||||
lang,
|
||||
slug: post.data.slug || post.slug,
|
||||
},
|
||||
props: { post },
|
||||
})),
|
||||
)
|
||||
|
|
|
@ -65,7 +65,7 @@ export async function GET({ params }: APIContext) {
|
|||
pubDate: post.data.published,
|
||||
description: post.data.description || getExcerpt(post.body),
|
||||
// Generate absolute URL with language prefix
|
||||
link: new URL(`${lang}/posts/${post.slug}/`, url).toString(),
|
||||
link: new URL(`${lang}/posts/${post.data.slug || post.slug}/`, url).toString(),
|
||||
// Convert markdown content to HTML, allowing img tags
|
||||
content: post.body
|
||||
? sanitizeHtml(parser.render(post.body), {
|
||||
|
|
|
@ -32,7 +32,7 @@ const allTags = await getAllTags()
|
|||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
<a href={`/${lang}/posts/${post.slug}/`}>{post.data.title}</a>
|
||||
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>{post.data.title}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
@ -13,7 +13,7 @@ const pinnedPosts = await getPinnedPosts()
|
|||
<ul>
|
||||
{pinnedPosts.map(post => (
|
||||
<li>
|
||||
<a href={`/posts/${post.slug}/`}>
|
||||
<a href={`/posts/${post.data.slug || post.slug}/`}>
|
||||
{post.data.title}
|
||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
||||
</a>
|
||||
|
@ -27,7 +27,7 @@ const pinnedPosts = await getPinnedPosts()
|
|||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
<a href={`/posts/${post.slug}/`}>
|
||||
<a href={`/posts/${post.data.slug || post.slug}/`}>
|
||||
{post.data.title}
|
||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
||||
</a>
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { checkSlugDuplication } from '@/utils/content.config'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('posts')
|
||||
|
||||
const duplicates = await checkSlugDuplication(posts)
|
||||
if (duplicates.length > 0) {
|
||||
throw new Error(`Slug conflicts found:\n${duplicates.join('\n')}`)
|
||||
}
|
||||
|
||||
return posts.map(post => ({
|
||||
params: { slug: post.slug },
|
||||
params: {
|
||||
slug: post.data.slug || post.slug,
|
||||
},
|
||||
props: { post },
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ export async function GET() {
|
|||
pubDate: post.data.published,
|
||||
description: post.data.description || getExcerpt(post.body),
|
||||
// Generate absolute URL for post
|
||||
link: new URL(`posts/${post.slug}/`, url).toString(),
|
||||
link: new URL(`posts/${post.data.slug || post.slug}/`, url).toString(),
|
||||
// Convert markdown content to HTML, allowing img tags
|
||||
content: post.body
|
||||
? sanitizeHtml(parser.render(post.body), {
|
||||
|
|
|
@ -28,7 +28,7 @@ const allTags = await getAllTags()
|
|||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
<a href={`/posts/${post.slug}/`}>{post.data.title}</a>
|
||||
<a href={`/posts/${post.data.slug || post.slug}/`}>{post.data.title}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
@ -7,6 +7,31 @@ export type Post = CollectionEntry<'posts'>
|
|||
export type PostData = Post['data']
|
||||
export type PostsGroupByYear = Map<number, Post[]>
|
||||
|
||||
// Check if the slug is duplicated under the same language.
|
||||
export async function checkSlugDuplication(posts: Post[]): Promise<string[]> {
|
||||
const slugMap = new Map<string, Set<string>>() // Map<lang, Set<slug>>
|
||||
const duplicates: string[] = []
|
||||
|
||||
posts.forEach((post) => {
|
||||
const lang = post.data.lang || ''
|
||||
const slug = post.data.slug || post.slug
|
||||
|
||||
if (!slugMap.has(lang)) {
|
||||
slugMap.set(lang, new Set())
|
||||
}
|
||||
|
||||
const slugSet = slugMap.get(lang)!
|
||||
if (slugSet.has(slug)) {
|
||||
duplicates.push(`Duplicate slug "${slug}" found in language "${lang || 'default'}"`)
|
||||
}
|
||||
else {
|
||||
slugSet.add(slug)
|
||||
}
|
||||
})
|
||||
|
||||
return duplicates
|
||||
}
|
||||
|
||||
// Get all posts except drafts (include pinned)
|
||||
export async function getPosts(lang?: string) {
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue