11import React from "react" ;
22import Link from "@docusaurus/Link" ;
3+ import useDocusaurusContext from "@docusaurus/useDocusaurusContext" ;
34import { useBlogPost } from "@docusaurus/plugin-content-blog/client" ;
45import BlogPostItemFooterOriginal from "@theme-original/BlogPostItem/Footer" ;
56import type BlogPostItemFooterType from "@theme/BlogPostItem/Footer" ;
67import type { WrapperProps } from "@docusaurus/types" ;
7- import { getAuthorProfile } from "@site/src /utils/authors" ;
8+ import { getAuthorProfile } from "../../.. /utils/authors" ;
89
910import styles from "./styles.module.css" ;
1011
1112type Props = WrapperProps < typeof BlogPostItemFooterType > ;
13+ const META_SEPARATOR = " • " ;
1214
1315function getGitHubUrl ( author : {
1416 key ?: string ;
@@ -26,7 +28,26 @@ function getGitHubUrl(author: {
2628 return undefined ;
2729}
2830
31+ function getGitHubHandle ( author : {
32+ key ?: string ;
33+ url ?: string ;
34+ } ) : string | undefined {
35+ if ( author . url && / g i t h u b \. c o m \/ / i. test ( author . url ) ) {
36+ const matched = author . url . match ( / g i t h u b \. c o m \/ ( [ ^ / ? # ] + ) / i) ;
37+ if ( matched ?. [ 1 ] ) {
38+ return `@${ matched [ 1 ] } ` ;
39+ }
40+ }
41+
42+ if ( author . key ) {
43+ return `@${ author . key } ` ;
44+ }
45+
46+ return undefined ;
47+ }
48+
2949export default function BlogPostItemFooterWrapper ( props : Props ) : JSX . Element {
50+ const { siteConfig } = useDocusaurusContext ( ) ;
3051 const { metadata, isBlogPostPage } = useBlogPost ( ) ;
3152 const primaryAuthor = metadata . authors ?. [ 0 ] ;
3253
@@ -46,20 +67,27 @@ export default function BlogPostItemFooterWrapper(props: Props): JSX.Element {
4667
4768 const roundedReadTime = Math . max ( 1 , Math . ceil ( metadata . readingTime || 0 ) ) ;
4869 const readTimeText = `${ roundedReadTime } min read` ;
70+ const authorHandle = primaryAuthor
71+ ? getGitHubHandle ( {
72+ key : primaryAuthor . key ,
73+ url : primaryAuthor . url ,
74+ } )
75+ : undefined ;
4976 const authorSummary =
5077 primaryAuthor ?. description ||
5178 profile ?. description ||
5279 primaryAuthor ?. title ||
5380 profile ?. title ;
5481 const blogDate =
55- metadata . formattedDate ||
56- ( metadata . date
57- ? new Date ( metadata . date ) . toLocaleDateString ( "en-US" , {
58- year : "numeric" ,
59- month : "short" ,
60- day : "numeric" ,
61- } )
62- : "" ) ;
82+ metadata . date &&
83+ new Intl . DateTimeFormat ( siteConfig . i18n ?. defaultLocale || "en-US" , {
84+ year : "numeric" ,
85+ month : "short" ,
86+ day : "numeric" ,
87+ } ) . format ( new Date ( metadata . date ) ) ;
88+ const metaItems = [ authorHandle , blogDate , readTimeText ]
89+ . filter ( Boolean )
90+ . join ( META_SEPARATOR ) ;
6391
6492 const showAuthorCard = Boolean ( isBlogPostPage && primaryAuthor && authorName ) ;
6593
@@ -68,41 +96,53 @@ export default function BlogPostItemFooterWrapper(props: Props): JSX.Element {
6896 < BlogPostItemFooterOriginal { ...props } />
6997 { showAuthorCard && (
7098 < section className = { styles . authorCard } aria-label = "Post author details" >
71- < div className = { styles . authorLeft } >
72- { authorAvatar ? (
73- < img
74- className = { styles . authorAvatar }
75- src = { authorAvatar }
76- alt = { `${ authorName } profile picture` }
77- loading = "lazy"
78- />
79- ) : (
80- < div className = { styles . authorAvatarFallback } aria-hidden = "true" >
81- { authorName ?. charAt ( 0 ) . toUpperCase ( ) }
82- </ div >
83- ) }
99+ < div className = { styles . authorBody } >
100+ < div className = { styles . authorAvatarWrapper } >
101+ { authorAvatar ? (
102+ < img
103+ className = { styles . authorAvatar }
104+ src = { authorAvatar }
105+ alt = { `${ authorName } profile picture` }
106+ loading = "lazy"
107+ />
108+ ) : (
109+ < div className = { styles . authorAvatarFallback } aria-hidden = "true" >
110+ { authorName ?. charAt ( 0 ) . toUpperCase ( ) }
111+ </ div >
112+ ) }
113+ < span className = { styles . verifiedBadge } aria-hidden = "true" >
114+ ✓
115+ </ span >
116+ </ div >
84117 < div className = { styles . authorIdentity } >
85- < p className = { styles . authorName } > { authorName } </ p >
86- { blogDate ? < p className = { styles . authorDate } > { blogDate } </ p > : null }
118+ < div className = { styles . authorNameRow } >
119+ < p className = { styles . authorName } > { authorName } </ p >
120+ < span className = { styles . authorBadge } > Author</ span >
121+ </ div >
122+ { metaItems ? < p className = { styles . authorMeta } > { metaItems } </ p > : null }
87123 { authorSummary ? (
88124 < p className = { styles . authorSummary } > { authorSummary } </ p >
89125 ) : null }
126+ { githubUrl ? (
127+ < Link
128+ className = { styles . githubButton }
129+ to = { githubUrl }
130+ target = "_blank"
131+ rel = "noopener noreferrer"
132+ >
133+ < span className = { styles . githubIcon } aria-hidden = "true" >
134+ < svg viewBox = "0 0 16 16" width = "14" height = "14" focusable = "false" >
135+ < path
136+ fill = "currentColor"
137+ d = "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.5-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.5 7.5 0 0 1 8 3.8c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z"
138+ />
139+ </ svg >
140+ </ span >
141+ GitHub
142+ </ Link >
143+ ) : null }
90144 </ div >
91145 </ div >
92-
93- < div className = { styles . authorRight } >
94- < p className = { styles . readTime } > { readTimeText } </ p >
95- { githubUrl ? (
96- < Link
97- className = { styles . githubButton }
98- to = { githubUrl }
99- target = "_blank"
100- rel = "noopener noreferrer"
101- >
102- GitHub Profile
103- </ Link >
104- ) : null }
105- </ div >
106146 </ section >
107147 ) }
108148 </ >
0 commit comments