upstream #1

Merged
jblu merged 1007 commits from upstream into main 2024-11-04 22:35:57 -06:00
15 changed files with 223 additions and 152 deletions
Showing only changes of commit 1dfa80e8bd - Show all commits

View File

@ -1,5 +1,3 @@
'use client'
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'

View File

@ -1,16 +0,0 @@
'use client'
import { MDXLayoutRenderer } from 'pliny/mdx-components'
import { MDXComponents } from '@/components/MDXComponents'
const DEFAULT_LAYOUT = 'AuthorLayout'
export default function About({ author }) {
return (
<MDXLayoutRenderer
layout={author.layout || DEFAULT_LAYOUT}
content={author}
MDXComponents={MDXComponents}
/>
)
}

View File

@ -1,7 +1,17 @@
import { allAuthors } from 'contentlayer/generated' import { Authors, allAuthors } from 'contentlayer/generated'
import About from './About' import { Mdx } from '@/components/MDXComponents'
import AuthorLayout from '@/layouts/AuthorLayout'
import { coreContent } from 'pliny/utils/contentlayer'
export default function Page() { export default function Page() {
const author = allAuthors.find((p) => p.slug === 'default') const author = allAuthors.find((p) => p.slug === 'default') as Authors
return <About author={author} /> const mainContent = coreContent(author)
return (
<>
<AuthorLayout content={mainContent}>
<Mdx code={author.body.code} />
</AuthorLayout>
</>
)
} }

View File

@ -1,21 +0,0 @@
'use client'
import { MDXLayoutRenderer } from 'pliny/mdx-components'
import { MDXComponents } from '@/components/MDXComponents'
import type { Blog } from 'contentlayer/generated'
const DEFAULT_LAYOUT = 'PostLayout'
export default function Blog({ post, authorDetails, prev, next }) {
return (
<MDXLayoutRenderer
layout={post.layout || DEFAULT_LAYOUT}
content={post}
MDXComponents={MDXComponents}
toc={post.toc}
authorDetails={authorDetails}
prev={prev}
next={next}
/>
)
}

View File

@ -1,8 +1,9 @@
import PageTitle from '@/components/PageTitle' import PageTitle from '@/components/PageTitle'
import { Mdx } from '@/components/MDXComponents'
import { sortedBlogPost, coreContent } from 'pliny/utils/contentlayer' 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 BlogPage from './Blog' import PostLayout from '@/layouts/PostLayout'
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('/') }))
@ -10,20 +11,19 @@ export const generateStaticParams = async () => {
return paths return paths
} }
export default function Page({ params }: { params: { slug: string[] } }) { export default async function Page({ params }: { params: { slug: string[] } }) {
const slug = params.slug.join('/') const slug = params.slug.join('/')
const sortedPosts = sortedBlogPost(allBlogs) as Blog[] const sortedPosts = sortedBlogPost(allBlogs) as Blog[]
const postIndex = sortedPosts.findIndex((p) => p.slug === slug) const postIndex = sortedPosts.findIndex((p) => p.slug === slug)
const prevContent = sortedPosts[postIndex + 1] || null const prev = coreContent(sortedPosts[postIndex + 1])
const prev = prevContent ? coreContent(prevContent) : null const next = coreContent(sortedPosts[postIndex - 1])
const nextContent = sortedPosts[postIndex - 1] || null const post = sortedPosts.find((p) => p.slug === slug) as Blog
const next = nextContent ? coreContent(nextContent) : null
const post = sortedPosts.find((p) => p.slug === slug)
const authorList = post?.authors || ['default'] const authorList = post?.authors || ['default']
const authorDetails = authorList.map((author) => { const authorDetails = authorList.map((author) => {
const authorResults = allAuthors.find((p) => p.slug === author) const authorResults = allAuthors.find((p) => p.slug === author)
return coreContent(authorResults as Authors) return coreContent(authorResults as Authors)
}) })
const mainContent = coreContent(post)
return ( return (
<> <>
@ -37,7 +37,9 @@ export default function Page({ params }: { params: { slug: string[] } }) {
</PageTitle> </PageTitle>
</div> </div>
) : ( ) : (
<BlogPage post={post} authorDetails={authorDetails} prev={prev} next={next} /> <PostLayout content={mainContent} authorDetails={authorDetails} next={next} prev={prev}>
<Mdx code={post.body.code} toc={post.toc} />
</PostLayout>
)} )}
</> </>
) )

View File

@ -1,4 +1,3 @@
import siteMetadata from '@/data/siteMetadata'
import projectsData from '@/data/projectsData' import projectsData from '@/data/projectsData'
import Card from '@/components/Card' import Card from '@/components/Card'

View File

@ -0,0 +1,7 @@
'use client'
// dot notation breaks RSC - https://github.com/vercel/next.js/issues/51593
// temporarily workaround to re-export
import { BlogNewsletterForm } from 'pliny/ui/NewsletterForm'
export default BlogNewsletterForm

