Add RSS feed, sitemap, robots.txt, and code syntax highlighting
Implements essential blog features: 1. RSS Feed (/feed.xml) - Latest 20 posts with full content - Proper XML escaping and CDATA sections - Includes tags, authors, and descriptions - Auto-discovery link in HTML head 2. Sitemap (/sitemap.xml) - All posts, pages, and tag pages - Proper lastModified dates and priorities - Automatic generation via Next.js built-in support 3. Robots.txt (/robots.txt) - Allow all crawlers - Disallow API and admin routes - Links to sitemap for better SEO 4. Code Syntax Highlighting - Using rehype-pretty-code + Shiki - GitHub Dark/Light themes based on user preference - Line numbers for all code blocks - Support for highlighted lines - Inline code styling - Code title support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
63
app/feed.xml/route.ts
Normal file
63
app/feed.xml/route.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { allPosts } from 'contentlayer2/generated';
|
||||||
|
import { siteConfig } from '@/lib/config';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const sortedPosts = allPosts
|
||||||
|
.filter((post) => post.status === 'published')
|
||||||
|
.sort((a, b) => {
|
||||||
|
const dateA = a.published_at ? new Date(a.published_at).getTime() : 0;
|
||||||
|
const dateB = b.published_at ? new Date(b.published_at).getTime() : 0;
|
||||||
|
return dateB - dateA;
|
||||||
|
})
|
||||||
|
.slice(0, 20); // Latest 20 posts
|
||||||
|
|
||||||
|
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
||||||
|
<channel>
|
||||||
|
<title>${escapeXml(siteConfig.name)}</title>
|
||||||
|
<link>${siteUrl}</link>
|
||||||
|
<description>${escapeXml(siteConfig.description)}</description>
|
||||||
|
<language>${siteConfig.defaultLocale.replace('_', '-')}</language>
|
||||||
|
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
||||||
|
<atom:link href="${siteUrl}/feed.xml" rel="self" type="application/rss+xml"/>
|
||||||
|
${sortedPosts
|
||||||
|
.map((post) => {
|
||||||
|
const postUrl = `${siteUrl}${post.url}`;
|
||||||
|
const pubDate = post.published_at
|
||||||
|
? new Date(post.published_at).toUTCString()
|
||||||
|
: new Date(post.created_at || Date.now()).toUTCString();
|
||||||
|
|
||||||
|
return `
|
||||||
|
<item>
|
||||||
|
<title>${escapeXml(post.title)}</title>
|
||||||
|
<link>${postUrl}</link>
|
||||||
|
<guid isPermaLink="true">${postUrl}</guid>
|
||||||
|
<description>${escapeXml(post.description || post.custom_excerpt || post.title)}</description>
|
||||||
|
${post.body?.html ? `<content:encoded><![CDATA[${post.body.html}]]></content:encoded>` : ''}
|
||||||
|
<pubDate>${pubDate}</pubDate>
|
||||||
|
${post.authors?.map((author) => `<author>${escapeXml(author)}</author>`).join('\n ') || ''}
|
||||||
|
${post.tags?.map((tag) => `<category>${escapeXml(tag)}</category>`).join('\n ') || ''}
|
||||||
|
</item>`;
|
||||||
|
})
|
||||||
|
.join('')}
|
||||||
|
</channel>
|
||||||
|
</rss>`;
|
||||||
|
|
||||||
|
return new Response(rss, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml; charset=utf-8',
|
||||||
|
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeXml(unsafe: string): string {
|
||||||
|
return unsafe
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
@@ -34,6 +34,11 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
icons: {
|
icons: {
|
||||||
icon: '/favicon.png'
|
icon: '/favicon.png'
|
||||||
|
},
|
||||||
|
alternates: {
|
||||||
|
types: {
|
||||||
|
'application/rss+xml': `${siteConfig.url}/feed.xml`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
14
app/robots.ts
Normal file
14
app/robots.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { MetadataRoute } from 'next';
|
||||||
|
|
||||||
|
export default function robots(): MetadataRoute.Robots {
|
||||||
|
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
return {
|
||||||
|
rules: {
|
||||||
|
userAgent: '*',
|
||||||
|
allow: '/',
|
||||||
|
disallow: ['/api/', '/_next/', '/admin/'],
|
||||||
|
},
|
||||||
|
sitemap: `${siteUrl}/sitemap.xml`,
|
||||||
|
};
|
||||||
|
}
|
||||||
68
app/sitemap.ts
Normal file
68
app/sitemap.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { MetadataRoute } from 'next';
|
||||||
|
import { allPosts, allPages } from 'contentlayer2/generated';
|
||||||
|
|
||||||
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
// Homepage
|
||||||
|
const homepage = {
|
||||||
|
url: siteUrl,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'daily' as const,
|
||||||
|
priority: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Blog listing page
|
||||||
|
const blogPage = {
|
||||||
|
url: `${siteUrl}/blog`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'daily' as const,
|
||||||
|
priority: 0.9,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tags page
|
||||||
|
const tagsPage = {
|
||||||
|
url: `${siteUrl}/tags`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'weekly' as const,
|
||||||
|
priority: 0.7,
|
||||||
|
};
|
||||||
|
|
||||||
|
// All blog posts
|
||||||
|
const posts = allPosts
|
||||||
|
.filter((post) => post.status === 'published')
|
||||||
|
.map((post) => ({
|
||||||
|
url: `${siteUrl}${post.url}`,
|
||||||
|
lastModified: new Date(post.updated_at || post.published_at || post.created_at || Date.now()),
|
||||||
|
changeFrequency: 'weekly' as const,
|
||||||
|
priority: 0.8,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// All pages
|
||||||
|
const pages = allPages
|
||||||
|
.filter((page) => page.status === 'published')
|
||||||
|
.map((page) => ({
|
||||||
|
url: `${siteUrl}${page.url}`,
|
||||||
|
lastModified: new Date(page.updated_at || page.published_at || page.created_at || Date.now()),
|
||||||
|
changeFrequency: 'monthly' as const,
|
||||||
|
priority: 0.6,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// All unique tags
|
||||||
|
const allTags = Array.from(
|
||||||
|
new Set(
|
||||||
|
allPosts
|
||||||
|
.filter((post) => post.status === 'published' && post.tags)
|
||||||
|
.flatMap((post) => post.tags || [])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const tagPages = allTags.map((tag) => ({
|
||||||
|
url: `${siteUrl}/tags/${encodeURIComponent(tag.toLowerCase().replace(/\s+/g, '-'))}`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'weekly' as const,
|
||||||
|
priority: 0.5,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [homepage, blogPage, tagsPage, ...posts, ...pages, ...tagPages];
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { visit } from 'unist-util-visit';
|
|||||||
import rehypeSlug from 'rehype-slug';
|
import rehypeSlug from 'rehype-slug';
|
||||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import rehypePrettyCode from 'rehype-pretty-code';
|
||||||
|
|
||||||
export const Post = defineDocumentType(() => ({
|
export const Post = defineDocumentType(() => ({
|
||||||
name: 'Post',
|
name: 'Post',
|
||||||
@@ -86,6 +87,16 @@ export default makeSource({
|
|||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [remarkGfm],
|
remarkPlugins: [remarkGfm],
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
|
[
|
||||||
|
rehypePrettyCode,
|
||||||
|
{
|
||||||
|
theme: {
|
||||||
|
dark: 'github-dark',
|
||||||
|
light: 'github-light',
|
||||||
|
},
|
||||||
|
keepBackground: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
rehypeSlug,
|
rehypeSlug,
|
||||||
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
||||||
/**
|
/**
|
||||||
|
|||||||
284
package-lock.json
generated
284
package-lock.json
generated
@@ -26,8 +26,10 @@
|
|||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
|
"rehype-pretty-code": "^0.14.1",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
|
"shiki": "^3.15.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
@@ -2667,6 +2669,73 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@shikijs/core": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "3.15.0",
|
||||||
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
|
"@types/hast": "^3.0.4",
|
||||||
|
"hast-util-to-html": "^9.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/engine-javascript": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "3.15.0",
|
||||||
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
|
"oniguruma-to-es": "^4.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/engine-oniguruma": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "3.15.0",
|
||||||
|
"@shikijs/vscode-textmate": "^10.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/langs": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "3.15.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/themes": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "3.15.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/types": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
|
"@types/hast": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/vscode-textmate": {
|
||||||
|
"version": "10.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
|
||||||
|
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@shuding/opentype.js": {
|
"node_modules/@shuding/opentype.js": {
|
||||||
"version": "1.4.0-beta.0",
|
"version": "1.4.0-beta.0",
|
||||||
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
|
||||||
@@ -4620,6 +4689,18 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.24.0",
|
"version": "1.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
|
||||||
@@ -6121,6 +6202,44 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-from-html": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"devlop": "^1.1.0",
|
||||||
|
"hast-util-from-parse5": "^8.0.0",
|
||||||
|
"parse5": "^7.0.0",
|
||||||
|
"vfile": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hast-util-from-parse5": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"@types/unist": "^3.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"hastscript": "^9.0.0",
|
||||||
|
"property-information": "^7.0.0",
|
||||||
|
"vfile": "^6.0.0",
|
||||||
|
"vfile-location": "^5.0.0",
|
||||||
|
"web-namespaces": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-heading-rank": {
|
"node_modules/hast-util-heading-rank": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz",
|
||||||
@@ -6147,6 +6266,19 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-parse-selector": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-to-estree": {
|
"node_modules/hast-util-to-estree": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz",
|
||||||
@@ -6251,6 +6383,23 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hastscript": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"comma-separated-tokens": "^2.0.0",
|
||||||
|
"hast-util-parse-selector": "^4.0.0",
|
||||||
|
"property-information": "^7.0.0",
|
||||||
|
"space-separated-tokens": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hermes-estree": {
|
"node_modules/hermes-estree": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
||||||
@@ -8714,6 +8863,23 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oniguruma-parser": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/oniguruma-to-es": {
|
||||||
|
"version": "4.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz",
|
||||||
|
"integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"oniguruma-parser": "^0.12.1",
|
||||||
|
"regex": "^6.0.1",
|
||||||
|
"regex-recursion": "^6.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/oo-ascii-tree": {
|
"node_modules/oo-ascii-tree": {
|
||||||
"version": "1.119.0",
|
"version": "1.119.0",
|
||||||
"resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.119.0.tgz",
|
"resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.119.0.tgz",
|
||||||
@@ -8870,6 +9036,24 @@
|
|||||||
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/parse-numeric-range": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pascal-case": {
|
"node_modules/pascal-case": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
||||||
@@ -9383,6 +9567,30 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regex": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"regex-utilities": "^2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/regex-recursion": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"regex-utilities": "^2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/regex-utilities": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
@@ -9422,6 +9630,41 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype-parse": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"hast-util-from-html": "^2.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-pretty-code": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-IpG4OL0iYlbx78muVldsK86hdfNoht0z63AP7sekQNW2QOTmjxB7RbTO+rhIYNGRljgHxgVZoPwUl6bIC9SbjA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.4",
|
||||||
|
"hast-util-to-string": "^3.0.0",
|
||||||
|
"parse-numeric-range": "^1.3.0",
|
||||||
|
"rehype-parse": "^9.0.0",
|
||||||
|
"unified": "^11.0.5",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"shiki": "^1.0.0 || ^2.0.0 || ^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rehype-recma": {
|
"node_modules/rehype-recma": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz",
|
||||||
@@ -9912,6 +10155,23 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shiki": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/core": "3.15.0",
|
||||||
|
"@shikijs/engine-javascript": "3.15.0",
|
||||||
|
"@shikijs/engine-oniguruma": "3.15.0",
|
||||||
|
"@shikijs/langs": "3.15.0",
|
||||||
|
"@shikijs/themes": "3.15.0",
|
||||||
|
"@shikijs/types": "3.15.0",
|
||||||
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
|
"@types/hast": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
@@ -11125,6 +11385,20 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vfile-location": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^3.0.0",
|
||||||
|
"vfile": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vfile-message": {
|
"node_modules/vfile-message": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
|
||||||
@@ -11139,6 +11413,16 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/web-namespaces": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -33,8 +33,10 @@
|
|||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
|
"rehype-pretty-code": "^0.14.1",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
|
"shiki": "^3.15.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -359,4 +359,70 @@ body {
|
|||||||
|
|
||||||
.pagefind-ui__search-input:focus {
|
.pagefind-ui__search-input:focus {
|
||||||
@apply ring-2 ring-blue-500 dark:ring-blue-400;
|
@apply ring-2 ring-blue-500 dark:ring-blue-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code Syntax Highlighting Styles (rehype-pretty-code) */
|
||||||
|
.prose pre {
|
||||||
|
@apply overflow-x-auto rounded-lg border border-slate-200 dark:border-slate-700;
|
||||||
|
padding: 1rem 1.2rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose pre {
|
||||||
|
background-color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre > code {
|
||||||
|
@apply grid;
|
||||||
|
counter-reset: line;
|
||||||
|
font-size: 0.9em;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre > code > [data-line] {
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre > code > [data-line]::before {
|
||||||
|
counter-increment: line;
|
||||||
|
content: counter(line);
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.5rem;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
text-align: right;
|
||||||
|
color: #94a3b8;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose pre > code > [data-line]::before {
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Highlighted lines */
|
||||||
|
.prose pre > code > [data-highlighted-line] {
|
||||||
|
background-color: rgba(59, 130, 246, 0.1);
|
||||||
|
border-left-color: rgb(59, 130, 246);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose pre > code > [data-highlighted-line] {
|
||||||
|
background-color: rgba(96, 165, 250, 0.15);
|
||||||
|
border-left-color: rgb(96, 165, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
.prose :not(pre) > code {
|
||||||
|
@apply rounded bg-slate-100 px-1.5 py-0.5 text-sm font-semibold text-slate-800 dark:bg-slate-800 dark:text-slate-200;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code title (if specified in markdown: ```js title="example.js") */
|
||||||
|
.prose [data-rehype-pretty-code-title] {
|
||||||
|
@apply rounded-t-lg border border-b-0 border-slate-200 bg-slate-100 px-4 py-2 text-sm font-semibold text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose [data-rehype-pretty-code-title] + pre {
|
||||||
|
@apply mt-0 rounded-t-none;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user