From 231bdfc07905adf88c8e490d8da49d1ea83913e6 Mon Sep 17 00:00:00 2001 From: HangerThem Date: Sun, 2 Aug 2026 17:49:15 +0200 Subject: [PATCH] Forgor to publish a bit --- .oxfmtrc.json | 18 + .oxlintrc.json | 24 + app/[slug]/page.tsx | 40 - app/categories/[category]/page.tsx | 37 + app/feed.xml/route.ts | 63 +- app/globals.css | 203 ++- app/layout.tsx | 30 +- app/page.tsx | 2 +- app/posts/[slug]/page.tsx | 54 + app/tags/[tag]/page.tsx | 39 - app/tags/page.tsx | 28 - bun.lock | 284 ++- components/common/BlockquoteWrapper.tsx | 27 + components/common/CodeWrapper.tsx | 56 + components/common/CopyButton.tsx | 25 + components/common/ExternalLink.tsx | 20 + components/common/ImageWrapper.tsx | 40 + components/common/TableOfContents.tsx | 60 + components/layout/Sidebar.tsx | 26 + content/posts/2024-08-05-self-development.md | 2 - content/posts/2024-08-20-standard-model.md | 410 +++++ content/posts/test-post.md | 99 ++ lib/mermaid-render.ts | 60 + lib/posts.ts | 57 - lib/posts.tsx | 180 ++ lib/react-node-text.ts | 58 + lib/rehype-code-meta.tsx | 33 + lib/rehype-definition.tsx | 72 + lib/rehype-mark.tsx | 52 + lib/rehype-mermaid-ssg.ts | 39 + lib/rehype-unwrap-images.tsx | 26 + package.json | 36 +- public/file.svg | 1 - public/globe.svg | 1 - public/image.png | Bin 0 -> 35795 bytes public/next.svg | 1 - public/standard_model.svg | 1658 ++++++++++++++++++ public/vercel.svg | 1 - public/window.svg | 1 - types/Note.type.ts | 1 + utils/cn.ts | 10 + utils/slug.ts | 12 + 42 files changed, 3655 insertions(+), 231 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 app/[slug]/page.tsx create mode 100644 app/categories/[category]/page.tsx create mode 100644 app/posts/[slug]/page.tsx delete mode 100644 app/tags/[tag]/page.tsx delete mode 100644 app/tags/page.tsx create mode 100644 components/common/BlockquoteWrapper.tsx create mode 100644 components/common/CodeWrapper.tsx create mode 100644 components/common/CopyButton.tsx create mode 100644 components/common/ExternalLink.tsx create mode 100644 components/common/ImageWrapper.tsx create mode 100644 components/common/TableOfContents.tsx create mode 100644 components/layout/Sidebar.tsx create mode 100644 content/posts/2024-08-20-standard-model.md create mode 100644 content/posts/test-post.md create mode 100644 lib/mermaid-render.ts delete mode 100644 lib/posts.ts create mode 100644 lib/posts.tsx create mode 100644 lib/react-node-text.ts create mode 100644 lib/rehype-code-meta.tsx create mode 100644 lib/rehype-definition.tsx create mode 100644 lib/rehype-mark.tsx create mode 100644 lib/rehype-mermaid-ssg.ts create mode 100644 lib/rehype-unwrap-images.tsx delete mode 100644 public/file.svg delete mode 100644 public/globe.svg create mode 100644 public/image.png delete mode 100644 public/next.svg create mode 100644 public/standard_model.svg delete mode 100644 public/vercel.svg delete mode 100644 public/window.svg create mode 100644 types/Note.type.ts create mode 100644 utils/cn.ts create mode 100644 utils/slug.ts diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..4baacea --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,18 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf", + "insertFinalNewline": true, + "ignorePatterns": [ + "node_modules/**", + ".cache/**", + "_site/**" + ] +} \ No newline at end of file diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..e36c434 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,24 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "env": { + "browser": true, + "node": true, + "es2024": true + }, + "plugins": ["typescript", "react", "unicorn", "import", "oxc"], + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn" + }, + "rules": { + "eqeqeq": "error", + "no-unused-vars": "off", + "typescript/no-unused-vars": "warn", + "typescript/no-explicit-any": "warn", + "import/no-cycle": "error", + "prefer-const": ["error", { "ignoreReadBeforeAssign": true }], + "react/react-in-jsx-scope": "allow" + }, + "ignorePatterns": ["node_modules/**", ".cache/**", "_site/**"] +} diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx deleted file mode 100644 index a91cd4d..0000000 --- a/app/[slug]/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { getAllPostSlugs, getPostBySlug } from "@/lib/posts" -import type { Metadata } from "next" -import readingTime from "reading-time" - -export async function generateStaticParams() { - return getAllPostSlugs().map((slug) => ({ slug })) -} - -export const dynamicParams = false - -export async function generateMetadata({ - params, -}: { - params: Promise<{ slug: string }> -}): Promise { - const { slug } = await params - const { meta } = await getPostBySlug(slug) - return { title: meta.title, description: meta.description } -} - -export default async function Post({ - params, -}: { - params: Promise<{ slug: string }> -}) { - const { slug } = await params - const { meta, contentHtml } = await getPostBySlug(slug) - - return ( -
-

{meta.title}

- - {readingTime(contentHtml).text} -
-
- ) -} diff --git a/app/categories/[category]/page.tsx b/app/categories/[category]/page.tsx new file mode 100644 index 0000000..7189e98 --- /dev/null +++ b/app/categories/[category]/page.tsx @@ -0,0 +1,37 @@ +import { getAllPostsMeta } from "@/lib/posts" +import type { Metadata } from "next" +import Link from "next/link" + +type CategoryPageProps = { + params: Promise<{ + category: string + }> +} + +export async function generateMetadata({ + params, +}: CategoryPageProps): Promise { + const { category } = await params + return { title: `Category: ${category}`, description: "List of posts in this category" } +} + +export default async function CategoryPage({ + params, +}: CategoryPageProps) { + const { category } = await params + const posts = getAllPostsMeta() + return ( +
+

Category: {category}

+
    + {posts + .filter((post) => post.categories.includes(category)) + .map((post) => ( +
  • + {post.title} +
  • + ))} +
+
+ ) +} diff --git a/app/feed.xml/route.ts b/app/feed.xml/route.ts index 4c118cd..1cf9099 100644 --- a/app/feed.xml/route.ts +++ b/app/feed.xml/route.ts @@ -1,29 +1,56 @@ import meta from "@/const/meta" import { getAllPostsMeta } from "@/lib/posts" +const SITE_URL = process.env.SITE_URL || "http://localhost:3000" + +function escapeXml(str: string) { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") +} + export async function GET() { const posts = getAllPostsMeta() - const items = posts - .map( - (p) => ` - - ${p.title} - https://yourdomain.com/blog/${p.slug} - ${p.description} - ${new Date(p.date).toUTCString()} - `, - ) + + const updated = + posts.length > 0 + ? new Date(posts[0].date).toISOString() + : new Date().toISOString() + + const entries = posts + .map((p) => { + const url = `${SITE_URL}/posts/${p.slug}` + const date = new Date(p.date).toISOString() + return ` + + ${escapeXml(p.title)} + + ${url} + ${date} + ${date} + + ${escapeXml(p.description)} + ` + }) .join("") const feed = ` - - ${meta.title} - https://yourdomain.com - Latest posts - ${items} -` + + ${escapeXml(meta.title)} + ${SITE_URL}/ + ${updated} + + ${escapeXml("d")} + + + + ${entries} +` return new Response(feed, { - headers: { "Content-Type": "application/xml" }, + headers: { "Content-Type": "application/atom+xml; charset=utf-8" }, }) -} +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index d1553ff..2025da7 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,31 +1,34 @@ -@import "tailwindcss"; +@import 'tailwindcss'; +@import '../node_modules/highlight.js/styles/monokai.min.css'; -:root { - --background: #ffffff; - --foreground: #171717; +@theme { + --color-bg: #040404; + --color-fg: #e0e0e0; + --color-fg-dim: #909090; + --color-accent: #ef1f1f; + --color-border: #374151; + + --color-warning: #facc15; + --color-tip: #22c55e; + --color-info: #3b82f6; + --color-note: #8b5cf6; } -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); +.hljs { + @apply bg-transparent; } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } +pre code.hljs { + @apply p-0; } body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + @apply bg-bg text-fg; } -.custom-prose { +.prose { + @apply space-y-4; + ul { @apply list-disc list-inside; } @@ -38,3 +41,167 @@ body { @apply mb-1; } } + +.categories::before { + @apply content-['$'] text-accent; +} + +.handle::before { + @apply content-['@'] text-current; +} + +/* ── Mark ────────────────────────────── */ + +mark { + @apply bg-accent/20 px-1 py-0.5 rounded text-accent; +} + +/* ── Tables ────────────────────────────────────────── */ + +table { + @apply border-collapse w-full; +} + +th, +td { + @apply border border-border px-4 py-2; +} + +th { + @apply bg-fg/10; +} + +tbody tr:nth-child(even) { + @apply bg-fg/5; +} + +/* ── Definition lists ─────────────────────────────── */ + +dl { + @apply my-4; +} + +dt { + @apply font-semibold text-fg mt-3; +} + +dt:first-child { + @apply mt-0; +} + +dd { + @apply ml-6 pl-3 border-l-2 border-fg/20 text-fg-dim; +} + +/* ── Prose (markdown) content ─────────────────────── */ + +.prose h1 { + @apply text-3xl md:text-4xl; +} + +.prose h2 { + @apply text-2xl md:text-3xl; +} + +.prose h3 { + @apply text-xl md:text-2xl; +} + +.prose h4 { + @apply text-lg md:text-xl; +} + +.prose h5 { + @apply text-base md:text-lg; +} + +.prose h6 { + @apply text-sm md:text-base; +} + +.prose h1, +.prose h2, +.prose h3, +.prose h4, +.prose h5, +.prose h6 { + @apply font-bold mb-2; + + &::before { + @apply content-['#'] text-accent mr-1; + } +} + +.prose code:not(pre code) { + @apply text-sm bg-fg/10 px-1 py-0.5 rounded; +} + +.prose a { + @apply text-accent; +} + +.prose ul:not(.task-list-container) { + @apply list-disc pl-6; +} + +hr { + @apply text-border; +} + +/* Only ol that do not include checkboxes */ +.prose ol { + @apply list-decimal pl-6; +} + +/* ── Task lists ────────────────────────────────────── */ + +.task-list-item { + @apply list-none ml-0 pl-0; +} + +.task-list-item input[type='checkbox'] { + @apply mr-2 accent-accent w-4 h-4 align-middle cursor-pointer; +} + +.task-list-item input[type='checkbox']:checked + label, +.task-list-item input[type='checkbox'][checked] + label { + @apply text-fg-dim line-through; +} + +/* ── Footnotes ─────────────────────────────────────── */ + +.footnotes { + @apply text-sm italic text-fg-dim pt-4 mt-4 border-t border-border; +} + +.footnotes ol { + @apply space-y-2; +} + +.sr-only { + @apply sr-only; +} + +.note { + @apply border-l-4 pl-4 py-1 text-fg-dim bg-accent/20; + + &.warning { + @apply border-warning bg-warning/20 text-warning; + } + + &.tip { + @apply border-tip bg-tip/20 text-tip; + } + + &.info { + @apply border-info bg-info/20 text-info; + } +} + +.no-js .js-only { + @apply hidden; +} + +#mermaid-diagram { + @apply mx-auto; +} diff --git a/app/layout.tsx b/app/layout.tsx index 1ecc759..009bad6 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,16 +1,18 @@ -import { Geist, Geist_Mono } from "next/font/google" -import "./globals.css" -import { Metadata } from "next" -import meta from "@/const/meta" +import { Geist, Geist_Mono } from 'next/font/google' +import './globals.css' +import { Metadata } from 'next' +import meta from '@/const/meta' +import Script from 'next/script' +import { Sidebar } from '@/components/layout/Sidebar' const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], + variable: '--font-geist-sans', + subsets: ['latin'], }) const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], + variable: '--font-geist-mono', + subsets: ['latin'], }) export const metadata: Metadata = { @@ -27,9 +29,17 @@ export default function RootLayout({ return ( - {children} +