upstream #1

Merged
jblu merged 1007 commits from upstream into main 2024-11-04 22:35:57 -06:00
39 changed files with 1146 additions and 3559 deletions
Showing only changes of commit 58f77e8db1 - Show all commits

View File

@ -30,6 +30,15 @@ Feature request? Check the past discussions to see if it has been brought up pre
- [fiqrychoerudin.dev](https://www.fiqrychoerudin.dev/) - simple portfolio. - [fiqrychoerudin.dev](https://www.fiqrychoerudin.dev/) - simple portfolio.
- [irvin.dev](https://www.irvin.dev/) - Irvin Lin's personal site. Added YouTube embedding. - [irvin.dev](https://www.irvin.dev/) - Irvin Lin's personal site. Added YouTube embedding.
- [the all JavaScript Blog](https://the-all-javascript-blog.vercel.app/) - a JavaScript enlightenment blog uses this - [the all JavaScript Blog](https://the-all-javascript-blog.vercel.app/) - a JavaScript enlightenment blog uses this
- [KirillSo.com](https://www.kirillso.com/) - Personal blog & website.
- [DevBoy Blog](https://devboy.vercel.app/) - M.Reza's personal blog
- [slightlysharpe.com](https://slightlysharpe.com) - [Tincre's](https://tincre.com) main company blog
- [blog.b00st.com](https://blog.b00st.com) - [b00st.com's](https://b00st.com) main music promotion blog
- [astrosaurus.me](https://astrosaurus.me/) - Ephraim Atta-Duncan's Personal Blog
- [dhanrajsp.me](https://dhanrajsp.me/) - Dhanraj's personal site and blog.
- [blog.r00ks.io](https://blog.r00ks.io/) - Austin Rooks's personal blog ([source code](https://github.com/Austionian/blog.r00ks)).
- [honghong.me](https://honghong.me) - Tszhong's personal website ([source code](https://github.com/tszhong0411/home))
- [alfoncode.com](https://alfoncode.com) - Alfonso García's personar website. Customized design ([source code](https://github.com/alfoncode/personal-web))
Using the template? Feel free to create a PR and add your blog to this list. Using the template? Feel free to create a PR and add your blog to this list.
@ -46,6 +55,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea
- Lightweight, 45kB first load JS, uses Preact in production build - Lightweight, 45kB first load JS, uses Preact in production build
- Mobile-friendly view - Mobile-friendly view
- Light and dark theme - Light and dark theme
- Self-hosted font with [Fontsource](https://fontsource.org/)
- Supports [plausible](https://plausible.io/), [simple analytics](https://simpleanalytics.com/) and google analytics - Supports [plausible](https://plausible.io/), [simple analytics](https://simpleanalytics.com/) and google analytics
- [MDX - write JSX in markdown documents!](https://mdxjs.com/) - [MDX - write JSX in markdown documents!](https://mdxjs.com/)
- Server-side syntax highlighting with line numbers and line highlighting via [rehype-prism-plus](https://github.com/timlrx/rehype-prism-plus) - Server-side syntax highlighting with line numbers and line highlighting via [rehype-prism-plus](https://github.com/timlrx/rehype-prism-plus)
@ -88,11 +98,13 @@ npx degit timlrx/tailwind-nextjs-starter-blog#typescript
``` ```
2. Personalize `siteMetadata.js` (site related information) 2. Personalize `siteMetadata.js` (site related information)
3. Personalize `authors/default.md` (main author) 3. Modify the content security policy in `next.config.js` if you want to use
4. Modify `projectsData.js` any analytics provider or a commenting solution other than giscus.
5. Modify `headerNavLinks.js` to customize navigation links 4. Personalize `authors/default.md` (main author)
6. Add blog posts 5. Modify `projectsData.js`
7. Deploy on Vercel 6. Modify `headerNavLinks.js` to customize navigation links
7. Add blog posts
8. Deploy on Vercel
## Installation ## Installation

View File

@ -2,11 +2,11 @@ import Image from './Image'
import Link from './Link' import Link from './Link'
const Card = ({ title, description, imgSrc, href }) => ( const Card = ({ title, description, imgSrc, href }) => (
<div className="p-4 md:w-1/2 md" style={{ maxWidth: '544px' }}> <div className="md p-4 md:w-1/2" style={{ maxWidth: '544px' }}>
<div <div
className={`${ className={`${
imgSrc && 'h-full' imgSrc && 'h-full'
} overflow-hidden border-2 border-gray-200 rounded-md border-opacity-60 dark:border-gray-700`} } overflow-hidden rounded-md border-2 border-gray-200 border-opacity-60 dark:border-gray-700`}
> >
{imgSrc && {imgSrc &&
(href ? ( (href ? (
@ -14,7 +14,7 @@ const Card = ({ title, description, imgSrc, href }) => (
<Image <Image
alt={title} alt={title}
src={imgSrc} src={imgSrc}
className="object-cover object-center lg:h-48 md:h-36" className="object-cover object-center md:h-36 lg:h-48"
width={544} width={544}
height={306} height={306}
/> />
@ -23,7 +23,7 @@ const Card = ({ title, description, imgSrc, href }) => (
<Image <Image
alt={title} alt={title}
src={imgSrc} src={imgSrc}
className="object-cover object-center lg:h-48 md:h-36" className="object-cover object-center md:h-36 lg:h-48"
width={544} width={544}
height={306} height={306}
/> />
@ -38,7 +38,7 @@ const Card = ({ title, description, imgSrc, href }) => (
title title
)} )}
</h2> </h2>
<p className="mb-3 prose text-gray-500 max-w-none dark:text-gray-400">{description}</p> <p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">{description}</p>
{href && ( {href && (
<Link <Link
href={href} href={href}

View File

@ -5,8 +5,8 @@ import SocialIcon from '@/components/social-icons'
export default function Footer() { export default function Footer() {
return ( return (
<footer> <footer>
<div className="flex flex-col items-center mt-16"> <div className="mt-16 flex flex-col items-center">
<div className="flex mb-3 space-x-4"> <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="github" href={siteMetadata.github} size="6" />
<SocialIcon kind="facebook" href={siteMetadata.facebook} size="6" /> <SocialIcon kind="facebook" href={siteMetadata.facebook} size="6" />
@ -14,7 +14,7 @@ export default function Footer() {
<SocialIcon kind="linkedin" href={siteMetadata.linkedin} size="6" /> <SocialIcon kind="linkedin" href={siteMetadata.linkedin} size="6" />
<SocialIcon kind="twitter" href={siteMetadata.twitter} size="6" /> <SocialIcon kind="twitter" href={siteMetadata.twitter} size="6" />
</div> </div>
<div className="flex mb-2 space-x-2 text-sm text-gray-500 dark:text-gray-400"> <div className="mb-2 flex space-x-2 text-sm text-gray-500 dark:text-gray-400">
<div>{siteMetadata.author}</div> <div>{siteMetadata.author}</div>
<div>{``}</div> <div>{``}</div>
<div>{`© ${new Date().getFullYear()}`}</div> <div>{`© ${new Date().getFullYear()}`}</div>

View File

@ -10,7 +10,7 @@ import ThemeSwitch from './ThemeSwitch'
const LayoutWrapper = ({ children }) => { const LayoutWrapper = ({ children }) => {
return ( return (
<SectionContainer> <SectionContainer>
<div className="flex flex-col justify-between h-screen"> <div className="flex h-screen flex-col justify-between">
<header className="flex items-center justify-between py-10"> <header className="flex items-center justify-between py-10">
<div> <div>
<Link href="/" aria-label={siteMetadata.headerTitle}> <Link href="/" aria-label={siteMetadata.headerTitle}>
@ -34,7 +34,7 @@ const LayoutWrapper = ({ children }) => {
<Link <Link
key={link.title} key={link.title}
href={link.href} href={link.href}
className="p-1 font-medium text-gray-900 sm:p-4 dark:text-gray-100" className="p-1 font-medium text-gray-900 dark:text-gray-100 sm:p-4"
> >
{link.title} {link.title}
</Link> </Link>

View File

@ -21,7 +21,7 @@ const MobileNav = () => {
<div className="sm:hidden"> <div className="sm:hidden">
<button <button
type="button" type="button"
className="w-8 h-8 py-1 ml-1 mr-1 rounded" className="ml-1 mr-1 h-8 w-8 rounded py-1"
aria-label="Toggle Menu" aria-label="Toggle Menu"
onClick={onToggleNav} onClick={onToggleNav}
> >
@ -47,17 +47,17 @@ const MobileNav = () => {
</svg> </svg>
</button> </button>
<div <div
className={`fixed w-full h-full top-24 right-0 bg-gray-200 dark:bg-gray-800 opacity-95 z-10 transform ease-in-out duration-300 ${ 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' navShow ? 'translate-x-0' : 'translate-x-full'
}`} }`}
> >
<button <button
type="button" type="button"
aria-label="toggle modal" aria-label="toggle modal"
className="fixed w-full h-full cursor-auto focus:outline-none" className="fixed h-full w-full cursor-auto focus:outline-none"
onClick={onToggleNav} onClick={onToggleNav}
></button> ></button>
<nav className="fixed h-full mt-8"> <nav className="fixed mt-8 h-full">
{headerNavLinks.map((link) => ( {headerNavLinks.map((link) => (
<div key={link.title} className="px-12 py-4"> <div key={link.title} className="px-12 py-4">
<Link <Link

View File

@ -44,7 +44,7 @@ const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
</label> </label>
<input <input
autoComplete="email" autoComplete="email"
className="px-4 rounded-md w-72 dark:bg-black focus:outline-none focus:ring-2 focus:border-transparent focus:ring-primary-600" 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" id="email-input"
name="email" name="email"
placeholder={subscribed ? "You're subscribed ! 🎉" : 'Enter your email'} placeholder={subscribed ? "You're subscribed ! 🎉" : 'Enter your email'}
@ -54,11 +54,11 @@ const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
disabled={subscribed} disabled={subscribed}
/> />
</div> </div>
<div className="flex w-full mt-2 rounded-md shadow-sm sm:mt-0 sm:ml-3"> <div className="mt-2 flex w-full rounded-md shadow-sm sm:mt-0 sm:ml-3">
<button <button
className={`py-2 sm:py-0 w-full bg-primary-500 px-4 rounded-md font-medium text-white ${ 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-offset-2 focus:ring-primary-600 dark:ring-offset-black`} } focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2 dark:ring-offset-black`}
type="submit" type="submit"
disabled={subscribed} disabled={subscribed}
> >
@ -67,7 +67,7 @@ const NewsletterForm = ({ title = 'Subscribe to the newsletter' }) => {
</div> </div>
</form> </form>
{error && ( {error && (
<div className="pt-2 text-sm text-red-500 w-72 sm:w-96 dark:text-red-400">{message}</div> <div className="w-72 pt-2 text-sm text-red-500 dark:text-red-400 sm:w-96">{message}</div>
)} )}
</div> </div>
) )
@ -77,7 +77,7 @@ export default NewsletterForm
export const BlogNewsletterForm = ({ title }) => ( export const BlogNewsletterForm = ({ title }) => (
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
<div className="p-6 bg-gray-100 dark:bg-gray-800 sm:px-14 sm:py-8"> <div className="bg-gray-100 p-6 dark:bg-gray-800 sm:px-14 sm:py-8">
<NewsletterForm title={title} /> <NewsletterForm title={title} />
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@ export default function Pagination({ totalPages, currentPage }) {
const nextPage = parseInt(currentPage) + 1 <= parseInt(totalPages) const nextPage = parseInt(currentPage) + 1 <= parseInt(totalPages)
return ( return (
<div className="pt-6 pb-8 space-y-2 md:space-y-5"> <div className="space-y-2 pt-6 pb-8 md:space-y-5">
<nav className="flex justify-between"> <nav className="flex justify-between">
{!prevPage && ( {!prevPage && (
<button rel="previous" className="cursor-auto disabled:opacity-50" disabled={!prevPage}> <button rel="previous" className="cursor-auto disabled:opacity-50" disabled={!prevPage}>

View File

@ -26,9 +26,9 @@ const Pre = (props) => {
<button <button
aria-label="Copy code" aria-label="Copy code"
type="button" type="button"
className={`absolute right-2 top-2 w-8 h-8 p-1 rounded border-2 bg-gray-700 dark:bg-gray-800 ${ className={`absolute right-2 top-2 h-8 w-8 rounded border-2 bg-gray-700 p-1 dark:bg-gray-800 ${
copied copied
? 'focus:outline-none focus:border-green-400 border-green-400' ? 'border-green-400 focus:border-green-400 focus:outline-none'
: 'border-gray-300' : 'border-gray-300'
}`} }`}
onClick={onCopy} onClick={onCopy}

View File

@ -23,15 +23,15 @@ const ScrollTopAndComment = () => {
} }
return ( return (
<div <div
className={`fixed flex-col hidden gap-3 right-8 bottom-8 ${show ? 'md:flex' : 'md:hidden'}`} className={`fixed right-8 bottom-8 hidden flex-col gap-3 ${show ? 'md:flex' : 'md:hidden'}`}
> >
<button <button
aria-label="Scroll To Comment" aria-label="Scroll To Comment"
type="button" type="button"
onClick={handleScrollToComment} onClick={handleScrollToComment}
className="p-2 text-gray-500 transition-all bg-gray-200 rounded-full dark:text-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 hover:bg-gray-300" 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="w-5 h-5" viewBox="0 0 20 20" fill="currentColor"> <svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path <path
fillRule="evenodd" 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" 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"
@ -43,9 +43,9 @@ const ScrollTopAndComment = () => {
aria-label="Scroll To Top" aria-label="Scroll To Top"
type="button" type="button"
onClick={handleScrollTop} onClick={handleScrollTop}
className="p-2 text-gray-500 transition-all bg-gray-200 rounded-full dark:text-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 hover:bg-gray-300" 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="w-5 h-5" viewBox="0 0 20 20" fill="currentColor"> <svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path <path
fillRule="evenodd" fillRule="evenodd"
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"

View File

@ -1,3 +1,3 @@
export default function SectionContainer({ children }) { export default function SectionContainer({ children }) {
return <div className="max-w-3xl px-4 mx-auto 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

@ -51,7 +51,7 @@ const TOCInline = ({
<> <>
{asDisclosure ? ( {asDisclosure ? (
<details open> <details open>
<summary className="pt-2 pb-2 ml-6 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> <div className="ml-6">{tocList}</div>
</details> </details>
) : ( ) : (

View File

@ -12,7 +12,7 @@ const ThemeSwitch = () => {
<button <button
aria-label="Toggle Dark Mode" aria-label="Toggle Dark Mode"
type="button" type="button"
className="w-8 h-8 p-1 ml-1 mr-1 rounded sm:ml-4" 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 <svg

View File

@ -44,7 +44,7 @@ const Utterances = ({ issueTerm }) => {
return ( return (
<div className="pt-6 pb-6 text-center text-gray-700 dark:text-gray-300"> <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="relative utterances-frame" id={COMMENTS_ID} /> <div className="utterances-frame relative" id={COMMENTS_ID} />
</div> </div>
) )
} }

View File

@ -31,7 +31,7 @@ const SocialIcon = ({ kind, href, size = 8 }) => {
> >
<span className="sr-only">{kind}</span> <span className="sr-only">{kind}</span>
<SocialSvg <SocialSvg
className={`fill-current text-gray-700 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 h-${size} w-${size}`} className={`fill-current text-gray-700 hover:text-blue-500 dark:text-gray-200 dark:hover:text-blue-400 h-${size} w-${size}`}
/> />
</a> </a>
) )

View File

@ -7,7 +7,7 @@
/* Code title styles */ /* Code title styles */
.remark-code-title { .remark-code-title {
@apply px-5 py-3 font-mono text-sm font-bold text-gray-200 bg-gray-700 rounded-t; @apply rounded-t bg-gray-700 px-5 py-3 font-mono text-sm font-bold text-gray-200;
} }
.remark-code-title + div > pre { .remark-code-title + div > pre {
@ -20,7 +20,7 @@
} }
.code-line { .code-line {
@apply block pl-4 pr-4 -mx-4 border-l-4 border-opacity-0; @apply -mx-4 block border-l-4 border-transparent pl-4 pr-4;
} }
.code-line.inserted { .code-line.inserted {
@ -32,11 +32,11 @@
} }
.highlight-line { .highlight-line {
@apply -mx-4 bg-gray-700 bg-opacity-50 border-l-4 border-primary-500; @apply -mx-4 border-l-4 border-primary-500 bg-gray-700 bg-opacity-50;
} }
.line-number::before { .line-number::before {
@apply inline-block w-4 mr-4 -ml-2 text-right text-gray-400; @apply mr-4 -ml-2 inline-block w-4 text-right text-gray-400;
content: attr(line); content: attr(line);
} }
@ -134,3 +134,7 @@
.token.italic { .token.italic {
font-style: italic; font-style: italic;
} }
.token.table {
display: inline;
}

View File

@ -1,7 +1,7 @@
--- ---
title: 'Introducing Tailwind Nextjs Starter Blog' title: 'Introducing Tailwind Nextjs Starter Blog'
date: '2021-01-12' date: '2021-01-12'
lastmod: '2021-12-22' lastmod: '2021-02-01'
tags: ['next-js', 'tailwind', 'guide'] tags: ['next-js', 'tailwind', 'guide']
draft: false draft: false
summary: 'Looking for a performant, out of the box template, with all the best in web technology to support your blogging needs? Checkout the Tailwind Nextjs Starter Blog template.' summary: 'Looking for a performant, out of the box template, with all the best in web technology to support your blogging needs? Checkout the Tailwind Nextjs Starter Blog template.'
@ -47,6 +47,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea
- Lightweight, 45kB first load JS, uses Preact in production build - Lightweight, 45kB first load JS, uses Preact in production build
- Mobile-friendly view - Mobile-friendly view
- Light and dark theme - Light and dark theme
- Self-hosted font with [Fontsource](https://fontsource.org/)
- Supports [plausible](https://plausible.io/), [simple analytics](https://simpleanalytics.com/) and google analytics - Supports [plausible](https://plausible.io/), [simple analytics](https://simpleanalytics.com/) and google analytics
- [MDX - write JSX in markdown documents!](https://mdxjs.com/) - [MDX - write JSX in markdown documents!](https://mdxjs.com/)
- Server-side syntax highlighting with line numbers and line highlighting via [rehype-prism-plus](https://github.com/timlrx/rehype-prism-plus) - Server-side syntax highlighting with line numbers and line highlighting via [rehype-prism-plus](https://github.com/timlrx/rehype-prism-plus)
@ -78,11 +79,13 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea
1. JS (official support) - `npx degit https://github.com/timlrx/tailwind-nextjs-starter-blog.git` or TS (community support) - `npx degit timlrx/tailwind-nextjs-starter-blog#typescript` 1. JS (official support) - `npx degit https://github.com/timlrx/tailwind-nextjs-starter-blog.git` or TS (community support) - `npx degit timlrx/tailwind-nextjs-starter-blog#typescript`
2. Personalize `siteMetadata.js` (site related information) 2. Personalize `siteMetadata.js` (site related information)
3. Personalize `authors/default.md` (main author) 3. Modify the content security policy in `next.config.js` if you want to use
4. Modify `projectsData.js` any analytics provider or a commenting solution other than giscus.
5. Modify `headerNavLinks.js` to customize navigation links 4. Personalize `authors/default.md` (main author)
6. Add blog posts 5. Modify `projectsData.js`
7. Deploy on Vercel 6. Modify `headerNavLinks.js` to customize navigation links
7. Add blog posts
8. Deploy on Vercel
## Development ## Development

View File

@ -1,7 +1,7 @@
--- ---
title: 'New features in v1' title: 'New features in v1'
date: 2021-08-07T15:32:14Z date: 2021-08-07T15:32:14Z
lastmod: '2021-12-15' lastmod: '2021-02-01'
tags: ['next-js', 'tailwind', 'guide'] tags: ['next-js', 'tailwind', 'guide']
draft: false draft: false
summary: 'An overview of the new features released in v1 - code block copy, multiple authors, frontmatter layout and more' summary: 'An overview of the new features released in v1 - code block copy, multiple authors, frontmatter layout and more'
@ -261,7 +261,7 @@ A long description of yourself...
You can use this information in multiple places across the template. For example in the about section of the page, we grab the default author information with this line of code: You can use this information in multiple places across the template. For example in the about section of the page, we grab the default author information with this line of code:
``` ```js
const authorDetails = await getFileBySlug('authors', ['default']) const authorDetails = await getFileBySlug('authors', ['default'])
``` ```
@ -273,7 +273,7 @@ The frontmatter of a blog post accepts an optional `authors` array field. If no
For example, the following frontmatter will display the authors given by `data/authors/default.md` and `data/authors/sparrowhawk.md` For example, the following frontmatter will display the authors given by `data/authors/default.md` and `data/authors/sparrowhawk.md`
``` ```yaml
title: 'My first post' title: 'My first post'
date: '2021-01-12' date: '2021-01-12'
draft: false draft: false
@ -322,7 +322,7 @@ To modify the styles, change the following class selectors in the `prism.css` fi
} }
.code-line { .code-line {
@apply block pl-4 pr-4 -mx-4 border-l-4 border-opacity-0; @apply -mx-4 block border-l-4 border-opacity-0 pl-4 pr-4;
} }
.code-line.inserted { .code-line.inserted {
@ -334,11 +334,11 @@ To modify the styles, change the following class selectors in the `prism.css` fi
} }
.highlight-line { .highlight-line {
@apply -mx-4 bg-gray-700 bg-opacity-50 border-l-4 border-primary-500; @apply -mx-4 border-l-4 border-primary-500 bg-gray-700 bg-opacity-50;
} }
.line-number::before { .line-number::before {
@apply inline-block w-4 mr-4 -ml-2 text-right text-gray-400; @apply mr-4 -ml-2 inline-block w-4 text-right text-gray-400;
content: attr(line); content: attr(line);
} }
``` ```
@ -399,6 +399,24 @@ The plugin uses APA citation formation, but also supports the following CSLs, 'a
See [rehype-citation readme](https://github.com/timlrx/rehype-citation) for more information on the configuration options. See [rehype-citation readme](https://github.com/timlrx/rehype-citation) for more information on the configuration options.
## Self-hosted font (v1.5.0)
Google font has been replaced with self-hosted font from [Fontsource](https://fontsource.org/). This gives the following [advantages](https://fontsource.org/docs/introduction):
> Self-hosting brings significant performance gains as loading fonts from hosted services, such as Google Fonts, lead to an extra (render blocking) network request. To provide perspective, for simple websites it has been seen to double visual load times.
>
> Fonts remain version locked. Google often pushes updates to their fonts without notice, which may interfere with your live production projects. Manage your fonts like any other NPM dependency.
>
> Commit to privacy. Google does track the usage of their fonts and for those who are extremely privacy concerned, self-hosting is an alternative.
This leads to a smaller font bundle and a 0.1s faster load time ([webpagetest comparison](https://www.webpagetest.org/video/compare.php?tests=220201_AiDcFH_f68a69b758454dd52d8e67493fdef7da,220201_BiDcMC_bf2d53f14483814ba61e794311dfa771)).
To change the default Inter font:
1. Install the preferred [font](https://fontsource.org/fonts) - `npm install -save @fontsource/<font-name>`
2. Update the import at `pages/_app.js`- `import '@fontsource/<font-name>.css'`
3. Update the `fontfamily` property in the tailwind css config file
## Upgrade guide ## Upgrade guide
There are significant portions of the code that has been changed from v0 to v1 including support for layouts and a new mdx engine. There are significant portions of the code that has been changed from v0 to v1 including support for layouts and a new mdx engine.
@ -420,7 +438,7 @@ You can see an example of such a migration in this [commit](https://github.com/t
v1 also uses `feed.xml` rather than `index.xml`, to avoid some build issues with Vercel. If you are migrating you should add a redirect to `next.config.js` like so: v1 also uses `feed.xml` rather than `index.xml`, to avoid some build issues with Vercel. If you are migrating you should add a redirect to `next.config.js` like so:
``` ```js
async redirects() { async redirects() {
return [ return [
{ {

View File

@ -18,6 +18,8 @@ const siteMetadata = {
linkedin: 'https://www.linkedin.com', linkedin: 'https://www.linkedin.com',
locale: 'en-US', locale: 'en-US',
analytics: { analytics: {
// If you want to use an analytics provider you have to add it to the
// content security policy in the `next.config.js` file.
// supports plausible, simpleAnalytics, umami or googleAnalytics // supports plausible, simpleAnalytics, umami or googleAnalytics
plausibleDataDomain: '', // e.g. tailwind-nextjs-starter-blog.vercel.app plausibleDataDomain: '', // e.g. tailwind-nextjs-starter-blog.vercel.app
simpleAnalytics: false, // true or false simpleAnalytics: false, // true or false
@ -30,6 +32,8 @@ const siteMetadata = {
provider: 'buttondown', provider: 'buttondown',
}, },
comment: { comment: {
// If you want to use a commenting system other than giscus you have to add it to the
// content security policy in the `next.config.js` file.
// Select a provider and use the environment variables associated to it // Select a provider and use the environment variables associated to it
// https://vercel.com/docs/environment-variables // https://vercel.com/docs/environment-variables
provider: 'giscus', // supported providers: giscus, utterances, disqus provider: 'giscus', // supported providers: giscus, utterances, disqus

View File

@ -9,31 +9,31 @@ export default function AuthorLayout({ children, frontMatter }) {
<> <>
<PageSEO title={`About - ${name}`} description={`About me - ${name}`} /> <PageSEO title={`About - ${name}`} description={`About me - ${name}`} />
<div className="divide-y"> <div className="divide-y">
<div className="pt-6 pb-8 space-y-2 md:space-y-5"> <div className="space-y-2 pt-6 pb-8 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"> <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">
About About
</h1> </h1>
</div> </div>
<div className="items-start space-y-2 xl:grid xl:grid-cols-3 xl:gap-x-8 xl:space-y-0"> <div className="items-start space-y-2 xl:grid xl:grid-cols-3 xl:gap-x-8 xl:space-y-0">
<div className="flex flex-col items-center pt-8 space-x-2"> <div className="flex flex-col items-center space-x-2 pt-8">
<Image <Image
src={avatar} src={avatar}
alt="avatar" alt="avatar"
width="192px" width="192px"
height="192px" height="192px"
className="w-48 h-48 rounded-full" className="h-48 w-48 rounded-full"
/> />
<h3 className="pt-4 pb-2 text-2xl font-bold leading-8 tracking-tight">{name}</h3> <h3 className="pt-4 pb-2 text-2xl font-bold leading-8 tracking-tight">{name}</h3>
<div className="text-gray-500 dark:text-gray-400">{occupation}</div> <div className="text-gray-500 dark:text-gray-400">{occupation}</div>
<div className="text-gray-500 dark:text-gray-400">{company}</div> <div className="text-gray-500 dark:text-gray-400">{company}</div>
<div className="flex pt-6 space-x-3"> <div className="flex space-x-3 pt-6">
<SocialIcon kind="mail" href={`mailto:${email}`} /> <SocialIcon kind="mail" href={`mailto:${email}`} />
<SocialIcon kind="github" href={github} /> <SocialIcon kind="github" href={github} />
<SocialIcon kind="linkedin" href={linkedin} /> <SocialIcon kind="linkedin" href={linkedin} />
<SocialIcon kind="twitter" href={twitter} /> <SocialIcon kind="twitter" href={twitter} />
</div> </div>
</div> </div>
<div className="pt-8 pb-8 prose dark:prose-dark max-w-none xl:col-span-2">{children}</div> <div className="prose max-w-none pt-8 pb-8 dark:prose-dark xl:col-span-2">{children}</div>
</div> </div>
</div> </div>
</> </>

View File

@ -19,7 +19,7 @@ export default function ListLayout({ posts, title, initialDisplayPosts = [], pag
return ( return (
<> <>
<div className="divide-y"> <div className="divide-y">
<div className="pt-6 pb-8 space-y-2 md:space-y-5"> <div className="space-y-2 pt-6 pb-8 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"> <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">
{title} {title}
</h1> </h1>
@ -29,10 +29,10 @@ export default function ListLayout({ posts, title, initialDisplayPosts = [], pag
type="text" type="text"
onChange={(e) => setSearchValue(e.target.value)} onChange={(e) => setSearchValue(e.target.value)}
placeholder="Search articles" placeholder="Search articles"
className="block w-full px-4 py-2 text-gray-900 bg-white border border-gray-300 rounded-md dark:border-gray-900 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100" className="block w-full rounded-md border border-gray-300 bg-white px-4 py-2 text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-900 dark:bg-gray-800 dark:text-gray-100"
/> />
<svg <svg
className="absolute w-5 h-5 text-gray-400 right-3 top-3 dark:text-gray-300" className="absolute right-3 top-3 h-5 w-5 text-gray-400 dark:text-gray-300"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -53,7 +53,7 @@ export default function ListLayout({ posts, title, initialDisplayPosts = [], pag
const { slug, date, title, summary, tags } = frontMatter const { slug, date, title, summary, tags } = frontMatter
return ( return (
<li key={slug} className="py-4"> <li key={slug} className="py-4">
<article className="space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline"> <article className="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
<dl> <dl>
<dt className="sr-only">Published on</dt> <dt className="sr-only">Published on</dt>
<dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400"> <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
@ -73,7 +73,7 @@ export default function ListLayout({ posts, title, initialDisplayPosts = [], pag
))} ))}
</div> </div>
</div> </div>
<div className="prose text-gray-500 max-w-none dark:text-gray-400"> <div className="prose max-w-none text-gray-500 dark:text-gray-400">
{summary} {summary}
</div> </div>
</div> </div>

View File

@ -47,13 +47,13 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
</div> </div>
</header> </header>
<div <div
className="pb-8 divide-y divide-gray-200 xl:divide-y-0 dark:divide-gray-700 xl:grid xl:grid-cols-4 xl:gap-x-6" className="divide-y divide-gray-200 pb-8 dark:divide-gray-700 xl:grid xl:grid-cols-4 xl:gap-x-6 xl:divide-y-0"
style={{ gridTemplateRows: 'auto 1fr' }} style={{ gridTemplateRows: 'auto 1fr' }}
> >
<dl className="pt-6 pb-10 xl:pt-11 xl:border-b xl:border-gray-200 xl:dark:border-gray-700"> <dl className="pt-6 pb-10 xl:border-b xl:border-gray-200 xl:pt-11 xl:dark:border-gray-700">
<dt className="sr-only">Authors</dt> <dt className="sr-only">Authors</dt>
<dd> <dd>
<ul className="flex justify-center space-x-8 xl:block sm:space-x-12 xl:space-x-0 xl:space-y-8"> <ul className="flex justify-center space-x-8 sm:space-x-12 xl:block xl:space-x-0 xl:space-y-8">
{authorDetails.map((author) => ( {authorDetails.map((author) => (
<li className="flex items-center space-x-2" key={author.name}> <li className="flex items-center space-x-2" key={author.name}>
{author.avatar && ( {author.avatar && (
@ -62,10 +62,10 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
width="38px" width="38px"
height="38px" height="38px"
alt="avatar" alt="avatar"
className="w-10 h-10 rounded-full" className="h-10 w-10 rounded-full"
/> />
)} )}
<dl className="text-sm font-medium leading-5 whitespace-nowrap"> <dl className="whitespace-nowrap text-sm font-medium leading-5">
<dt className="sr-only">Name</dt> <dt className="sr-only">Name</dt>
<dd className="text-gray-900 dark:text-gray-100">{author.name}</dd> <dd className="text-gray-900 dark:text-gray-100">{author.name}</dd>
<dt className="sr-only">Twitter</dt> <dt className="sr-only">Twitter</dt>
@ -85,8 +85,8 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
</ul> </ul>
</dd> </dd>
</dl> </dl>
<div className="divide-y divide-gray-200 dark:divide-gray-700 xl:pb-0 xl:col-span-3 xl:row-span-2"> <div className="divide-y divide-gray-200 dark:divide-gray-700 xl:col-span-3 xl:row-span-2 xl:pb-0">
<div className="pt-10 pb-8 prose dark:prose-dark max-w-none">{children}</div> <div className="prose max-w-none pt-10 pb-8 dark:prose-dark">{children}</div>
<div className="pt-6 pb-6 text-sm text-gray-700 dark:text-gray-300"> <div className="pt-6 pb-6 text-sm text-gray-700 dark:text-gray-300">
<Link href={discussUrl(slug)} rel="nofollow"> <Link href={discussUrl(slug)} rel="nofollow">
{'Discuss on Twitter'} {'Discuss on Twitter'}
@ -97,10 +97,10 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
<Comments frontMatter={frontMatter} /> <Comments frontMatter={frontMatter} />
</div> </div>
<footer> <footer>
<div className="text-sm font-medium leading-5 divide-gray-200 xl:divide-y dark:divide-gray-700 xl:col-start-1 xl:row-start-2"> <div className="divide-gray-200 text-sm font-medium leading-5 dark:divide-gray-700 xl:col-start-1 xl:row-start-2 xl:divide-y">
{tags && ( {tags && (
<div className="py-4 xl:py-8"> <div className="py-4 xl:py-8">
<h2 className="text-xs tracking-wide text-gray-500 uppercase dark:text-gray-400"> <h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Tags Tags
</h2> </h2>
<div className="flex flex-wrap"> <div className="flex flex-wrap">
@ -114,7 +114,7 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
<div className="flex justify-between py-4 xl:block xl:space-y-8 xl:py-8"> <div className="flex justify-between py-4 xl:block xl:space-y-8 xl:py-8">
{prev && ( {prev && (
<div> <div>
<h2 className="text-xs tracking-wide text-gray-500 uppercase dark:text-gray-400"> <h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Previous Article Previous Article
</h2> </h2>
<div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"> <div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">
@ -124,7 +124,7 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
)} )}
{next && ( {next && (
<div> <div>
<h2 className="text-xs tracking-wide text-gray-500 uppercase dark:text-gray-400"> <h2 className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
Next Article Next Article
</h2> </h2>
<div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"> <div className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400">

View File

@ -17,7 +17,7 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
<article> <article>
<div> <div>
<header> <header>
<div className="pb-10 space-y-1 text-center border-b border-gray-200 dark:border-gray-700"> <div className="space-y-1 border-b border-gray-200 pb-10 text-center dark:border-gray-700">
<dl> <dl>
<div> <div>
<dt className="sr-only">Published on</dt> <dt className="sr-only">Published on</dt>
@ -32,11 +32,11 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
</div> </div>
</header> </header>
<div <div
className="pb-8 divide-y divide-gray-200 xl:divide-y-0 dark:divide-gray-700 " className="divide-y divide-gray-200 pb-8 dark:divide-gray-700 xl:divide-y-0 "
style={{ gridTemplateRows: 'auto 1fr' }} style={{ gridTemplateRows: 'auto 1fr' }}
> >
<div className="divide-y divide-gray-200 dark:divide-gray-700 xl:pb-0 xl:col-span-3 xl:row-span-2"> <div className="divide-y divide-gray-200 dark:divide-gray-700 xl:col-span-3 xl:row-span-2 xl:pb-0">
<div className="pt-10 pb-8 prose dark:prose-dark max-w-none">{children}</div> <div className="prose max-w-none pt-10 pb-8 dark:prose-dark">{children}</div>
</div> </div>
<Comments frontMatter={frontMatter} /> <Comments frontMatter={frontMatter} />
<footer> <footer>

View File

@ -9,6 +9,7 @@ import getAllFilesRecursively from './utils/files'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
import remarkFootnotes from 'remark-footnotes' import remarkFootnotes from 'remark-footnotes'
import remarkMath from 'remark-math' import remarkMath from 'remark-math'
import remarkExtractFrontmatter from './remark-extract-frontmatter'
import remarkCodeTitles from './remark-code-title' import remarkCodeTitles from './remark-code-title'
import remarkTocHeadings from './remark-toc-headings' import remarkTocHeadings from './remark-toc-headings'
import remarkImgToJsx from './remark-img-to-jsx' import remarkImgToJsx from './remark-img-to-jsx'
@ -55,18 +56,17 @@ export async function getFileBySlug(type, slug) {
let toc = [] let toc = []
// Parsing frontmatter here to pass it in as options to rehype plugin const { code, frontmatter } = await bundleMDX({
const { data: frontmatter } = matter(source)
const { code } = await bundleMDX({
source, source,
// mdx imports can be automatically source from the components directory // mdx imports can be automatically source from the components directory
cwd: path.join(root, 'components'), cwd: path.join(root, 'components'),
xdmOptions(options) { xdmOptions(options, frontmatter) {
// this is the recommended way to add custom remark/rehype plugins: // this is the recommended way to add custom remark/rehype plugins:
// The syntax might look weird, but it protects you in case we add/remove // The syntax might look weird, but it protects you in case we add/remove
// plugins in the future. // plugins in the future.
options.remarkPlugins = [ options.remarkPlugins = [
...(options.remarkPlugins ?? []), ...(options.remarkPlugins ?? []),
remarkExtractFrontmatter,
[remarkTocHeadings, { exportRef: toc }], [remarkTocHeadings, { exportRef: toc }],
remarkGfm, remarkGfm,
remarkCodeTitles, remarkCodeTitles,
@ -79,10 +79,7 @@ export async function getFileBySlug(type, slug) {
rehypeSlug, rehypeSlug,
rehypeAutolinkHeadings, rehypeAutolinkHeadings,
rehypeKatex, rehypeKatex,
[ [rehypeCitation, { path: path.join(root, 'data') }],
rehypeCitation,
{ bibliography: frontmatter?.bibliography, path: path.join(root, 'data') },
],
[rehypePrismPlus, { ignoreMissing: true }], [rehypePrismPlus, { ignoreMissing: true }],
rehypePresetMinify, rehypePresetMinify,
] ]

View File

@ -2,7 +2,7 @@ import { visit } from 'unist-util-visit'
export default function remarkCodeTitles() { export default function remarkCodeTitles() {
return (tree) => return (tree) =>
visit(tree, 'code', (node, index) => { visit(tree, 'code', (node, index, parent) => {
const nodeLang = node.lang || '' const nodeLang = node.lang || ''
let language = '' let language = ''
let title = '' let title = ''
@ -26,7 +26,7 @@ export default function remarkCodeTitles() {
data: { _xdmExplicitJsx: true }, data: { _xdmExplicitJsx: true },
} }
tree.children.splice(index, 0, titleNode) parent.children.splice(index, 0, titleNode)
node.lang = language node.lang = language
}) })
} }

View File

@ -0,0 +1,10 @@
import { visit } from 'unist-util-visit'
import { load } from 'js-yaml'
export default function extractFrontmatter() {
return (tree, file) => {
visit(tree, 'yaml', (node, index, parent) => {
file.data.frontmatter = load(node.value)
})
}
}

View File

@ -1,7 +1,10 @@
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x) const pipe =
(...fns) =>
(x) =>
fns.reduce((v, f) => f(v), x)
const flattenArray = (input) => const flattenArray = (input) =>
input.reduce((acc, item) => [...acc, ...(Array.isArray(item) ? item : [item])], []) input.reduce((acc, item) => [...acc, ...(Array.isArray(item) ? item : [item])], [])

View File

@ -6,11 +6,11 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
const ContentSecurityPolicy = ` const ContentSecurityPolicy = `
default-src 'self'; default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' giscus.app; script-src 'self' 'unsafe-eval' 'unsafe-inline' giscus.app;
style-src 'self' 'unsafe-inline' *.googleapis.com cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' cdn.jsdelivr.net;
img-src * blob: data:; img-src * blob: data:;
media-src 'none'; media-src 'none';
connect-src *; connect-src *;
font-src 'self' fonts.gstatic.com cdn.jsdelivr.net; font-src 'self' cdn.jsdelivr.net;
frame-src giscus.app frame-src giscus.app
` `

4383
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "tailwind-nextjs-starter-blog", "name": "tailwind-nextjs-starter-blog",
"version": "1.4.2", "version": "1.5.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "cross-env SOCKET=true node ./scripts/next-remote-watch.js ./data", "start": "cross-env SOCKET=true node ./scripts/next-remote-watch.js ./data",
@ -12,6 +12,7 @@
"prepare": "husky install" "prepare": "husky install"
}, },
"dependencies": { "dependencies": {
"@fontsource/inter": "4.5.2",
"@mailchimp/mailchimp_marketing": "^3.0.58", "@mailchimp/mailchimp_marketing": "^3.0.58",
"@tailwindcss/forms": "^0.4.0", "@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.0", "@tailwindcss/typography": "^0.5.0",
@ -21,7 +22,7 @@
"gray-matter": "^4.0.2", "gray-matter": "^4.0.2",
"image-size": "1.0.0", "image-size": "1.0.0",
"mdx-bundler": "^8.0.0", "mdx-bundler": "^8.0.0",
"next": "12.0.7", "next": "12.0.9",
"next-themes": "^0.0.14", "next-themes": "^0.0.14",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"preact": "^10.6.2", "preact": "^10.6.2",
@ -29,7 +30,7 @@
"react-dom": "17.0.2", "react-dom": "17.0.2",
"reading-time": "1.3.0", "reading-time": "1.3.0",
"rehype-autolink-headings": "^6.1.0", "rehype-autolink-headings": "^6.1.0",
"rehype-citation": "^0.1.2", "rehype-citation": "^0.2.0",
"rehype-katex": "^6.0.2", "rehype-katex": "^6.0.2",
"rehype-preset-minify": "6.0.0", "rehype-preset-minify": "6.0.0",
"rehype-prism-plus": "^1.1.3", "rehype-prism-plus": "^1.1.3",
@ -39,16 +40,16 @@
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"sharp": "^0.28.3", "sharp": "^0.28.3",
"smoothscroll-polyfill": "^0.4.4", "smoothscroll-polyfill": "^0.4.4",
"tailwindcss": "^3.0.2", "tailwindcss": "^3.0.18",
"unist-util-visit": "^4.0.0" "unist-util-visit": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.0.7", "@next/bundle-analyzer": "12.0.9",
"@svgr/webpack": "^6.1.2", "@svgr/webpack": "^6.1.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dedent": "^0.7.0", "dedent": "^0.7.0",
"eslint": "^7.29.0", "eslint": "^7.29.0",
"eslint-config-next": "12.0.7", "eslint-config-next": "12.0.9",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^3.3.1",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
@ -57,9 +58,10 @@
"inquirer": "^8.1.1", "inquirer": "^8.1.1",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
"next-remote-watch": "^1.0.0", "next-remote-watch": "^1.0.0",
"prettier": "2.2.1", "prettier": "^2.5.1",
"socket.io": "^4.1.3", "prettier-plugin-tailwindcss": "^0.1.4",
"socket.io-client": "^4.1.3" "socket.io": "^4.4.0",
"socket.io-client": "^4.4.0"
}, },
"lint-staged": { "lint-staged": {
"*.+(js|jsx|ts|tsx)": [ "*.+(js|jsx|ts|tsx)": [

View File

@ -2,9 +2,9 @@ import Link from '@/components/Link'
export default function FourZeroFour() { export default function FourZeroFour() {
return ( return (
<div className="flex flex-col items-start justify-start md:justify-center md:items-center md:flex-row md:space-x-6 md:mt-24"> <div className="flex flex-col items-start justify-start md:mt-24 md:flex-row md:items-center md:justify-center md:space-x-6">
<div className="pt-6 pb-8 space-x-2 md:space-y-5"> <div className="space-x-2 pt-6 pb-8 md:space-y-5">
<h1 className="text-6xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 md:text-8xl md:leading-14 md:border-r-2 md:px-6"> <h1 className="text-6xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 md:border-r-2 md:px-6 md:text-8xl md:leading-14">
404 404
</h1> </h1>
</div> </div>
@ -14,7 +14,7 @@ export default function FourZeroFour() {
</p> </p>
<p className="mb-8">But dont worry, you can find plenty of other things on our homepage.</p> <p className="mb-8">But dont worry, you can find plenty of other things on our homepage.</p>
<Link href="/"> <Link href="/">
<button className="inline px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-blue-600 border border-transparent rounded-lg shadow focus:outline-none focus:shadow-outline-blue hover:bg-blue-700 dark:hover:bg-blue-500"> <button className="focus:shadow-outline-blue inline rounded-lg border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium leading-5 text-white shadow transition-colors duration-150 hover:bg-blue-700 focus:outline-none dark:hover:bg-blue-500">
Back to homepage Back to homepage
</button> </button>
</Link> </Link>

View File

@ -1,6 +1,8 @@
import '@/css/tailwind.css' import '@/css/tailwind.css'
import '@/css/prism.css' import '@/css/prism.css'
import '@fontsource/inter/variable-full.css'
import { ThemeProvider } from 'next-themes' import { ThemeProvider } from 'next-themes'
import Head from 'next/head' import Head from 'next/head'

View File

@ -22,11 +22,6 @@ class MyDocument extends Document {
<meta name="msapplication-TileColor" content="#000000" /> <meta name="msapplication-TileColor" content="#000000" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<link rel="alternate" type="application/rss+xml" href="/feed.xml" /> <link rel="alternate" type="application/rss+xml" href="/feed.xml" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css"
@ -34,7 +29,7 @@ class MyDocument extends Document {
crossOrigin="anonymous" crossOrigin="anonymous"
/> />
</Head> </Head>
<body className="antialiased text-black bg-white dark:bg-gray-900 dark:text-white"> <body className="bg-white text-black antialiased dark:bg-gray-900 dark:text-white">
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>

View File

@ -1,3 +1,4 @@
/* eslint-disable import/no-anonymous-default-export */
export default async (req, res) => { export default async (req, res) => {
const { email } = req.body const { email } = req.body
if (!email) { if (!email) {

View File

@ -20,7 +20,7 @@ export default function Home({ posts }) {
<> <>
<PageSEO title={siteMetadata.title} description={siteMetadata.description} /> <PageSEO title={siteMetadata.title} description={siteMetadata.description} />
<div className="divide-y divide-gray-200 dark:divide-gray-700"> <div className="divide-y divide-gray-200 dark:divide-gray-700">
<div className="pt-6 pb-8 space-y-2 md:space-y-5"> <div className="space-y-2 pt-6 pb-8 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"> <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">
Latest Latest
</h1> </h1>
@ -35,7 +35,7 @@ export default function Home({ posts }) {
return ( return (
<li key={slug} className="py-12"> <li key={slug} className="py-12">
<article> <article>
<div className="space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline"> <div className="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
<dl> <dl>
<dt className="sr-only">Published on</dt> <dt className="sr-only">Published on</dt>
<dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400"> <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
@ -59,7 +59,7 @@ export default function Home({ posts }) {
))} ))}
</div> </div>
</div> </div>
<div className="prose text-gray-500 max-w-none dark:text-gray-400"> <div className="prose max-w-none text-gray-500 dark:text-gray-400">
{summary} {summary}
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@ export default function Projects() {
<> <>
<PageSEO title={`Projects - ${siteMetadata.author}`} description={siteMetadata.description} /> <PageSEO title={`Projects - ${siteMetadata.author}`} description={siteMetadata.description} />
<div className="divide-y divide-gray-200 dark:divide-gray-700"> <div className="divide-y divide-gray-200 dark:divide-gray-700">
<div className="pt-6 pb-8 space-y-2 md:space-y-5"> <div className="space-y-2 pt-6 pb-8 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"> <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">
Projects Projects
</h1> </h1>
@ -17,7 +17,7 @@ export default function Projects() {
</p> </p>
</div> </div>
<div className="container py-12"> <div className="container py-12">
<div className="flex flex-wrap -m-4"> <div className="-m-4 flex flex-wrap">
{projectsData.map((d) => ( {projectsData.map((d) => (
<Card <Card
key={d.title} key={d.title}

View File

@ -16,13 +16,13 @@ export default function Tags({ tags }) {
return ( return (
<> <>
<PageSEO title={`Tags - ${siteMetadata.author}`} description="Things I blog about" /> <PageSEO title={`Tags - ${siteMetadata.author}`} description="Things I blog about" />
<div className="flex flex-col items-start justify-start divide-y divide-gray-200 dark:divide-gray-700 md:justify-center md:items-center md:divide-y-0 md:flex-row md:space-x-6 md:mt-24"> <div className="flex flex-col items-start justify-start divide-y divide-gray-200 dark:divide-gray-700 md:mt-24 md:flex-row md:items-center md:justify-center md:space-x-6 md:divide-y-0">
<div className="pt-6 pb-8 space-x-2 md:space-y-5"> <div className="space-x-2 pt-6 pb-8 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 md:border-r-2 md:px-6"> <h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:border-r-2 md:px-6 md:text-6xl md:leading-14">
Tags Tags
</h1> </h1>
</div> </div>
<div className="flex flex-wrap max-w-lg"> <div className="flex max-w-lg flex-wrap">
{Object.keys(tags).length === 0 && 'No tags found.'} {Object.keys(tags).length === 0 && 'No tags found.'}
{sortedTags.map((t) => { {sortedTags.map((t) => {
return ( return (
@ -30,7 +30,7 @@ export default function Tags({ tags }) {
<Tag text={t} /> <Tag text={t} />
<Link <Link
href={`/tags/${kebabCase(t)}`} href={`/tags/${kebabCase(t)}`}
className="-ml-2 text-sm font-semibold text-gray-600 uppercase dark:text-gray-300" className="-ml-2 text-sm font-semibold uppercase text-gray-600 dark:text-gray-300"
> >
{` (${tags[t]})`} {` (${tags[t]})`}
</Link> </Link>

View File

@ -107,6 +107,7 @@ inquirer
.replace(/ /g, '-') .replace(/ /g, '-')
.replace(/-+/g, '-') .replace(/-+/g, '-')
const frontMatter = genFrontMatter(answers) const frontMatter = genFrontMatter(answers)
if (!fs.existsSync('data/blog')) fs.mkdirSync('data/blog', { recursive: true })
const filePath = `data/blog/${fileName ? fileName : 'untitled'}.${ const filePath = `data/blog/${fileName ? fileName : 'untitled'}.${
answers.extension ? answers.extension : 'md' answers.extension ? answers.extension : 'md'
}` }`

View File

@ -1,5 +1,6 @@
const fs = require('fs') const fs = require('fs')
const globby = require('globby') const globby = require('globby')
const matter = require('gray-matter')
const prettier = require('prettier') const prettier = require('prettier')
const siteMetadata = require('../data/siteMetadata') const siteMetadata = require('../data/siteMetadata')
@ -7,10 +8,12 @@ const siteMetadata = require('../data/siteMetadata')
const prettierConfig = await prettier.resolveConfig('./.prettierrc.js') const prettierConfig = await prettier.resolveConfig('./.prettierrc.js')
const pages = await globby([ const pages = await globby([
'pages/*.js', 'pages/*.js',
'pages/*.tsx',
'data/blog/**/*.mdx', 'data/blog/**/*.mdx',
'data/blog/**/*.md', 'data/blog/**/*.md',
'public/tags/**/*.xml', 'public/tags/**/*.xml',
'!pages/_*.js', '!pages/_*.js',
'!pages/_*.tsx',
'!pages/api', '!pages/api',
]) ])
@ -19,16 +22,26 @@ const siteMetadata = require('../data/siteMetadata')
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages ${pages
.map((page) => { .map((page) => {
// Exclude drafts from the sitemap
if (page.search('.md') >= 1 && fs.existsSync(page)) {
const source = fs.readFileSync(page, 'utf8')
const fm = matter(source)
if (fm.data.draft) {
return
}
}
const path = page const path = page
.replace('pages/', '/') .replace('pages/', '/')
.replace('data/blog', '/blog') .replace('data/blog', '/blog')
.replace('public/', '/') .replace('public/', '/')
.replace('.js', '') .replace('.js', '')
.replace('.tsx', '')
.replace('.mdx', '') .replace('.mdx', '')
.replace('.md', '') .replace('.md', '')
.replace('/feed.xml', '') .replace('/feed.xml', '')
const route = path === '/index' ? '' : path const route = path === '/index' ? '' : path
if (page === `pages/404.js` || page === `pages/blog/[...slug].js`) {
if (page.search('pages/404.') > -1 || page.search(`pages/blog/[...slug].`) > -1) {
return return
} }
return ` return `

View File

@ -2,6 +2,9 @@ const defaultTheme = require('tailwindcss/defaultTheme')
const colors = require('tailwindcss/colors') const colors = require('tailwindcss/colors')
module.exports = { module.exports = {
experimental: {
optimizeUniversalDefaults: true,
},
content: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js', './lib/**/*.js'], content: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js', './lib/**/*.js'],
darkMode: 'class', darkMode: 'class',
theme: { theme: {
@ -16,7 +19,7 @@ module.exports = {
14: '3.5rem', 14: '3.5rem',
}, },
fontFamily: { fontFamily: {
sans: ['Inter', ...defaultTheme.fontFamily.sans], sans: ['InterVariable', ...defaultTheme.fontFamily.sans],
}, },
colors: { colors: {
primary: colors.teal, primary: colors.teal,
@ -29,7 +32,7 @@ module.exports = {
a: { a: {
color: theme('colors.primary.500'), color: theme('colors.primary.500'),
'&:hover': { '&:hover': {
color: theme('colors.primary.600'), color: `${theme('colors.primary.600')} !important`,
}, },
code: { color: theme('colors.primary.400') }, code: { color: theme('colors.primary.400') },
}, },
@ -97,7 +100,7 @@ module.exports = {
a: { a: {
color: theme('colors.primary.500'), color: theme('colors.primary.500'),
'&:hover': { '&:hover': {
color: theme('colors.primary.400'), color: `${theme('colors.primary.400')} !important`,
}, },
code: { color: theme('colors.primary.400') }, code: { color: theme('colors.primary.400') },
}, },