From 3932a2b61254ab5fbc50509bd4a4a28f6537c1f8 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Wed, 16 Aug 2023 23:55:57 +0800 Subject: [PATCH] =?UTF-8?q?style:=20=E8=BF=81=E7=A7=BB=E5=88=B0=20v2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc.js | 6 -- app/Main.tsx | 31 ++++---- app/about/page.tsx | 18 ++--- app/api/newsletter/route.ts | 8 +- app/blog/[...slug]/page.tsx | 114 +++++++++++++++-------------- app/blog/page.tsx | 24 +++--- app/blog/page/[page]/page.tsx | 30 ++++---- app/head.tsx | 40 ++++++++-- app/layout.tsx | 82 ++++++++++++++------- app/not-found.tsx | 11 +-- app/page.tsx | 12 +-- app/projects/page.tsx | 10 +-- app/robots.ts | 6 +- app/seo.tsx | 21 ++++-- app/sitemap.ts | 14 ++-- app/tags/[tag]/page.tsx | 51 +++++++------ app/tags/page.tsx | 28 +++---- app/theme-providers.tsx | 13 ++-- components/Card.tsx | 18 ++--- components/Comments.tsx | 16 ++-- components/Footer.tsx | 14 ++-- components/Header.tsx | 23 +++--- components/Image.tsx | 6 +- components/LayoutWrapper.tsx | 23 +++--- components/Link.tsx | 25 ++++--- components/MDXComponents.tsx | 14 ++-- components/MobileNav.tsx | 48 ++++++------ components/PageTitle.tsx | 6 +- components/ScrollTopAndComment.tsx | 45 ++++++------ components/SearchButton.tsx | 20 ++--- components/SectionContainer.tsx | 10 ++- components/Tag.tsx | 15 ++-- components/ThemeSwitch.tsx | 26 +++---- components/social-icons/icons.tsx | 16 ++-- components/social-icons/index.tsx | 39 ++++++---- data/siteMetadata.js | 35 +++++++-- layouts/AuthorLayout.tsx | 29 +++++--- layouts/ListLayout.tsx | 91 +++++++++++++---------- layouts/ListLayoutWithTags.tsx | 101 ++++++++++++++----------- layouts/PostBanner.tsx | 66 ++++++++++------- layouts/PostLayout.tsx | 87 +++++++++++++--------- layouts/PostSimple.tsx | 57 +++++++++------ prettier.config.js | 9 +-- scripts/postbuild.mjs | 6 +- scripts/rss.mjs | 48 ++++++------ 45 files changed, 806 insertions(+), 606 deletions(-) delete mode 100644 .prettierrc.js diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 2e7c768..0000000 --- a/.prettierrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - singleQuote: true, - trailingCommas: 'all', - bracketSpacing: true, - bracketSameLine: true, -}; diff --git a/app/Main.tsx b/app/Main.tsx index 41dfc50..04318ac 100644 --- a/app/Main.tsx +++ b/app/Main.tsx @@ -1,10 +1,10 @@ -import Link from '@/components/Link' -import Tag from '@/components/Tag' -import siteMetadata from '@/data/siteMetadata' -import { formatDate } from 'pliny/utils/formatDate' -import NewsletterForm from 'pliny/ui/NewsletterForm' +import Link from '@/components/Link'; +import Tag from '@/components/Tag'; +import siteMetadata from '@/data/siteMetadata'; +import { formatDate } from 'pliny/utils/formatDate'; +import NewsletterForm from 'pliny/ui/NewsletterForm'; -const MAX_DISPLAY = 5 +const MAX_DISPLAY = 5; export default function Home({ posts }) { return ( @@ -21,7 +21,7 @@ export default function Home({ posts }) { @@ -75,8 +75,7 @@ export default function Home({ posts }) { + aria-label="All posts"> All Posts → @@ -87,5 +86,5 @@ export default function Home({ posts }) { )} - ) + ); } diff --git a/app/about/page.tsx b/app/about/page.tsx index 63aa32d..f06ab7c 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -1,14 +1,14 @@ -import { Authors, allAuthors } from 'contentlayer/generated' -import { MDXLayoutRenderer } from 'pliny/mdx-components' -import AuthorLayout from '@/layouts/AuthorLayout' -import { coreContent } from 'pliny/utils/contentlayer' -import { genPageMetadata } from 'app/seo' +import { Authors, allAuthors } from 'contentlayer/generated'; +import { MDXLayoutRenderer } from 'pliny/mdx-components'; +import AuthorLayout from '@/layouts/AuthorLayout'; +import { coreContent } from 'pliny/utils/contentlayer'; +import { genPageMetadata } from 'app/seo'; -export const metadata = genPageMetadata({ title: 'About' }) +export const metadata = genPageMetadata({ title: 'About' }); export default function Page() { - const author = allAuthors.find((p) => p.slug === 'default') as Authors - const mainContent = coreContent(author) + const author = allAuthors.find((p) => p.slug === 'default') as Authors; + const mainContent = coreContent(author); return ( <> @@ -16,5 +16,5 @@ export default function Page() { - ) + ); } diff --git a/app/api/newsletter/route.ts b/app/api/newsletter/route.ts index 41e205d..0898913 100644 --- a/app/api/newsletter/route.ts +++ b/app/api/newsletter/route.ts @@ -1,9 +1,9 @@ -import { NewsletterAPI } from 'pliny/newsletter' -import siteMetadata from '@/data/siteMetadata' +import { NewsletterAPI } from 'pliny/newsletter'; +import siteMetadata from '@/data/siteMetadata'; const handler = NewsletterAPI({ // @ts-ignore provider: siteMetadata.newsletter.provider, -}) +}); -export { handler as GET, handler as POST } +export { handler as GET, handler as POST }; diff --git a/app/blog/[...slug]/page.tsx b/app/blog/[...slug]/page.tsx index 5e81b25..c341d88 100644 --- a/app/blog/[...slug]/page.tsx +++ b/app/blog/[...slug]/page.tsx @@ -1,54 +1,54 @@ -import 'css/prism.css' -import 'katex/dist/katex.css' +import 'css/prism.css'; +import 'katex/dist/katex.css'; -import PageTitle from '@/components/PageTitle' -import { components } from '@/components/MDXComponents' -import { MDXLayoutRenderer } from 'pliny/mdx-components' -import { sortPosts, coreContent } from 'pliny/utils/contentlayer' -import { allBlogs, allAuthors } from 'contentlayer/generated' -import type { Authors, Blog } from 'contentlayer/generated' -import PostSimple from '@/layouts/PostSimple' -import PostLayout from '@/layouts/PostLayout' -import PostBanner from '@/layouts/PostBanner' -import { Metadata } from 'next' -import siteMetadata from '@/data/siteMetadata' +import PageTitle from '@/components/PageTitle'; +import { components } from '@/components/MDXComponents'; +import { MDXLayoutRenderer } from 'pliny/mdx-components'; +import { sortPosts, coreContent } from 'pliny/utils/contentlayer'; +import { allBlogs, allAuthors } from 'contentlayer/generated'; +import type { Authors, Blog } from 'contentlayer/generated'; +import PostSimple from '@/layouts/PostSimple'; +import PostLayout from '@/layouts/PostLayout'; +import PostBanner from '@/layouts/PostBanner'; +import { Metadata } from 'next'; +import siteMetadata from '@/data/siteMetadata'; -const isProduction = process.env.NODE_ENV === 'production' -const defaultLayout = 'PostLayout' +const isProduction = process.env.NODE_ENV === 'production'; +const defaultLayout = 'PostLayout'; const layouts = { PostSimple, PostLayout, PostBanner, -} +}; export async function generateMetadata({ params, }: { - params: { slug: string[] } + params: { slug: string[] }; }): Promise { - const slug = decodeURI(params.slug.join('/')) - const post = allBlogs.find((p) => p.slug === slug) - const authorList = post?.authors || ['default'] + const slug = decodeURI(params.slug.join('/')); + const post = allBlogs.find((p) => p.slug === slug); + const authorList = post?.authors || ['default']; const authorDetails = authorList.map((author) => { - const authorResults = allAuthors.find((p) => p.slug === author) - return coreContent(authorResults as Authors) - }) + const authorResults = allAuthors.find((p) => p.slug === author); + return coreContent(authorResults as Authors); + }); if (!post) { - return + return; } - const publishedAt = new Date(post.date).toISOString() - const modifiedAt = new Date(post.lastmod || post.date).toISOString() - const authors = authorDetails.map((author) => author.name) - let imageList = [siteMetadata.socialBanner] + const publishedAt = new Date(post.date).toISOString(); + const modifiedAt = new Date(post.lastmod || post.date).toISOString(); + const authors = authorDetails.map((author) => author.name); + let imageList = [siteMetadata.socialBanner]; if (post.images) { - imageList = typeof post.images === 'string' ? [post.images] : post.images + imageList = typeof post.images === 'string' ? [post.images] : post.images; } const ogImages = imageList.map((img) => { return { url: img.includes('http') ? img : siteMetadata.siteUrl + img, - } - }) + }; + }); return { title: post.title, @@ -71,37 +71,37 @@ export async function generateMetadata({ description: post.summary, images: imageList, }, - } + }; } export const generateStaticParams = async () => { - const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') })) + const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') })); - return paths -} + return paths; +}; export default async function Page({ params }: { params: { slug: string[] } }) { - const slug = decodeURI(params.slug.join('/')) - const sortedPosts = sortPosts(allBlogs) as Blog[] - const postIndex = sortedPosts.findIndex((p) => p.slug === slug) - const prev = coreContent(sortedPosts[postIndex + 1]) - const next = coreContent(sortedPosts[postIndex - 1]) - const post = sortedPosts.find((p) => p.slug === slug) as Blog - const authorList = post?.authors || ['default'] + const slug = decodeURI(params.slug.join('/')); + const sortedPosts = sortPosts(allBlogs) as Blog[]; + const postIndex = sortedPosts.findIndex((p) => p.slug === slug); + const prev = coreContent(sortedPosts[postIndex + 1]); + const next = coreContent(sortedPosts[postIndex - 1]); + const post = sortedPosts.find((p) => p.slug === slug) as Blog; + const authorList = post?.authors || ['default']; const authorDetails = authorList.map((author) => { - const authorResults = allAuthors.find((p) => p.slug === author) - return coreContent(authorResults as Authors) - }) - const mainContent = coreContent(post) - const jsonLd = post.structuredData + const authorResults = allAuthors.find((p) => p.slug === author); + return coreContent(authorResults as Authors); + }); + const mainContent = coreContent(post); + const jsonLd = post.structuredData; jsonLd['author'] = authorDetails.map((author) => { return { '@type': 'Person', name: author.name, - } - }) + }; + }); - const Layout = layouts[post.layout || defaultLayout] + const Layout = layouts[post.layout || defaultLayout]; return ( <> @@ -120,11 +120,19 @@ export default async function Page({ params }: { params: { slug: string[] } }) { type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> - - + + )} - ) + ); } diff --git a/app/blog/page.tsx b/app/blog/page.tsx index 4dcbddc..256c072 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -1,23 +1,23 @@ -import ListLayout from '@/layouts/ListLayoutWithTags' -import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer' -import { allBlogs } from 'contentlayer/generated' -import { genPageMetadata } from 'app/seo' +import ListLayout from '@/layouts/ListLayoutWithTags'; +import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'; +import { allBlogs } from 'contentlayer/generated'; +import { genPageMetadata } from 'app/seo'; -const POSTS_PER_PAGE = 5 +const POSTS_PER_PAGE = 5; -export const metadata = genPageMetadata({ title: 'Blog' }) +export const metadata = genPageMetadata({ title: 'Blog' }); export default function BlogPage() { - const posts = allCoreContent(sortPosts(allBlogs)) - const pageNumber = 1 + const posts = allCoreContent(sortPosts(allBlogs)); + const pageNumber = 1; const initialDisplayPosts = posts.slice( POSTS_PER_PAGE * (pageNumber - 1), - POSTS_PER_PAGE * pageNumber - ) + POSTS_PER_PAGE * pageNumber, + ); const pagination = { currentPage: pageNumber, totalPages: Math.ceil(posts.length / POSTS_PER_PAGE), - } + }; return ( - ) + ); } diff --git a/app/blog/page/[page]/page.tsx b/app/blog/page/[page]/page.tsx index a0a8b48..e28e236 100644 --- a/app/blog/page/[page]/page.tsx +++ b/app/blog/page/[page]/page.tsx @@ -1,27 +1,29 @@ -import ListLayout from '@/layouts/ListLayoutWithTags' -import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer' -import { allBlogs } from 'contentlayer/generated' +import ListLayout from '@/layouts/ListLayoutWithTags'; +import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'; +import { allBlogs } from 'contentlayer/generated'; -const POSTS_PER_PAGE = 5 +const POSTS_PER_PAGE = 5; export const generateStaticParams = async () => { - const totalPages = Math.ceil(allBlogs.length / POSTS_PER_PAGE) - const paths = Array.from({ length: totalPages }, (_, i) => ({ page: (i + 1).toString() })) + const totalPages = Math.ceil(allBlogs.length / POSTS_PER_PAGE); + const paths = Array.from({ length: totalPages }, (_, i) => ({ + page: (i + 1).toString(), + })); - return paths -} + return paths; +}; export default function Page({ params }: { params: { page: string } }) { - const posts = allCoreContent(sortPosts(allBlogs)) - const pageNumber = parseInt(params.page as string) + const posts = allCoreContent(sortPosts(allBlogs)); + const pageNumber = parseInt(params.page as string); const initialDisplayPosts = posts.slice( POSTS_PER_PAGE * (pageNumber - 1), - POSTS_PER_PAGE * pageNumber - ) + POSTS_PER_PAGE * pageNumber, + ); const pagination = { currentPage: pageNumber, totalPages: Math.ceil(posts.length / POSTS_PER_PAGE), - } + }; return ( - ) + ); } diff --git a/app/head.tsx b/app/head.tsx index 3cd643a..bbe3e05 100644 --- a/app/head.tsx +++ b/app/head.tsx @@ -1,16 +1,42 @@ export default function Head() { return ( <> - - - + + + - + - - + + - ) + ); } diff --git a/app/layout.tsx b/app/layout.tsx index 32219cd..0afe352 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,21 +1,21 @@ -import 'css/tailwind.css' -import 'pliny/search/algolia.css' +import 'css/tailwind.css'; +import 'pliny/search/algolia.css'; -import { Space_Grotesk } from 'next/font/google' -import { Analytics, AnalyticsConfig } from 'pliny/analytics' -import { SearchProvider, SearchConfig } from 'pliny/search' -import Header from '@/components/Header' -import SectionContainer from '@/components/SectionContainer' -import Footer from '@/components/Footer' -import siteMetadata from '@/data/siteMetadata' -import { ThemeProviders } from './theme-providers' -import { Metadata } from 'next' +import { Space_Grotesk } from 'next/font/google'; +import { Analytics, AnalyticsConfig } from 'pliny/analytics'; +import { SearchProvider, SearchConfig } from 'pliny/search'; +import Header from '@/components/Header'; +import SectionContainer from '@/components/SectionContainer'; +import Footer from '@/components/Footer'; +import siteMetadata from '@/data/siteMetadata'; +import { ThemeProviders } from './theme-providers'; +import { Metadata } from 'next'; const space_grotesk = Space_Grotesk({ subsets: ['latin'], display: 'swap', variable: '--font-space-grotesk', -}) +}); export const metadata: Metadata = { metadataBase: new URL(siteMetadata.siteUrl), @@ -55,30 +55,62 @@ export const metadata: Metadata = { card: 'summary_large_image', images: [siteMetadata.socialBanner], }, -} +}; -export default function RootLayout({ children }: { children: React.ReactNode }) { +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { return ( - - - + suppressHydrationWarning> + + + - + - - + + - +
- +
{children}
@@ -88,5 +120,5 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - ) + ); } diff --git a/app/not-found.tsx b/app/not-found.tsx index 6eb0c5b..b0aef20 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -1,4 +1,4 @@ -import Link from '@/components/Link' +import Link from '@/components/Link'; export default function NotFound() { return ( @@ -12,14 +12,15 @@ export default function NotFound() {

Sorry we couldn't find this page.

-

But dont worry, you can find plenty of other things on our homepage.

+

+ But dont worry, you can find plenty of other things on our homepage. +

+ 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
- ) + ); } diff --git a/app/page.tsx b/app/page.tsx index 1498297..8240624 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,9 +1,9 @@ -import { sortPosts, allCoreContent } from 'pliny/utils/contentlayer' -import { allBlogs } from 'contentlayer/generated' -import Main from './Main' +import { sortPosts, allCoreContent } from 'pliny/utils/contentlayer'; +import { allBlogs } from 'contentlayer/generated'; +import Main from './Main'; export default async function Page() { - const sortedPosts = sortPosts(allBlogs) - const posts = allCoreContent(sortedPosts) - return
+ const sortedPosts = sortPosts(allBlogs); + const posts = allCoreContent(sortedPosts); + return
; } diff --git a/app/projects/page.tsx b/app/projects/page.tsx index d108fe3..92330d3 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -1,8 +1,8 @@ -import projectsData from '@/data/projectsData' -import Card from '@/components/Card' -import { genPageMetadata } from 'app/seo' +import projectsData from '@/data/projectsData'; +import Card from '@/components/Card'; +import { genPageMetadata } from 'app/seo'; -export const metadata = genPageMetadata({ title: 'Projects' }) +export const metadata = genPageMetadata({ title: 'Projects' }); export default function Projects() { return ( @@ -31,5 +31,5 @@ export default function Projects() { - ) + ); } diff --git a/app/robots.ts b/app/robots.ts index abb35c4..26c6e4d 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -1,5 +1,5 @@ -import { MetadataRoute } from 'next' -import siteMetadata from '@/data/siteMetadata' +import { MetadataRoute } from 'next'; +import siteMetadata from '@/data/siteMetadata'; export default function robots(): MetadataRoute.Robots { return { @@ -9,5 +9,5 @@ export default function robots(): MetadataRoute.Robots { }, sitemap: `${siteMetadata.siteUrl}/sitemap.xml`, host: siteMetadata.siteUrl, - } + }; } diff --git a/app/seo.tsx b/app/seo.tsx index 09b6234..4a48e9f 100644 --- a/app/seo.tsx +++ b/app/seo.tsx @@ -1,15 +1,20 @@ -import { Metadata } from 'next' -import siteMetadata from '@/data/siteMetadata' +import { Metadata } from 'next'; +import siteMetadata from '@/data/siteMetadata'; interface PageSEOProps { - title: string - description?: string - image?: string + title: string; + description?: string; + image?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any + [key: string]: any; } -export function genPageMetadata({ title, description, image, ...rest }: PageSEOProps): Metadata { +export function genPageMetadata({ + title, + description, + image, + ...rest +}: PageSEOProps): Metadata { return { title, openGraph: { @@ -27,5 +32,5 @@ export function genPageMetadata({ title, description, image, ...rest }: PageSEOP images: image ? [image] : [siteMetadata.socialBanner], }, ...rest, - } + }; } diff --git a/app/sitemap.ts b/app/sitemap.ts index bd2af54..fa8b6b8 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,18 +1,18 @@ -import { MetadataRoute } from 'next' -import { allBlogs } from 'contentlayer/generated' -import siteMetadata from '@/data/siteMetadata' +import { MetadataRoute } from 'next'; +import { allBlogs } from 'contentlayer/generated'; +import siteMetadata from '@/data/siteMetadata'; export default function sitemap(): MetadataRoute.Sitemap { - const siteUrl = siteMetadata.siteUrl + const siteUrl = siteMetadata.siteUrl; const blogRoutes = allBlogs.map((post) => ({ url: `${siteUrl}/${post.path}`, lastModified: post.lastmod || post.date, - })) + })); const routes = ['', 'blog', 'projects', 'tags'].map((route) => ({ url: `${siteUrl}/${route}`, lastModified: new Date().toISOString().split('T')[0], - })) + })); - return [...routes, ...blogRoutes] + return [...routes, ...blogRoutes]; } diff --git a/app/tags/[tag]/page.tsx b/app/tags/[tag]/page.tsx index 90a3f71..78eadbb 100644 --- a/app/tags/[tag]/page.tsx +++ b/app/tags/[tag]/page.tsx @@ -1,14 +1,18 @@ -import { slug } from 'github-slugger' -import { allCoreContent } from 'pliny/utils/contentlayer' -import siteMetadata from '@/data/siteMetadata' -import ListLayout from '@/layouts/ListLayoutWithTags' -import { allBlogs } from 'contentlayer/generated' -import tagData from 'app/tag-data.json' -import { genPageMetadata } from 'app/seo' -import { Metadata } from 'next' +import { slug } from 'github-slugger'; +import { allCoreContent } from 'pliny/utils/contentlayer'; +import siteMetadata from '@/data/siteMetadata'; +import ListLayout from '@/layouts/ListLayoutWithTags'; +import { allBlogs } from 'contentlayer/generated'; +import tagData from 'app/tag-data.json'; +import { genPageMetadata } from 'app/seo'; +import { Metadata } from 'next'; -export async function generateMetadata({ params }: { params: { tag: string } }): Promise { - const tag = params.tag +export async function generateMetadata({ + params, +}: { + params: { tag: string }; +}): Promise { + const tag = params.tag; return genPageMetadata({ title: tag, description: `${siteMetadata.title} ${tag} tagged content`, @@ -18,26 +22,29 @@ export async function generateMetadata({ params }: { params: { tag: string } }): 'application/rss+xml': `${siteMetadata.siteUrl}/tags/${tag}/feed.xml`, }, }, - }) + }); } export const generateStaticParams = async () => { - const tagCounts = tagData as Record - const tagKeys = Object.keys(tagCounts) + const tagCounts = tagData as Record; + const tagKeys = Object.keys(tagCounts); const paths = tagKeys.map((tag) => ({ tag: tag, - })) - return paths -} + })); + return paths; +}; export default function TagPage({ params }: { params: { tag: string } }) { - const { tag } = params + const { tag } = params; // Capitalize first letter and convert space to dash - const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1) + const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1); const filteredPosts = allCoreContent( allBlogs.filter( - (post) => post.draft !== true && post.tags && post.tags.map((t) => slug(t)).includes(tag) - ) - ) - return + (post) => + post.draft !== true && + post.tags && + post.tags.map((t) => slug(t)).includes(tag), + ), + ); + return ; } diff --git a/app/tags/page.tsx b/app/tags/page.tsx index 5b7a83c..a7131e0 100644 --- a/app/tags/page.tsx +++ b/app/tags/page.tsx @@ -1,15 +1,18 @@ -import Link from '@/components/Link' -import Tag from '@/components/Tag' -import { slug } from 'github-slugger' -import tagData from 'app/tag-data.json' -import { genPageMetadata } from 'app/seo' +import Link from '@/components/Link'; +import Tag from '@/components/Tag'; +import { slug } from 'github-slugger'; +import tagData from 'app/tag-data.json'; +import { genPageMetadata } from 'app/seo'; -export const metadata = genPageMetadata({ title: 'Tags', description: 'Things I blog about' }) +export const metadata = genPageMetadata({ + title: 'Tags', + description: 'Things I blog about', +}); export default async function Page() { - const tagCounts = tagData as Record - const tagKeys = Object.keys(tagCounts) - const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a]) + const tagCounts = tagData as Record; + const tagKeys = Object.keys(tagCounts); + const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a]); return ( <>
@@ -27,15 +30,14 @@ export default async function Page() { + aria-label={`View posts tagged ${t}`}> {` (${tagCounts[t]})`}
- ) + ); })} - ) + ); } diff --git a/app/theme-providers.tsx b/app/theme-providers.tsx index 79d31dd..0538132 100644 --- a/app/theme-providers.tsx +++ b/app/theme-providers.tsx @@ -1,12 +1,15 @@ -'use client' +'use client'; -import { ThemeProvider } from 'next-themes' -import siteMetadata from '@/data/siteMetadata' +import { ThemeProvider } from 'next-themes'; +import siteMetadata from '@/data/siteMetadata'; export function ThemeProviders({ children }: { children: React.ReactNode }) { return ( - + {children} - ) + ); } diff --git a/components/Card.tsx b/components/Card.tsx index 900c714..ce0a0ed 100644 --- a/components/Card.tsx +++ b/components/Card.tsx @@ -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 }) => (
+ } overflow-hidden rounded-md border-2 border-gray-200 border-opacity-60 dark:border-gray-700`}> {imgSrc && (href ? ( @@ -38,19 +37,20 @@ const Card = ({ title, description, imgSrc, href }) => ( title )} -

{description}

+

+ {description} +

{href && ( + aria-label={`Link to ${title}`}> Learn more → )}
-) +); -export default Card +export default Card; diff --git a/components/Comments.tsx b/components/Comments.tsx index b6ff077..c2e78fc 100644 --- a/components/Comments.tsx +++ b/components/Comments.tsx @@ -1,17 +1,19 @@ -'use client' +'use client'; -import { Comments as CommentsComponent } from 'pliny/comments' -import { useState } from 'react' -import siteMetadata from '@/data/siteMetadata' +import { Comments as CommentsComponent } from 'pliny/comments'; +import { useState } from 'react'; +import siteMetadata from '@/data/siteMetadata'; export default function Comments({ slug }: { slug: string }) { - const [loadComments, setLoadComments] = useState(false) + const [loadComments, setLoadComments] = useState(false); return ( <> - {!loadComments && } + {!loadComments && ( + + )} {siteMetadata.comments && loadComments && ( )} - ) + ); } diff --git a/components/Footer.tsx b/components/Footer.tsx index a03e5cc..ab9a65e 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -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 (
- + @@ -33,5 +37,5 @@ export default function Footer() {
- ) + ); } diff --git a/components/Header.tsx b/components/Header.tsx index 38cc341..099a1a7 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -1,10 +1,10 @@ -import siteMetadata from '@/data/siteMetadata' -import headerNavLinks from '@/data/headerNavLinks' -import Logo from '@/data/logo.svg' -import Link from './Link' -import MobileNav from './MobileNav' -import ThemeSwitch from './ThemeSwitch' -import SearchButton from './SearchButton' +import siteMetadata from '@/data/siteMetadata'; +import headerNavLinks from '@/data/headerNavLinks'; +import Logo from '@/data/logo.svg'; +import Link from './Link'; +import MobileNav from './MobileNav'; +import ThemeSwitch from './ThemeSwitch'; +import SearchButton from './SearchButton'; const Header = () => { return ( @@ -32,8 +32,7 @@ const Header = () => { + className="hidden sm:block font-medium text-gray-900 dark:text-gray-100"> {link.title} ))} @@ -42,7 +41,7 @@ const Header = () => { - ) -} + ); +}; -export default Header +export default Header; diff --git a/components/Image.tsx b/components/Image.tsx index fde277a..d5022ca 100644 --- a/components/Image.tsx +++ b/components/Image.tsx @@ -1,5 +1,5 @@ -import NextImage, { ImageProps } from 'next/image' +import NextImage, { ImageProps } from 'next/image'; -const Image = ({ ...rest }: ImageProps) => +const Image = ({ ...rest }: ImageProps) => ; -export default Image +export default Image; diff --git a/components/LayoutWrapper.tsx b/components/LayoutWrapper.tsx index d407766..ab594d2 100644 --- a/components/LayoutWrapper.tsx +++ b/components/LayoutWrapper.tsx @@ -1,27 +1,28 @@ -import { Inter } from 'next/font/google' -import SectionContainer from './SectionContainer' -import Footer from './Footer' -import { ReactNode } from 'react' -import Header from './Header' +import { Inter } from 'next/font/google'; +import SectionContainer from './SectionContainer'; +import Footer from './Footer'; +import { ReactNode } from 'react'; +import Header from './Header'; interface Props { - children: ReactNode + children: ReactNode; } const inter = Inter({ subsets: ['latin'], -}) +}); const LayoutWrapper = ({ children }: Props) => { return ( -
+
{children}
- ) -} + ); +}; -export default LayoutWrapper +export default LayoutWrapper; diff --git a/components/Link.tsx b/components/Link.tsx index 00c168c..83b0299 100644 --- a/components/Link.tsx +++ b/components/Link.tsx @@ -1,21 +1,24 @@ /* eslint-disable jsx-a11y/anchor-has-content */ -import Link from 'next/link' -import type { LinkProps } from 'next/link' -import { AnchorHTMLAttributes } from 'react' +import Link from 'next/link'; +import type { LinkProps } from 'next/link'; +import { AnchorHTMLAttributes } from 'react'; -const CustomLink = ({ href, ...rest }: LinkProps & AnchorHTMLAttributes) => { - const isInternalLink = href && href.startsWith('/') - const isAnchorLink = href && href.startsWith('#') +const CustomLink = ({ + href, + ...rest +}: LinkProps & AnchorHTMLAttributes) => { + const isInternalLink = href && href.startsWith('/'); + const isAnchorLink = href && href.startsWith('#'); if (isInternalLink) { - return + return ; } if (isAnchorLink) { - return + return ; } - return -} + return ; +}; -export default CustomLink +export default CustomLink; diff --git a/components/MDXComponents.tsx b/components/MDXComponents.tsx index a440b7a..994f09e 100644 --- a/components/MDXComponents.tsx +++ b/components/MDXComponents.tsx @@ -1,9 +1,9 @@ -import TOCInline from 'pliny/ui/TOCInline' -import Pre from 'pliny/ui/Pre' -import BlogNewsletterForm from 'pliny/ui/BlogNewsletterForm' -import type { MDXComponents } from 'mdx/types' -import Image from './Image' -import CustomLink from './Link' +import TOCInline from 'pliny/ui/TOCInline'; +import Pre from 'pliny/ui/Pre'; +import BlogNewsletterForm from 'pliny/ui/BlogNewsletterForm'; +import type { MDXComponents } from 'mdx/types'; +import Image from './Image'; +import CustomLink from './Link'; export const components: MDXComponents = { Image, @@ -11,4 +11,4 @@ export const components: MDXComponents = { a: CustomLink, pre: Pre, BlogNewsletterForm, -} +}; diff --git a/components/MobileNav.tsx b/components/MobileNav.tsx index 45db769..103feb7 100644 --- a/components/MobileNav.tsx +++ b/components/MobileNav.tsx @@ -1,33 +1,35 @@ -'use client' +'use client'; -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 ( <> -
@@ -71,7 +73,7 @@ const MobileNav = () => { - ) -} + ); +}; -export default MobileNav +export default MobileNav; diff --git a/components/PageTitle.tsx b/components/PageTitle.tsx index fa05fb5..7d01b15 100644 --- a/components/PageTitle.tsx +++ b/components/PageTitle.tsx @@ -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) {

{children}

- ) + ); } diff --git a/components/ScrollTopAndComment.tsx b/components/ScrollTopAndComment.tsx index 080d72f..64d4717 100644 --- a/components/ScrollTopAndComment.tsx +++ b/components/ScrollTopAndComment.tsx @@ -1,37 +1,37 @@ -'use client' +'use client'; -import siteMetadata from '@/data/siteMetadata' -import { useEffect, useState } from 'react' +import siteMetadata from '@/data/siteMetadata'; +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 }) - } + window.scrollTo({ top: 0 }); + }; const handleScrollToComment = () => { - document.getElementById('comment')?.scrollIntoView() - } + document.getElementById('comment')?.scrollIntoView(); + }; return ( - ) -} + ); +}; -export default ScrollTopAndComment +export default ScrollTopAndComment; diff --git a/components/SearchButton.tsx b/components/SearchButton.tsx index 2fabf8e..40828c2 100644 --- a/components/SearchButton.tsx +++ b/components/SearchButton.tsx @@ -1,14 +1,15 @@ -import { AlgoliaButton } from 'pliny/search/AlgoliaButton' -import { KBarButton } from 'pliny/search/KBarButton' -import siteMetadata from '@/data/siteMetadata' +import { AlgoliaButton } from 'pliny/search/AlgoliaButton'; +import { KBarButton } from 'pliny/search/KBarButton'; +import siteMetadata from '@/data/siteMetadata'; const SearchButton = () => { if ( siteMetadata.search && - (siteMetadata.search.provider === 'algolia' || siteMetadata.search.provider === 'kbar') + (siteMetadata.search.provider === 'algolia' || + siteMetadata.search.provider === 'kbar') ) { const SearchButtonWrapper = - siteMetadata.search.provider === 'algolia' ? AlgoliaButton : KBarButton + siteMetadata.search.provider === 'algolia' ? AlgoliaButton : KBarButton; return ( @@ -18,8 +19,7 @@ const SearchButton = () => { viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" - className="text-gray-900 dark:text-gray-100 h-6 w-6" - > + className="text-gray-900 dark:text-gray-100 h-6 w-6"> { /> - ) + ); } -} +}; -export default SearchButton +export default SearchButton; diff --git a/components/SectionContainer.tsx b/components/SectionContainer.tsx index ee6d9fe..2289956 100644 --- a/components/SectionContainer.tsx +++ b/components/SectionContainer.tsx @@ -1,11 +1,13 @@ -import { ReactNode } from 'react' +import { ReactNode } from 'react'; interface Props { - children: ReactNode + children: ReactNode; } export default function SectionContainer({ children }: Props) { return ( -
{children}
- ) +
+ {children} +
+ ); } diff --git a/components/Tag.tsx b/components/Tag.tsx index bfd4487..84eb729 100644 --- a/components/Tag.tsx +++ b/components/Tag.tsx @@ -1,18 +1,17 @@ -import Link from 'next/link' -import { slug } from 'github-slugger' +import Link from 'next/link'; +import { slug } from 'github-slugger'; interface Props { - text: string + text: string; } const Tag = ({ text }: Props) => { return ( + className="mr-3 text-sm font-medium uppercase text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"> {text.split(' ').join('-')} - ) -} + ); +}; -export default Tag +export default Tag; diff --git a/components/ThemeSwitch.tsx b/components/ThemeSwitch.tsx index ea22cce..643c3a4 100644 --- a/components/ThemeSwitch.tsx +++ b/components/ThemeSwitch.tsx @@ -1,30 +1,28 @@ -'use client' +'use client'; -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 } = useTheme() + const [mounted, setMounted] = useState(false); + const { theme, setTheme } = useTheme(); // When mounted on client, now we can show the UI - useEffect(() => setMounted(true), []) + useEffect(() => setMounted(true), []); if (!mounted) { - return null + return null; } return ( - ) -} + ); +}; -export default ThemeSwitch +export default ThemeSwitch; diff --git a/components/social-icons/icons.tsx b/components/social-icons/icons.tsx index e9efeba..772b2b3 100644 --- a/components/social-icons/icons.tsx +++ b/components/social-icons/icons.tsx @@ -1,4 +1,4 @@ -import { SVGProps } from 'react' +import { SVGProps } from 'react'; // Icons taken from: https://simpleicons.org/ // To add a new icon, add a new function here and add it to components in social-icons/index.tsx @@ -8,7 +8,7 @@ export function Facebook(svgProps: SVGProps) { - ) + ); } export function Github(svgProps: SVGProps) { @@ -16,7 +16,7 @@ export function Github(svgProps: SVGProps) { - ) + ); } export function Linkedin(svgProps: SVGProps) { @@ -24,7 +24,7 @@ export function Linkedin(svgProps: SVGProps) { - ) + ); } export function Mail(svgProps: SVGProps) { @@ -33,7 +33,7 @@ export function Mail(svgProps: SVGProps) { - ) + ); } export function Twitter(svgProps: SVGProps) { @@ -41,7 +41,7 @@ export function Twitter(svgProps: SVGProps) { - ) + ); } export function Youtube(svgProps: SVGProps) { @@ -49,7 +49,7 @@ export function Youtube(svgProps: SVGProps) { - ) + ); } export function Mastodon(svgProps: SVGProps) { @@ -57,5 +57,5 @@ export function Mastodon(svgProps: SVGProps) { - ) + ); } diff --git a/components/social-icons/index.tsx b/components/social-icons/index.tsx index 32e757e..35852a9 100644 --- a/components/social-icons/index.tsx +++ b/components/social-icons/index.tsx @@ -1,4 +1,12 @@ -import { Mail, Github, Facebook, Youtube, Linkedin, Twitter, Mastodon } from './icons' +import { + Mail, + Github, + Facebook, + Youtube, + Linkedin, + Twitter, + Mastodon, +} from './icons'; const components = { mail: Mail, @@ -8,33 +16,36 @@ const components = { linkedin: Linkedin, twitter: Twitter, mastodon: Mastodon, -} +}; type SocialIconProps = { - kind: keyof typeof components - href: string | undefined - size?: number -} + kind: keyof typeof components; + href: string | undefined; + size?: number; +}; const SocialIcon = ({ kind, href, size = 8 }: SocialIconProps) => { - 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 (
+ href={href}> {kind} - ) -} + ); +}; -export default SocialIcon +export default SocialIcon; diff --git a/data/siteMetadata.js b/data/siteMetadata.js index a02d61b..839dbab 100644 --- a/data/siteMetadata.js +++ b/data/siteMetadata.js @@ -21,12 +21,21 @@ const siteMetadata = { 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 - plausibleDataDomain: '', // e.g. tailwind-nextjs-starter-blog.vercel.app - simpleAnalytics: false, // true or false - umamiWebsiteId: '', // e.g. 123e4567-e89b-12d3-a456-426614174000 - googleAnalyticsId: '', // e.g. UA-000000-2 or G-XXXXXXX - posthogAnalyticsId: '', // posthog.init e.g. phc_5yXvArzvRdqtZIsHkEm3Fkkhm3d0bEYUXCaFISzqPSQ + // supports Plausible, Simple Analytics, Umami, Posthog or Google Analytics. + umamiAnalytics: { + // We use an env variable for this site to avoid other users cloning our analytics ID + umamiWebsiteId: process.env.NEXT_UMAMI_ID, // e.g. 123e4567-e89b-12d3-a456-426614174000 + }, + // plausibleAnalytics: { + // plausibleDataDomain: '', // e.g. tailwind-nextjs-starter-blog.vercel.app + // }, + // simpleAnalytics: {}, + // posthogAnalytics: { + // posthogProjectApiKey: '', // e.g. 123e4567-e89b-12d3-a456-426614174000 + // }, + // googleAnalytics: { + // googleAnalyticsId: '', // e.g. G-XXXXXXX + // }, }, newsletter: { // supports mailchimp, buttondown, convertkit, klaviyo, revue, emailoctopus @@ -80,6 +89,20 @@ const siteMetadata = { url: process.env.NEXT_PUBLIC_COMMENTO_URL, }, }, + search: { + provider: 'kbar', // kbar or algolia + kbarConfig: { + searchDocumentsPath: 'search.json', // path to load documents to search + }, + // provider: 'algolia', + // algoliaConfig: { + // // The application ID provided by Algolia + // appId: 'R2IYF7ETH7', + // // Public API key: it is safe to commit it + // apiKey: '599cec31baffa4868cae4e79f180729b', + // indexName: 'docsearch', + // }, + }, }; module.exports = siteMetadata; diff --git a/layouts/AuthorLayout.tsx b/layouts/AuthorLayout.tsx index 004b8f1..58bf880 100644 --- a/layouts/AuthorLayout.tsx +++ b/layouts/AuthorLayout.tsx @@ -1,15 +1,24 @@ -import { ReactNode } from 'react' -import type { Authors } from 'contentlayer/generated' -import SocialIcon from '@/components/social-icons' -import Image from '@/components/Image' +import { ReactNode } from 'react'; +import type { Authors } from 'contentlayer/generated'; +import SocialIcon from '@/components/social-icons'; +import Image from '@/components/Image'; interface Props { - children: ReactNode - content: Omit + children: ReactNode; + content: Omit; } export default function AuthorLayout({ children, content }: Props) { - const { name, avatar, occupation, company, email, twitter, linkedin, github } = content + const { + name, + avatar, + occupation, + company, + email, + twitter, + linkedin, + github, + } = content; return ( <> @@ -30,7 +39,9 @@ export default function AuthorLayout({ children, content }: Props) { className="h-48 w-48 rounded-full" /> )} -

{name}

+

+ {name} +

{occupation}
{company}
@@ -46,5 +57,5 @@ export default function AuthorLayout({ children, content }: Props) {
- ) + ); } diff --git a/layouts/ListLayout.tsx b/layouts/ListLayout.tsx index 8949720..da4c75a 100644 --- a/layouts/ListLayout.tsx +++ b/layouts/ListLayout.tsx @@ -1,44 +1,49 @@ -'use client' +'use client'; -import { useState } from 'react' -import { usePathname } from 'next/navigation' -import { formatDate } from 'pliny/utils/formatDate' -import { CoreContent } from 'pliny/utils/contentlayer' -import type { Blog } from 'contentlayer/generated' -import Link from '@/components/Link' -import Tag from '@/components/Tag' -import siteMetadata from '@/data/siteMetadata' +import { useState } from 'react'; +import { usePathname } from 'next/navigation'; +import { formatDate } from 'pliny/utils/formatDate'; +import { CoreContent } from 'pliny/utils/contentlayer'; +import type { Blog } from 'contentlayer/generated'; +import Link from '@/components/Link'; +import Tag from '@/components/Tag'; +import siteMetadata from '@/data/siteMetadata'; interface PaginationProps { - totalPages: number - currentPage: number + totalPages: number; + currentPage: number; } interface ListLayoutProps { - posts: CoreContent[] - title: string - initialDisplayPosts?: CoreContent[] - pagination?: PaginationProps + posts: CoreContent[]; + title: string; + initialDisplayPosts?: CoreContent[]; + pagination?: PaginationProps; } function Pagination({ totalPages, currentPage }: PaginationProps) { - const pathname = usePathname() - const basePath = pathname.split('/')[1] - const prevPage = currentPage - 1 > 0 - const nextPage = currentPage + 1 <= totalPages + const pathname = usePathname(); + const basePath = pathname.split('/')[1]; + const prevPage = currentPage - 1 > 0; + const nextPage = currentPage + 1 <= totalPages; return (
- ) + ); } export default function ListLayout({ @@ -66,15 +73,17 @@ export default function ListLayout({ initialDisplayPosts = [], pagination, }: ListLayoutProps) { - const [searchValue, setSearchValue] = useState('') + const [searchValue, setSearchValue] = useState(''); const filteredBlogPosts = posts.filter((post) => { - const searchContent = post.title + post.summary + post.tags?.join(' ') - return searchContent.toLowerCase().includes(searchValue.toLowerCase()) - }) + const searchContent = post.title + post.summary + post.tags?.join(' '); + return searchContent.toLowerCase().includes(searchValue.toLowerCase()); + }); // If initialDisplayPosts exist, display it if no searchValue is specified const displayPosts = - initialDisplayPosts.length > 0 && !searchValue ? initialDisplayPosts : filteredBlogPosts + initialDisplayPosts.length > 0 && !searchValue + ? initialDisplayPosts + : filteredBlogPosts; return ( <> @@ -99,8 +108,7 @@ export default function ListLayout({ xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" - stroke="currentColor" - > + stroke="currentColor"> {!filteredBlogPosts.length && 'No posts found.'} {displayPosts.map((post) => { - const { path, date, title, summary, tags } = post + const { path, date, title, summary, tags } = post; return (
  • Published on
    - +

    - + {title}

    @@ -140,13 +152,16 @@ export default function ListLayout({
  • - ) + ); })} {pagination && pagination.totalPages > 1 && !searchValue && ( - + )} - ) + ); } diff --git a/layouts/ListLayoutWithTags.tsx b/layouts/ListLayoutWithTags.tsx index 2f481ab..b261e97 100644 --- a/layouts/ListLayoutWithTags.tsx +++ b/layouts/ListLayoutWithTags.tsx @@ -1,46 +1,51 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -'use client' +'use client'; -import { usePathname } from 'next/navigation' -import { slug } from 'github-slugger' -import { formatDate } from 'pliny/utils/formatDate' -import { CoreContent } from 'pliny/utils/contentlayer' -import type { Blog } from 'contentlayer/generated' -import Link from '@/components/Link' -import Tag from '@/components/Tag' -import siteMetadata from '@/data/siteMetadata' -import tagData from 'app/tag-data.json' +import { usePathname } from 'next/navigation'; +import { slug } from 'github-slugger'; +import { formatDate } from 'pliny/utils/formatDate'; +import { CoreContent } from 'pliny/utils/contentlayer'; +import type { Blog } from 'contentlayer/generated'; +import Link from '@/components/Link'; +import Tag from '@/components/Tag'; +import siteMetadata from '@/data/siteMetadata'; +import tagData from 'app/tag-data.json'; interface PaginationProps { - totalPages: number - currentPage: number + totalPages: number; + currentPage: number; } interface ListLayoutProps { - posts: CoreContent[] - title: string - initialDisplayPosts?: CoreContent[] - pagination?: PaginationProps + posts: CoreContent[]; + title: string; + initialDisplayPosts?: CoreContent[]; + pagination?: PaginationProps; } function Pagination({ totalPages, currentPage }: PaginationProps) { - const pathname = usePathname() - const basePath = pathname.split('/')[1] - const prevPage = currentPage - 1 > 0 - const nextPage = currentPage + 1 <= totalPages + const pathname = usePathname(); + const basePath = pathname.split('/')[1]; + const prevPage = currentPage - 1 > 0; + const nextPage = currentPage + 1 <= totalPages; return (
    - ) + ); } export default function ListLayoutWithTags({ @@ -68,12 +75,13 @@ export default function ListLayoutWithTags({ initialDisplayPosts = [], pagination, }: ListLayoutProps) { - const pathname = usePathname() - const tagCounts = tagData as Record - const tagKeys = Object.keys(tagCounts) - const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a]) + const pathname = usePathname(); + const tagCounts = tagData as Record; + const tagKeys = Object.keys(tagCounts); + const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a]); - const displayPosts = initialDisplayPosts.length > 0 ? initialDisplayPosts : posts + const displayPosts = + initialDisplayPosts.length > 0 ? initialDisplayPosts : posts; return ( <> @@ -87,12 +95,13 @@ export default function ListLayoutWithTags({
    {pathname.startsWith('/blog') ? ( -

    All Posts

    +

    + All Posts +

    ) : ( + className="font-bold uppercase text-gray-700 dark:text-gray-300 hover:text-primary-500 dark:hover:text-primary-500"> All Posts )} @@ -108,13 +117,12 @@ export default function ListLayoutWithTags({ + aria-label={`View posts tagged ${t}`}> {`${t} (${tagCounts[t]})`} )} - ) + ); })}
    @@ -122,20 +130,24 @@ export default function ListLayoutWithTags({
      {displayPosts.map((post) => { - const { path, date, title, summary, tags } = post + const { path, date, title, summary, tags } = post; return (
    • Published on
      - +

      - + {title}

      @@ -149,15 +161,18 @@ export default function ListLayoutWithTags({
    • - ) + ); })}
    {pagination && pagination.totalPages > 1 && ( - + )}
    - ) + ); } diff --git a/layouts/PostBanner.tsx b/layouts/PostBanner.tsx index 7ba6b02..0232d63 100644 --- a/layouts/PostBanner.tsx +++ b/layouts/PostBanner.tsx @@ -1,26 +1,33 @@ -import { ReactNode } from 'react' -import Image from '@/components/Image' -import Bleed from 'pliny/ui/Bleed' -import { CoreContent } from 'pliny/utils/contentlayer' -import type { Blog } from 'contentlayer/generated' -import Comments from '@/components/Comments' -import Link from '@/components/Link' -import PageTitle from '@/components/PageTitle' -import SectionContainer from '@/components/SectionContainer' -import siteMetadata from '@/data/siteMetadata' -import ScrollTopAndComment from '@/components/ScrollTopAndComment' +import { ReactNode } from 'react'; +import Image from '@/components/Image'; +import Bleed from 'pliny/ui/Bleed'; +import { CoreContent } from 'pliny/utils/contentlayer'; +import type { Blog } from 'contentlayer/generated'; +import Comments from '@/components/Comments'; +import Link from '@/components/Link'; +import PageTitle from '@/components/PageTitle'; +import SectionContainer from '@/components/SectionContainer'; +import siteMetadata from '@/data/siteMetadata'; +import ScrollTopAndComment from '@/components/ScrollTopAndComment'; interface LayoutProps { - content: CoreContent - children: ReactNode - next?: { path: string; title: string } - prev?: { path: string; title: string } + content: CoreContent; + children: ReactNode; + next?: { path: string; title: string }; + prev?: { path: string; title: string }; } -export default function PostMinimal({ content, next, prev, children }: LayoutProps) { - const { slug, title, images } = content +export default function PostMinimal({ + content, + next, + prev, + children, +}: LayoutProps) { + const { slug, title, images } = content; const displayImage = - images && images.length > 0 ? images[0] : 'https://picsum.photos/seed/picsum/800/400' + images && images.length > 0 + ? images[0] + : 'https://picsum.photos/seed/picsum/800/400'; return ( @@ -31,7 +38,12 @@ export default function PostMinimal({ content, next, prev, children }: LayoutPro
    - {title} + {title}
    @@ -39,9 +51,13 @@ export default function PostMinimal({ content, next, prev, children }: LayoutPro {title} -
    {children}
    +
    + {children} +
    {siteMetadata.comments && ( -
    +
    )} @@ -52,8 +68,7 @@ export default function PostMinimal({ content, next, prev, children }: LayoutPro + aria-label={`Previous post: ${prev.title}`}> ← {prev.title}
    @@ -63,8 +78,7 @@ export default function PostMinimal({ content, next, prev, children }: LayoutPro + aria-label={`Next post: ${next.title}`}> {next.title} → @@ -74,5 +88,5 @@ export default function PostMinimal({ content, next, prev, children }: LayoutPro
    - ) + ); } diff --git a/layouts/PostLayout.tsx b/layouts/PostLayout.tsx index 2153f91..068ac88 100644 --- a/layouts/PostLayout.tsx +++ b/layouts/PostLayout.tsx @@ -1,23 +1,23 @@ -import { ReactNode } from 'react' -import { CoreContent } from 'pliny/utils/contentlayer' -import type { Blog, Authors } from 'contentlayer/generated' -import Comments from '@/components/Comments' -import Link from '@/components/Link' -import PageTitle from '@/components/PageTitle' -import SectionContainer from '@/components/SectionContainer' -import Image from '@/components/Image' -import Tag from '@/components/Tag' -import siteMetadata from '@/data/siteMetadata' -import ScrollTopAndComment from '@/components/ScrollTopAndComment' +import { ReactNode } from 'react'; +import { CoreContent } from 'pliny/utils/contentlayer'; +import type { Blog, Authors } from 'contentlayer/generated'; +import Comments from '@/components/Comments'; +import Link from '@/components/Link'; +import PageTitle from '@/components/PageTitle'; +import SectionContainer from '@/components/SectionContainer'; +import Image from '@/components/Image'; +import Tag from '@/components/Tag'; +import siteMetadata from '@/data/siteMetadata'; +import ScrollTopAndComment from '@/components/ScrollTopAndComment'; -const editUrl = (path) => `${siteMetadata.siteRepo}/raw/branch/master/data/${path}` +const editUrl = (path) => + `${siteMetadata.siteRepo}/raw/branch/master/data/${path}`; const Copyright = () => ( + className="inline-flex self-center"> 知识共享许可协议 ( src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /> -) +); const postDateTemplate: Intl.DateTimeFormatOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', -} +}; interface LayoutProps { - content: CoreContent - authorDetails: CoreContent[] - next?: { path: string; title: string } - prev?: { path: string; title: string } - children: ReactNode + content: CoreContent; + authorDetails: CoreContent[]; + next?: { path: string; title: string }; + prev?: { path: string; title: string }; + children: ReactNode; } -export default function PostLayout({ content, authorDetails, next, prev, children }: LayoutProps) { - const { filePath, path, slug, date, title, tags } = content - const basePath = path.split('/')[0] +export default function PostLayout({ + content, + authorDetails, + next, + prev, + children, +}: LayoutProps) { + const { filePath, path, slug, date, title, tags } = content; + const basePath = path.split('/')[0]; return ( @@ -59,7 +65,10 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
    Published on
    @@ -75,7 +84,9 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
      {authorDetails.map((author) => ( -
    • +
    • {author.avatar && (
      Name
      -
      {author.name}
      +
      + {author.name} +
      Twitter
      {author.twitter && ( - {author.twitter.replace('https://twitter.com/', '@')} + className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"> + {author.twitter.replace( + 'https://twitter.com/', + '@', + )} )}
      @@ -106,7 +121,9 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
    -
    {children}
    +
    + {children} +
    {'View source'} @@ -114,8 +131,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre {siteMetadata.comments && (
    + id="comment">
    )} @@ -163,8 +179,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre + aria-label="Back to the blog"> ← Back to the blog
    @@ -173,5 +188,5 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
    - ) + ); } diff --git a/layouts/PostSimple.tsx b/layouts/PostSimple.tsx index 074a551..6777fa3 100644 --- a/layouts/PostSimple.tsx +++ b/layouts/PostSimple.tsx @@ -1,23 +1,28 @@ -import { ReactNode } from 'react' -import { formatDate } from 'pliny/utils/formatDate' -import { CoreContent } from 'pliny/utils/contentlayer' -import type { Blog } from 'contentlayer/generated' -import Comments from '@/components/Comments' -import Link from '@/components/Link' -import PageTitle from '@/components/PageTitle' -import SectionContainer from '@/components/SectionContainer' -import siteMetadata from '@/data/siteMetadata' -import ScrollTopAndComment from '@/components/ScrollTopAndComment' +import { ReactNode } from 'react'; +import { formatDate } from 'pliny/utils/formatDate'; +import { CoreContent } from 'pliny/utils/contentlayer'; +import type { Blog } from 'contentlayer/generated'; +import Comments from '@/components/Comments'; +import Link from '@/components/Link'; +import PageTitle from '@/components/PageTitle'; +import SectionContainer from '@/components/SectionContainer'; +import siteMetadata from '@/data/siteMetadata'; +import ScrollTopAndComment from '@/components/ScrollTopAndComment'; interface LayoutProps { - content: CoreContent - children: ReactNode - next?: { path: string; title: string } - prev?: { path: string; title: string } + content: CoreContent; + children: ReactNode; + next?: { path: string; title: string }; + prev?: { path: string; title: string }; } -export default function PostLayout({ content, next, prev, children }: LayoutProps) { - const { path, slug, date, title } = content +export default function PostLayout({ + content, + next, + prev, + children, +}: LayoutProps) { + const { path, slug, date, title } = content; return ( @@ -30,7 +35,9 @@ export default function PostLayout({ content, next, prev, children }: LayoutProp
    Published on
    - +
    @@ -41,10 +48,14 @@ export default function PostLayout({ content, next, prev, children }: LayoutProp
    -
    {children}
    +
    + {children} +
    {siteMetadata.comments && ( -
    +
    )} @@ -55,8 +66,7 @@ export default function PostLayout({ content, next, prev, children }: LayoutProp + aria-label={`Previous post: ${prev.title}`}> ← {prev.title}
    @@ -66,8 +76,7 @@ export default function PostLayout({ content, next, prev, children }: LayoutProp + aria-label={`Next post: ${next.title}`}> {next.title} →
    @@ -78,5 +87,5 @@ export default function PostLayout({ content, next, prev, children }: LayoutProp
    - ) + ); } diff --git a/prettier.config.js b/prettier.config.js index 0423726..2e7c768 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,9 +1,6 @@ module.exports = { - semi: false, singleQuote: true, - printWidth: 100, - tabWidth: 2, - useTabs: false, - trailingComma: 'es5', + trailingCommas: 'all', bracketSpacing: true, -} + bracketSameLine: true, +}; diff --git a/scripts/postbuild.mjs b/scripts/postbuild.mjs index 23172c5..c4d3a4c 100644 --- a/scripts/postbuild.mjs +++ b/scripts/postbuild.mjs @@ -1,7 +1,7 @@ -import rss from './rss.mjs' +import rss from './rss.mjs'; async function postbuild() { - await rss() + await rss(); } -postbuild() +postbuild(); diff --git a/scripts/rss.mjs b/scripts/rss.mjs index 7c4fb1d..8d5546f 100644 --- a/scripts/rss.mjs +++ b/scripts/rss.mjs @@ -1,10 +1,10 @@ -import { writeFileSync, mkdirSync } from 'fs' -import path from 'path' -import GithubSlugger from 'github-slugger' -import { escape } from 'pliny/utils/htmlEscaper.js' -import siteMetadata from '../data/siteMetadata.js' -import tagData from '../app/tag-data.json' assert { type: 'json' } -import { allBlogs } from '../.contentlayer/generated/index.mjs' +import { writeFileSync, mkdirSync } from 'fs'; +import path from 'path'; +import GithubSlugger from 'github-slugger'; +import { escape } from 'pliny/utils/htmlEscaper.js'; +import siteMetadata from '../data/siteMetadata.js'; +import tagData from '../app/tag-data.json' assert { type: 'json' }; +import { allBlogs } from '../.contentlayer/generated/index.mjs'; const generateRssItem = (config, post) => ` @@ -16,7 +16,7 @@ const generateRssItem = (config, post) => ` ${config.email} (${config.author}) ${post.tags && post.tags.map((t) => `${t}`).join('')} -` +`; const generateRss = (config, posts, page = 'feed.xml') => ` @@ -28,35 +28,37 @@ const generateRss = (config, posts, page = 'feed.xml') => ` ${config.email} (${config.author}) ${config.email} (${config.author}) ${new Date(posts[0].date).toUTCString()} - + ${posts.map((post) => generateRssItem(config, post)).join('')} -` +`; async function generateRSS(config, allBlogs, page = 'feed.xml') { - const publishPosts = allBlogs.filter((post) => post.draft !== true) + const publishPosts = allBlogs.filter((post) => post.draft !== true); // RSS for blog post if (publishPosts.length > 0) { - const rss = generateRss(config, publishPosts) - writeFileSync(`./public/${page}`, rss) + const rss = generateRss(config, publishPosts); + writeFileSync(`./public/${page}`, rss); } if (publishPosts.length > 0) { for (const tag of Object.keys(tagData)) { const filteredPosts = allBlogs.filter((post) => - post.tags.map((t) => GithubSlugger.slug(t)).includes(tag) - ) - const rss = generateRss(config, filteredPosts, `tags/${tag}/${page}`) - const rssPath = path.join('public', 'tags', tag) - mkdirSync(rssPath, { recursive: true }) - writeFileSync(path.join(rssPath, page), rss) + post.tags.map((t) => GithubSlugger.slug(t)).includes(tag), + ); + const rss = generateRss(config, filteredPosts, `tags/${tag}/${page}`); + const rssPath = path.join('public', 'tags', tag); + mkdirSync(rssPath, { recursive: true }); + writeFileSync(path.join(rssPath, page), rss); } } } const rss = () => { - generateRSS(siteMetadata, allBlogs) - console.log('RSS feed generated...') -} -export default rss + generateRSS(siteMetadata, allBlogs); + console.log('RSS feed generated...'); +}; +export default rss;