feat: ship blog platform admin and deploy stack

This commit is contained in:
2026-03-31 21:48:39 +08:00
parent a9a05aa105
commit 313f174fbc
210 changed files with 25476 additions and 5803 deletions

View File

@@ -9,7 +9,15 @@ export type ParsedMarkdownMeta = {
image: string
images: string[]
pinned: boolean
published: boolean
status: string
visibility: string
publishAt: string
unpublishAt: string
canonicalUrl: string
noindex: boolean
ogImage: string
redirectFrom: string[]
redirectTo: string
tags: string[]
}
@@ -28,7 +36,15 @@ const defaultMeta: ParsedMarkdownMeta = {
image: '',
images: [],
pinned: false,
published: true,
status: 'published',
visibility: 'public',
publishAt: '',
unpublishAt: '',
canonicalUrl: '',
noindex: false,
ogImage: '',
redirectFrom: [],
redirectTo: '',
tags: [],
}
@@ -102,7 +118,7 @@ export function parseMarkdownDocument(markdown: string): ParsedMarkdownDocument
const frontmatter = normalized.slice(4, endIndex)
const body = normalized.slice(endIndex + 5).trimStart()
let currentListKey: 'tags' | 'images' | 'categories' | null = null
let currentListKey: 'tags' | 'images' | 'categories' | 'redirect_from' | null = null
const categories: string[] = []
frontmatter.split('\n').forEach((line) => {
@@ -118,6 +134,8 @@ export function parseMarkdownDocument(markdown: string): ParsedMarkdownDocument
meta.tags.push(nextValue)
} else if (currentListKey === 'images') {
meta.images.push(nextValue)
} else if (currentListKey === 'redirect_from') {
meta.redirectFrom.push(nextValue)
} else {
categories.push(nextValue)
}
@@ -155,6 +173,16 @@ export function parseMarkdownDocument(markdown: string): ParsedMarkdownDocument
return
}
if (key === 'redirect_from') {
const redirectFrom = toStringList(value)
if (redirectFrom.length) {
meta.redirectFrom = redirectFrom
} else if (!String(rawValue).trim()) {
currentListKey = 'redirect_from'
}
return
}
if (key === 'categories' || key === 'category') {
const parsedCategories = toStringList(value)
if (parsedCategories.length) {
@@ -184,12 +212,36 @@ export function parseMarkdownDocument(markdown: string): ParsedMarkdownDocument
case 'pinned':
meta.pinned = Boolean(value)
break
case 'status':
meta.status = String(value).trim() || 'published'
break
case 'visibility':
meta.visibility = String(value).trim() || 'public'
break
case 'publish_at':
meta.publishAt = String(value).trim()
break
case 'unpublish_at':
meta.unpublishAt = String(value).trim()
break
case 'canonical_url':
meta.canonicalUrl = String(value).trim()
break
case 'noindex':
meta.noindex = Boolean(value)
break
case 'og_image':
meta.ogImage = String(value).trim()
break
case 'redirect_to':
meta.redirectTo = String(value).trim()
break
case 'published':
meta.published = value !== false
meta.status = value === false ? 'draft' : 'published'
break
case 'draft':
if (value === true) {
meta.published = false
meta.status = 'draft'
}
break
default:
@@ -223,7 +275,17 @@ export function buildMarkdownDocument(meta: ParsedMarkdownMeta, body: string) {
lines.push(`post_type: ${JSON.stringify(meta.postType.trim() || 'article')}`)
lines.push(`pinned: ${meta.pinned ? 'true' : 'false'}`)
lines.push(`published: ${meta.published ? 'true' : 'false'}`)
lines.push(`status: ${JSON.stringify(meta.status.trim() || 'published')}`)
lines.push(`visibility: ${JSON.stringify(meta.visibility.trim() || 'public')}`)
lines.push(`noindex: ${meta.noindex ? 'true' : 'false'}`)
if (meta.publishAt.trim()) {
lines.push(`publish_at: ${JSON.stringify(meta.publishAt.trim())}`)
}
if (meta.unpublishAt.trim()) {
lines.push(`unpublish_at: ${JSON.stringify(meta.unpublishAt.trim())}`)
}
if (meta.image.trim()) {
lines.push(`image: ${JSON.stringify(meta.image.trim())}`)
@@ -243,5 +305,24 @@ export function buildMarkdownDocument(meta: ParsedMarkdownMeta, body: string) {
})
}
if (meta.canonicalUrl.trim()) {
lines.push(`canonical_url: ${JSON.stringify(meta.canonicalUrl.trim())}`)
}
if (meta.ogImage.trim()) {
lines.push(`og_image: ${JSON.stringify(meta.ogImage.trim())}`)
}
if (meta.redirectFrom.length) {
lines.push('redirect_from:')
meta.redirectFrom.forEach((item) => {
lines.push(` - ${JSON.stringify(item)}`)
})
}
if (meta.redirectTo.trim()) {
lines.push(`redirect_to: ${JSON.stringify(meta.redirectTo.trim())}`)
}
return `${lines.join('\n')}\n---\n\n${body.trim()}\n`
}