Merge branch 'main' of https://github.com/timlrx/tailwind-nextjs-starter-blog into upstream
This commit is contained in:
commit
c2af905066
33
.env.example
Normal file
33
.env.example
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# visit https://giscus.app to get your Giscus ids
|
||||||
|
NEXT_PUBLIC_GISCUS_REPO=
|
||||||
|
NEXT_PUBLIC_GISCUS_REPOSITORY_ID=
|
||||||
|
NEXT_PUBLIC_GISCUS_CATEGORY=
|
||||||
|
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
|
||||||
|
NEXT_PUBLIC_UTTERANCES_REPO=
|
||||||
|
NEXT_PUBLIC_DISQUS_SHORTNAME=
|
||||||
|
|
||||||
|
|
||||||
|
MAILCHIMP_API_KEY=
|
||||||
|
MAILCHIMP_API_SERVER=
|
||||||
|
MAILCHIMP_AUDIENCE_ID=
|
||||||
|
|
||||||
|
BUTTONDOWN_API_KEY=
|
||||||
|
|
||||||
|
CONVERTKIT_API_KEY=
|
||||||
|
# curl https://api.convertkit.com/v3/forms?api_key=<your_public_api_key> to get your form ID
|
||||||
|
CONVERTKIT_FORM_ID=
|
||||||
|
|
||||||
|
KLAVIYO_API_KEY=
|
||||||
|
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=
|
||||||
|
|
||||||
|
# Create Beehive API key at https://developers.beehiiv.com/docs/v2/bktd9a7mxo67n-create-an-api-key
|
||||||
|
BEEHIVE_API_KEY=
|
||||||
|
BEEHIVE_PUBLICATION_ID=
|
@ -44,8 +44,8 @@ Internationalization support - [Template with i18n](https://tailwind-nextjs-star
|
|||||||
- [thetalhatahir.com](https://www.thetalhatahir.com) - Talha Tahir's personal blog. Added article thumbnails, linkedIn card, Beautiful hero content, technology emoticons.
|
- [thetalhatahir.com](https://www.thetalhatahir.com) - Talha Tahir's personal blog. Added article thumbnails, linkedIn card, Beautiful hero content, technology emoticons.
|
||||||
- [homing.so](https://homing.so) - 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))
|
- [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
|
- [dariuszwozniak.net](https://dariuszwozniak.net/) - Software development blog ([source code](https://github.com/dariusz-wozniak/dariuszwozniak.net-v2))
|
||||||
- [Terminals.run](https://terminals.run) - Blog site for some thoughts and records for life and technology.
|
- [dreams.plus](https://dreams.plus) - Blog site for some thoughts and records for life and technology.
|
||||||
- [francisaguilar.co blog](https://francisaguilar.co) - Francis Aguilar's personal blog that talks about tech, fitness, and personal development.
|
- [francisaguilar.co blog](https://francisaguilar.co) - Francis Aguilar's personal blog that talks about tech, fitness, and personal development.
|
||||||
- [Min71 Dev Blog](https://min71.dev) - Personal blog about Blockchain, Development and etc. ([source code](https://github.com/mingi3442/blog))
|
- [Min71 Dev Blog](https://min71.dev) - Personal blog about Blockchain, Development and etc. ([source code](https://github.com/mingi3442/blog))
|
||||||
- [Bryce Yu's Blog](https://earayu.github.io/) - Bryce Yu's personal Blog about distributed system, database, and web development. ([source code](https://github.com/earayu/earayu.github.io))
|
- [Bryce Yu's Blog](https://earayu.github.io/) - Bryce Yu's personal Blog about distributed system, database, and web development. ([source code](https://github.com/earayu/earayu.github.io))
|
||||||
@ -70,6 +70,9 @@ Internationalization support - [Template with i18n](https://tailwind-nextjs-star
|
|||||||
- [LyricsDecode.com](https://lyricsdecode.com) - A song lyrics website offering original lyrics, Romanisation, and English translations with customisable viewing options.
|
- [LyricsDecode.com](https://lyricsdecode.com) - A song lyrics website offering original lyrics, Romanisation, and English translations with customisable viewing options.
|
||||||
- [bmacharia.com](https://bmacharia.com/) - Benson Macharia's technical blog about Cybersecurity and IT Risk Management.
|
- [bmacharia.com](https://bmacharia.com/) - Benson Macharia's technical blog about Cybersecurity and IT Risk Management.
|
||||||
- [armujahid.me](https://armujahid.me/) - Abdul Rauf's personal blog about tech and random stuff.
|
- [armujahid.me](https://armujahid.me/) - Abdul Rauf's personal blog about tech and random stuff.
|
||||||
|
- [leohuynh.dev](leohuynh.dev) - 🇻🇳 Leo's dev blog – stories, insights, and ideas. Add `/snippets`, `/books` pages, add `ProfileCard`, `CareerTimeline` components and many more.
|
||||||
|
- [OpenSats Blog](https://opensats.org/blog) - A 501(c)(3) public charity which aims to sustainably fund free and open-source projects. ([source code](https://github.com/OpenSats/website))
|
||||||
|
- [Schedles Blog](https://schedles.com/blog) - Social media scheduling tips, strategies, and product updates for content creators. ([Project Link](https://schedles.com))
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@ -82,7 +85,6 @@ Thanks to the community of users and contributors to the template! We are no lon
|
|||||||
- [Aloisdg's cookbook](https://tambouille.vercel.app/) - with pictures and recipes!
|
- [Aloisdg's cookbook](https://tambouille.vercel.app/) - with pictures and recipes!
|
||||||
- [GautierArcin's demo with next translate](https://tailwind-nextjs-starter-blog-seven.vercel.app/) - includes translation of mdx posts, [source code](https://github.com/GautierArcin/tailwind-nextjs-starter-blog/tree/demo/next-translate)
|
- [GautierArcin's demo with next translate](https://tailwind-nextjs-starter-blog-seven.vercel.app/) - includes translation of mdx posts, [source code](https://github.com/GautierArcin/tailwind-nextjs-starter-blog/tree/demo/next-translate)
|
||||||
- [David Levai's digital garden](https://davidlevai.com/) - customized design and added email subscriptions
|
- [David Levai's digital garden](https://davidlevai.com/) - customized design and added email subscriptions
|
||||||
- [Leo's Blog](https://leohuynh.dev) - Tuan Anh Huynh's personal site. Add Snippets Page, Author Profile Card, Image Lightbox, ...
|
|
||||||
- [thvu.dev](https://thvu.dev) - Added `mdx-embed`, view count, reading minutes and more.
|
- [thvu.dev](https://thvu.dev) - Added `mdx-embed`, view count, reading minutes and more.
|
||||||
- [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.
|
||||||
- [KirillSo.com](https://www.kirillso.com/) - Personal blog & website.
|
- [KirillSo.com](https://www.kirillso.com/) - Personal blog & website.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { NewsletterAPI } from 'pliny/newsletter'
|
import { NewsletterAPI } from 'pliny/newsletter'
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
|
|
||||||
|
export const dynamic = 'force-static'
|
||||||
|
|
||||||
const handler = NewsletterAPI({
|
const handler = NewsletterAPI({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
provider: siteMetadata.newsletter.provider,
|
provider: siteMetadata.newsletter.provider,
|
||||||
|
@ -21,11 +21,10 @@ const layouts = {
|
|||||||
PostBanner,
|
PostBanner,
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata(props: {
|
||||||
params,
|
params: Promise<{ slug: string[] }>
|
||||||
}: {
|
|
||||||
params: { slug: string[] }
|
|
||||||
}): Promise<Metadata | undefined> {
|
}): Promise<Metadata | undefined> {
|
||||||
|
const params = await props.params
|
||||||
const slug = decodeURI(params.slug.join('/'))
|
const slug = decodeURI(params.slug.join('/'))
|
||||||
const post = allBlogs.find((p) => p.slug === slug)
|
const post = allBlogs.find((p) => p.slug === slug)
|
||||||
const authorList = post?.authors || ['default']
|
const authorList = post?.authors || ['default']
|
||||||
@ -78,7 +77,8 @@ export const generateStaticParams = async () => {
|
|||||||
return allBlogs.map((p) => ({ slug: p.slug.split('/').map((name) => decodeURI(name)) }))
|
return allBlogs.map((p) => ({ slug: p.slug.split('/').map((name) => decodeURI(name)) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }: { params: { slug: string[] } }) {
|
export default async function Page(props: { params: Promise<{ slug: string[] }> }) {
|
||||||
|
const params = await props.params
|
||||||
const slug = decodeURI(params.slug.join('/'))
|
const slug = decodeURI(params.slug.join('/'))
|
||||||
// Filter out drafts in production
|
// Filter out drafts in production
|
||||||
const sortedCoreContents = allCoreContent(sortPosts(allBlogs))
|
const sortedCoreContents = allCoreContent(sortPosts(allBlogs))
|
||||||
|
@ -11,7 +11,8 @@ export const generateStaticParams = async () => {
|
|||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Page({ params }: { params: { page: string } }) {
|
export default async function Page(props: { params: Promise<{ page: string }> }) {
|
||||||
|
const params = await props.params
|
||||||
const posts = allCoreContent(sortPosts(allBlogs))
|
const posts = allCoreContent(sortPosts(allBlogs))
|
||||||
const pageNumber = parseInt(params.page as string)
|
const pageNumber = parseInt(params.page as string)
|
||||||
const initialDisplayPosts = posts.slice(
|
const initialDisplayPosts = posts.slice(
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { MetadataRoute } from 'next'
|
import { MetadataRoute } from 'next'
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
|
|
||||||
|
export const dynamic = 'force-static'
|
||||||
|
|
||||||
export default function robots(): MetadataRoute.Robots {
|
export default function robots(): MetadataRoute.Robots {
|
||||||
return {
|
return {
|
||||||
rules: {
|
rules: {
|
||||||
|
@ -2,6 +2,8 @@ import { MetadataRoute } from 'next'
|
|||||||
import { allBlogs } from 'contentlayer/generated'
|
import { allBlogs } from 'contentlayer/generated'
|
||||||
import siteMetadata from '@/data/siteMetadata'
|
import siteMetadata from '@/data/siteMetadata'
|
||||||
|
|
||||||
|
export const dynamic = 'force-static'
|
||||||
|
|
||||||
export default function sitemap(): MetadataRoute.Sitemap {
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
const siteUrl = siteMetadata.siteUrl
|
const siteUrl = siteMetadata.siteUrl
|
||||||
|
|
||||||
|
@ -8,7 +8,10 @@ import { genPageMetadata } from 'app/seo'
|
|||||||
import { Metadata } from 'next'
|
import { Metadata } from 'next'
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
|
|
||||||
export async function generateMetadata({ params }: { params: { tag: string } }): Promise<Metadata> {
|
export async function generateMetadata(props: {
|
||||||
|
params: Promise<{ tag: string }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const params = await props.params
|
||||||
const tag = decodeURI(params.tag)
|
const tag = decodeURI(params.tag)
|
||||||
return genPageMetadata({
|
return genPageMetadata({
|
||||||
title: tag,
|
title: tag,
|
||||||
@ -31,7 +34,8 @@ export const generateStaticParams = async () => {
|
|||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TagPage({ params }: { params: { tag: string } }) {
|
export default async function TagPage(props: { params: Promise<{ tag: string }> }) {
|
||||||
|
const params = await props.params
|
||||||
const tag = decodeURI(params.tag)
|
const tag = decodeURI(params.tag)
|
||||||
// Capitalize first letter and convert space to dash
|
// 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)
|
||||||
|
@ -16,6 +16,7 @@ export default function Footer() {
|
|||||||
<SocialIcon kind="x" href={siteMetadata.x} size={6} />
|
<SocialIcon kind="x" href={siteMetadata.x} size={6} />
|
||||||
<SocialIcon kind="instagram" href={siteMetadata.instagram} size={6} />
|
<SocialIcon kind="instagram" href={siteMetadata.instagram} size={6} />
|
||||||
<SocialIcon kind="threads" href={siteMetadata.threads} size={6} />
|
<SocialIcon kind="threads" href={siteMetadata.threads} size={6} />
|
||||||
|
<SocialIcon kind="medium" href={siteMetadata.medium} size={6} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-2 flex 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>
|
||||||
|
@ -28,10 +28,8 @@ const Header = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{/* Changed this so I can get all of my links in the header 10-19-2024. Backup Config */}
|
|
||||||
<div className="flex items-center space-x-4 leading-5 sm:space-x-6">
|
<div className="flex items-center space-x-4 leading-5 sm:space-x-6">
|
||||||
<div className="no-scrollbar max-w-50 hidden items-center space-x-4 overflow-x-auto sm:flex md:flex lg:flex">
|
<div className="no-scrollbar hidden max-w-40 items-center space-x-4 overflow-x-auto sm:flex sm:space-x-6 md:max-w-72 lg:max-w-96">
|
||||||
{/* <div className="no-scrollbar hidden max-w-50 items-center space-x-4 overflow-x-auto sm:flex sm:space-x-6 md:max-w-72 lg:max-w-96"> */}
|
|
||||||
{headerNavLinks
|
{headerNavLinks
|
||||||
.filter((link) => link.href !== '/')
|
.filter((link) => link.href !== '/')
|
||||||
.map((link) => (
|
.map((link) => (
|
||||||
|
@ -93,3 +93,12 @@ export function Instagram(svgProps: SVGProps<SVGSVGElement>) {
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Medium(svgProps: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...svgProps}>
|
||||||
|
<title>Medium</title>
|
||||||
|
<path d="M13.54 12a6.8 6.8 0 01-6.77 6.82A6.8 6.8 0 010 12a6.8 6.8 0 016.77-6.82A6.8 6.8 0 0113.54 12zM20.96 12c0 3.54-1.51 6.42-3.38 6.42-1.87 0-3.39-2.88-3.39-6.42s1.52-6.42 3.39-6.42 3.38 2.88 3.38 6.42M24 12c0 3.17-.53 5.75-1.19 5.75-.66 0-1.19-2.58-1.19-5.75s.53-5.75 1.19-5.75C23.47 6.25 24 8.83 24 12z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
Mastodon,
|
Mastodon,
|
||||||
Threads,
|
Threads,
|
||||||
Instagram,
|
Instagram,
|
||||||
|
Medium,
|
||||||
} from './icons'
|
} from './icons'
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
@ -22,6 +23,7 @@ const components = {
|
|||||||
mastodon: Mastodon,
|
mastodon: Mastodon,
|
||||||
threads: Threads,
|
threads: Threads,
|
||||||
instagram: Instagram,
|
instagram: Instagram,
|
||||||
|
medium: Medium,
|
||||||
}
|
}
|
||||||
|
|
||||||
type SocialIconProps = {
|
type SocialIconProps = {
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
import rehypeSlug from 'rehype-slug'
|
import rehypeSlug from 'rehype-slug'
|
||||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
||||||
import rehypeKatex from 'rehype-katex'
|
import rehypeKatex from 'rehype-katex'
|
||||||
|
import rehypeKatexNoTranslate from 'rehype-katex-notranslate'
|
||||||
import rehypeCitation from 'rehype-citation'
|
import rehypeCitation from 'rehype-citation'
|
||||||
import rehypePrismPlus from 'rehype-prism-plus'
|
import rehypePrismPlus from 'rehype-prism-plus'
|
||||||
import rehypePresetMinify from 'rehype-preset-minify'
|
import rehypePresetMinify from 'rehype-preset-minify'
|
||||||
@ -169,6 +170,7 @@ export default makeSource({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
rehypeKatex,
|
rehypeKatex,
|
||||||
|
rehypeKatexNoTranslate,
|
||||||
[rehypeCitation, { path: path.join(root, 'data') }],
|
[rehypeCitation, { path: path.join(root, 'data') }],
|
||||||
[rehypePrismPlus, { defaultLanguage: 'js', ignoreMissing: true }],
|
[rehypePrismPlus, { defaultLanguage: 'js', ignoreMissing: true }],
|
||||||
rehypePresetMinify,
|
rehypePresetMinify,
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.token.boolean {
|
.token.boolean {
|
||||||
color: rgb(138, 21, 40);
|
color: rgb(255, 88, 116);
|
||||||
}
|
}
|
||||||
|
|
||||||
.token.number {
|
.token.number {
|
||||||
|
@ -20,6 +20,7 @@ const siteMetadata = {
|
|||||||
linkedin: 'https://www.linkedin.com/in/jonathanbranan/',
|
linkedin: 'https://www.linkedin.com/in/jonathanbranan/',
|
||||||
// threads: 'https://www.threads.net',
|
// threads: 'https://www.threads.net',
|
||||||
// instagram: 'https://www.instagram.com',
|
// instagram: 'https://www.instagram.com',
|
||||||
|
// medium: 'https://medium.com',
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
// set to true if you want a navbar fixed to the top
|
// set to true if you want a navbar fixed to the top
|
||||||
stickyNav: false,
|
stickyNav: false,
|
||||||
|
@ -66,7 +66,7 @@ function createSearchIndex(allBlogs) {
|
|||||||
) {
|
) {
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
`public/${siteMetadata.search.kbarConfig.searchDocumentsPath}`,
|
`public/${siteMetadata.search.kbarConfig.searchDocumentsPath}`,
|
||||||
JSON.stringify((sortPosts(allBlogs)))
|
JSON.stringify(sortPosts(allBlogs))
|
||||||
)
|
)
|
||||||
console.log('Local search index generated...')
|
console.log('Local search index generated...')
|
||||||
}
|
}
|
||||||
|
20
faq/deploy-with-docker.md
Normal file
20
faq/deploy-with-docker.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Deploy with Docker
|
||||||
|
|
||||||
|
Follow the [official Next.js repo docker build example and instructions](https://github.com/vercel/next.js/tree/canary/examples/with-docker) to deploy with docker. Copy the [`Dockerfile`](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile) into the root of the project and modify the `next.config.js` file:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// next.config.js
|
||||||
|
module.exports = {
|
||||||
|
// ... rest of the configuration.
|
||||||
|
output: 'standalone',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now build the docker image and run it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t nextjs-docker .
|
||||||
|
docker run -p 3000:3000 nextjs-docker
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, to use docker compose, refer to the [docker compose repo](https://github.com/vercel/next.js/tree/canary/examples/with-docker-compose).
|
@ -100,7 +100,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
|
|||||||
Discuss on Twitter
|
Discuss on Twitter
|
||||||
</Link>
|
</Link>
|
||||||
{` • `}
|
{` • `}
|
||||||
<Link href={editUrl(filePath)}>View on gitea</Link>
|
<Link href={editUrl(filePath)}>View on Gitea</Link>
|
||||||
</div>
|
</div>
|
||||||
{siteMetadata.comments && (
|
{siteMetadata.comments && (
|
||||||
<div
|
<div
|
||||||
|
37
package.json
37
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tailwind-nextjs-starter-blog",
|
"name": "tailwind-nextjs-starter-blog",
|
||||||
"version": "2.2.0",
|
"version": "2.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "next dev",
|
"start": "next dev",
|
||||||
@ -12,29 +12,30 @@
|
|||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "1.7.19",
|
"@headlessui/react": "2.2.0",
|
||||||
"@next/bundle-analyzer": "14.2.3",
|
"@next/bundle-analyzer": "15.0.2",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.12",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"body-scroll-lock": "^4.0.0-beta.0",
|
"body-scroll-lock": "^4.0.0-beta.0",
|
||||||
"contentlayer2": "0.5.1",
|
"contentlayer2": "0.5.3",
|
||||||
"esbuild": "0.20.2",
|
"esbuild": "0.20.2",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
"gray-matter": "^4.0.2",
|
"gray-matter": "^4.0.2",
|
||||||
"hast-util-from-html-isomorphic": "^2.0.0",
|
"hast-util-from-html-isomorphic": "^2.0.0",
|
||||||
"image-size": "1.0.0",
|
"image-size": "1.0.0",
|
||||||
"next": "14.2.3",
|
"next": "15.0.2",
|
||||||
"next-contentlayer2": "0.5.1",
|
"next-contentlayer2": "0.5.3",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"pliny": "0.2.1",
|
"pliny": "0.4.0",
|
||||||
"postcss": "^8.4.24",
|
"postcss": "^8.4.24",
|
||||||
"react": "18.3.1",
|
"react": "rc",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "rc",
|
||||||
"reading-time": "1.5.0",
|
"reading-time": "1.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-citation": "^2.0.0",
|
"rehype-citation": "^2.0.0",
|
||||||
"rehype-katex": "^7.0.0",
|
"rehype-katex": "^7.0.0",
|
||||||
|
"rehype-katex-notranslate": "^1.1.4",
|
||||||
"rehype-preset-minify": "7.0.0",
|
"rehype-preset-minify": "7.0.0",
|
||||||
"rehype-prism-plus": "^2.0.0",
|
"rehype-prism-plus": "^2.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
@ -42,20 +43,20 @@
|
|||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-github-blockquote-alert": "^1.2.1",
|
"remark-github-blockquote-alert": "^1.2.1",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.14",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@svgr/webpack": "^8.0.1",
|
"@svgr/webpack": "^8.0.1",
|
||||||
"@types/mdx": "^2.0.12",
|
"@types/mdx": "^2.0.12",
|
||||||
"@types/react": "^18.2.73",
|
"@types/react": "^18.2.73",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
"@typescript-eslint/eslint-plugin": "^8.12.0",
|
||||||
"@typescript-eslint/parser": "^6.1.0",
|
"@typescript-eslint/parser": "^8.12.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-config-next": "14.2.3",
|
"eslint-config-next": "15.0.2",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.2.0",
|
||||||
"husky": "^9.0.0",
|
"husky": "^9.0.0",
|
||||||
"lint-staged": "^13.0.0",
|
"lint-staged": "^13.0.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
9
public/static/favicons/browserconfig.xml
Normal file
9
public/static/favicons/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#000000</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
@ -3,10 +3,12 @@ import path from 'path'
|
|||||||
import { slug } from 'github-slugger'
|
import { slug } from 'github-slugger'
|
||||||
import { escape } from 'pliny/utils/htmlEscaper.js'
|
import { escape } from 'pliny/utils/htmlEscaper.js'
|
||||||
import siteMetadata from '../data/siteMetadata.js'
|
import siteMetadata from '../data/siteMetadata.js'
|
||||||
import tagData from '../app/tag-data.json' with { type: 'json' }
|
import tagData from '../app/tag-data.json' assert { type: 'json' }
|
||||||
import { allBlogs } from '../.contentlayer/generated/index.mjs'
|
import { allBlogs } from '../.contentlayer/generated/index.mjs'
|
||||||
import { sortPosts } from 'pliny/utils/contentlayer.js'
|
import { sortPosts } from 'pliny/utils/contentlayer.js'
|
||||||
|
|
||||||
|
const outputFolder = process.env.EXPORT ? 'out' : 'public'
|
||||||
|
|
||||||
const generateRssItem = (config, post) => `
|
const generateRssItem = (config, post) => `
|
||||||
<item>
|
<item>
|
||||||
<guid>${config.siteUrl}/blog/${post.slug}</guid>
|
<guid>${config.siteUrl}/blog/${post.slug}</guid>
|
||||||
@ -40,14 +42,14 @@ async function generateRSS(config, allBlogs, page = 'feed.xml') {
|
|||||||
// RSS for blog post
|
// RSS for blog post
|
||||||
if (publishPosts.length > 0) {
|
if (publishPosts.length > 0) {
|
||||||
const rss = generateRss(config, sortPosts(publishPosts))
|
const rss = generateRss(config, sortPosts(publishPosts))
|
||||||
writeFileSync(`./public/${page}`, rss)
|
writeFileSync(`./${outputFolder}/${page}`, rss)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publishPosts.length > 0) {
|
if (publishPosts.length > 0) {
|
||||||
for (const tag of Object.keys(tagData)) {
|
for (const tag of Object.keys(tagData)) {
|
||||||
const filteredPosts = allBlogs.filter((post) => post.tags.map((t) => slug(t)).includes(tag))
|
const filteredPosts = allBlogs.filter((post) => post.tags.map((t) => slug(t)).includes(tag))
|
||||||
const rss = generateRss(config, filteredPosts, `tags/${tag}/${page}`)
|
const rss = generateRss(config, filteredPosts, `tags/${tag}/${page}`)
|
||||||
const rssPath = path.join('public', 'tags', tag)
|
const rssPath = path.join(outputFolder, 'tags', tag)
|
||||||
mkdirSync(rssPath, { recursive: true })
|
mkdirSync(rssPath, { recursive: true })
|
||||||
writeFileSync(path.join(rssPath, page), rss)
|
writeFileSync(path.join(rssPath, page), rss)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user