style: auto fix.

This commit is contained in:
2022-10-17 15:37:01 +00:00
parent 10f64a9ba4
commit c081d55a32
68 changed files with 1403 additions and 1114 deletions

View File

@ -1,13 +1,12 @@
import Image from './Image'
import Link from './Link'
import Image from './Image';
import Link from './Link';
const Card = ({ title, description, imgSrc, href }) => (
<div className="md p-4 md:w-1/2" style={{ maxWidth: '544px' }}>
<div
className={`${
imgSrc && 'h-full'
} overflow-hidden rounded-md border-2 border-gray-200 border-opacity-60 dark:border-gray-700`}
>
} overflow-hidden rounded-md border-2 border-gray-200 border-opacity-60 dark:border-gray-700`}>
{imgSrc &&
(href ? (
<Link href={href} aria-label={`Link to ${title}`}>
@ -38,19 +37,20 @@ const Card = ({ title, description, imgSrc, href }) => (
title
)}
</h2>
<p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">{description}</p>
<p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">
{description}
</p>
{href && (
<Link
href={href}
className="text-base font-medium leading-6 text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
aria-label={`Link to ${title}`}
>
aria-label={`Link to ${title}`}>
Learn more &rarr;
</Link>
)}
</div>
</div>
</div>
)
);
export default Card
export default Card;

View File

@ -1,5 +1,5 @@
import { useEffect } from 'react'
import Router from 'next/router'
import { useEffect } from 'react';
import Router from 'next/router';
/**
* Client-side complement to next-remote-watch
@ -10,14 +10,14 @@ export const ClientReload = () => {
// Exclude socket.io from prod bundle
useEffect(() => {
import('socket.io-client').then((module) => {
const socket = module.io()
const socket = module.io();
socket.on('reload', () => {
Router.replace(Router.asPath, undefined, {
scroll: false,
})
})
})
}, [])
});
});
});
}, []);
return null
}
return null;
};

View File

@ -1,13 +1,17 @@
import Link from './Link'
import siteMetadata from '@/data/siteMetadata'
import SocialIcon from '@/components/social-icons'
import Link from './Link';
import siteMetadata from '@/data/siteMetadata';
import SocialIcon from '@/components/social-icons';
export default function Footer() {
return (
<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="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} />
@ -22,11 +26,13 @@ export default function Footer() {
<Link href="/">{siteMetadata.title}</Link>
</div>
<div className="mb-8 text-sm text-gray-500 dark:text-gray-400">
<Link href="https://github.com/timlrx/tailwind-nextjs-starter-blog" rel="nofollow">
<Link
href="https://github.com/timlrx/tailwind-nextjs-starter-blog"
rel="nofollow">
Tailwind Nextjs Theme
</Link>
</div>
</div>
</footer>
)
);
}

View File

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

View File

@ -1,15 +1,15 @@
import siteMetadata from '@/data/siteMetadata'
import headerNavLinks from '@/data/headerNavLinks'
import Logo from '@/data/logo.svg'
import Link from './Link'
import SectionContainer from './SectionContainer'
import Footer from './Footer'
import MobileNav from './MobileNav'
import ThemeSwitch from './ThemeSwitch'
import { ReactNode } from 'react'
import siteMetadata from '@/data/siteMetadata';
import headerNavLinks from '@/data/headerNavLinks';
import Logo from '@/data/logo.svg';
import Link from './Link';
import SectionContainer from './SectionContainer';
import Footer from './Footer';
import MobileNav from './MobileNav';
import ThemeSwitch from './ThemeSwitch';
import { ReactNode } from 'react';
interface Props {
children: ReactNode
children: ReactNode;
}
const LayoutWrapper = ({ children }: Props) => {
@ -39,8 +39,7 @@ const LayoutWrapper = ({ children }: Props) => {
<Link
key={link.title}
href={link.href}
className="p-1 font-medium text-gray-900 dark:text-gray-100 sm:p-4"
>
className="p-1 font-medium text-gray-900 dark:text-gray-100 sm:p-4">
{link.title}
</Link>
))}
@ -53,7 +52,7 @@ const LayoutWrapper = ({ children }: Props) => {
<Footer />
</div>
</SectionContainer>
)
}
);
};
export default LayoutWrapper
export default LayoutWrapper;

View File

@ -1,27 +1,30 @@
/* eslint-disable jsx-a11y/anchor-has-content */
import Link from 'next/link'
import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react'
import Link from 'next/link';
import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react';
const CustomLink = ({
href,
...rest
}: DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) => {
const isInternalLink = href && href.startsWith('/')
const isAnchorLink = href && href.startsWith('#')
}: DetailedHTMLProps<
AnchorHTMLAttributes<HTMLAnchorElement>,
HTMLAnchorElement
>) => {
const isInternalLink = href && href.startsWith('/');
const isAnchorLink = href && href.startsWith('#');
if (isInternalLink) {
return (
<Link href={href}>
<a {...rest} />
</Link>
)
);
}
if (isAnchorLink) {
return <a href={href} {...rest} />
return <a href={href} {...rest} />;
}
return <a target="_blank" rel="noopener noreferrer" href={href} {...rest} />
}
return <a target="_blank" rel="noopener noreferrer" href={href} {...rest} />;
};
export default CustomLink
export default CustomLink;

View File

@ -1,16 +1,19 @@
/* 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'
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} />
}
const Wrapper: React.ComponentType<{ layout: string }> = ({
layout,
...rest
}) => {
const Layout = require(`../layouts/${layout}`).default;
return <Layout {...rest} />;
};
export const MDXComponents: ComponentMap = {
Image,
@ -21,16 +24,16 @@ export const MDXComponents: ComponentMap = {
wrapper: Wrapper,
//@ts-ignore
BlogNewsletterForm,
}
};
interface Props {
layout: string
mdxSource: string
[key: string]: unknown
layout: string;
mdxSource: string;
[key: string]: unknown;
}
export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }: Props) => {
const MDXLayout = useMemo(() => getMDXComponent(mdxSource), [mdxSource])
const MDXLayout = useMemo(() => getMDXComponent(mdxSource), [mdxSource]);
return <MDXLayout layout={layout} components={MDXComponents} {...rest} />
}
return <MDXLayout layout={layout} components={MDXComponents} {...rest} />;
};

View File

@ -1,21 +1,21 @@
import { useState } from 'react'
import Link from './Link'
import headerNavLinks from '@/data/headerNavLinks'
import { useState } from 'react';
import Link from './Link';
import headerNavLinks from '@/data/headerNavLinks';
const MobileNav = () => {
const [navShow, setNavShow] = useState(false)
const [navShow, setNavShow] = useState(false);
const onToggleNav = () => {
setNavShow((status) => {
if (status) {
document.body.style.overflow = 'auto'
document.body.style.overflow = 'auto';
} else {
// Prevent scrolling
document.body.style.overflow = 'hidden'
document.body.style.overflow = 'hidden';
}
return !status
})
}
return !status;
});
};
return (
<div className="sm:hidden">
@ -23,14 +23,12 @@ const MobileNav = () => {
type="button"
className="ml-1 mr-1 h-8 w-8 rounded py-1"
aria-label="Toggle Menu"
onClick={onToggleNav}
>
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"
>
className="text-gray-900 dark:text-gray-100">
{navShow ? (
<path
fillRule="evenodd"
@ -49,22 +47,19 @@ const MobileNav = () => {
<div
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'
}`}
>
}`}>
<button
type="button"
aria-label="toggle modal"
className="fixed h-full w-full cursor-auto focus:outline-none"
onClick={onToggleNav}
></button>
onClick={onToggleNav}></button>
<nav className="fixed mt-8 h-full">
{headerNavLinks.map((link) => (
<div key={link.title} className="px-12 py-4">
<Link
href={link.href}
className="text-2xl font-bold tracking-widest text-gray-900 dark:text-gray-100"
onClick={onToggleNav}
>
onClick={onToggleNav}>
{link.title}
</Link>
</div>
@ -72,7 +67,7 @@ const MobileNav = () => {
</nav>
</div>
</div>
)
}
);
};
export default MobileNav
export default MobileNav;

View File

@ -1,15 +1,15 @@
import React, { useRef, useState } from 'react'
import React, { useRef, useState } from 'react';
import siteMetadata from '@/data/siteMetadata'
import siteMetadata from '@/data/siteMetadata';
const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
const inputEl = useRef<HTMLInputElement>(null)
const [error, setError] = useState(false)
const [message, setMessage] = useState('')
const [subscribed, setSubscribed] = useState(false)
const inputEl = useRef<HTMLInputElement>(null);
const [error, setError] = useState(false);
const [message, setMessage] = useState('');
const [subscribed, setSubscribed] = useState(false);
const subscribe = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
e.preventDefault();
const res = await fetch(`/api/${siteMetadata.newsletter.provider}`, {
body: JSON.stringify({
@ -19,24 +19,28 @@ const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
'Content-Type': 'application/json',
},
method: 'POST',
})
});
const { error } = await res.json()
const { error } = await res.json();
if (error) {
setError(true)
setMessage('Your e-mail address is invalid or you are already subscribed!')
return
setError(true);
setMessage(
'Your e-mail address is invalid or you are already subscribed!'
);
return;
}
inputEl.current.value = ''
setError(false)
setSubscribed(true)
setMessage('Successfully! 🎉 You are now subscribed.')
}
inputEl.current.value = '';
setError(false);
setSubscribed(true);
setMessage('Successfully! 🎉 You are now subscribed.');
};
return (
<div>
<div className="pb-1 text-lg font-semibold text-gray-800 dark:text-gray-100">{title}</div>
<div className="pb-1 text-lg font-semibold text-gray-800 dark:text-gray-100">
{title}
</div>
<form className="flex flex-col sm:flex-row" onSubmit={subscribe}>
<div>
<label className="sr-only" htmlFor="email-input">
@ -47,7 +51,9 @@ const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
className="w-72 rounded-md px-4 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-primary-600 dark:bg-black"
id="email-input"
name="email"
placeholder={subscribed ? "You're subscribed ! 🎉" : 'Enter your email'}
placeholder={
subscribed ? "You're subscribed ! 🎉" : 'Enter your email'
}
ref={inputEl}
required
type="email"
@ -57,23 +63,26 @@ const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
<div className="mt-2 flex w-full rounded-md shadow-sm sm:mt-0 sm:ml-3">
<button
className={`w-full rounded-md bg-primary-500 py-2 px-4 font-medium text-white sm:py-0 ${
subscribed ? 'cursor-default' : 'hover:bg-primary-700 dark:hover:bg-primary-400'
subscribed
? 'cursor-default'
: 'hover:bg-primary-700 dark:hover:bg-primary-400'
} focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2 dark:ring-offset-black`}
type="submit"
disabled={subscribed}
>
disabled={subscribed}>
{subscribed ? 'Thank you!' : 'Sign up'}
</button>
</div>
</form>
{error && (
<div className="w-72 pt-2 text-sm text-red-500 dark:text-red-400 sm:w-96">{message}</div>
<div className="w-72 pt-2 text-sm text-red-500 dark:text-red-400 sm:w-96">
{message}
</div>
)}
</div>
)
}
);
};
export default NewsletterForm
export default NewsletterForm;
export const BlogNewsletterForm = ({ title }) => (
<div className="flex items-center justify-center">
@ -81,4 +90,4 @@ export const BlogNewsletterForm = ({ title }) => (
<NewsletterForm title={title} />
</div>
</div>
)
);

View File

@ -1,7 +1,7 @@
import { ReactNode } from 'react'
import { ReactNode } from 'react';
interface Props {
children: ReactNode
children: ReactNode;
}
export default function PageTitle({ children }: Props) {
@ -9,5 +9,5 @@ export default function PageTitle({ children }: Props) {
<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}
</h1>
)
);
}

View File

@ -1,24 +1,29 @@
import Link from '@/components/Link'
import Link from '@/components/Link';
interface Props {
totalPages: number
currentPage: number
totalPages: number;
currentPage: number;
}
export default function Pagination({ totalPages, currentPage }: Props) {
const prevPage = currentPage - 1 > 0
const nextPage = currentPage + 1 <= totalPages
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 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}`}>
<Link
href={
currentPage - 1 === 1 ? `/blog/` : `/blog/page/${currentPage - 1}`
}>
<button>Previous</button>
</Link>
)}
@ -26,7 +31,9 @@ export default function Pagination({ totalPages, currentPage }: Props) {
{currentPage} of {totalPages}
</span>
{!nextPage && (
<button className="cursor-auto disabled:opacity-50" disabled={!nextPage}>
<button
className="cursor-auto disabled:opacity-50"
disabled={!nextPage}>
Next
</button>
)}
@ -37,5 +44,5 @@ export default function Pagination({ totalPages, currentPage }: Props) {
)}
</nav>
</div>
)
);
}

