This commit is contained in:
2025-07-18 16:43:10 +02:00
parent ad40616249
commit 8c37084d94
94 changed files with 14759 additions and 0 deletions

66
src/utils/date.ts Normal file
View File

@@ -0,0 +1,66 @@
import { themeConfig } from '@/config'
import type { DateFormat } from '@/types'
const MONTHS_EN = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const VALID_SEPARATORS = ['.', '-', '/']
/**
* @param date
* @param format
* @returns
*/
export function formatDate(date: Date, format?: string): string {
const formatStr = (format || themeConfig.date.dateFormat).trim()
const configSeparator = themeConfig.date.dateSeparator || '-'
const separator = VALID_SEPARATORS.includes(configSeparator.trim()) ? configSeparator.trim() : '.'
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const monthName = MONTHS_EN[date.getMonth()]
const pad = (num: number) => String(num).padStart(2, '0')
switch (formatStr) {
case 'YYYY-MM-DD':
return `${year}${separator}${pad(month)}${separator}${pad(day)}`
case 'MM-DD-YYYY':
return `${pad(month)}${separator}${pad(day)}${separator}${year}`
case 'DD-MM-YYYY':
return `${pad(day)}${separator}${pad(month)}${separator}${year}`
case 'MONTH DAY YYYY':
return `<span class="month">${monthName}</span> ${day} ${year}`
case 'DAY MONTH YYYY':
return `${day} <span class="month">${monthName}</span> ${year}`
default:
return `${year}${separator}${pad(month)}${separator}${pad(day)}`
}
}
export const SUPPORTED_DATE_FORMATS: readonly DateFormat[] = [
'YYYY-MM-DD',
'MM-DD-YYYY',
'DD-MM-YYYY',
'MONTH DAY YYYY',
'DAY MONTH YYYY'
] as const

20
src/utils/draft.ts Normal file
View File

@@ -0,0 +1,20 @@
import { getCollection, type CollectionEntry } from 'astro:content'
/**
* Get all posts, filtering out posts whose filenames start with _
*/
export async function getFilteredPosts() {
const posts = await getCollection('posts')
return posts.filter((post: CollectionEntry<'posts'>) => !post.id.startsWith('_'))
}
/**
* Get all posts sorted by publication date, filtering out posts whose filenames start with _
*/
export async function getSortedFilteredPosts() {
const posts = await getFilteredPosts()
return posts.sort(
(a: CollectionEntry<'posts'>, b: CollectionEntry<'posts'>) =>
b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
)
}

81
src/utils/feed.ts Normal file
View File

@@ -0,0 +1,81 @@
import { getCollection, type CollectionEntry } from 'astro:content'
import { themeConfig } from '@/config'
import type { APIContext } from 'astro'
export async function generateRSS(context: APIContext) {
const posts = await getCollection('posts')
const filteredPosts = posts.filter((post: CollectionEntry<'posts'>) => !post.id.startsWith('_'))
const sortedPosts = filteredPosts.sort(
(a: CollectionEntry<'posts'>, b: CollectionEntry<'posts'>) =>
b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
)
const rss = `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${themeConfig.site.title}</title>
<link>${context.site}</link>
<description>${themeConfig.site.description}</description>
<language>zh-CN</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<atom:link href="${context.site}/rss.xml" rel="self" type="application/rss+xml" />
${sortedPosts
.map(
(post: CollectionEntry<'posts'>) => `
<item>
<title><![CDATA[${post.data.title}]]></title>
<link>${context.site}/${post.id}/</link>
<guid>${context.site}/${post.id}/</guid>
<pubDate>${post.data.pubDate.toUTCString()}</pubDate>
<content:encoded><![CDATA[${post.body}]]></content:encoded>
</item>
`
)
.join('')}
</channel>
</rss>`
return new Response(rss, {
headers: {
'Content-Type': 'application/xml; charset=utf-8'
}
})
}
export async function generateAtom(context: APIContext) {
const posts = await getCollection('posts')
const filteredPosts = posts.filter((post: CollectionEntry<'posts'>) => !post.id.startsWith('_'))
const sortedPosts = filteredPosts.sort(
(a: CollectionEntry<'posts'>, b: CollectionEntry<'posts'>) =>
b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
)
const atom = `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>${themeConfig.site.title}</title>
<subtitle>${themeConfig.site.description}</subtitle>
<link href="${context.site}/atom.xml" rel="self" type="application/atom+xml" />
<link href="${context.site}" />
<id>${context.site}</id>
<updated>${new Date().toISOString()}</updated>
${sortedPosts
.map(
(post: CollectionEntry<'posts'>) => `
<entry>
<title>${post.data.title}</title>
<link href="${context.site}/${post.id}/" />
<id>${context.site}/${post.id}/</id>
<published>${post.data.pubDate.toISOString()}</published>
<content type="html"><![CDATA[${post.body}]]></content>
</entry>
`
)
.join('')}
</feed>`
return new Response(atom, {
headers: {
'Content-Type': 'application/xml; charset=utf-8'
}
})
}

27
src/utils/image-config.ts Normal file
View File

@@ -0,0 +1,27 @@
export const imageConfig = {
// Enhanced image optimization settings
limitInputPixels: 268402689, // ~16K x 16K pixels
jpeg: {
quality: 85,
progressive: true,
optimizeScans: true,
mozjpeg: true
},
png: {
quality: 85,
progressive: true,
compressionLevel: 9,
adaptiveFiltering: true
},
webp: {
quality: 85,
lossless: false,
nearLossless: true,
smartSubsample: true
},
avif: {
quality: 85,
lossless: false,
speed: 5
}
}