@@ -4,6 +4,7 @@ import groq from 'groq';
44import { getTranslations } from 'next-intl/server' ;
55import { client } from '@/client' ;
66import { Link } from '@/i18n/routing' ;
7+ import { formatBlogDate } from '@/lib/blog/date' ;
78
89export const revalidate = 0 ;
910
@@ -67,6 +68,41 @@ function linkifyText(text: string) {
6768 } ) ;
6869}
6970
71+ function renderPortableTextSpans (
72+ children : Array < { _type ?: string ; text ?: string ; marks ?: string [ ] } > = [ ] ,
73+ markDefs : Array < { _key ?: string ; _type ?: string ; href ?: string } > = [ ]
74+ ) {
75+ const linkMap = new Map (
76+ markDefs
77+ . filter ( def => def ?. _type === 'link' && def ?. _key && def ?. href )
78+ . map ( def => [ def . _key as string , def . href as string ] )
79+ ) ;
80+
81+ return children . map ( ( child , index ) => {
82+ const text = child ?. text || '' ;
83+ if ( ! text ) return null ;
84+ const marks = child ?. marks || [ ] ;
85+ const linkKey = marks . find ( mark => linkMap . has ( mark ) ) ;
86+
87+ if ( linkKey ) {
88+ const href = linkMap . get ( linkKey ) ! ;
89+ return (
90+ < a
91+ key = { `mark-link-${ index } ` }
92+ href = { href }
93+ target = "_blank"
94+ rel = "noopener noreferrer"
95+ className = "text-[var(--accent-primary)] underline underline-offset-4"
96+ >
97+ { text }
98+ </ a >
99+ ) ;
100+ }
101+
102+ return < span key = { `mark-text-${ index } ` } > { linkifyText ( text ) } </ span > ;
103+ } ) ;
104+ }
105+
70106function seededShuffle < T > ( items : T [ ] , seed : number ) {
71107 const result = [ ...items ] ;
72108 let value = seed ;
@@ -219,9 +255,7 @@ export default async function PostDetails({
219255 </ Link >
220256 ) }
221257 { authorName && post . publishedAt && < span > ·</ span > }
222- { post . publishedAt && (
223- < span > { new Date ( post . publishedAt ) . toLocaleDateString ( ) } </ span >
224- ) }
258+ { post . publishedAt && < span > { formatBlogDate ( post . publishedAt ) } </ span > }
225259 </ div >
226260 ) }
227261
@@ -241,15 +275,12 @@ export default async function PostDetails({
241275 < article className = "prose prose-gray max-w-none" >
242276 { post . body ?. map ( ( block : any , index : number ) => {
243277 if ( block ?. _type === 'block' ) {
244- const text = ( block . children || [ ] )
245- . map ( ( c : any ) => c . text || '' )
246- . join ( '' ) ;
247278 return (
248279 < p
249280 key = { block . _key || `block-${ index } ` }
250281 className = "whitespace-pre-line"
251282 >
252- { linkifyText ( text ) }
283+ { renderPortableTextSpans ( block . children , block . markDefs ) }
253284 </ p >
254285 ) ;
255286 }
@@ -320,9 +351,7 @@ export default async function PostDetails({
320351 { item . author ?. name && < span > { item . author . name } </ span > }
321352 { item . author ?. name && item . publishedAt && < span > ·</ span > }
322353 { item . publishedAt && (
323- < span >
324- { new Date ( item . publishedAt ) . toLocaleDateString ( ) }
325- </ span >
354+ < span > { formatBlogDate ( item . publishedAt ) } </ span >
326355 ) }
327356 </ div >
328357 ) }
0 commit comments