upstream #1

Merged
jblu merged 1007 commits from upstream into main 2024-11-04 22:35:57 -06:00
10 changed files with 227 additions and 91 deletions
Showing only changes of commit b6dac68f61 - Show all commits

View File

@ -2,6 +2,9 @@ import { Authors, allAuthors } from 'contentlayer/generated'
import { Mdx } from '@/components/MDXComponents' import { Mdx } from '@/components/MDXComponents'
import AuthorLayout from '@/layouts/AuthorLayout' import AuthorLayout from '@/layouts/AuthorLayout'
import { coreContent } from 'pliny/utils/contentlayer' import { coreContent } from 'pliny/utils/contentlayer'
import { genPageMetadata } from 'app/seo'
export const metadata = genPageMetadata({ title: 'About' })
export default function Page() { export default function Page() {
const author = allAuthors.find((p) => p.slug === 'default') as Authors const author = allAuthors.find((p) => p.slug === 'default') as Authors

View File

@ -4,6 +4,61 @@ import { sortedBlogPost, coreContent } from 'pliny/utils/contentlayer'
import { allBlogs, allAuthors } from 'contentlayer/generated' import { allBlogs, allAuthors } from 'contentlayer/generated'
import type { Authors, Blog } from 'contentlayer/generated' import type { Authors, Blog } from 'contentlayer/generated'
import PostLayout from '@/layouts/PostLayout' import PostLayout from '@/layouts/PostLayout'
import { Metadata } from 'next'
import siteMetadata from '@/data/siteMetadata'
export async function generateMetadata({
params,
}: {
params: { slug: string[] }
}): Promise<Metadata | undefined> {
const slug = params.slug.join('/')
const post = allBlogs.find((p) => p.slug === slug)
const authorList = post?.authors || ['default']
const authorDetails = authorList.map((author) => {
const authorResults = allAuthors.find((p) => p.slug === author)
return coreContent(authorResults as Authors)
})
if (!post) {
return
}
const publishedAt = new Date(post.date).toISOString()
const modifiedAt = new Date(post.lastmod || post.date).toISOString()
const authors = authorDetails.map((author) => author.name)
let imageList = [siteMetadata.socialBanner]
if (post.images) {
imageList = typeof post.images === 'string' ? [post.images] : post.images
}
const ogImages = imageList.map((img) => {
return {
url: img.includes('http') ? img : siteMetadata.siteUrl + img,
}
})
return {
title: post.title,
description: post.summary,
openGraph: {
title: post.title,
description: post.summary,
siteName: siteMetadata.title,
locale: 'en_US',
type: 'article',
publishedTime: publishedAt,
modifiedTime: modifiedAt,
url: './',
images: ogImages,
authors: authors.length > 0 ? authors : [siteMetadata.author],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.summary,
images: imageList,
},
}
}
export const generateStaticParams = async () => { export const generateStaticParams = async () => {
const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') })) const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') }))

View File

