docs(core): Add blog author details to nx-dev blog page (#23206)
This PR adds a context view for blog authors that displays details about their social. It will be show on the blog details page. Here is an example:  
This commit is contained in:
parent
8160f5879b
commit
5edc64af92
50
docs/blog/authors.json
Normal file
50
docs/blog/authors.json
Normal file
@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"name": "Juri Strumpflohner",
|
||||
"image": "/blog/images/Juri Strumpfloner.jpeg",
|
||||
"twitter": "juristr",
|
||||
"github": "juristr"
|
||||
},
|
||||
{
|
||||
"name": "Colum Ferry",
|
||||
"image": "/blog/images/Colum Ferry.jpeg",
|
||||
"twitter": "FerryColum",
|
||||
"github": "Coly010"
|
||||
},
|
||||
{
|
||||
"name": "Emily Xiong",
|
||||
"image": "/blog/images/Emily Xiong.jpeg",
|
||||
"twitter": "xiongemily",
|
||||
"github": "xiongemi"
|
||||
},
|
||||
{
|
||||
"name": "Isaac Mann",
|
||||
"image": "/blog/images/Isaac Mann.jpeg",
|
||||
"twitter": "mannisaac",
|
||||
"github": "isaacplmann"
|
||||
},
|
||||
{
|
||||
"name": "Katerina Skroumpelou",
|
||||
"image": "/blog/images/Katerina Skroumpelou.jpeg",
|
||||
"twitter": "psybercity",
|
||||
"github": "mandarini"
|
||||
},
|
||||
{
|
||||
"name": "Max Kless",
|
||||
"image": "/blog/images/Max Kless.jpeg",
|
||||
"twitter": "MaxKless",
|
||||
"github": "MaxKless"
|
||||
},
|
||||
{
|
||||
"name": "Victor Savkin",
|
||||
"image": "/blog/images/Victor Savkin.jpeg",
|
||||
"twitter": "victorsavkin",
|
||||
"github": "vsavkin"
|
||||
},
|
||||
{
|
||||
"name": "Zack DeRose",
|
||||
"image": "/blog/images/Zack DeRose.jpeg",
|
||||
"twitter": "zackderose",
|
||||
"github": "ZackDeRose"
|
||||
}
|
||||
]
|
||||
@ -21,6 +21,9 @@ export class BlogApi {
|
||||
|
||||
getBlogPosts(): BlogPostDataEntry[] {
|
||||
const files: string[] = readdirSync(this.options.blogRoot);
|
||||
const authors = JSON.parse(
|
||||
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
|
||||
);
|
||||
const allPosts: BlogPostDataEntry[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
@ -35,7 +38,9 @@ export class BlogApi {
|
||||
content,
|
||||
title: frontmatter.title ?? null,
|
||||
description: frontmatter.description ?? null,
|
||||
authors: frontmatter.authors ?? [],
|
||||
authors: authors.filter((author) =>
|
||||
frontmatter.authors.includes(author.name)
|
||||
),
|
||||
date: this.calculateDate(file, frontmatter),
|
||||
cover_image: frontmatter.cover_image
|
||||
? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser
|
||||
|
||||
@ -2,7 +2,7 @@ export type BlogPostDataEntry = {
|
||||
title: string;
|
||||
content: string;
|
||||
description: string;
|
||||
authors: string[];
|
||||
authors: BlogAuthor[];
|
||||
date: string;
|
||||
cover_image: string | null;
|
||||
tags: string[];
|
||||
@ -12,3 +12,10 @@ export type BlogPostDataEntry = {
|
||||
filePath: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type BlogAuthor = {
|
||||
name: string;
|
||||
image: string;
|
||||
twitter: string;
|
||||
github: string;
|
||||
};
|
||||
|
||||
39
nx-dev/ui-blog/src/lib/author-detail.tsx
Normal file
39
nx-dev/ui-blog/src/lib/author-detail.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import type { BlogAuthor } from '@nx/nx-dev/data-access-documents/node-only';
|
||||
import { GithubIcon, TwitterIcon } from '@nx/nx-dev/ui-common';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface AuthorDetailProps {
|
||||
author: BlogAuthor;
|
||||
}
|
||||
|
||||
export default function AuthorDetail({ author }: AuthorDetailProps) {
|
||||
return (
|
||||
<div className="space-between invisible absolute left-[65%] right-0 z-30 mt-2 flex w-60 translate-x-[-50%] items-center gap-4 rounded bg-slate-50 p-4 text-sm text-slate-700 opacity-0 shadow-lg ring-1 ring-slate-200 transition-all delay-75 duration-300 ease-in-out md:group-hover:visible md:group-hover:opacity-100 dark:bg-slate-900 dark:text-slate-400 dark:ring-slate-800">
|
||||
<span>
|
||||
<Image
|
||||
alt={author.name}
|
||||
title={author.name}
|
||||
loading="lazy"
|
||||
width="40"
|
||||
height="40"
|
||||
decoding="async"
|
||||
src={`/documentation/blog/images/authors/${author.name}.jpeg`}
|
||||
className="rounded-full ring-1 ring-white grayscale dark:ring-slate-900"
|
||||
/>
|
||||
</span>
|
||||
<span className="text-balance">{author.name}</span>
|
||||
<a
|
||||
href={`https://twitter.com/${author.twitter}`}
|
||||
aria-label={`Follow ${author.name} on X`}
|
||||
>
|
||||
<TwitterIcon aria-hidden="true" className="h-5 w-5" />
|
||||
</a>
|
||||
<a
|
||||
href={`https://github.com/${author.github}`}
|
||||
aria-label={`View ${author.name}'s GitHub profile`}
|
||||
>
|
||||
<GithubIcon aria-hidden="true" className="h-5 w-5" />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,20 +1,28 @@
|
||||
import Image from 'next/image';
|
||||
import AuthorDetail from './author-detail';
|
||||
import type { BlogAuthor } from '@nx/nx-dev/data-access-documents/node-only';
|
||||
|
||||
export function BlogAuthors({ authors }: { authors: string[] }): JSX.Element {
|
||||
export function BlogAuthors({
|
||||
authors,
|
||||
}: {
|
||||
authors: BlogAuthor[];
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className="isolate flex items-center -space-x-2 overflow-hidden">
|
||||
<div className="relative isolate flex items-center -space-x-2">
|
||||
{authors.map((author, index) => (
|
||||
<Image
|
||||
key={index}
|
||||
alt={author}
|
||||
title={author}
|
||||
loading="lazy"
|
||||
width="48"
|
||||
height="48"
|
||||
decoding="async"
|
||||
src={`/documentation/blog/images/authors/${author}.jpeg`}
|
||||
className="relative inline-block h-6 w-6 rounded-full ring-1 ring-white grayscale dark:ring-slate-900"
|
||||
/>
|
||||
<div key={index} className="group">
|
||||
<Image
|
||||
alt={author.name}
|
||||
title={author.name}
|
||||
loading="lazy"
|
||||
width="48"
|
||||
height="48"
|
||||
decoding="async"
|
||||
src={`/documentation/blog/images/authors/${author.name}.jpeg`}
|
||||
className="relative inline-block h-6 w-6 rounded-full ring-1 ring-white grayscale dark:ring-slate-900"
|
||||
/>
|
||||
<AuthorDetail author={author} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -40,7 +40,7 @@ export function MoreBlogs({ blogs }: MoreBlogsProps) {
|
||||
<span className="hidden w-2/12 flex-none sm:inline-block">
|
||||
{formattedDate}
|
||||
</span>
|
||||
<span className="hidden w-2/12 flex-none sm:inline-block">
|
||||
<span className="hidden flex-1 overflow-hidden sm:inline-block">
|
||||
<BlogAuthors authors={post.authors} />
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
@ -20,6 +20,7 @@ export * from './lib/typography';
|
||||
export * from './lib/github-star-widget';
|
||||
export * from './lib/youtube.component';
|
||||
export * from './lib/image-theme';
|
||||
export * from './lib/twitter-icon';
|
||||
export { resourceMenuItems } from './lib/headers/menu-items';
|
||||
export { solutionsMenuItems } from './lib/headers/menu-items';
|
||||
export { eventItems } from './lib/headers/menu-items';
|
||||
|
||||
14
nx-dev/ui-common/src/lib/twitter-icon.tsx
Normal file
14
nx-dev/ui-common/src/lib/twitter-icon.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { FC, SVGProps } from 'react';
|
||||
|
||||
export const TwitterIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" />
|
||||
</svg>
|
||||
);
|
||||
Loading…
x
Reference in New Issue
Block a user