View File

@ -1,31 +1,35 @@
import { useState, useRef, ReactNode } from 'react'
import { useState, useRef, ReactNode } from 'react';
interface Props {
children: ReactNode
children: ReactNode;
}
const Pre = ({ children }: Props) => {
const textInput = useRef(null)
const [hovered, setHovered] = useState(false)
const [copied, setCopied] = useState(false)
const textInput = useRef(null);
const [hovered, setHovered] = useState(false);
const [copied, setCopied] = useState(false);
const onEnter = () => {
setHovered(true)
}
setHovered(true);
};
const onExit = () => {
setHovered(false)
setCopied(false)
}
setHovered(false);
setCopied(false);
};
const onCopy = () => {
setCopied(true)
navigator.clipboard.writeText(textInput.current.textContent)
setCopied(true);
navigator.clipboard.writeText(textInput.current.textContent);
setTimeout(() => {
setCopied(false)
}, 2000)
}
setCopied(false);
}, 2000);
};
return (
<div ref={textInput} onMouseEnter={onEnter} onMouseLeave={onExit} className="relative">
<div
ref={textInput}
onMouseEnter={onEnter}
onMouseLeave={onExit}
className="relative">
{hovered && (
<button
aria-label="Copy code"
@ -35,15 +39,13 @@ const Pre = ({ children }: Props) => {
? 'border-green-400 focus:border-green-400 focus:outline-none'
: 'border-gray-300'
}`}
onClick={onCopy}
>
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'}
>
className={copied ? 'text-green-400' : 'text-gray-300'}>
{copied ? (
<>
<path
@ -69,7 +71,7 @@ const Pre = ({ children }: Props) => {
<pre>{children}</pre>
</div>
)
}
);
};
export default Pre
export default Pre;

View File

@ -1,21 +1,21 @@
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'
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';
interface CommonSEOProps {
title: string
description: string
ogType: string
title: string;
description: string;
ogType: string;
ogImage:
| string
| {
'@type': string
url: string
}[]
twImage: string
canonicalUrl?: string
'@type': string;
url: string;
}[];
twImage: string;
canonicalUrl?: string;
}
const CommonSEO = ({
@ -26,19 +26,24 @@ const CommonSEO = ({
twImage,
canonicalUrl,
}: CommonSEOProps) => {
const router = useRouter()
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: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} />)
ogImage.map(({ url }) => (
<meta property="og:image" content={url} key={url} />
))
) : (
<meta property="og:image" content={ogImage} key={ogImage} />
)}
@ -49,20 +54,24 @@ const CommonSEO = ({
<meta name="twitter:image" content={twImage} />
<link
rel="canonical"
href={canonicalUrl ? canonicalUrl : `${siteMetadata.siteUrl}${router.asPath}`}
href={
canonicalUrl
? canonicalUrl
: `${siteMetadata.siteUrl}${router.asPath}`
}
/>
</Head>
)
}
);
};
interface PageSEOProps {
title: string
description: string
title: string;
description: string;
}
export const PageSEO = ({ title, description }: PageSEOProps) => {
const ogImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner
const twImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner
const ogImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner;
const twImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner;
return (
<CommonSEO
title={title}
@ -71,13 +80,13 @@ export const PageSEO = ({ title, description }: PageSEOProps) => {
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()
const ogImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner;
const twImageUrl = siteMetadata.siteUrl + siteMetadata.socialBanner;
const router = useRouter();
return (
<>
<CommonSEO
@ -96,12 +105,12 @@ export const TagSEO = ({ title, description }: PageSEOProps) => {
/>
</Head>
</>
)
}
);
};
interface BlogSeoProps extends PostFrontMatter {
authorDetails?: AuthorFrontMatter[]
url: string
authorDetails?: AuthorFrontMatter[];
url: string;
}
export const BlogSEO = ({
@ -114,35 +123,35 @@ export const BlogSEO = ({
images = [],
canonicalUrl,
}: BlogSeoProps) => {
const publishedAt = new Date(date).toISOString()
const modifiedAt = new Date(lastmod || date).toISOString()
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
: images;
const featuredImages = imagesArr.map((img) => {
return {
'@type': 'ImageObject',
url: `${siteMetadata.siteUrl}${img}`,
}
})
};
});
let authorList
let authorList;
if (authorDetails) {
authorList = authorDetails.map((author) => {
return {
'@type': 'Person',
name: author.name,
}
})
};
});
} else {
authorList = {
'@type': 'Person',
name: siteMetadata.author,
}
};
}
const structuredData = {
@ -166,9 +175,9 @@ export const BlogSEO = ({
},
},
description: summary,
}
};
const twImageUrl = featuredImages[0].url
const twImageUrl = featuredImages[0].url;
return (
<>
@ -181,8 +190,12 @@ export const BlogSEO = ({
canonicalUrl={canonicalUrl}
/>
<Head>
{date && <meta property="article:published_time" content={publishedAt} />}
{lastmod && <meta property="article:modified_time" content={modifiedAt} />}
{date && (
<meta property="article:published_time" content={publishedAt} />
)}
{lastmod && (
<meta property="article:modified_time" content={modifiedAt} />
)}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
@ -191,5 +204,5 @@ export const BlogSEO = ({
/>
</Head>
</>
)
}
);
};

View File

@ -1,34 +1,34 @@
import { useEffect, useState } from 'react'
import { useEffect, useState } from 'react';
const ScrollTopAndComment = () => {
const [show, setShow] = useState(false)
const [show, setShow] = useState(false);
useEffect(() => {
const handleWindowScroll = () => {
if (window.scrollY > 50) setShow(true)
else setShow(false)
}
if (window.scrollY > 50) setShow(true);
else setShow(false);
};
window.addEventListener('scroll', handleWindowScroll)
return () => window.removeEventListener('scroll', handleWindowScroll)
}, [])
window.addEventListener('scroll', handleWindowScroll);
return () => window.removeEventListener('scroll', handleWindowScroll);
}, []);
const handleScrollTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const handleScrollToComment = () => {
document.getElementById('comment').scrollIntoView()
}
document.getElementById('comment').scrollIntoView();
};
return (
<div
className={`fixed right-8 bottom-8 hidden flex-col gap-3 ${show ? 'md:flex' : 'md:hidden'}`}
>
className={`fixed right-8 bottom-8 hidden flex-col gap-3 ${
show ? 'md:flex' : 'md:hidden'
}`}>
<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"
>
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"
@ -41,8 +41,7 @@ const ScrollTopAndComment = () => {
aria-label="Scroll To Top"
type="button"
onClick={handleScrollTop}
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"
>
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"
@ -52,7 +51,7 @@ const ScrollTopAndComment = () => {
</svg>
</button>
</div>
)
}
);
};
export default ScrollTopAndComment
export default ScrollTopAndComment;

View File

@ -1,9 +1,13 @@
import { ReactNode } from 'react'
import { ReactNode } from 'react';
interface Props {
children: ReactNode
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>
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,12 +1,12 @@
import { Toc } from 'types/Toc'
import { Toc } from 'types/Toc';
interface TOCInlineProps {
toc: Toc
indentDepth?: number
fromHeading?: number
toHeading?: number
asDisclosure?: boolean
exclude?: string | string[]
toc: Toc;
indentDepth?: number;
fromHeading?: number;
toHeading?: number;
asDisclosure?: boolean;
exclude?: string | string[];
}
/**
@ -34,35 +34,41 @@ const TOCInline = ({
}: TOCInlineProps) => {
const re = Array.isArray(exclude)
? new RegExp('^(' + exclude.join('|') + ')$', 'i')
: new RegExp('^(' + exclude + ')$', 'i')
: new RegExp('^(' + exclude + ')$', 'i');
const filteredToc = toc.filter(
(heading) =>
heading.depth >= fromHeading && heading.depth <= toHeading && !re.test(heading.value)
)
heading.depth >= fromHeading &&
heading.depth <= toHeading &&
!re.test(heading.value)
);
const tocList = (
<ul>
{filteredToc.map((heading) => (
<li key={heading.value} className={`${heading.depth >= indentDepth && 'ml-6'}`}>
<li
key={heading.value}
className={`${heading.depth >= indentDepth && 'ml-6'}`}>
<a href={heading.url}>{heading.value}</a>
</li>
))}
</ul>
)
);
return (
<>
{asDisclosure ? (
<details open>
<summary className="ml-6 pt-2 pb-2 text-xl font-bold">Table of Contents</summary>
<summary className="ml-6 pt-2 pb-2 text-xl font-bold">
Table of Contents
</summary>
<div className="ml-6">{tocList}</div>
</details>
) : (
tocList
)}
</>
)
}
);
};
export default TOCInline
export default TOCInline;

View File

@ -1,8 +1,8 @@
import Link from 'next/link'
import kebabCase from '@/lib/utils/kebabCase'
import Link from 'next/link';
import kebabCase from '@/lib/utils/kebabCase';
interface Props {
text: string
text: string;
}
const Tag = ({ text }: Props) => {
@ -12,7 +12,7 @@ const Tag = ({ text }: Props) => {
{text.split(' ').join('-')}
</a>
</Link>
)
}
);
};
export default Tag
export default Tag;

View File

@ -1,26 +1,28 @@
import { useEffect, useState } from 'react'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react';
import { useTheme } from 'next-themes';
const ThemeSwitch = () => {
const [mounted, setMounted] = useState(false)
const { theme, setTheme, resolvedTheme } = useTheme()
const [mounted, setMounted] = useState(false);
const { theme, setTheme, resolvedTheme } = useTheme();
// When mounted on client, now we can show the UI
useEffect(() => setMounted(true), [])
useEffect(() => setMounted(true), []);
return (
<button
aria-label="Toggle Dark Mode"
type="button"
className="ml-1 mr-1 h-8 w-8 rounded p-1 sm:ml-4"
onClick={() => setTheme(theme === 'dark' || resolvedTheme === 'dark' ? 'light' : 'dark')}
>
onClick={() =>
setTheme(
theme === 'dark' || resolvedTheme === 'dark' ? 'light' : 'dark'
)
}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="text-gray-900 dark:text-gray-100"
>
className="text-gray-900 dark:text-gray-100">
{mounted && (theme === 'dark' || resolvedTheme === 'dark') ? (
<path
fillRule="evenodd"
@ -32,7 +34,7 @@ const ThemeSwitch = () => {
)}
</svg>
</button>
)
}
);
};
export default ThemeSwitch
export default ThemeSwitch;

View File

@ -1,6 +1,6 @@
import Script from 'next/script'
import Script from 'next/script';
import siteMetadata from '@/data/siteMetadata'
import siteMetadata from '@/data/siteMetadata';
const GAScript = () => {
return (
@ -21,10 +21,10 @@ const GAScript = () => {
`}
</Script>
</>
)
}
);
};
export default GAScript
export default GAScript;
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
export const logEvent = (action, category, label, value) => {
@ -32,5 +32,5 @@ export const logEvent = (action, category, label, value) => {
event_category: category,
event_label: label,
value: value,
})
}
});
};

