feat: add date formatting component and configuration options

- Add DateFormat and PostTime components, supporting multiple date display formats
- Add dateFormat option in theme configuration, allowing customization of date display styles
- Refactor time display logic in article list and article detail pages
- Update configuration type definitions to support new date format options
This commit is contained in:
radishzzz 2025-03-11 15:33:39 +00:00
parent 4b21e6ee39
commit 8ac9b865f5
7 changed files with 184 additions and 30 deletions

View file

@ -0,0 +1,78 @@
---
import { themeConfig } from '@/config'
import { isPostPage } from '@/utils/path'
interface Props {
date: Date
updatedDate?: Date
minutes?: number
}
const { date, updatedDate, minutes } = Astro.props
const format = themeConfig.global.dateFormat
const currentPath = Astro.url.pathname
const isPost = isPostPage(currentPath)
const updatedTimeMarginClass = isPost ? 'ml-1.75' : 'ml-1.5'
const readingTimeMarginClass = isPost ? 'ml-1.75' : 'ml-1.5'
function formatDate(date: Date, format: 'YYYY-MM-DD' | 'MM-DD-YYYY' | 'DD-MM-YYYY' | 'MONTH DAY YYYY' | 'DAY MONTH YYYY') {
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: format === 'MONTH DAY YYYY' || format === 'DAY MONTH YYYY' ? 'short' : '2-digit',
day: format === 'MONTH DAY YYYY' || format === 'DAY MONTH YYYY' ? 'numeric' : '2-digit',
}
switch (format) {
// ISO format: 2024-03-04
case 'YYYY-MM-DD':
return date.toISOString().split('T')[0]
// US date format: 03-04-2024
case 'MM-DD-YYYY':
return date.toLocaleDateString('en-US', options).replace(/\//g, '-')
// European date format: 04-03-2024
case 'DD-MM-YYYY':
return date.toLocaleDateString('en-GB', options).replace(/\//g, '-')
// US month text format: Mar 4 2024
case 'MONTH DAY YYYY':
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
}).replace(',', '')
// British month text format: 4 Mar 2024
case 'DAY MONTH YYYY':
return date.toLocaleDateString('en-GB', {
year: 'numeric',
month: 'short',
day: 'numeric',
}).replace(',', '')
// Default to ISO format
default:
return date.toISOString().split('T')[0]
}
}
---
<!-- published time -->
<time datetime={date.toISOString().split('T')[0]}>
{formatDate(date, format)}
</time>
<!-- updated time -->
{updatedDate && (
<time datetime={updatedDate.toISOString().split('T')[0]} class={updatedTimeMarginClass}>
updated {formatDate(updatedDate, format)}
</time>
)}
<!-- reading time -->
{minutes !== undefined && (
<span class={readingTimeMarginClass}>
{minutes} min
</span>
)}

View file

