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 TOCInline from 'pliny/ui/TOCInline'
|
||||||
import Pre from './Pre'
|
import Pre from 'pliny/ui/Pre'
|
||||||
import BlogNewsletterForm from 'pliny/ui/BlogNewsletterForm'
|
import BlogNewsletterForm from 'pliny/ui/BlogNewsletterForm'
|
||||||
import type { MDXComponents } from 'mdx/types'
|
import type { MDXComponents } from 'mdx/types'
|
||||||
import Image from './Image'
|
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>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user