View File

@ -1,6 +1,6 @@
import Script from 'next/script'
import Script from 'next/script';
import siteMetadata from '@/data/siteMetadata'
import siteMetadata from '@/data/siteMetadata';
const PlausibleScript = () => {
return (
@ -16,12 +16,12 @@ const PlausibleScript = () => {
`}
</Script>
</>
)
}
);
};
export default PlausibleScript
export default PlausibleScript;
// https://plausible.io/docs/custom-event-goals
export const logEvent = (eventName, ...rest) => {
return window.plausible?.(eventName, ...rest)
}
return window.plausible?.(eventName, ...rest);
};

View File

@ -1,4 +1,4 @@
import Script from 'next/script'
import Script from 'next/script';
const SimpleAnalyticsScript = () => {
return (
@ -8,18 +8,21 @@ const SimpleAnalyticsScript = () => {
window.sa_event=window.sa_event||function(){var a=[].slice.call(arguments);window.sa_event.q?window.sa_event.q.push(a):window.sa_event.q=[a]};
`}
</Script>
<Script strategy="lazyOnload" src="https://scripts.simpleanalyticscdn.com/latest.js" />
<Script
strategy="lazyOnload"
src="https://scripts.simpleanalyticscdn.com/latest.js"
/>
</>
)
}
);
};
// https://docs.simpleanalytics.com/events
export const logEvent = (eventName, callback) => {
if (callback) {
return window.sa_event?.(eventName, callback)
return window.sa_event?.(eventName, callback);
} else {
return window.sa_event?.(eventName)
return window.sa_event?.(eventName);
}
}
};
export default SimpleAnalyticsScript
export default SimpleAnalyticsScript;

