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 {
data: {
title: string
@ -31,9 +32,9 @@ function getPostUrl(post: Post) {
---
<ul>
{posts.map(post => (
// Single Post
<li class="mt-4.375">
{/* post title */}
<a
class="hover:c-primary"
href={getPostUrl(post)}
@ -43,26 +44,24 @@ function getPostUrl(post: Post) {
{post.data.title}
</a>
{/* mobile post time */}
<div
class="uno-mobile-time"
transition:name={`time-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme
>
<time>
{post.data.published.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')}
</time>
<span class="ml-1.25">
{post.remarkPluginFrontmatter?.minutes} min
</span>
<PostTime
date={post.data.published}
minutes={post.remarkPluginFrontmatter?.minutes}
/>
</div>
{/* desktop post time */}
<div class="uno-desktop-time">
<time>
{post.data.published.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')}
</time>
<span class="ml-1.25">
{post.remarkPluginFrontmatter?.minutes} min
</span>
<PostTime
date={post.data.published}
minutes={post.remarkPluginFrontmatter?.minutes}
/>
</div>
</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',
// favicon url
// 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
@ -55,6 +55,8 @@ export const themeConfig: ThemeConfig = {
moreLocale: ['zh-tw', 'ja', 'en', 'es', 'ru'], // zh, zh-tw, ja, en, es, ru
// font styles for text
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
titleSpace: 3, // 1, 2, 3
},
@ -151,12 +153,12 @@ export const themeConfig: ThemeConfig = {
// PRELOAD SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START
preload: {
// link prefetch strategy
// details: https://docs.astro.build/en/guides/prefetch/#prefetch-strategies
// link prefetch
// docs: https://docs.astro.build/en/guides/prefetch/#prefetch-strategies
linkPrefetch: 'viewport', // hover, tap, viewport, load
// comment server url
commentURL: 'https://comment.radishzz.cc',
// image hosting domain
// image hosting url
imageHostURL: 'https://image.radishzz.cc',
// If you proxy analytics js to the custom domain, you can fill in below.
// See https://gist.github.com/xiaopc/0602f06ca465d76bd9efd3dda9393738

View file

@ -25,7 +25,7 @@ const { Content, remarkPluginFrontmatter } = await post.render()
<h1>{post.data.title}</h1>
<time>
{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>
<Content />
</article>

View file

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

View file

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