refactor: 改用 TypeScript。close #1.

This commit is contained in:
2022-10-07 13:55:39 +08:00
parent 11b9017a07
commit 1dfd5e5271
84 changed files with 3459 additions and 3836 deletions

View File

@ -11,7 +11,7 @@ export const ClientReload = () => {
useEffect(() => {
import('socket.io-client').then((module) => {
const socket = module.io()
socket.on('reload', (data) => {
socket.on('reload', () => {
Router.replace(Router.asPath, undefined, {
scroll: false,
})

View File

@ -7,12 +7,12 @@ export default function Footer() {
<footer>
<div className="mt-16 flex flex-col items-center">
<div className="mb-3 flex space-x-4">
<SocialIcon kind="mail" href={`mailto:${siteMetadata.email}`} size="6" />
<SocialIcon kind="github" href={siteMetadata.github} size="6" />
<SocialIcon kind="facebook" href={siteMetadata.facebook} size="6" />
<SocialIcon kind="youtube" href={siteMetadata.youtube} size="6" />
<SocialIcon kind="linkedin" href={siteMetadata.linkedin} size="6" />
<SocialIcon kind="twitter" href={siteMetadata.twitter} size="6" />
<SocialIcon kind="mail" href={`mailto:${siteMetadata.email}`} size={6} />
<SocialIcon kind="github" href={siteMetadata.github} size={6} />
<SocialIcon kind="facebook" href={siteMetadata.facebook} size={6} />
<SocialIcon kind="youtube" href={siteMetadata.youtube} size={6} />
<SocialIcon kind="linkedin" href={siteMetadata.linkedin} size={6} />
<SocialIcon kind="twitter" href={siteMetadata.twitter} size={6} />
</div>
<div className="mb-2 flex space-x-2 text-sm text-gray-500 dark:text-gray-400">
<div>{siteMetadata.author}</div>

View File

@ -1,6 +0,0 @@
import NextImage from 'next/image'
// eslint-disable-next-line jsx-a11y/alt-text
const Image = ({ ...rest }) => <NextImage {...rest} />
export default Image

5
components/Image.tsx Normal file
View File

@ -0,0 +1,5 @@
import NextImage, { ImageProps } from 'next/image'
const Image = ({ ...rest }: ImageProps) => <NextImage {...rest} />
export default Image

View File

@ -6,8 +6,13 @@ import SectionContainer from './SectionContainer'
import Footer from './Footer'
import MobileNav from './MobileNav'
import ThemeSwitch from './ThemeSwitch'
import { ReactNode } from 'react'
const LayoutWrapper = ({ children }) => {
interface Props {
children: ReactNode
}
const LayoutWrapper = ({ children }: Props) => {
return (
<SectionContainer>
<div className="flex h-screen flex-col justify-between">

View File

@ -1,7 +1,11 @@
/* eslint-disable jsx-a11y/anchor-has-content */
import Link from 'next/link'
import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react'
const CustomLink = ({ href, ...rest }) => {
const CustomLink = ({
href,
...rest
}: DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) => {
const isInternalLink = href && href.startsWith('/')
const isAnchorLink = href && href.startsWith('#')

View File

@ -1,26 +0,0 @@
/* eslint-disable react/display-name */
import { useMemo } from 'react'
import { getMDXComponent } from 'mdx-bundler/client'
import Image from './Image'
import CustomLink from './Link'
import TOCInline from './TOCInline'
import Pre from './Pre'
import { BlogNewsletterForm } from './NewsletterForm'
export const MDXComponents = {
Image,
TOCInline,
a: CustomLink,
pre: Pre,
BlogNewsletterForm: BlogNewsletterForm,
wrapper: ({ components, layout, ...rest }) => {
const Layout = require(`../layouts/${layout}`).default
return <Layout {...rest} />
},
}
export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }) => {
const MDXLayout = useMemo(() => getMDXComponent(mdxSource), [mdxSource])
return <MDXLayout layout={layout} components={MDXComponents} {...rest} />
}

View File

@ -0,0 +1,36 @@
/* eslint-disable react/display-name */
import React, { useMemo } from 'react'
import { ComponentMap, getMDXComponent } from 'mdx-bundler/client'
import Image from './Image'
import CustomLink from './Link'
import TOCInline from './TOCInline'
import Pre from './Pre'
import { BlogNewsletterForm } from './NewsletterForm'
const Wrapper: React.ComponentType<{ layout: string }> = ({ layout, ...rest }) => {
const Layout = require(`../layouts/${layout}`).default
return <Layout {...rest} />
}
export const MDXComponents: ComponentMap = {
Image,
//@ts-ignore
TOCInline,
a: CustomLink,
pre: Pre,
wrapper: Wrapper,
//@ts-ignore
BlogNewsletterForm,
}
interface Props {
layout: string
mdxSource: string
[key: string]: unknown
}
export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }: Props) => {
const MDXLayout = useMemo(() => getMDXComponent(mdxSource), [mdxSource])
return <MDXLayout layout={layout} components={MDXComponents} {...rest} />
}

View File

@ -31,39 +31,32 @@ const MobileNav = () => {
fill="currentColor"
className="text-gray-900 dark:text-gray-100"
>
<path
fillRule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clipRule="evenodd"
/>
{navShow ? (
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
) : (
<path
fillRule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clipRule="evenodd"
/>
)}
</svg>
</button>
<div
className={`fixed top-0 left-0 z-10 h-full w-full transform bg-gray-200 opacity-95 duration-300 ease-in-out dark:bg-gray-800 ${
className={`fixed top-24 right-0 z-10 h-full w-full transform bg-gray-200 opacity-95 duration-300 ease-in-out dark:bg-gray-800 ${
navShow ? 'translate-x-0' : 'translate-x-full'
}`}
>
<div className="flex justify-end">
<button
type="button"
className="mr-5 mt-11 h-8 w-8 rounded"
aria-label="Toggle Menu"
onClick={onToggleNav}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="text-gray-900 dark:text-gray-100"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
<button
type="button"
aria-label="toggle modal"
className="fixed h-full w-full cursor-auto focus:outline-none"
onClick={onToggleNav}
></button>
<nav className="fixed mt-8 h-full">
{headerNavLinks.map((link) => (
<div key={link.title} className="px-12 py-4">

View File

@ -1,14 +1,14 @@
import { useRef, useState } from 'react'
import React, { useRef, useState } from 'react'
import siteMetadata from '@/data/siteMetadata'
const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
const inputEl = useRef(null)
const inputEl = useRef<HTMLInputElement>(null)
const [error, setError] = useState(false)
const [message, setMessage] = useState('')
const [subscribed, setSubscribed] = useState(false)
const subscribe = async (e) => {
const subscribe = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const res = await fetch(`/api/${siteMetadata.newsletter.provider}`, {

View File

@ -1,4 +1,10 @@
export default function PageTitle({ children }) {
import { ReactNode } from 'react'
interface Props {
children: ReactNode
}
export default function PageTitle({ children }: Props) {
return (
<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-5xl md:leading-14">
{children}

View File

@ -1,33 +1,38 @@
import Link from '@/components/Link'
export default function Pagination({ totalPages, currentPage }) {
const prevPage = parseInt(currentPage) - 1 > 0
const nextPage = parseInt(currentPage) + 1 <= parseInt(totalPages)
interface Props {
totalPages: number
currentPage: number
}
export default function Pagination({ totalPages, currentPage }: Props) {
const prevPage = currentPage - 1 > 0
const nextPage = currentPage + 1 <= totalPages
return (
<div className="space-y-2 pt-6 pb-8 md:space-y-5">
<nav className="flex justify-between">
{!prevPage && (
<button rel="previous" className="cursor-auto disabled:opacity-50" disabled={!prevPage}>
<button className="cursor-auto disabled:opacity-50" disabled={!prevPage}>
Previous
</button>
)}
{prevPage && (
<Link href={currentPage - 1 === 1 ? `/blog/` : `/blog/page/${currentPage - 1}`}>
<button rel="previous">Previous</button>
<button>Previous</button>
</Link>
)}
<span>
{currentPage} of {totalPages}
</span>
{!nextPage && (
<button rel="next" className="cursor-auto disabled:opacity-50" disabled={!nextPage}>
<button className="cursor-auto disabled:opacity-50" disabled={!nextPage}>
Next
</button>
)}
{nextPage && (
<Link href={`/blog/page/${currentPage + 1}`}>
<button rel="next">Next</button>
<button>Next</button>
</Link>
)}
</nav>

View File

@ -1,6 +1,10 @@
import { useState, useRef } from 'react'
import { useState, useRef, ReactNode } from 'react'
const Pre = (props) => {
interface Props {
children: ReactNode
}
const Pre = ({ children }: Props) => {
const textInput = useRef(null)
const [hovered, setHovered] = useState(false)
const [copied, setCopied] = useState(false)
@ -63,7 +67,7 @@ const Pre = (props) => {
</button>
)}
<pre>{props.children}</pre>
<pre>{children}</pre>
</div>
)
}

View File

@ -1,8 +1,31 @@
import Head from 'next/head'
import { useRouter } from 'next/router'
import siteMetadata from '@/data/siteMetadata'
import { AuthorFrontMatter } from 'types/AuthorFrontMatter'
import { PostFrontMatter } from 'types/PostFrontMatter'
const CommonSEO = ({ title, description, ogType, ogImage, twImage, canonicalUrl }) => {
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>
@ -14,7 +37,7 @@ const CommonSEO = ({ title, description, ogType, ogImage, twImage, canonicalUrl
<meta property="og:site_name" content={siteMetadata.title} />
<meta property="og:description" content={description} />
<meta property="og:title" content={title} />
{ogImage.constructor.name === 'Array' ? (
{Array.isArray(ogImage) ? (
ogImage.map(({ url }) => <meta property="og:image" content={url} key={url} />)
) : (
<meta property="og:image" content={ogImage} key={ogImage} />
@ -32,7 +55,12 @@ const CommonSEO = ({ title, description, ogType, ogImage, twImage, canonicalUrl
)
}
export const PageSEO = ({ title, description }) => {
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 (
@ -46,7 +74,7 @@ export const PageSEO = ({ title, description }) => {
)
}
export const TagSEO = ({ title, description }) => {
export const TagSEO = ({ title, description }: PageSEOProps) => {
const ogImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner
const twImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner
const router = useRouter()
@ -71,6 +99,11 @@ export const TagSEO = ({ title, description }) => {
)
}
interface BlogSeoProps extends PostFrontMatter {
authorDetails?: AuthorFrontMatter[]
url: string
}
export const BlogSEO = ({
authorDetails,
title,
@ -80,11 +113,10 @@ export const BlogSEO = ({
url,
images = [],
canonicalUrl,
}) => {
const router = useRouter()
}: BlogSeoProps) => {
const publishedAt = new Date(date).toISOString()
const modifiedAt = new Date(lastmod || date).toISOString()
let imagesArr =
const imagesArr =
images.length === 0
? [siteMetadata.socialBanner]
: typeof images === 'string'
@ -94,7 +126,7 @@ export const BlogSEO = ({
const featuredImages = imagesArr.map((img) => {
return {
'@type': 'ImageObject',
url: img.includes('http') ? img : siteMetadata.siteUrl + img,
url: `${siteMetadata.siteUrl}${img}`,
}
})

View File

@ -1,4 +1,3 @@
import siteMetadata from '@/data/siteMetadata'
import { useEffect, useState } from 'react'
const ScrollTopAndComment = () => {
@ -15,7 +14,7 @@ const ScrollTopAndComment = () => {
}, [])
const handleScrollTop = () => {
window.scrollTo({ top: 0 })
window.scrollTo({ top: 0, behavior: 'smooth' })
}
const handleScrollToComment = () => {
document.getElementById('comment').scrollIntoView()
@ -24,22 +23,20 @@ const ScrollTopAndComment = () => {
<div
className={`fixed right-8 bottom-8 hidden flex-col gap-3 ${show ? 'md:flex' : 'md:hidden'}`}
>
{siteMetadata.comment.provider && (
<button
aria-label="Scroll To Comment"
type="button"
onClick={handleScrollToComment}
className="rounded-full bg-gray-200 p-2 text-gray-500 transition-all hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600"
>
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z"
clipRule="evenodd"
/>
</svg>
</button>
)}
<button
aria-label="Scroll To Comment"
type="button"
onClick={handleScrollToComment}
className="rounded-full bg-gray-200 p-2 text-gray-500 transition-all hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600"
>
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z"
clipRule="evenodd"
/>
</svg>
</button>
<button
aria-label="Scroll To Top"
type="button"

View File

@ -1,3 +0,0 @@
export default function SectionContainer({ children }) {
return <div className="mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl xl:px-0">{children}</div>
}

View File

@ -0,0 +1,9 @@
import { ReactNode } from 'react'
interface Props {
children: ReactNode
}
export default function SectionContainer({ children }: Props) {
return <div className="mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl xl:px-0">{children}</div>
}

View File

@ -1,23 +1,27 @@
/**
* @typedef TocHeading
* @prop {string} value
* @prop {number} depth
* @prop {string} url
*/
import { Toc } from 'types/Toc'
interface TOCInlineProps {
toc: Toc
indentDepth?: number
fromHeading?: number
toHeading?: number
asDisclosure?: boolean
exclude?: string | string[]
}
/**
* Generates an inline table of contents
* Exclude titles matching this string (new RegExp('^(' + string + ')$', 'i')).
* If an array is passed the array gets joined with a pipe (new RegExp('^(' + array.join('|') + ')$', 'i')).
*
* @param {{
* toc: TocHeading[],
* indentDepth?: number,
* fromHeading?: number,
* toHeading?: number,
* asDisclosure?: boolean,
* exclude?: string|string[]
* }} props
* @param {TOCInlineProps} {
* toc,
* indentDepth = 3,
* fromHeading = 1,
* toHeading = 6,
* asDisclosure = false,
* exclude = '',
* }
*
*/
const TOCInline = ({
@ -27,7 +31,7 @@ const TOCInline = ({
toHeading = 6,
asDisclosure = false,
exclude = '',
}) => {
}: TOCInlineProps) => {
const re = Array.isArray(exclude)
? new RegExp('^(' + exclude.join('|') + ')$', 'i')
: new RegExp('^(' + exclude + ')$', 'i')

View File

@ -1,7 +1,11 @@
import Link from 'next/link'
import kebabCase from '@/lib/utils/kebabCase'
const Tag = ({ text }) => {
interface Props {
text: string
}
const Tag = ({ text }: Props) => {
return (
<Link href={`/tags/${kebabCase(text)}`}>
<a className="mr-3 text-sm font-medium uppercase text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">

View File

@ -1,18 +0,0 @@
import Script from 'next/script'
import siteMetadata from '@/data/siteMetadata'
const PosthogScript = () => {
return (
<>
<Script strategy="lazyOnload" id="posthog-script">
{`
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('${siteMetadata.analytics.posthogAnalyticsId}',{api_host:'https://app.posthog.com'})
`}
</Script>
</>
)
}
export default PosthogScript

View File

@ -2,9 +2,16 @@ import GA from './GoogleAnalytics'
import Plausible from './Plausible'
import SimpleAnalytics from './SimpleAnalytics'
import Umami from './Umami'
import Posthog from './Posthog'
import siteMetadata from '@/data/siteMetadata'
declare global {
interface Window {
gtag?: (...args: any[]) => void
plausible?: (...args: any[]) => void
sa_event?: (...args: any[]) => void
}
}
const isProduction = process.env.NODE_ENV === 'production'
const Analytics = () => {
@ -14,7 +21,6 @@ const Analytics = () => {
{isProduction && siteMetadata.analytics.simpleAnalytics && <SimpleAnalytics />}
{isProduction && siteMetadata.analytics.umamiWebsiteId && <Umami />}
{isProduction && siteMetadata.analytics.googleAnalyticsId && <GA />}
{isProduction && siteMetadata.analytics.posthogAnalyticsId && <Posthog />}
</>
)
}

View File

@ -1,8 +1,13 @@
import React, { useState } from 'react'
import siteMetadata from '@/data/siteMetadata'
import { PostFrontMatter } from 'types/PostFrontMatter'
const Disqus = ({ frontMatter }) => {
interface Props {
frontMatter: PostFrontMatter
}
const Disqus = ({ frontMatter }: Props) => {
const [enableLoadComments, setEnabledLoadComments] = useState(true)
const COMMENTS_ID = 'disqus_thread'
@ -10,18 +15,22 @@ const Disqus = ({ frontMatter }) => {
function LoadComments() {
setEnabledLoadComments(false)
// @ts-ignore
window.disqus_config = function () {
this.page.url = window.location.href
this.page.identifier = frontMatter.slug
}
// @ts-ignore
if (window.DISQUS === undefined) {
const script = document.createElement('script')
script.src = 'https://' + siteMetadata.comment.disqusConfig.shortname + '.disqus.com/embed.js'
// @ts-ignore
script.setAttribute('data-timestamp', +new Date())
script.setAttribute('crossorigin', 'anonymous')
script.async = true
document.body.appendChild(script)
} else {
// @ts-ignore
window.DISQUS.reset({ reload: true })
}
}

View File

@ -3,7 +3,11 @@ import { useTheme } from 'next-themes'
import siteMetadata from '@/data/siteMetadata'
const Giscus = () => {
interface Props {
mapping: string
}
const Giscus = ({ mapping }: Props) => {
const [enableLoadComments, setEnabledLoadComments] = useState(true)
const { theme, resolvedTheme } = useTheme()
const commentsTheme =
@ -17,30 +21,15 @@ const Giscus = () => {
const LoadComments = useCallback(() => {
setEnabledLoadComments(false)
const {
repo,
repositoryId,
category,
categoryId,
mapping,
reactions,
metadata,
inputPosition,
lang,
} = siteMetadata?.comment?.giscusConfig
const script = document.createElement('script')
script.src = 'https://giscus.app/client.js'
script.setAttribute('data-repo', repo)
script.setAttribute('data-repo-id', repositoryId)
script.setAttribute('data-category', category)
script.setAttribute('data-category-id', categoryId)
script.setAttribute('data-repo', siteMetadata.comment.giscusConfig.repo)
script.setAttribute('data-repo-id', siteMetadata.comment.giscusConfig.repositoryId)
script.setAttribute('data-category', siteMetadata.comment.giscusConfig.category)
script.setAttribute('data-category-id', siteMetadata.comment.giscusConfig.categoryId)
script.setAttribute('data-mapping', mapping)
script.setAttribute('data-reactions-enabled', reactions)
script.setAttribute('data-emit-metadata', metadata)
script.setAttribute('data-input-position', inputPosition)
script.setAttribute('data-lang', lang)
script.setAttribute('data-reactions-enabled', siteMetadata.comment.giscusConfig.reactions)
script.setAttribute('data-emit-metadata', siteMetadata.comment.giscusConfig.metadata)
script.setAttribute('data-theme', commentsTheme)
script.setAttribute('crossorigin', 'anonymous')
script.async = true
@ -52,7 +41,7 @@ const Giscus = () => {
const comments = document.getElementById(COMMENTS_ID)
if (comments) comments.innerHTML = ''
}
}, [commentsTheme])
}, [commentsTheme, mapping])
// Reload on theme change
useEffect(() => {

View File

@ -3,7 +3,11 @@ import { useTheme } from 'next-themes'
import siteMetadata from '@/data/siteMetadata'
const Utterances = () => {
interface Props {
issueTerm: string
}
const Utterances = ({ issueTerm }: Props) => {
const [enableLoadComments, setEnabledLoadComments] = useState(true)
const { theme, resolvedTheme } = useTheme()
const commentsTheme =
@ -18,7 +22,7 @@ const Utterances = () => {
const script = document.createElement('script')
script.src = 'https://utteranc.es/client.js'
script.setAttribute('repo', siteMetadata.comment.utterancesConfig.repo)
script.setAttribute('issue-term', siteMetadata.comment.utterancesConfig.issueTerm)
script.setAttribute('issue-term', issueTerm)
script.setAttribute('label', siteMetadata.comment.utterancesConfig.label)
script.setAttribute('theme', commentsTheme)
script.setAttribute('crossorigin', 'anonymous')
@ -31,7 +35,7 @@ const Utterances = () => {
const comments = document.getElementById(COMMENTS_ID)
if (comments) comments.innerHTML = ''
}
}, [commentsTheme])
}, [commentsTheme, issueTerm])
// Reload on theme change
useEffect(() => {

View File

@ -1,5 +1,10 @@
import siteMetadata from '@/data/siteMetadata'
import dynamic from 'next/dynamic'
import { PostFrontMatter } from 'types/PostFrontMatter'
interface Props {
frontMatter: PostFrontMatter
}
const UtterancesComponent = dynamic(
() => {
@ -20,14 +25,29 @@ const DisqusComponent = dynamic(
{ ssr: false }
)
const Comments = ({ frontMatter }) => {
const comment = siteMetadata?.comment
if (!comment || Object.keys(comment).length === 0) return <></>
const Comments = ({ frontMatter }: Props) => {
let term
switch (
siteMetadata.comment.giscusConfig.mapping ||
siteMetadata.comment.utterancesConfig.issueTerm
) {
case 'pathname':
term = frontMatter.slug
break
case 'url':
term = window.location.href
break
case 'title':
term = frontMatter.title
break
}
return (
<div id="comment">
{siteMetadata.comment && siteMetadata.comment.provider === 'giscus' && <GiscusComponent />}
{siteMetadata.comment && siteMetadata.comment.provider === 'giscus' && (
<GiscusComponent mapping={term} />
)}
{siteMetadata.comment && siteMetadata.comment.provider === 'utterances' && (
<UtterancesComponent />
<UtterancesComponent issueTerm={term} />
)}
{siteMetadata.comment && siteMetadata.comment.provider === 'disqus' && (
<DisqusComponent frontMatter={frontMatter} />