View File

@ -1,6 +1,6 @@
import Script from 'next/script'
import Script from 'next/script';
import siteMetadata from '@/data/siteMetadata'
import siteMetadata from '@/data/siteMetadata';
const UmamiScript = () => {
return (
@ -12,7 +12,7 @@ const UmamiScript = () => {
src="https://umami.example.com/umami.js" // Replace with your umami instance
/>
</>
)
}
);
};
export default UmamiScript
export default UmamiScript;

View File

@ -1,28 +1,32 @@
import GA from './GoogleAnalytics'
import Plausible from './Plausible'
import SimpleAnalytics from './SimpleAnalytics'
import Umami from './Umami'
import siteMetadata from '@/data/siteMetadata'
import GA from './GoogleAnalytics';
import Plausible from './Plausible';
import SimpleAnalytics from './SimpleAnalytics';
import Umami from './Umami';
import siteMetadata from '@/data/siteMetadata';
declare global {
interface Window {
gtag?: (...args: any[]) => void
plausible?: (...args: any[]) => void
sa_event?: (...args: any[]) => void
gtag?: (...args: any[]) => void;
plausible?: (...args: any[]) => void;
sa_event?: (...args: any[]) => void;
}
}
const isProduction = process.env.NODE_ENV === 'production'
const isProduction = process.env.NODE_ENV === 'production';
const Analytics = () => {
return (
<>
{isProduction && siteMetadata.analytics.plausibleDataDomain && <Plausible />}
{isProduction && siteMetadata.analytics.simpleAnalytics && <SimpleAnalytics />}
{isProduction && siteMetadata.analytics.plausibleDataDomain && (
<Plausible />
)}
{isProduction && siteMetadata.analytics.simpleAnalytics && (
<SimpleAnalytics />
)}
{isProduction && siteMetadata.analytics.umamiWebsiteId && <Umami />}
{isProduction && siteMetadata.analytics.googleAnalyticsId && <GA />}
</>
)
}
);
};
export default Analytics
export default Analytics;

