|
| 1 | +import React from "react"; |
| 2 | +import Link from "@docusaurus/Link"; |
| 3 | +import { useBlogPost } from "@docusaurus/plugin-content-blog/client"; |
| 4 | +import BlogPostItemFooterOriginal from "@theme-original/BlogPostItem/Footer"; |
| 5 | +import type BlogPostItemFooterType from "@theme/BlogPostItem/Footer"; |
| 6 | +import type { WrapperProps } from "@docusaurus/types"; |
| 7 | +import { getAuthorProfile } from "@site/src/utils/authors"; |
| 8 | + |
| 9 | +import styles from "./styles.module.css"; |
| 10 | + |
| 11 | +type Props = WrapperProps<typeof BlogPostItemFooterType>; |
| 12 | + |
| 13 | +function getGitHubUrl(author: { |
| 14 | + key?: string; |
| 15 | + url?: string; |
| 16 | + name?: string; |
| 17 | +}): string | undefined { |
| 18 | + if (author.key) { |
| 19 | + return getAuthorProfile(author.key).githubUrl; |
| 20 | + } |
| 21 | + |
| 22 | + if (author.url && /github\.com\//i.test(author.url)) { |
| 23 | + return author.url.startsWith("http") ? author.url : `https://${author.url}`; |
| 24 | + } |
| 25 | + |
| 26 | + return undefined; |
| 27 | +} |
| 28 | + |
| 29 | +export default function BlogPostItemFooterWrapper(props: Props): JSX.Element { |
| 30 | + const { metadata, isBlogPostPage } = useBlogPost(); |
| 31 | + const primaryAuthor = metadata.authors?.[0]; |
| 32 | + |
| 33 | + const profile = primaryAuthor?.key |
| 34 | + ? getAuthorProfile(primaryAuthor.key) |
| 35 | + : undefined; |
| 36 | + |
| 37 | + const authorName = primaryAuthor?.name || profile?.name; |
| 38 | + const authorAvatar = primaryAuthor?.imageURL || profile?.imageUrl; |
| 39 | + const githubUrl = primaryAuthor |
| 40 | + ? getGitHubUrl({ |
| 41 | + key: primaryAuthor.key, |
| 42 | + name: primaryAuthor.name, |
| 43 | + url: primaryAuthor.url, |
| 44 | + }) |
| 45 | + : undefined; |
| 46 | + |
| 47 | + const roundedReadTime = Math.max(1, Math.ceil(metadata.readingTime || 0)); |
| 48 | + const readTimeText = `${roundedReadTime} min read`; |
| 49 | + const authorSummary = |
| 50 | + primaryAuthor?.description || |
| 51 | + profile?.description || |
| 52 | + primaryAuthor?.title || |
| 53 | + profile?.title; |
| 54 | + 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 | + : ""); |
| 63 | + |
| 64 | + const showAuthorCard = Boolean(isBlogPostPage && primaryAuthor && authorName); |
| 65 | + |
| 66 | + return ( |
| 67 | + <> |
| 68 | + <BlogPostItemFooterOriginal {...props} /> |
| 69 | + {showAuthorCard && ( |
| 70 | + <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 | + )} |
| 84 | + <div className={styles.authorIdentity}> |
| 85 | + <p className={styles.authorName}>{authorName}</p> |
| 86 | + {blogDate ? <p className={styles.authorDate}>{blogDate}</p> : null} |
| 87 | + {authorSummary ? ( |
| 88 | + <p className={styles.authorSummary}>{authorSummary}</p> |
| 89 | + ) : null} |
| 90 | + </div> |
| 91 | + </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> |
| 106 | + </section> |
| 107 | + )} |
| 108 | + </> |
| 109 | + ); |
| 110 | +} |
0 commit comments