upstream #1
| @@ -1,13 +0,0 @@ | ||||
| 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> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import TOCInline from 'pliny/ui/TOCInline' | ||||
| import Pre from './Pre' | ||||
| import Pre from 'pliny/ui/Pre' | ||||
| import BlogNewsletterForm from 'pliny/ui/BlogNewsletterForm' | ||||
| import type { MDXComponents } from 'mdx/types' | ||||
| import Image from './Image' | ||||
|   | ||||
| @@ -1,72 +0,0 @@ | ||||
| '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 | ||||
| @@ -1,194 +0,0 @@ | ||||
| import Head from 'next/head' | ||||
| import { useRouter } from 'next/router' | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
| import { CoreContent } from 'pliny/utils/contentlayer' | ||||
| import type { Blog, Authors } from 'contentlayer/generated' | ||||
| interface CommonSEOProps { | ||||
|   title: string | ||||
|   description: string | ||||
|   ogType: string | ||||
|   ogImage: | ||||
|     | string | ||||
|     | { | ||||
|         '@type': string | ||||
|         url: string | ||||
|       }[] | ||||
|   twImage: string | ||||
|   canonicalUrl?: string | ||||
| } | ||||
|  | ||||
| const CommonSEO = ({ | ||||
|   title, | ||||
|   description, | ||||
|   ogType, | ||||
|   ogImage, | ||||
|   twImage, | ||||
|   canonicalUrl, | ||||
| }: CommonSEOProps) => { | ||||
|   const router = useRouter() | ||||
|   return ( | ||||
|     <Head> | ||||
|       <title>{title}</title> | ||||
|       <meta name="robots" content="follow, index" /> | ||||
|       <meta name="description" content={description} /> | ||||
|       <meta property="og:url" content={`${siteMetadata.siteUrl}${router.asPath}`} /> | ||||
|       <meta property="og:type" content={ogType} /> | ||||
|       <meta property="og:site_name" content={siteMetadata.title} /> | ||||
|       <meta property="og:description" content={description} /> | ||||
|       <meta property="og:title" content={title} /> | ||||
|       {Array.isArray(ogImage) ? ( | ||||
|         ogImage.map(({ url }) => <meta property="og:image" content={url} key={url} />) | ||||
|       ) : ( | ||||
|         <meta property="og:image" content={ogImage} key={ogImage} /> | ||||
|       )} | ||||
|       <meta name="twitter:card" content="summary_large_image" /> | ||||
|       <meta name="twitter:site" content={siteMetadata.twitter} /> | ||||
|       <meta name="twitter:title" content={title} /> | ||||
|       <meta name="twitter:description" content={description} /> | ||||
|       <meta name="twitter:image" content={twImage} /> | ||||
|       <link | ||||
|         rel="canonical" | ||||
|         href={canonicalUrl ? canonicalUrl : `${siteMetadata.siteUrl}${router.asPath}`} | ||||
|       /> | ||||
|     </Head> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| interface PageSEOProps { | ||||
|   title: string | ||||
|   description: string | ||||
| } | ||||
|  | ||||
| export const PageSEO = ({ title, description }: PageSEOProps) => { | ||||
|   const ogImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner | ||||
|   const twImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner | ||||
|   return ( | ||||
|     <CommonSEO | ||||
|       title={title} | ||||
|       description={description} | ||||
|       ogType="website" | ||||
|       ogImage={ogImageUrl} | ||||
|       twImage={twImageUrl} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export const TagSEO = ({ title, description }: PageSEOProps) => { | ||||
|   const ogImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner | ||||
|   const twImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner | ||||
|   const router = useRouter() | ||||
|   return ( | ||||
|     <> | ||||
|       <CommonSEO | ||||
|         title={title} | ||||
|         description={description} | ||||
|         ogType="website" | ||||
|         ogImage={ogImageUrl} | ||||
|         twImage={twImageUrl} | ||||
|       /> | ||||
|       <Head> | ||||
|         <link | ||||
|           rel="alternate" | ||||
|           type="application/rss+xml" | ||||
|           title={`${description} - RSS feed`} | ||||
|           href={`${siteMetadata.siteUrl}${router.asPath}/feed.xml`} | ||||
|         /> | ||||
|       </Head> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| interface BlogSeoProps extends CoreContent<Blog> { | ||||
|   authorDetails?: CoreContent<Authors>[] | ||||
|   url: string | ||||
| } | ||||
|  | ||||
| export const BlogSEO = ({ | ||||
|   authorDetails, | ||||
|   title, | ||||
|   summary, | ||||
|   date, | ||||
|   lastmod, | ||||
|   url, | ||||
|   images = [], | ||||
|   canonicalUrl, | ||||
| }: BlogSeoProps) => { | ||||
|   const publishedAt = new Date(date).toISOString() | ||||
|   const modifiedAt = new Date(lastmod || date).toISOString() | ||||
|   const imagesArr = | ||||
|     images.length === 0 | ||||
|       ? [siteMetadata.socialBanner] | ||||
|       : typeof images === 'string' | ||||
|       ? [images] | ||||
|       : images | ||||
|  | ||||
|   const featuredImages = imagesArr.map((img) => { | ||||
|     return { | ||||
|       '@type': 'ImageObject', | ||||
|       url: img.includes('http') ? img : siteMetadata.siteUrl + img, | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   let authorList | ||||
|   if (authorDetails) { | ||||
|     authorList = authorDetails.map((author) => { | ||||
|       return { | ||||
|         '@type': 'Person', | ||||
|         name: author.name, | ||||
|       } | ||||
|     }) | ||||
|   } else { | ||||
|     authorList = { | ||||
|       '@type': 'Person', | ||||
|       name: siteMetadata.author, | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const structuredData = { | ||||
|     '@context': 'https://schema.org', | ||||
|     '@type': 'Article', | ||||
|     mainEntityOfPage: { | ||||
|       '@type': 'WebPage', | ||||
|       '@id': url, | ||||
|     }, | ||||
|     headline: title, | ||||
|     image: featuredImages, | ||||
|     datePublished: publishedAt, | ||||
|     dateModified: modifiedAt, | ||||
|     author: authorList, | ||||
|     publisher: { | ||||
|       '@type': 'Organization', | ||||
|       name: siteMetadata.author, | ||||
|       logo: { | ||||
|         '@type': 'ImageObject', | ||||
|         url: `${siteMetadata.siteUrl}${siteMetadata.siteLogo}`, | ||||
|       }, | ||||
|     }, | ||||
|     description: summary, | ||||
|   } | ||||
|  | ||||
|   const twImageUrl = featuredImages[0].url | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <CommonSEO | ||||
|         title={title} | ||||
|         description={summary || ''} | ||||
|         ogType="article" | ||||
|         ogImage={featuredImages} | ||||
|         twImage={twImageUrl} | ||||
|         canonicalUrl={canonicalUrl} | ||||
|       /> | ||||
|       <Head> | ||||
|         {date && <meta property="article:published_time" content={publishedAt} />} | ||||
|         {lastmod && <meta property="article:modified_time" content={modifiedAt} />} | ||||
|         <script | ||||
|           type="application/ld+json" | ||||
|           dangerouslySetInnerHTML={{ | ||||
|             __html: JSON.stringify(structuredData, null, 2), | ||||
|           }} | ||||
|         /> | ||||
|       </Head> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user