View File

@ -1,30 +1,33 @@
import React, { useMemo, useState } from 'react'
import React, { useMemo, useState } from 'react';
import siteMetadata from '@/data/siteMetadata'
import { PostFrontMatter } from 'types/PostFrontMatter'
import { useTheme } from 'next-themes'
import ReactCommento from './commento/ReactCommento'
import siteMetadata from '@/data/siteMetadata';
import { PostFrontMatter } from 'types/PostFrontMatter';
import { useTheme } from 'next-themes';
import ReactCommento from './commento/ReactCommento';
interface Props {
frontMatter: PostFrontMatter
frontMatter: PostFrontMatter;
}
const Commento = ({ frontMatter }: Props) => {
const { resolvedTheme } = useTheme()
const { resolvedTheme } = useTheme();
const commentsTheme = useMemo(() => {
switch (resolvedTheme) {
case 'light':
case 'dark':
return resolvedTheme
return resolvedTheme;
default:
return 'auto'
return 'auto';
}
}, [resolvedTheme])
}, [resolvedTheme]);
return (
<div className="my-2">
<ReactCommento url={siteMetadata.comment.commentoConfig.url} pageId={frontMatter.slug} />
<ReactCommento
url={siteMetadata.comment.commentoConfig.url}
pageId={frontMatter.slug}
/>
</div>
)
}
);
};
export default Commento
export default Commento;