View File

@ -0,0 +1,13 @@
import { useState } from 'react'
export default function ClientComponent() {
const [count, setCount] = useState(0)
return (
<>
<h1>Client Component</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Inc</button>
</>
)
}

View File

@ -2,7 +2,7 @@
import Link from 'next/link' import Link from 'next/link'
import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react' import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react'
const CustomLink = ({ const CustomLink = async ({
href, href,
...rest ...rest
}: DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) => { }: DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) => {

View File

@ -1,16 +1,17 @@
/* eslint-disable react/display-name */
import React from 'react' import React from 'react'
import { MDXLayout, ComponentMap } from 'pliny/mdx-components' import { useMDXComponent } from 'next-contentlayer/hooks'
import { ComponentMap } from 'pliny/mdx-components'
import { TOCInline } from 'pliny/ui/TOCInline' import { TOCInline } from 'pliny/ui/TOCInline'
import { Pre } from 'pliny/ui/Pre' import Pre from './Pre'
import { BlogNewsletterForm } from 'pliny/ui/NewsletterForm' import BlogNewsletterForm from './BlogNewsletterForm'
// import { BlogNewsletterForm } from 'pliny/ui/NewsletterForm'
import Image from './Image' import Image from './Image'
import CustomLink from './Link' import CustomLink from './Link'
export const Wrapper = ({ layout, content, ...rest }: MDXLayout) => { interface MdxProps {
const Layout = require(`../layouts/${layout}`).default code: string
return <Layout content={content} {...rest} /> [key: string]: any
} }
export const MDXComponents: ComponentMap = { export const MDXComponents: ComponentMap = {
@ -18,6 +19,11 @@ export const MDXComponents: ComponentMap = {
TOCInline, TOCInline,
a: CustomLink, a: CustomLink,
pre: Pre, pre: Pre,
wrapper: Wrapper,
BlogNewsletterForm, BlogNewsletterForm,
} }
export function Mdx({ code, ...rest }: MdxProps) {
const Component = useMDXComponent(code)
return <Component components={{ ...MDXComponents }} {...rest} />
}

72
components/Pre.tsx Normal file
View File

@ -0,0 +1,72 @@
'use client'
import { useState, useRef, ReactNode } from 'react'
const Pre = ({ children }: { children: ReactNode }) => {
const textInput = useRef(null)
const [hovered, setHovered] = useState(false)
const [copied, setCopied] = useState(false)
const onEnter = () => {
setHovered(true)
}
const onExit = () => {
setHovered(false)
setCopied(false)
}
const onCopy = () => {
setCopied(true)
// @ts-ignore
navigator.clipboard.writeText(textInput.current.textContent)
setTimeout(() => {
setCopied(false)
}, 2000)
}
return (
<div ref={textInput} onMouseEnter={onEnter} onMouseLeave={onExit} className="relative">
{hovered && (
<button
aria-label="Copy code"
className={`absolute right-2 top-2 h-8 w-8 rounded border-2 bg-gray-700 p-1 dark:bg-gray-800 ${
copied
? 'border-green-400 focus:border-green-400 focus:outline-none'
: 'border-gray-300'
}`}
onClick={onCopy}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
stroke="currentColor"
fill="none"
className={copied ? 'text-green-400' : 'text-gray-300'}
>
{copied ? (
<>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
/>
</>
) : (
<>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
/>
</>
)}
</svg>
</button>
)}
<pre>{children}</pre>
</div>
)
}
export default Pre

View File

@ -1,3 +1,5 @@
'use client'
import siteMetadata from '@/data/siteMetadata' import siteMetadata from '@/data/siteMetadata'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'

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) =>
@ -33,12 +33,12 @@ interface LayoutProps {
export default function PostLayout({ content, authorDetails, next, prev, children }: LayoutProps) { export default function PostLayout({ content, authorDetails, next, prev, children }: LayoutProps) {
const { filePath, path, slug, date, title, tags } = content const { filePath, path, slug, date, title, tags } = content
const basePath = path.split('/')[0] const basePath = path.split('/')[0]
const [loadComments, setLoadComments] = useState(false) // const [loadComments, setLoadComments] = useState(false)
return ( return (
<SectionContainer> <SectionContainer>
{/* <BlogSEO url={`${siteMetadata.siteUrl}/${path}`} authorDetails={authorDetails} {...content} /> */} {/* <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">
@ -108,10 +108,10 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300" className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300"
id="comment" 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>
)} )}
</div> </div>

View File

@ -1,89 +1,89 @@
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 { 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} /> */} // {/* <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>
) // )
} // }

1
next-env.d.ts vendored
View File

@ -1,6 +1,5 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information. // see https://nextjs.org/docs/basic-features/typescript for more information.