Merge branch 'main' into feature/update-read-me

This commit is contained in:
Frank Mendez 2024-04-17 17:58:32 +08:00 committed by GitHub
commit 60b893c872
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 3734 additions and 3705 deletions

View File

@ -22,5 +22,8 @@ KLAVIYO_LIST_ID=
REVUE_API_KEY=
# Create EmailOctopus API key at https://emailoctopus.com/api-documentation
EMAILOCTOPUS_API_KEY=
# List ID can be found in the URL as a UUID after clicking a list on https://emailoctopus.com/lists
# or the settings page of your list https://emailoctopus.com/lists/{UUID}/settings
EMAILOCTOPUS_LIST_ID=

View File

@ -1,4 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install lint-staged

View File

@ -42,7 +42,7 @@ Internationalization support - [Template with i18n](https://tailwind-nextjs-star
- [enscribe.dev](https://enscribe.dev) - enscribe's personal blog; cybersecurity shenanigans, frontend webdev, etc. ([source code](https://github.com/jktrn/enscribe.dev))
- [dalelarroder.com](https://dalelarroder.com) - Dale Larroder's personal website upgraded from V1 ([source code](https://github.com/dlarroder/dalelarroder))
- [thetalhatahir.com](https://www.thetalhatahir.com) - Talha Tahir's personal blog. Added article thumbnails, linkedIn card, Beautiful hero content, technology emoticons.
- [hauhau.cn](https://www.hauhau.cn) - Homing's personal blog about the stuff he's learning ([source code](https://github.com/hominsu/blog))
- [homing.so](https://homing.so) - Homing's personal blog about the stuff he's learning ([source code](https://github.com/hominsu/blog))
- [zS1m's Blog](https://contrails.space) - zS1m's personal blog for recording and sharing daily learning technical content ([source code](https://github.com/zS1m/nextjs-contrails))
- [dariuszwozniak.net](https://dariuszwozniak.net/) - Software development blog
- [Terminals.run](https://terminals.run) - Blog site for some thoughts and records for life and technology.
@ -57,6 +57,8 @@ Internationalization support - [Template with i18n](https://tailwind-nextjs-star
- [Hans Blog](https://www.hansking.cn/) - Hans' personal blog, front-end technology, gallery and travel diary 中文. ([source code](https://github.com/hansking98/hans-nextjs-blog))
- [CuB3y0nd's Portfolio](https://www.cubeyond.net/) - CuB3y0nds cyber security study notes「中文」
- [London Tech Talk](https://london-tech-talk.com/) - A podcast exploring technology trends and expatriate living experiences. - 日本語
- [CRUD Flow Blog](http://blog.ndamulelo.co.za/) - A technical blog about AI, Cloud Engineering, Data Science and Personal development
- [Trillium's Blog](https://trilliumsmith.com/) - Modified to render resume pdf on `/resume` page
- [Frank's Tech Blog](https://frank-tech-blog.vercel.app/) - Frank's personal blog about software development and technology. ([source code](https://github.com/frank-mendez/frank-blog))
Using the template? Feel free to create a PR and add your blog to this list.
@ -98,7 +100,8 @@ Thanks to the community of users and contributors to the template! We are no lon
- [https://bitoflearning-9a57.fly.dev/](https://bitoflearning-9a57.fly.dev/) - Sangeet Agarwal's personal blog, replatformed to [remix](https://remix.run/remix) using the [indie stack](https://github.com/remix-run/indie-stack) ([source code](https://github.com/SangeetAgarwal/bitoflearning))
- [raphaelchelly.com](https://www.raphaelchelly.com/) - Raphaël Chelly's personal website and blog ([source code](https://github.com/raphaelchelly/raph_www))
- [kaveh.page](https://kaveh.page) - Kaveh Tehrani's personal blog. Added tags directory, profile card, time-to-read on posts directory, etc.
- [drakerossman.com](https://drakerossman.com/) - Drake Rossman's blog about NixOS, Rust, Software Architecture and Engineering Management, as well as general musings.
## Motivation
I wanted to port my existing blog to Nextjs and Tailwind CSS but there was no easy out of the box template to use so I decided to create one. Design is adapted from [Tailwindlabs blog](https://github.com/tailwindlabs/blog.tailwindcss.com).

View File

@ -73,7 +73,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000" />
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
<body className="bg-white text-black antialiased dark:bg-gray-950 dark:text-white">
<body className="bg-white pl-[calc(100vw-100%)] text-black antialiased dark:bg-gray-950 dark:text-white">
<ThemeProviders>
<Analytics analyticsConfig={siteMetadata.analytics as AnalyticsConfig} />
<SectionContainer>

View File

@ -1 +1,20 @@
{"next-js":6,"tailwind":3,"guide":5,"feature":2,"multi-author":1,"hello":1,"math":1,"ols":1,"github":1,"writings":1,"book":1,"reflection":1,"holiday":1,"canada":1,"images":1,"markdown":1,"code":1,"features":1}
{
"markdown": 1,
"code": 1,
"features": 1,
"next-js": 6,
"math": 1,
"ols": 1,
"github": 1,
"guide": 5,
"tailwind": 3,
"hello": 1,
"holiday": 1,
"canada": 1,
"images": 1,
"feature": 2,
"writings": 1,
"book": 1,
"reflection": 1,
"multi-author": 1
}

View File

@ -6,11 +6,16 @@ import siteMetadata from '@/data/siteMetadata'
export default function Comments({ slug }: { slug: string }) {
const [loadComments, setLoadComments] = useState(false)
if (!siteMetadata.comments?.provider) {
return null
}
return (
<>
{!loadComments && <button onClick={() => setLoadComments(true)}>Load Comments</button>}
{siteMetadata.comments && loadComments && (
{loadComments ? (
<CommentsComponent commentsConfig={siteMetadata.comments} slug={slug} />
) : (
<button onClick={() => setLoadComments(true)}>Load Comments</button>
)}
</>
)

View File

@ -1,7 +1,49 @@
'use client'
import { useEffect, useState } from 'react'
import { Fragment, useEffect, useState } from 'react'
import { useTheme } from 'next-themes'
import { Menu, RadioGroup, Transition } from '@headlessui/react'
const Sun = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-6 w-6 text-gray-900 dark:text-gray-100"
>
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
</svg>
)
const Moon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-6 w-6 text-gray-900 dark:text-gray-100"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
)
const Monitor = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
className="h-6 w-6 text-gray-900 dark:text-gray-100"
>
<rect x="3" y="3" width="14" height="10" rx="2" ry="2"></rect>
<line x1="7" y1="17" x2="13" y2="17"></line>
<line x1="10" y1="13" x2="10" y2="17"></line>
</svg>
)
const ThemeSwitch = () => {
const [mounted, setMounted] = useState(false)
@ -10,32 +52,60 @@ const ThemeSwitch = () => {
// When mounted on client, now we can show the UI
useEffect(() => setMounted(true), [])
if (!mounted) {
return null
}
return (
<button
aria-label="Toggle Dark Mode"
onClick={() => setTheme(theme === 'dark' || resolvedTheme === 'dark' ? 'light' : 'dark')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-6 w-6 text-gray-900 dark:text-gray-100"
>
{mounted && (theme === 'dark' || resolvedTheme === 'dark') ? (
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
) : (
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
)}
</svg>
</button>
<div className="mr-5">
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button>{resolvedTheme === 'dark' ? <Moon /> : <Sun />}</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-2 w-32 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-800">
<RadioGroup value={theme} onChange={setTheme}>
<div className="p-1">
<RadioGroup.Option value="light">
<Menu.Item>
<button className="group flex w-full items-center rounded-md px-2 py-2 text-sm">
<div className="mr-2">
<Sun />
</div>
Light
</button>
</Menu.Item>
</RadioGroup.Option>
<RadioGroup.Option value="dark">
<Menu.Item>
<button className="group flex w-full items-center rounded-md px-2 py-2 text-sm">
<div className="mr-2">
<Moon />
</div>
Dark
</button>
</Menu.Item>
</RadioGroup.Option>
<RadioGroup.Option value="system">
<Menu.Item>
<button className="group flex w-full items-center rounded-md px-2 py-2 text-sm">
<div className="mr-2">
<Monitor />
</div>
System
</button>
</Menu.Item>
</RadioGroup.Option>
</div>
</RadioGroup>
</Menu.Items>
</Transition>
</Menu>
</div>
)
}

View File

@ -1,8 +1,9 @@
import { defineDocumentType, ComputedFields, makeSource } from 'contentlayer/source-files'
import { defineDocumentType, ComputedFields, makeSource } from 'contentlayer2/source-files'
import { writeFileSync } from 'fs'
import readingTime from 'reading-time'
import { slug } from 'github-slugger'
import path from 'path'
import { fromHtmlIsomorphic } from 'hast-util-from-html-isomorphic'
// Remark packages
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
@ -25,6 +26,19 @@ import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer.js'
const root = process.cwd()
const isProduction = process.env.NODE_ENV === 'production'
// heroicon mini link
const icon = fromHtmlIsomorphic(
`
<span class="content-header-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-5 h-5 linkicon">
<path d="M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667l3-3Z" />
<path d="M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865Z" />
</svg>
</span>
`,
{ fragment: true }
)
const computedFields: ComputedFields = {
readingTime: { type: 'json', resolve: (doc) => readingTime(doc.body.raw) },
slug: {
@ -142,7 +156,16 @@ export default makeSource({
],
rehypePlugins: [
rehypeSlug,
rehypeAutolinkHeadings,
[
rehypeAutolinkHeadings,
{
behavior: 'prepend',
headingProperties: {
className: ['content-header'],
},
content: icon,
},
],
rehypeKatex,
[rehypeCitation, { path: path.join(root, 'data') }],
[rehypePrismPlus, { defaultLanguage: 'js', ignoreMissing: true }],

View File

@ -33,3 +33,19 @@ input:-webkit-autofill:focus {
.katex-display {
overflow: auto hidden;
}
.content-header-link {
opacity: 0;
margin-left: -24px;
padding-right: 4px;
}
.content-header:hover .content-header-link,
.content-header-link:hover {
opacity: 1;
}
.linkicon {
display: inline-block;
vertical-align: middle;
}

View File

@ -33,7 +33,7 @@ export default Home
For a markdown file, the default image tag can be used and the default `img` tag gets replaced by the `Image` component in the build process.
Assuming we have a file called `ocean.jpg` in `data/img/ocean.jpg`, the following line of code would generate the optimized image.
Assuming we have a file called `ocean.jpg` in `static/images/ocean.jpg`, the following line of code would generate the optimized image.
```
![ocean](/static/images/ocean.jpg)

View File

@ -81,7 +81,9 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
href={author.twitter}
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
>
{author.twitter.replace('https://twitter.com/', '@')}
{author.twitter
.replace('https://twitter.com/', '@')
.replace('https://x.com/', '@')}
</Link>
)}
</dd>

View File

@ -1,4 +1,4 @@
const { withContentlayer } = require('next-contentlayer')
const { withContentlayer } = require('next-contentlayer2')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',

View File

@ -8,64 +8,58 @@
"build": "cross-env INIT_CWD=$PWD next build && cross-env NODE_OPTIONS='--experimental-json-modules' node ./scripts/postbuild.mjs",
"serve": "next start",
"analyze": "cross-env ANALYZE=true next build",
"lint": "next lint --fix --dir pages --dir app --dir components --dir lib --dir layouts --dir scripts"
"lint": "next lint --fix --dir pages --dir app --dir components --dir lib --dir layouts --dir scripts",
"prepare": "husky"
},
"dependencies": {
"@next/bundle-analyzer": "14.1.0",
"@tailwindcss/forms": "^0.5.4",
"@tailwindcss/typography": "^0.5.9",
"@headlessui/react": "1.7.19",
"@next/bundle-analyzer": "14.2.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.12",
"autoprefixer": "^10.4.13",
"contentlayer": "0.3.4",
"esbuild": "0.18.11",
"contentlayer2": "0.4.4",
"esbuild": "0.20.2",
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.2",
"hast-util-from-html-isomorphic": "^2.0.0",
"image-size": "1.0.0",
"next": "14.1.0",
"next-contentlayer": "0.3.4",
"next-themes": "^0.2.1",
"pliny": "0.1.7",
"next": "14.2.1",
"next-contentlayer2": "0.4.4",
"next-themes": "^0.3.0",
"pliny": "0.2.0",
"postcss": "^8.4.24",
"react": "18.2.0",
"react-dom": "18.2.0",
"reading-time": "1.5.0",
"rehype-autolink-headings": "^6.1.0",
"rehype-citation": "^1.0.2",
"rehype-katex": "^6.0.3",
"rehype-preset-minify": "6.0.0",
"rehype-prism-plus": "^1.6.0",
"rehype-slug": "^5.1.0",
"remark": "^14.0.2",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"tailwindcss": "^3.4.1",
"unist-util-visit": "^4.1.0"
"rehype-autolink-headings": "^7.1.0",
"rehype-citation": "^2.0.0",
"rehype-katex": "^7.0.0",
"rehype-preset-minify": "7.0.0",
"rehype-prism-plus": "^2.0.0",
"rehype-slug": "^6.0.0",
"remark": "^15.0.0",
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"tailwindcss": "^3.4.3",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@svgr/webpack": "^8.0.1",
"@types/mdx": "^2.0.5",
"@types/react": "^18.2.14",
"@types/mdx": "^2.0.12",
"@types/react": "^18.2.73",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"cross-env": "^7.0.3",
"eslint": "^8.45.0",
"eslint-config-next": "14.1.0",
"eslint-config-next": "14.2.1",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^5.0.0",
"husky": "^8.0.0",
"husky": "^9.0.0",
"lint-staged": "^13.0.0",
"prettier": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.5.11",
"typescript": "^5.1.3"
},
"resolutions": {
"@opentelemetry/api": "1.4.1",
"@opentelemetry/core": "1.13.0",
"@opentelemetry/exporter-trace-otlp-grpc": "0.39.1",
"@opentelemetry/resources": "1.13.0",
"@opentelemetry/sdk-trace-base": "1.13.0",
"@opentelemetry/sdk-trace-node": "1.13.0",
"@opentelemetry/semantic-conventions": "1.13.0"
},
"lint-staged": {
"*.+(js|jsx|ts|tsx)": [
"eslint --fix"

7159
yarn.lock

File diff suppressed because it is too large Load Diff