View File

@ -1,25 +1,25 @@
import React, { useMemo, useState } from 'react'
import React, { useMemo, useState } from 'react';
import siteMetadata from '@/data/siteMetadata'
import { PostFrontMatter } from 'types/PostFrontMatter'
import { ReactCusdis } from 'react-cusdis'
import { useTheme } from 'next-themes'
import siteMetadata from '@/data/siteMetadata';
import { PostFrontMatter } from 'types/PostFrontMatter';
import { ReactCusdis } from 'react-cusdis';
import { useTheme } from 'next-themes';
interface Props {
frontMatter: PostFrontMatter
frontMatter: PostFrontMatter;
}
const Cusdis = ({ frontMatter }: Props) => {
const { resolvedTheme } = useTheme()
const { resolvedTheme } = useTheme();
const commentsTheme = useMemo(() => {
switch (resolvedTheme) {
case 'light':
case 'dark':
return resolvedTheme
return resolvedTheme;
default:
return 'auto'
return 'auto';
}
}, [resolvedTheme])
}, [resolvedTheme]);
return (
<div className="my-2">
<ReactCusdis
@ -35,7 +35,7 @@ const Cusdis = ({ frontMatter }: Props) => {
}}
/>
</div>
)
}
);
};
export default Cusdis
export default Cusdis;

View File

@ -1,46 +1,51 @@
import React, { useState } from 'react'
import React, { useState } from 'react';
import siteMetadata from '@/data/siteMetadata'
import { PostFrontMatter } from 'types/PostFrontMatter'
import siteMetadata from '@/data/siteMetadata';
import { PostFrontMatter } from 'types/PostFrontMatter';
interface Props {
frontMatter: PostFrontMatter
frontMatter: PostFrontMatter;
}
const Disqus = ({ frontMatter }: Props) => {
const [enableLoadComments, setEnabledLoadComments] = useState(true)
const [enableLoadComments, setEnabledLoadComments] = useState(true);
const COMMENTS_ID = 'disqus_thread'
const COMMENTS_ID = 'disqus_thread';
function LoadComments() {
setEnabledLoadComments(false)
setEnabledLoadComments(false);
// @ts-ignore
window.disqus_config = function () {
this.page.url = window.location.href
this.page.identifier = frontMatter.slug
}
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'
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)
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 })
window.DISQUS.reset({ reload: true });
}
}
return (
<div className="pt-6 pb-6 text-center text-gray-700 dark:text-gray-300">
{enableLoadComments && <button onClick={LoadComments}>Load Comments</button>}
{enableLoadComments && (
<button onClick={LoadComments}>Load Comments</button>
)}
<div className="disqus-frame" id={COMMENTS_ID} />
</div>
)
}
);
};
export default Disqus
export default Disqus;

View File

@ -1,61 +1,78 @@
import React, { useState, useEffect, useCallback } from 'react'
import { useTheme } from 'next-themes'
import React, { useState, useEffect, useCallback } from 'react';
import { useTheme } from 'next-themes';
import siteMetadata from '@/data/siteMetadata'
import siteMetadata from '@/data/siteMetadata';
interface Props {
mapping: string
mapping: string;
}
const Giscus = ({ mapping }: Props) => {
const [enableLoadComments, setEnabledLoadComments] = useState(true)
const { theme, resolvedTheme } = useTheme()
const [enableLoadComments, setEnabledLoadComments] = useState(true);
const { theme, resolvedTheme } = useTheme();
const commentsTheme =
siteMetadata.comment.giscusConfig.themeURL === ''
? theme === 'dark' || resolvedTheme === 'dark'
? siteMetadata.comment.giscusConfig.darkTheme
: siteMetadata.comment.giscusConfig.theme
: siteMetadata.comment.giscusConfig.themeURL
: siteMetadata.comment.giscusConfig.themeURL;
const COMMENTS_ID = 'comments-container'
const COMMENTS_ID = 'comments-container';
const LoadComments = useCallback(() => {
setEnabledLoadComments(false)
const script = document.createElement('script')
script.src = 'https://giscus.app/client.js'
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', 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
setEnabledLoadComments(false);
const script = document.createElement('script');
script.src = 'https://giscus.app/client.js';
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',
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;
const comments = document.getElementById(COMMENTS_ID)
if (comments) comments.appendChild(script)
const comments = document.getElementById(COMMENTS_ID);
if (comments) comments.appendChild(script);
return () => {
const comments = document.getElementById(COMMENTS_ID)
if (comments) comments.innerHTML = ''
}
}, [commentsTheme, mapping])
const comments = document.getElementById(COMMENTS_ID);
if (comments) comments.innerHTML = '';
};
}, [commentsTheme, mapping]);
// Reload on theme change
useEffect(() => {
const iframe = document.querySelector('iframe.giscus-frame')
if (!iframe) return
LoadComments()
}, [LoadComments])
const iframe = document.querySelector('iframe.giscus-frame');
if (!iframe) return;
LoadComments();
}, [LoadComments]);
return (
<div className="pt-6 pb-6 text-center text-gray-700 dark:text-gray-300">
{enableLoadComments && <button onClick={LoadComments}>Load Comments</button>}
{enableLoadComments && (
<button onClick={LoadComments}>Load Comments</button>
)}
<div className="giscus" id={COMMENTS_ID} />
</div>
)
}
);
};
export default Giscus
export default Giscus;

