diff --git a/src/theme/BlogPostItem/Footer/index.tsx b/src/theme/BlogPostItem/Footer/index.tsx new file mode 100644 index 00000000..b83a11b2 --- /dev/null +++ b/src/theme/BlogPostItem/Footer/index.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import Link from "@docusaurus/Link"; +import { useBlogPost } from "@docusaurus/plugin-content-blog/client"; +import BlogPostItemFooterOriginal from "@theme-original/BlogPostItem/Footer"; +import type BlogPostItemFooterType from "@theme/BlogPostItem/Footer"; +import type { WrapperProps } from "@docusaurus/types"; +import { getAuthorProfile } from "@site/src/utils/authors"; + +import styles from "./styles.module.css"; + +type Props = WrapperProps; + +function getGitHubUrl(author: { + key?: string; + url?: string; + name?: string; +}): string | undefined { + if (author.key) { + return getAuthorProfile(author.key).githubUrl; + } + + if (author.url && /github\.com\//i.test(author.url)) { + return author.url.startsWith("http") ? author.url : `https://${author.url}`; + } + + return undefined; +} + +export default function BlogPostItemFooterWrapper(props: Props): JSX.Element { + const { metadata, isBlogPostPage } = useBlogPost(); + const primaryAuthor = metadata.authors?.[0]; + + const profile = primaryAuthor?.key + ? getAuthorProfile(primaryAuthor.key) + : undefined; + + const authorName = primaryAuthor?.name || profile?.name; + const authorAvatar = primaryAuthor?.imageURL || profile?.imageUrl; + const githubUrl = primaryAuthor + ? getGitHubUrl({ + key: primaryAuthor.key, + name: primaryAuthor.name, + url: primaryAuthor.url, + }) + : undefined; + + const roundedReadTime = Math.max(1, Math.ceil(metadata.readingTime || 0)); + const readTimeText = `${roundedReadTime} min read`; + const authorSummary = + primaryAuthor?.description || + profile?.description || + primaryAuthor?.title || + profile?.title; + const blogDate = + metadata.formattedDate || + (metadata.date + ? new Date(metadata.date).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }) + : ""); + + const showAuthorCard = Boolean(isBlogPostPage && primaryAuthor && authorName); + + return ( + <> + + {showAuthorCard && ( +
+
+ {authorAvatar ? ( + {`${authorName} + ) : ( + + )} +
+

{authorName}

+ {blogDate ?

{blogDate}

: null} + {authorSummary ? ( +

{authorSummary}

+ ) : null} +
+
+ +
+

{readTimeText}

+ {githubUrl ? ( + + GitHub Profile + + ) : null} +
+
+ )} + + ); +} diff --git a/src/theme/BlogPostItem/Footer/styles.module.css b/src/theme/BlogPostItem/Footer/styles.module.css new file mode 100644 index 00000000..975aa455 --- /dev/null +++ b/src/theme/BlogPostItem/Footer/styles.module.css @@ -0,0 +1,120 @@ +.authorCard { + position: relative; + width: 100%; + margin-top: 1.25rem; + padding: 1rem; + padding-bottom: 3.75rem; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 12px; + display: flex; + align-items: stretch; + justify-content: space-between; + gap: 1rem; + background: var(--ifm-background-surface-color); + box-sizing: border-box; +} + +.authorLeft { + display: flex; + align-items: center; + gap: 0.75rem; + min-width: 0; +} + +.authorAvatar, +.authorAvatarFallback { + width: 52px; + height: 52px; + border-radius: 50%; + flex-shrink: 0; +} + +.authorAvatar { + object-fit: cover; + border: 1px solid var(--ifm-color-emphasis-300); +} + +.authorAvatarFallback { + display: grid; + place-items: center; + font-weight: 700; + color: var(--ifm-color-primary-darkest); + background: var(--ifm-color-emphasis-200); +} + +.authorIdentity { + min-width: 0; +} + +.authorName { + margin: 0; + font-weight: 700; + line-height: 1.2; +} + +.authorDate { + margin: 0.25rem 0 0; + color: var(--ifm-color-emphasis-700); + font-size: 0.9rem; +} + +.authorSummary { + margin: 0.45rem 0 0; + color: var(--ifm-color-emphasis-800); + font-size: 0.92rem; + line-height: 1.4; + max-width: 56ch; +} + +.authorRight { + margin-left: auto; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: flex-start; + gap: 0.75rem; + text-align: right; +} + +.readTime { + margin: 0; + color: var(--ifm-color-emphasis-700); + font-weight: 600; +} + +.githubButton { + position: absolute; + right: 1rem; + bottom: 1rem; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.45rem 0.8rem; + border-radius: 8px; + border: 1px solid var(--ifm-color-primary); + font-weight: 600; + text-decoration: none; +} + +.githubButton:hover { + text-decoration: none; +} + +@media (max-width: 640px) { + .authorCard { + padding-bottom: 1rem; + flex-direction: column; + align-items: flex-start; + } + + .authorRight { + width: 100%; + margin-left: 0; + align-items: flex-start; + text-align: left; + } + + .githubButton { + position: static; + } +}