@ -2,9 +2,12 @@ import ListLayout from '@/layouts/ListLayout'
import { sortedBlogPost } from 'pliny/utils/contentlayer' import { sortedBlogPost } from 'pliny/utils/contentlayer'
import { allBlogs } from 'contentlayer/generated' import { allBlogs } from 'contentlayer/generated'
import type { Blog } from 'contentlayer/generated' import type { Blog } from 'contentlayer/generated'
import { genPageMetadata } from 'app/seo'
const POSTS_PER_PAGE = 5 const POSTS_PER_PAGE = 5
export const metadata = genPageMetadata({ title: 'Blog' })
export default function BlogPage() { export default function BlogPage() {
const posts = sortedBlogPost(allBlogs) as Blog[] const posts = sortedBlogPost(allBlogs) as Blog[]
const pageNumber = 1 const pageNumber = 1

View File

@ -8,14 +8,61 @@ import SectionContainer from '@/components/SectionContainer'
import Footer from '@/components/Footer' import Footer from '@/components/Footer'
import siteMetadata from '@/data/siteMetadata' import siteMetadata from '@/data/siteMetadata'
import { ThemeProviders } from './theme-providers' import { ThemeProviders } from './theme-providers'
import { Metadata } from 'next'
const inter = Inter({ const inter = Inter({
subsets: ['latin'], subsets: ['latin'],
}) })
export const metadata: Metadata = {
metadataBase: new URL(siteMetadata.siteUrl),
title: {
default: siteMetadata.title,
template: `%s | ${siteMetadata.title}`,
},
description: siteMetadata.description,
openGraph: {
title: siteMetadata.title,
description: siteMetadata.description,
url: './',
siteName: siteMetadata.title,
images: [siteMetadata.socialBanner],
locale: 'en_US',
type: 'website',
},
alternates: {
canonical: './',
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
twitter: {
title: siteMetadata.title,
card: 'summary_large_image',
images: [siteMetadata.socialBanner],
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
return ( return (
<html lang={siteMetadata.language} className={`${inter.className} scroll-smooth`}> <html lang={siteMetadata.language} className={`${inter.className} scroll-smooth`}>
<link rel="apple-touch-icon" sizes="76x76" href="/static/favicons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png" />
<link rel="manifest" href="/static/favicons/site.webmanifest" />
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#000000" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000" />
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
<body className="bg-white text-black antialiased dark:bg-gray-900 dark:text-white"> <body className="bg-white text-black antialiased dark:bg-gray-900 dark:text-white">
<ThemeProviders> <ThemeProviders>
{/* <Analytics analyticsConfig={siteMetadata.analytics} /> */} {/* <Analytics analyticsConfig={siteMetadata.analytics} /> */}

View File

@ -1,5 +1,8 @@
import projectsData from '@/data/projectsData' import projectsData from '@/data/projectsData'
import Card from '@/components/Card' import Card from '@/components/Card'
import { genPageMetadata } from 'app/seo'
export const metadata = genPageMetadata({ title: 'Projects' })
export default function Projects() { export default function Projects() {
return ( return (

28
app/seo.tsx Normal file
View File

@ -0,0 +1,28 @@
import { Metadata } from 'next'
import siteMetadata from '@/data/siteMetadata'
interface PageSEOProps {
title: string
description?: string
image?: string
}
export function genPageMetadata({ title, description, image }: PageSEOProps): Metadata {
return {
title,
openGraph: {
title: `${title} | ${siteMetadata.title}`,
description: description || siteMetadata.description,
url: './',
siteName: siteMetadata.title,
images: image ? [image] : [siteMetadata.socialBanner],
locale: 'en_US',
type: 'website',
},
twitter: {
title: `${title} | ${siteMetadata.title}`,
card: 'summary_large_image',
images: image ? [image] : [siteMetadata.socialBanner],
},
}
}

View File

@ -2,9 +2,11 @@ import { getAllTags } from 'pliny/utils/contentlayer'
import Link from '@/components/Link' import Link from '@/components/Link'
// import { PageSEO } from '@/components/SEO' // import { PageSEO } from '@/components/SEO'
import Tag from '@/components/Tag' import Tag from '@/components/Tag'
import siteMetadata from '@/data/siteMetadata'
import { kebabCase } from 'pliny/utils/kebabCase' import { kebabCase } from 'pliny/utils/kebabCase'
import { allBlogs } from 'contentlayer/generated' import { allBlogs } from 'contentlayer/generated'
import { genPageMetadata } from 'app/seo'
export const metadata = genPageMetadata({ title: 'Tags', description: 'Things I blog about' })
export default async function Page() { export default async function Page() {
const tags = await getAllTags(allBlogs) const tags = await getAllTags(allBlogs)

View File

@ -2,7 +2,6 @@ import { ReactNode } from 'react'
import type { Authors } from 'contentlayer/generated' import type { Authors } from 'contentlayer/generated'
import SocialIcon from '@/components/social-icons' import SocialIcon from '@/components/social-icons'
import Image from '@/components/Image' import Image from '@/components/Image'
// import { PageSEO } from '@/components/SEO'
interface Props { interface Props {
children: ReactNode children: ReactNode
@ -14,7 +13,6 @@ export default function AuthorLayout({ children, content }: Props) {
return ( return (
<> <>
{/* <PageSEO title={`About - ${name}`} description={`About me - ${name}`} /> */}
<div className="divide-y divide-gray-200 dark:divide-gray-700"> <div className="divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-2 pb-8 pt-6 md:space-y-5"> <div className="space-y-2 pb-8 pt-6 md:space-y-5">
<h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14"> <h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">

View File

@ -9,7 +9,7 @@ import SectionContainer from '@/components/SectionContainer'
import Image from '@/components/Image' import Image from '@/components/Image'
import Tag from '@/components/Tag' import Tag from '@/components/Tag'
import siteMetadata from '@/data/siteMetadata' import siteMetadata from '@/data/siteMetadata'
// import ScrollTopAndComment from '@/components/ScrollTopAndComment' import ScrollTopAndComment from '@/components/ScrollTopAndComment'
const editUrl = (path) => `${siteMetadata.siteRepo}/blob/master/data/${path}` const editUrl = (path) => `${siteMetadata.siteRepo}/blob/master/data/${path}`
const discussUrl = (path) => const discussUrl = (path) =>
@ -37,8 +37,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
return ( return (
<SectionContainer> <SectionContainer>
{/* <BlogSEO url={`${siteMetadata.siteUrl}/${path}`} authorDetails={authorDetails} {...content} /> */} <ScrollTopAndComment />
{/* <ScrollTopAndComment /> */}
<article> <article>
<div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700"> <div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700">
<header className="pt-6 xl:pb-6"> <header className="pt-6 xl:pb-6">

View File

@ -1,89 +1,87 @@
// import { useState, ReactNode } from 'react' import { useState, ReactNode } from 'react'
// import { Comments } from 'pliny/comments' import { Comments } from 'pliny/comments'
// import { formatDate } from 'pliny/utils/formatDate' import { formatDate } from 'pliny/utils/formatDate'
// import { CoreContent } from 'pliny/utils/contentlayer' import { CoreContent } from 'pliny/utils/contentlayer'
// import type { Blog } from 'contentlayer/generated' import type { Blog } from 'contentlayer/generated'
// import Link from '@/components/Link' import Link from '@/components/Link'
// import PageTitle from '@/components/PageTitle' import PageTitle from '@/components/PageTitle'
// import SectionContainer from '@/components/SectionContainer' import SectionContainer from '@/components/SectionContainer'
// // import { BlogSEO } from '@/components/SEO' import siteMetadata from '@/data/siteMetadata'
// import siteMetadata from '@/data/siteMetadata' import ScrollTopAndComment from '@/components/ScrollTopAndComment'
// import ScrollTopAndComment from '@/components/ScrollTopAndComment'
// interface LayoutProps { interface LayoutProps {
// content: CoreContent<Blog> content: CoreContent<Blog>
// children: ReactNode children: ReactNode
// next?: { path: string; title: string } next?: { path: string; title: string }
// prev?: { path: string; title: string } prev?: { path: string; title: string }
// } }
// export default function PostLayout({ content, next, prev, children }: LayoutProps) { export default function PostLayout({ content, next, prev, children }: LayoutProps) {
// const [loadComments, setLoadComments] = useState(false) const [loadComments, setLoadComments] = useState(false)
// const { path, slug, date, title } = content const { path, slug, date, title } = content
// return ( return (
// <SectionContainer> <SectionContainer>
// {/* <BlogSEO url={`${siteMetadata.siteUrl}/${path}`} {...content} /> */} <ScrollTopAndComment />
// <ScrollTopAndComment /> <article>
// <article> <div>
// <div> <header>
// <header> <div className="space-y-1 border-b border-gray-200 pb-10 text-center dark:border-gray-700">
// <div className="space-y-1 border-b border-gray-200 pb-10 text-center dark:border-gray-700"> <dl>
// <dl> <div>
// <div> <dt className="sr-only">Published on</dt>
// <dt className="sr-only">Published on</dt> <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
// <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400"> <time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time>
// <time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time> </dd>
// </dd> </div>
// </div> </dl>
// </dl> <div>
// <div> <PageTitle>{title}</PageTitle>
// <PageTitle>{title}</PageTitle> </div>
// </div> </div>
// </div> </header>
// </header> <div className="grid-rows-[auto_1fr] divide-y divide-gray-200 pb-8 dark:divide-gray-700 xl:divide-y-0">
// <div className="grid-rows-[auto_1fr] divide-y divide-gray-200 pb-8 dark:divide-gray-700 xl:divide-y-0"> <div className="divide-y divide-gray-200 dark:divide-gray-700 xl:col-span-3 xl:row-span-2 xl:pb-0">
// <div className="divide-y divide-gray-200 dark:divide-gray-700 xl:col-span-3 xl:row-span-2 xl:pb-0"> <div className="prose max-w-none pb-8 pt-10 dark:prose-dark">{children}</div>
// <div className="prose max-w-none pb-8 pt-10 dark:prose-dark">{children}</div> </div>
// </div> {siteMetadata.comments && (
// {siteMetadata.comments && ( <div className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300" id="comment">
// <div className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300" id="comment"> {!loadComments && (
// {!loadComments && ( <button onClick={() => setLoadComments(true)}>Load Comments</button>
// <button onClick={() => setLoadComments(true)}>Load Comments</button> )}
// )} {loadComments && <Comments commentsConfig={siteMetadata.comments} slug={slug} />}
// {loadComments && <Comments commentsConfig={siteMetadata.comments} slug={slug} />} </div>
// </div> )}
// )} <footer>
// <footer> <div className="flex flex-col text-sm font-medium sm:flex-row sm:justify-between sm:text-base">
// <div className="flex flex-col text-sm font-medium sm:flex-row sm:justify-between sm:text-base"> {prev && (
// {prev && ( <div className="pt-4 xl:pt-8">
// <div className="pt-4 xl:pt-8"> <Link
// <Link href={`/${prev.path}`}
// href={`/${prev.path}`} className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
// className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" aria-label={`Previous post: ${prev.title}`}
// aria-label={`Previous post: ${prev.title}`} >
// > &larr; {prev.title}
// &larr; {prev.title} </Link>
// </Link> </div>
// </div> )}
// )} {next && (
// {next && ( <div className="pt-4 xl:pt-8">
// <div className="pt-4 xl:pt-8"> <Link
// <Link href={`/${next.path}`}
// href={`/${next.path}`} className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
// className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" aria-label={`Next post: ${next.title}`}
// aria-label={`Next post: ${next.title}`} >
// > {next.title} &rarr;
// {next.title} &rarr; </Link>
// </Link> </div>
// </div> )}
// )} </div>
// </div> </footer>
// </footer> </div>
// </div> </div>
// </div> </article>
// </article> </SectionContainer>
// </SectionContainer> )
// ) }
// }