View File

@ -1,56 +1,58 @@
import React, { useState, useEffect, useCallback } from 'react'
import { useTheme } from 'next-themes'
import React, { useState, useEffect, useCallback } from 'react';
import { useTheme } from 'next-themes';
import siteMetadata from '@/data/siteMetadata'
import siteMetadata from '@/data/siteMetadata';
interface Props {
issueTerm: string
issueTerm: string;
}
const Utterances = ({ issueTerm }: Props) => {
const [enableLoadComments, setEnabledLoadComments] = useState(true)
const { theme, resolvedTheme } = useTheme()
const [enableLoadComments, setEnabledLoadComments] = useState(true);
const { theme, resolvedTheme } = useTheme();
const commentsTheme =
theme === 'dark' || resolvedTheme === 'dark'
? siteMetadata.comment.utterancesConfig.darkTheme
: siteMetadata.comment.utterancesConfig.theme
: siteMetadata.comment.utterancesConfig.theme;
const COMMENTS_ID = 'comments-container'
const COMMENTS_ID = 'comments-container';
const LoadComments = useCallback(() => {
setEnabledLoadComments(false)
const script = document.createElement('script')
script.src = 'https://utteranc.es/client.js'
script.setAttribute('repo', siteMetadata.comment.utterancesConfig.repo)
script.setAttribute('issue-term', issueTerm)
script.setAttribute('label', siteMetadata.comment.utterancesConfig.label)
script.setAttribute('theme', commentsTheme)
script.setAttribute('crossorigin', 'anonymous')
script.async = true
setEnabledLoadComments(false);
const script = document.createElement('script');
script.src = 'https://utteranc.es/client.js';
script.setAttribute('repo', siteMetadata.comment.utterancesConfig.repo);
script.setAttribute('issue-term', issueTerm);
script.setAttribute('label', siteMetadata.comment.utterancesConfig.label);
script.setAttribute('theme', commentsTheme);
script.setAttribute('crossorigin', 'anonymous');
script.async = true;
const comments = document.getElementById(COMMENTS_ID)
if (comments) comments.appendChild(script)
const comments = document.getElementById(COMMENTS_ID);
if (comments) comments.appendChild(script);
return () => {
const comments = document.getElementById(COMMENTS_ID)
if (comments) comments.innerHTML = ''
}
}, [commentsTheme, issueTerm])
const comments = document.getElementById(COMMENTS_ID);
if (comments) comments.innerHTML = '';
};
}, [commentsTheme, issueTerm]);
// Reload on theme change
useEffect(() => {
const iframe = document.querySelector('iframe.utterances-frame')
if (!iframe) return
LoadComments()
}, [LoadComments])
const iframe = document.querySelector('iframe.utterances-frame');
if (!iframe) return;
LoadComments();
}, [LoadComments]);
// Added `relative` to fix a weird bug with `utterances-frame` position
return (
<div className="pt-6 pb-6 text-center text-gray-700 dark:text-gray-300">
{enableLoadComments && <button onClick={LoadComments}>Load Comments</button>}
{enableLoadComments && (
<button onClick={LoadComments}>Load Comments</button>
)}
<div className="utterances-frame relative" id={COMMENTS_ID} />
</div>
)
}
);
};
export default Utterances
export default Utterances;

View File