@ -1,5 +1,6 @@
--- ---
// Define props received by the component import PostTime from '@/components/PostTime.astro'
interface Post { interface Post {
data: { data: {
title: string title: string
@ -31,9 +32,9 @@ function getPostUrl(post: Post) {
--- ---
<ul> <ul>
{posts.map(post => ( {posts.map(post => (
// Single Post
<li class="mt-4.375"> <li class="mt-4.375">
{/* post title */}
<a <a
class="hover:c-primary" class="hover:c-primary"
href={getPostUrl(post)} href={getPostUrl(post)}
@ -43,26 +44,24 @@ function getPostUrl(post: Post) {
{post.data.title} {post.data.title}
</a> </a>
{/* mobile post time */}
<div <div
class="uno-mobile-time" class="uno-mobile-time"
transition:name={`time-${post.data.abbrlink || post.slug}`} transition:name={`time-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme data-disable-transition-on-theme
> >
<time> <PostTime
{post.data.published.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')} date={post.data.published}
</time> minutes={post.remarkPluginFrontmatter?.minutes}
<span class="ml-1.25"> />
{post.remarkPluginFrontmatter?.minutes} min
</span>
</div> </div>
{/* desktop post time */}
<div class="uno-desktop-time"> <div class="uno-desktop-time">
<time> <PostTime
{post.data.published.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')} date={post.data.published}
</time> minutes={post.remarkPluginFrontmatter?.minutes}
<span class="ml-1.25"> />
{post.remarkPluginFrontmatter?.minutes} min
</span>
</div> </div>
</li> </li>

View file

@ -0,0 +1,78 @@
---
import { themeConfig } from '@/config'
import { isPostPage } from '@/utils/path'
interface Props {
date: Date
updatedDate?: Date
minutes?: number
}
const { date, updatedDate, minutes } = Astro.props
const format = themeConfig.global.dateFormat
const currentPath = Astro.url.pathname
const isPost = isPostPage(currentPath)
const updatedTimeMarginClass = isPost ? 'ml-1.75' : 'ml-1.5'
const readingTimeMarginClass = isPost ? 'ml-1.75' : 'ml-1.5'
function formatDate(date: Date, format: 'YYYY-MM-DD' | 'MM-DD-YYYY' | 'DD-MM-YYYY' | 'MONTH DAY YYYY' | 'DAY MONTH YYYY') {
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: format === 'MONTH DAY YYYY' || format === 'DAY MONTH YYYY' ? 'short' : '2-digit',
day: format === 'MONTH DAY YYYY' || format === 'DAY MONTH YYYY' ? 'numeric' : '2-digit',
}
switch (format) {
// ISO format: 2024-03-04
case 'YYYY-MM-DD':
return date.toISOString().split('T')[0]
// US date format: 03-04-2024
case 'MM-DD-YYYY':
return date.toLocaleDateString('en-US', options).replace(/\//g, '-')
// European date format: 04-03-2024
case 'DD-MM-YYYY':
return date.toLocaleDateString('en-GB', options).replace(/\//g, '-')
// US month text format: Mar 4 2024
case 'MONTH DAY YYYY':
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
}).replace(',', '')
// British month text format: 4 Mar 2024
case 'DAY MONTH YYYY':
return date.toLocaleDateString('en-GB', {
year: 'numeric',
month: 'short',
day: 'numeric',
}).replace(',', '')
// Default to ISO format
default:
return date.toISOString().split('T')[0]
}
}
---
<!-- published time -->
<time datetime={date.toISOString().split('T')[0]}>
{formatDate(date, format)}
</time>
<!-- updated time -->
{updatedDate && (
<time datetime={updatedDate.toISOString().split('T')[0]} class={updatedTimeMarginClass}>
updated {formatDate(updatedDate, format)}
</time>
)}
<!-- reading time -->
{minutes !== undefined && (
<span class={readingTimeMarginClass}>
{minutes} min
</span>
)}

View file

@ -15,7 +15,7 @@ export const themeConfig: ThemeConfig = {
url: 'https://retypeset.radishzz.cc', url: 'https://retypeset.radishzz.cc',
// favicon url // favicon url
// support only webp, svg or png // support only webp, svg or png
favicon: '/image/logo.svg', // https://example.com/logo.svg favicon: '/image/Logo.svg', // https://example.com/logo.svg
}, },
// SITE INFORMATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END // SITE INFORMATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
@ -55,6 +55,8 @@ export const themeConfig: ThemeConfig = {
moreLocale: ['zh-tw', 'ja', 'en', 'es', 'ru'], // zh, zh-tw, ja, en, es, ru moreLocale: ['zh-tw', 'ja', 'en', 'es', 'ru'], // zh, zh-tw, ja, en, es, ru
// font styles for text // font styles for text
fontStyle: 'sans', // sans, serif fontStyle: 'sans', // sans, serif
// date format for posts
dateFormat: 'MONTH DAY YYYY', // YYYY-MM-DD, MM-DD-YYYY, DD-MM-YYYY, MONTH DAY YYYY, DAY MONTH YYYY
// space between title and subtitle // space between title and subtitle
titleSpace: 3, // 1, 2, 3 titleSpace: 3, // 1, 2, 3
}, },
@ -151,12 +153,12 @@ export const themeConfig: ThemeConfig = {
// PRELOAD SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START // PRELOAD SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START
preload: { preload: {
// link prefetch strategy // link prefetch
// details: https://docs.astro.build/en/guides/prefetch/#prefetch-strategies // docs: https://docs.astro.build/en/guides/prefetch/#prefetch-strategies
linkPrefetch: 'viewport', // hover, tap, viewport, load linkPrefetch: 'viewport', // hover, tap, viewport, load
// comment server url // comment server url
commentURL: 'https://comment.radishzz.cc', commentURL: 'https://comment.radishzz.cc',
// image hosting domain // image hosting url
imageHostURL: 'https://image.radishzz.cc', imageHostURL: 'https://image.radishzz.cc',
// If you proxy analytics js to the custom domain, you can fill in below. // If you proxy analytics js to the custom domain, you can fill in below.
// See https://gist.github.com/xiaopc/0602f06ca465d76bd9efd3dda9393738 // See https://gist.github.com/xiaopc/0602f06ca465d76bd9efd3dda9393738

View file

@ -25,7 +25,7 @@ const { Content, remarkPluginFrontmatter } = await post.render()
<h1>{post.data.title}</h1> <h1>{post.data.title}</h1>
<time> <time>
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')} {post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
{remarkPluginFrontmatter.minutes && <span> · {remarkPluginFrontmatter.minutes} min</span>} {remarkPluginFrontmatter.minutes && <span> {remarkPluginFrontmatter.minutes} min</span>}
</time> </time>
<Content /> <Content />
</article> </article>

View file

@ -1,5 +1,6 @@
--- ---
import Comments from '@/components/Comments/index.astro' import Comments from '@/components/Comments/index.astro'
import PostTime from '@/components/PostTime.astro'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
import { checkSlugDuplication } from '@/utils/content' import { checkSlugDuplication } from '@/utils/content'
import { generatePostPaths } from '@/utils/i18n/route' import { generatePostPaths } from '@/utils/i18n/route'
@ -40,17 +41,12 @@ const { Content, remarkPluginFrontmatter } = await post.render()
transition:name={`time-${post.data.abbrlink || post.slug}`} transition:name={`time-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme data-disable-transition-on-theme
> >
<time> <!-- published and updated time -->
{post.data.published.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')} <PostTime
</time> date={post.data.published}
<span class="ml-1.5"> updatedDate={post.data.updated}
{remarkPluginFrontmatter.minutes} min minutes={remarkPluginFrontmatter.minutes}
</span> />
{post.data.updated && (
<span class="ml-1.5">
updated {post.data.updated.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')}
</span>
)}
</div> </div>
<Content /> <Content />
</article> </article>

View file

@ -33,6 +33,7 @@ export interface ThemeConfig {
locale: typeof langPath[number] locale: typeof langPath[number]
moreLocale: typeof langPath[number][] moreLocale: typeof langPath[number][]
fontStyle: 'sans' | 'serif' fontStyle: 'sans' | 'serif'
dateFormat: 'YYYY-MM-DD' | 'MM-DD-YYYY' | 'DD-MM-YYYY' | 'MONTH DAY YYYY' | 'DAY MONTH YYYY'
titleSpace: 1 | 2 | 3 titleSpace: 1 | 2 | 3
} }