upstream #1
| @@ -2,6 +2,9 @@ import { Authors, allAuthors } from 'contentlayer/generated' | ||||
| import { Mdx } from '@/components/MDXComponents' | ||||
| import AuthorLayout from '@/layouts/AuthorLayout' | ||||
| import { coreContent } from 'pliny/utils/contentlayer' | ||||
| import { genPageMetadata } from 'app/seo' | ||||
|  | ||||
| export const metadata = genPageMetadata({ title: 'About' }) | ||||
|  | ||||
| export default function Page() { | ||||
|   const author = allAuthors.find((p) => p.slug === 'default') as Authors | ||||
|   | ||||
| @@ -4,6 +4,61 @@ import { sortedBlogPost, coreContent } from 'pliny/utils/contentlayer' | ||||
| import { allBlogs, allAuthors } from 'contentlayer/generated' | ||||
| import type { Authors, Blog } from 'contentlayer/generated' | ||||
| 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 () => { | ||||
|   const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') })) | ||||
|   | ||||
| @@ -2,9 +2,12 @@ import ListLayout from '@/layouts/ListLayout' | ||||
| import { sortedBlogPost } from 'pliny/utils/contentlayer' | ||||
| import { allBlogs } from 'contentlayer/generated' | ||||
| import type { Blog } from 'contentlayer/generated' | ||||
| import { genPageMetadata } from 'app/seo' | ||||
|  | ||||
| const POSTS_PER_PAGE = 5 | ||||
|  | ||||
| export const metadata = genPageMetadata({ title: 'Blog' }) | ||||
|  | ||||
| export default function BlogPage() { | ||||
|   const posts = sortedBlogPost(allBlogs) as Blog[] | ||||
|   const pageNumber = 1 | ||||
|   | ||||
| @@ -8,14 +8,61 @@ import SectionContainer from '@/components/SectionContainer' | ||||
| import Footer from '@/components/Footer' | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
| import { ThemeProviders } from './theme-providers' | ||||
| import { Metadata } from 'next' | ||||
|  | ||||
| const inter = Inter({ | ||||
|   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 }) { | ||||
|   return ( | ||||
|     <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"> | ||||
|         <ThemeProviders> | ||||
|           {/* <Analytics analyticsConfig={siteMetadata.analytics} /> */} | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| import projectsData from '@/data/projectsData' | ||||
| import Card from '@/components/Card' | ||||
| import { genPageMetadata } from 'app/seo' | ||||
|  | ||||
| export const metadata = genPageMetadata({ title: 'Projects' }) | ||||
|  | ||||
| export default function Projects() { | ||||
|   return ( | ||||
|   | ||||
							
								
								
									
										28
									
								
								app/seo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/seo.tsx
									
									
									
									
									
										Normal 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], | ||||
|     }, | ||||
|   } | ||||
| } | ||||
| @@ -2,9 +2,11 @@ import { getAllTags } from 'pliny/utils/contentlayer' | ||||
| import Link from '@/components/Link' | ||||
| // import { PageSEO } from '@/components/SEO' | ||||
| import Tag from '@/components/Tag' | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
| import { kebabCase } from 'pliny/utils/kebabCase' | ||||
| 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() { | ||||
|   const tags = await getAllTags(allBlogs) | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import { ReactNode } from 'react' | ||||
| import type { Authors } from 'contentlayer/generated' | ||||
| import SocialIcon from '@/components/social-icons' | ||||
| import Image from '@/components/Image' | ||||
| // import { PageSEO } from '@/components/SEO' | ||||
|  | ||||
| interface Props { | ||||
|   children: ReactNode | ||||
| @@ -14,7 +13,6 @@ export default function AuthorLayout({ children, content }: Props) { | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {/* <PageSEO title={`About - ${name}`} description={`About me - ${name}`} /> */} | ||||
|       <div className="divide-y divide-gray-200 dark:divide-gray-700"> | ||||
|         <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"> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import SectionContainer from '@/components/SectionContainer' | ||||
| import Image from '@/components/Image' | ||||
| import Tag from '@/components/Tag' | ||||
| 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 discussUrl = (path) => | ||||
| @@ -37,8 +37,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre | ||||
|  | ||||
|   return ( | ||||
|     <SectionContainer> | ||||
|       {/* <BlogSEO url={`${siteMetadata.siteUrl}/${path}`} authorDetails={authorDetails} {...content} /> */} | ||||
|       {/* <ScrollTopAndComment /> */} | ||||
|       <ScrollTopAndComment /> | ||||
|       <article> | ||||
|         <div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700"> | ||||
|           <header className="pt-6 xl:pb-6"> | ||||
|   | ||||
| @@ -1,89 +1,87 @@ | ||||
| // import { useState, ReactNode } from 'react' | ||||
| // import { Comments } from 'pliny/comments' | ||||
| // import { formatDate } from 'pliny/utils/formatDate' | ||||
| // import { CoreContent } from 'pliny/utils/contentlayer' | ||||
| // import type { Blog } from 'contentlayer/generated' | ||||
| // import Link from '@/components/Link' | ||||
| // import PageTitle from '@/components/PageTitle' | ||||
| // import SectionContainer from '@/components/SectionContainer' | ||||
| // // import { BlogSEO } from '@/components/SEO' | ||||
| // import siteMetadata from '@/data/siteMetadata' | ||||
| // import ScrollTopAndComment from '@/components/ScrollTopAndComment' | ||||
| import { useState, ReactNode } from 'react' | ||||
| import { Comments } from 'pliny/comments' | ||||
| import { formatDate } from 'pliny/utils/formatDate' | ||||
| import { CoreContent } from 'pliny/utils/contentlayer' | ||||
| import type { Blog } from 'contentlayer/generated' | ||||
| import Link from '@/components/Link' | ||||
| import PageTitle from '@/components/PageTitle' | ||||
| import SectionContainer from '@/components/SectionContainer' | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
| import ScrollTopAndComment from '@/components/ScrollTopAndComment' | ||||
|  | ||||
| // interface LayoutProps { | ||||
| //   content: CoreContent<Blog> | ||||
| //   children: ReactNode | ||||
| //   next?: { path: string; title: string } | ||||
| //   prev?: { path: string; title: string } | ||||
| // } | ||||
| interface LayoutProps { | ||||
|   content: CoreContent<Blog> | ||||
|   children: ReactNode | ||||
|   next?: { path: string; title: string } | ||||
|   prev?: { path: string; title: string } | ||||
| } | ||||
|  | ||||
| // export default function PostLayout({ content, next, prev, children }: LayoutProps) { | ||||
| //   const [loadComments, setLoadComments] = useState(false) | ||||
| export default function PostLayout({ content, next, prev, children }: LayoutProps) { | ||||
|   const [loadComments, setLoadComments] = useState(false) | ||||
|  | ||||
| //   const { path, slug, date, title } = content | ||||
|   const { path, slug, date, title } = content | ||||
|  | ||||
| //   return ( | ||||
| //     <SectionContainer> | ||||
| //       {/* <BlogSEO url={`${siteMetadata.siteUrl}/${path}`} {...content} /> */} | ||||
| //       <ScrollTopAndComment /> | ||||
| //       <article> | ||||
| //         <div> | ||||
| //           <header> | ||||
| //             <div className="space-y-1 border-b border-gray-200 pb-10 text-center dark:border-gray-700"> | ||||
| //               <dl> | ||||
| //                 <div> | ||||
| //                   <dt className="sr-only">Published on</dt> | ||||
| //                   <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400"> | ||||
| //                     <time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time> | ||||
| //                   </dd> | ||||
| //                 </div> | ||||
| //               </dl> | ||||
| //               <div> | ||||
| //                 <PageTitle>{title}</PageTitle> | ||||
| //               </div> | ||||
| //             </div> | ||||
| //           </header> | ||||
| //           <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="prose max-w-none pb-8 pt-10 dark:prose-dark">{children}</div> | ||||
| //             </div> | ||||
| //             {siteMetadata.comments && ( | ||||
| //               <div className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300" id="comment"> | ||||
| //                 {!loadComments && ( | ||||
| //                   <button onClick={() => setLoadComments(true)}>Load Comments</button> | ||||
| //                 )} | ||||
| //                 {loadComments && <Comments commentsConfig={siteMetadata.comments} slug={slug} />} | ||||
| //               </div> | ||||
| //             )} | ||||
| //             <footer> | ||||
| //               <div className="flex flex-col text-sm font-medium sm:flex-row sm:justify-between sm:text-base"> | ||||
| //                 {prev && ( | ||||
| //                   <div className="pt-4 xl:pt-8"> | ||||
| //                     <Link | ||||
| //                       href={`/${prev.path}`} | ||||
| //                       className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" | ||||
| //                       aria-label={`Previous post: ${prev.title}`} | ||||
| //                     > | ||||
| //                       ← {prev.title} | ||||
| //                     </Link> | ||||
| //                   </div> | ||||
| //                 )} | ||||
| //                 {next && ( | ||||
| //                   <div className="pt-4 xl:pt-8"> | ||||
| //                     <Link | ||||
| //                       href={`/${next.path}`} | ||||
| //                       className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" | ||||
| //                       aria-label={`Next post: ${next.title}`} | ||||
| //                     > | ||||
| //                       {next.title} → | ||||
| //                     </Link> | ||||
| //                   </div> | ||||
| //                 )} | ||||
| //               </div> | ||||
| //             </footer> | ||||
| //           </div> | ||||
| //         </div> | ||||
| //       </article> | ||||
| //     </SectionContainer> | ||||
| //   ) | ||||
| // } | ||||
|   return ( | ||||
|     <SectionContainer> | ||||
|       <ScrollTopAndComment /> | ||||
|       <article> | ||||
|         <div> | ||||
|           <header> | ||||
|             <div className="space-y-1 border-b border-gray-200 pb-10 text-center dark:border-gray-700"> | ||||
|               <dl> | ||||
|                 <div> | ||||
|                   <dt className="sr-only">Published on</dt> | ||||
|                   <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400"> | ||||
|                     <time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time> | ||||
|                   </dd> | ||||
|                 </div> | ||||
|               </dl> | ||||
|               <div> | ||||
|                 <PageTitle>{title}</PageTitle> | ||||
|               </div> | ||||
|             </div> | ||||
|           </header> | ||||
|           <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="prose max-w-none pb-8 pt-10 dark:prose-dark">{children}</div> | ||||
|             </div> | ||||
|             {siteMetadata.comments && ( | ||||
|               <div className="pb-6 pt-6 text-center text-gray-700 dark:text-gray-300" id="comment"> | ||||
|                 {!loadComments && ( | ||||
|                   <button onClick={() => setLoadComments(true)}>Load Comments</button> | ||||
|                 )} | ||||
|                 {loadComments && <Comments commentsConfig={siteMetadata.comments} slug={slug} />} | ||||
|               </div> | ||||
|             )} | ||||
|             <footer> | ||||
|               <div className="flex flex-col text-sm font-medium sm:flex-row sm:justify-between sm:text-base"> | ||||
|                 {prev && ( | ||||
|                   <div className="pt-4 xl:pt-8"> | ||||
|                     <Link | ||||
|                       href={`/${prev.path}`} | ||||
|                       className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" | ||||
|                       aria-label={`Previous post: ${prev.title}`} | ||||
|                     > | ||||
|                       ← {prev.title} | ||||
|                     </Link> | ||||
|                   </div> | ||||
|                 )} | ||||
|                 {next && ( | ||||
|                   <div className="pt-4 xl:pt-8"> | ||||
|                     <Link | ||||
|                       href={`/${next.path}`} | ||||
|                       className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" | ||||
|                       aria-label={`Next post: ${next.title}`} | ||||
|                     > | ||||
|                       {next.title} → | ||||
|                     </Link> | ||||
|                   </div> | ||||
|                 )} | ||||
|               </div> | ||||
|             </footer> | ||||
|           </div> | ||||
|         </div> | ||||
|       </article> | ||||
|     </SectionContainer> | ||||
|   ) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user