@ -1,8 +1,8 @@
import { createRef } from 'preact'
import React, { useLayoutEffect, useMemo, useRef } from 'react'
import { createRef } from 'preact';
import React, { useLayoutEffect, useMemo, useRef } from 'react';
interface DataAttributes {
[key: string]: string | boolean | undefined
[key: string]: string | boolean | undefined;
}
const insertScript = (
@ -11,28 +11,28 @@ const insertScript = (
dataAttributes: DataAttributes,
onload = () => {}
) => {
const script = window.document.createElement('script')
script.async = true
script.src = src
script.id = id
const script = window.document.createElement('script');
script.async = true;
script.src = src;
script.id = id;
if (document.getElementById(id)) {
return
return;
}
script.addEventListener('load', onload, { capture: true, once: true })
script.addEventListener('load', onload, { capture: true, once: true });
Object.entries(dataAttributes).forEach(([key, value]) => {
if (value === undefined) {
return
return;
}
script.setAttribute(`data-${key}`, value.toString())
})
script.setAttribute(`data-${key}`, value.toString());
});
document.body.appendChild(script)
document.body.appendChild(script);
return () => {
script.remove()
}
}
script.remove();
};
};
const ReactCommento = ({
url,
@ -42,22 +42,25 @@ const ReactCommento = ({
hideDeleted,
pageId,
}: {
url: string
cssOverride?: string
autoInit?: boolean
noFonts?: boolean
hideDeleted?: boolean
pageId?: string
url: string;
cssOverride?: string;
autoInit?: boolean;
noFonts?: boolean;
hideDeleted?: boolean;
pageId?: string;
}) => {
const containerId = useMemo(() => `commento-${Math.random().toString().slice(2, 8)}`, [])
const container = createRef<HTMLDivElement>()
const containerId = useMemo(
() => `commento-${Math.random().toString().slice(2, 8)}`,
[]
);
const container = createRef<HTMLDivElement>();
useLayoutEffect(() => {
if (!window) {
return
return;
}
window['commento'] = container.current
window['commento'] = container.current;
const removeScript = insertScript(
url,
@ -71,11 +74,20 @@ const ReactCommento = ({
'id-root': containerId,
},
() => {
removeScript()
removeScript();
}
)
}, [autoInit, cssOverride, hideDeleted, noFonts, pageId, url, containerId, container])
);
}, [
autoInit,
cssOverride,
hideDeleted,
noFonts,
pageId,
url,
containerId,
container,
]);
return <div ref={container} id={containerId} />
}
export default ReactCommento
return <div ref={container} id={containerId} />;
};
export default ReactCommento;

View File

@ -1,68 +1,69 @@
import React from 'react'
import React from 'react';
import siteMetadata from '@/data/siteMetadata'
import dynamic from 'next/dynamic'
import { PostFrontMatter } from 'types/PostFrontMatter'
import siteMetadata from '@/data/siteMetadata';
import dynamic from 'next/dynamic';
import { PostFrontMatter } from 'types/PostFrontMatter';
interface Props {
frontMatter: PostFrontMatter
frontMatter: PostFrontMatter;
}
const UtterancesComponent = dynamic(
() => {
return import('@/components/comments/Utterances')
return import('@/components/comments/Utterances');
},
{ ssr: false }
)
);
const GiscusComponent = dynamic(
() => {
return import('@/components/comments/Giscus')
return import('@/components/comments/Giscus');
},
{ ssr: false }
)
);
const DisqusComponent = dynamic(
() => {
return import('@/components/comments/Disqus')
return import('@/components/comments/Disqus');
},
{ ssr: false }
)
);
const CusdisComponent = dynamic(
() => {
return import('@/components/comments/Cusdis')
return import('@/components/comments/Cusdis');
},
{ ssr: false }
)
);
const CommentoComponent = dynamic(
() => {
return import('@/components/comments/Commento')
return import('@/components/comments/Commento');
},
{ ssr: false }
)
);
const Comments = ({ frontMatter }: Props) => {
let term
let term;
switch (
siteMetadata.comment.giscusConfig.mapping ||
siteMetadata.comment.utterancesConfig.issueTerm
) {
case 'pathname':
term = frontMatter.slug
break
term = frontMatter.slug;
break;
case 'url':
term = window.location.href
break
term = window.location.href;
break;
case 'title':
term = frontMatter.title
break
term = frontMatter.title;
break;
}
return (
<div id="comment">
{siteMetadata.comment && siteMetadata.comment.provider === 'giscus' && (
<GiscusComponent mapping={term} />
)}
{siteMetadata.comment && siteMetadata.comment.provider === 'utterances' && (
<UtterancesComponent issueTerm={term} />
)}
{siteMetadata.comment &&
siteMetadata.comment.provider === 'utterances' && (
<UtterancesComponent issueTerm={term} />
)}
{siteMetadata.comment && siteMetadata.comment.provider === 'disqus' && (
<DisqusComponent frontMatter={frontMatter} />
)}
@ -73,7 +74,7 @@ const Comments = ({ frontMatter }: Props) => {
<CommentoComponent frontMatter={frontMatter} />
)}
</div>
)
}
);
};
export default Comments
export default Comments;

View File

@ -1,9 +1,9 @@
import Mail from './mail.svg'
import Github from './github.svg'
import Facebook from './facebook.svg'
import Youtube from './youtube.svg'
import Linkedin from './linkedin.svg'
import Twitter from './twitter.svg'
import Mail from './mail.svg';
import Github from './github.svg';
import Facebook from './facebook.svg';
import Youtube from './youtube.svg';
import Linkedin from './linkedin.svg';
import Twitter from './twitter.svg';
// Icons taken from: https://simpleicons.org/
@ -14,27 +14,30 @@ const components = {
youtube: Youtube,
linkedin: Linkedin,
twitter: Twitter,
}
};
const SocialIcon = ({ kind, href, size = 8 }) => {
if (!href || (kind === 'mail' && !/^mailto:\w+([.-]?\w+)@\w+([.-]?\w+)(.\w{2,3})+$/.test(href)))
return null
if (
!href ||
(kind === 'mail' &&
!/^mailto:\w+([.-]?\w+)@\w+([.-]?\w+)(.\w{2,3})+$/.test(href))
)
return null;
const SocialSvg = components[kind]
const SocialSvg = components[kind];
return (
<a
className="text-sm text-gray-500 transition hover:text-gray-600"
target="_blank"
rel="noopener noreferrer"
href={href}
>
href={href}>
<span className="sr-only">{kind}</span>
<SocialSvg
className={`fill-current text-gray-700 hover:text-blue-500 dark:text-gray-200 dark:hover:text-blue-400 h-${size} w-${size}`}
/>
</a>
)
}
);
};
export default SocialIcon
export default SocialIcon;