Fixed few formatting things; started on support for mobile/PWA
This commit is contained in:
parent
1a713fe1b6
commit
b1de88f998
31 changed files with 276 additions and 239 deletions
|
|
@ -10,9 +10,5 @@
|
||||||
"arrowParens": "always",
|
"arrowParens": "always",
|
||||||
"endOfLine": "lf",
|
"endOfLine": "lf",
|
||||||
"insertFinalNewline": true,
|
"insertFinalNewline": true,
|
||||||
"ignorePatterns": [
|
"ignorePatterns": ["node_modules/**", ".cache/**", "_site/**"]
|
||||||
"node_modules/**",
|
|
||||||
".cache/**",
|
|
||||||
"_site/**"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ import type { Metadata } from 'next'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { metadata } from '@/app/metadata'
|
import { metadata } from '@/app/metadata'
|
||||||
import { dateFormatter } from '@/utils/time'
|
import { dateFormatter } from '@/utils/time'
|
||||||
import { RecentlyUpdatedPosts } from '@/components/common/RecentlyUpdatedPosts'
|
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default function BlogIndex() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-3xl mx-auto px-4 py-8 lg:py-16">
|
<main className="max-w-3xl mx-auto px-4 py-8 mb-16 lg:py-16">
|
||||||
<section>
|
<section>
|
||||||
{posts.length === 0 ? (
|
{posts.length === 0 ? (
|
||||||
<div className="rounded-xl border border-border p-6 text-fg-dim">
|
<div className="rounded-xl border border-border p-6 text-fg-dim">
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,22 @@
|
||||||
import { metadata } from "@/app/metadata"
|
import { metadata } from '@/app/metadata'
|
||||||
import { getAllPostsMeta } from "@/lib/posts"
|
import { getAllPostsMeta } from '@/lib/posts'
|
||||||
|
|
||||||
const SITE_URL = process.env.SITE_URL || "http://localhost:3000"
|
const SITE_URL = process.env.SITE_URL || 'http://localhost:3000'
|
||||||
|
|
||||||
function escapeXml(str: string) {
|
function escapeXml(str: string) {
|
||||||
return str
|
return str
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, "<")
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, ">")
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, """)
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, "'")
|
.replace(/'/g, ''')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(req: Request) {
|
export async function GET(req: Request) {
|
||||||
const posts = getAllPostsMeta()
|
const posts = getAllPostsMeta()
|
||||||
|
|
||||||
const updated =
|
const updated =
|
||||||
posts.length > 0
|
posts.length > 0 ? new Date(posts[0].date).toISOString() : new Date().toISOString()
|
||||||
? new Date(posts[0].date).toISOString()
|
|
||||||
: new Date().toISOString()
|
|
||||||
|
|
||||||
const entries = posts
|
const entries = posts
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
|
|
@ -35,7 +33,7 @@ export async function GET(req: Request) {
|
||||||
<summary>${escapeXml(p.description)}</summary>
|
<summary>${escapeXml(p.description)}</summary>
|
||||||
</entry>`
|
</entry>`
|
||||||
})
|
})
|
||||||
.join("")
|
.join('')
|
||||||
|
|
||||||
const feed = `<?xml version="1.0" encoding="UTF-8"?>
|
const feed = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||||
|
|
@ -51,12 +49,12 @@ export async function GET(req: Request) {
|
||||||
${entries}
|
${entries}
|
||||||
</feed>`
|
</feed>`
|
||||||
|
|
||||||
const accept = req.headers.get("accept") || ""
|
const accept = req.headers.get('accept') || ''
|
||||||
const contentType = accept.includes("application/atom+xml")
|
const contentType = accept.includes('application/atom+xml')
|
||||||
? "application/atom+xml; charset=utf-8"
|
? 'application/atom+xml; charset=utf-8'
|
||||||
: "application/xml; charset=utf-8"
|
: 'application/xml; charset=utf-8'
|
||||||
|
|
||||||
return new Response(feed, {
|
return new Response(feed, {
|
||||||
headers: { "Content-Type": contentType },
|
headers: { 'Content-Type': contentType },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { Space_Grotesk } from 'next/font/google'
|
import { Space_Grotesk } from 'next/font/google'
|
||||||
import Script from 'next/script'
|
import Script from 'next/script'
|
||||||
import { Sidebar } from '@/components/layout/Sidebar'
|
import { Sidebar } from '@/components/layout/Sidebar'
|
||||||
|
import { MobileNavbar } from '@/components/layout/MobileNavbar'
|
||||||
import { metadata } from './metadata'
|
import { metadata } from './metadata'
|
||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
import './globals.css'
|
import './globals.css'
|
||||||
|
|
||||||
const spaceGrotesk = Space_Grotesk({
|
const spaceGrotesk = Space_Grotesk({
|
||||||
|
|
@ -24,6 +26,7 @@ export default function RootLayout({
|
||||||
/>
|
/>
|
||||||
<body className="min-h-full flex">
|
<body className="min-h-full flex">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
<MobileNavbar />
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { Metadata } from "next";
|
import { Metadata } from 'next'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Ephemeris",
|
title: 'Ephemeris',
|
||||||
description: "A blog about me and my interests",
|
description: 'A blog about me and my interests',
|
||||||
creator: "hangerthem",
|
creator: 'hangerthem',
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: "website",
|
type: 'website',
|
||||||
title: "Ephemeris",
|
title: 'Ephemeris',
|
||||||
description: "A blog about me and my interests",
|
description: 'A blog about me and my interests',
|
||||||
url: process.env.SITE_URL,
|
url: process.env.SITE_URL,
|
||||||
siteName: "Ephemeris",
|
siteName: 'Ephemeris',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ export default async function Post({ params }: { params: Promise<{ slug: string
|
||||||
{meta.category && (
|
{meta.category && (
|
||||||
<Link
|
<Link
|
||||||
href={`/categories/${slugify(meta.category)}`}
|
href={`/categories/${slugify(meta.category)}`}
|
||||||
className="text-accent hover:underline font-sm mb-2 block"
|
className="text-accent hover:underline font-sm mb-2 inline-block"
|
||||||
>
|
>
|
||||||
{meta.category}
|
{meta.category}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const noteTypeIcons: Record<NoteType, React.ReactNode> = {
|
||||||
warning: <TriangleAlert className="w-4 h-4" />,
|
warning: <TriangleAlert className="w-4 h-4" />,
|
||||||
tip: <Lightbulb className="w-4 h-4" />,
|
tip: <Lightbulb className="w-4 h-4" />,
|
||||||
info: <FileWarning className="w-4 h-4" />,
|
info: <FileWarning className="w-4 h-4" />,
|
||||||
quote: <Quote className="w-4 h-4" />
|
quote: <Quote className="w-4 h-4" />,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BlockquoteWrapper({ type, children }: BlockquoteWrapperProps) {
|
export function BlockquoteWrapper({ type, children }: BlockquoteWrapperProps) {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@ export function CopyButton({ textToCopy, className, ...props }: CopyButtonProps)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={cn('text-fg-dim hover:text-fg transition-colors js-only text-xs cursor-pointer disabled:cursor-default disabled:hover:text-fg-dim', className)}
|
className={cn(
|
||||||
|
'text-fg-dim hover:text-fg transition-colors js-only text-xs cursor-pointer disabled:cursor-default disabled:hover:text-fg-dim',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function ImageWrapper({ src, alt, isListing }: ImageWrapperProps) {
|
||||||
alt={alt ?? ''}
|
alt={alt ?? ''}
|
||||||
width={isListing ? 400 : 800}
|
width={isListing ? 400 : 800}
|
||||||
height={isListing ? 240 : 400}
|
height={isListing ? 240 : 400}
|
||||||
quality={100}
|
quality={isListing ? 75 : 100}
|
||||||
priority={true}
|
priority={true}
|
||||||
className="w-auto mx-auto object-cover h-full"
|
className="w-auto mx-auto object-cover h-full"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { siBluesky, siX, siFacebook, siReddit } from 'simple-icons';
|
import { siBluesky, siX, siFacebook, siReddit } from 'simple-icons'
|
||||||
import { ExternalLink } from '@/components/common/ExternalLink';
|
import { ExternalLink } from '@/components/common/ExternalLink'
|
||||||
|
|
||||||
type ShareLinksProps = {
|
type ShareLinksProps = {
|
||||||
url: string;
|
url: string
|
||||||
title: string;
|
title: string
|
||||||
description?: string;
|
description?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
export function ShareLinks({ url, title, description }: ShareLinksProps) {
|
export function ShareLinks({ url, title, description }: ShareLinksProps) {
|
||||||
const shareText = `Check out this post: ${title} - ${description}\n\n${url}`;
|
const shareText = `Check out this post: ${title} - ${description}\n\n${url}`
|
||||||
|
|
||||||
const networks = [
|
const networks = [
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +33,7 @@ export function ShareLinks({ url, title, description }: ShareLinksProps) {
|
||||||
title,
|
title,
|
||||||
)}&text=${encodeURIComponent(description ?? '')}`,
|
)}&text=${encodeURIComponent(description ?? '')}`,
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-4 mx-auto">
|
<div className="flex gap-4 mx-auto">
|
||||||
|
|
@ -46,5 +46,5 @@ export function ShareLinks({ url, title, description }: ShareLinksProps) {
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
38
components/layout/MobileNavbar.tsx
Normal file
38
components/layout/MobileNavbar.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { navItems } from './navItems'
|
||||||
|
import { cn } from '@/utils/cn'
|
||||||
|
|
||||||
|
export function MobileNavbar() {
|
||||||
|
const pathname = usePathname()
|
||||||
|
|
||||||
|
const isActive = (href: string) => {
|
||||||
|
if (href === '/') {
|
||||||
|
return pathname === href
|
||||||
|
}
|
||||||
|
return pathname?.startsWith(href)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-bg border-t border-border z-50">
|
||||||
|
<div className="flex justify-between items-center p-4 max-w-90 mx-auto">
|
||||||
|
{navItems
|
||||||
|
.filter((item) => !item.isExternal)
|
||||||
|
.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={cn(
|
||||||
|
'transition-colors',
|
||||||
|
isActive(item.href) ? 'text-accent' : 'text-fg-dim hover:text-fg',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,36 +1,10 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { cn } from '@/utils/cn'
|
import { cn } from '@/utils/cn'
|
||||||
import { Folder, GitBranch, Home, Info, Rss, Tags } from 'lucide-react'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { ExternalLink } from '../common/ExternalLink'
|
import { ExternalLink } from '../common/ExternalLink'
|
||||||
|
import { navItems } from './navItems'
|
||||||
type NavItem = {
|
|
||||||
href: string
|
|
||||||
label: string
|
|
||||||
icon: React.ReactNode
|
|
||||||
isExternal?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
|
||||||
{ href: '/', label: 'Home', icon: <Home className="w-4 h-4" /> },
|
|
||||||
{ href: '/categories', label: 'Categories', icon: <Folder className="w-4 h-4" /> },
|
|
||||||
{ href: '/about', label: 'About', icon: <Info className="w-4 h-4" /> },
|
|
||||||
{ href: '/tags', label: 'Tags', icon: <Tags className="w-4 h-4" /> },
|
|
||||||
{
|
|
||||||
href: '/feed.xml',
|
|
||||||
label: 'RSS Feed',
|
|
||||||
icon: <Rss className="w-4 h-4" />,
|
|
||||||
isExternal: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://git.hangerthem.com/hangerthem/blog.hangerthem.com',
|
|
||||||
label: 'Source Code',
|
|
||||||
icon: <GitBranch className="w-4 h-4" />,
|
|
||||||
isExternal: true,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function NavLinks() {
|
export function NavLinks() {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
|
||||||
29
components/layout/navItems.tsx
Normal file
29
components/layout/navItems.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Folder, GitBranch, Home, Info, Rss, Tags } from 'lucide-react'
|
||||||
|
|
||||||
|
type NavItem = {
|
||||||
|
href: string
|
||||||
|
label: string
|
||||||
|
icon: React.ReactNode
|
||||||
|
isExternal?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const navIconsClassName = 'w-6 h-6'
|
||||||
|
|
||||||
|
export const navItems: NavItem[] = [
|
||||||
|
{ href: '/', label: 'Home', icon: <Home /> },
|
||||||
|
{ href: '/about', label: 'About', icon: <Info className={navIconsClassName} /> },
|
||||||
|
{ href: '/categories', label: 'Categories', icon: <Folder className={navIconsClassName} /> },
|
||||||
|
{ href: '/tags', label: 'Tags', icon: <Tags className={navIconsClassName} /> },
|
||||||
|
{
|
||||||
|
href: '/feed.xml',
|
||||||
|
label: 'RSS Feed',
|
||||||
|
icon: <Rss className={navIconsClassName} />,
|
||||||
|
isExternal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://git.hangerthem.com/hangerthem/blog.hangerthem.com',
|
||||||
|
label: 'Source Code',
|
||||||
|
icon: <GitBranch className={navIconsClassName} />,
|
||||||
|
isExternal: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -10,6 +10,7 @@ mermaid: true
|
||||||
---
|
---
|
||||||
|
|
||||||
> [!QUOTE] "The only person you are destined to become is the person you decide to be."
|
> [!QUOTE] "The only person you are destined to become is the person you decide to be."
|
||||||
|
>
|
||||||
> - Ralph Waldo Emerson
|
> - Ralph Waldo Emerson
|
||||||
|
|
||||||
Right at the beginning of this post, I want to make it clear that I am not a self-help guru or a life coach. I am just a regular person who is passionate about personal development and enjoys exploring ways to improve myself. In this post, I want to share some thoughts on self-development and how gamification can be used to enhance productivity and personal growth.
|
Right at the beginning of this post, I want to make it clear that I am not a self-help guru or a life coach. I am just a regular person who is passionate about personal development and enjoys exploring ways to improve myself. In this post, I want to share some thoughts on self-development and how gamification can be used to enhance productivity and personal growth.
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,6 @@ $$
|
||||||
\label{eq:em_field_strength_tensor}
|
\label{eq:em_field_strength_tensor}
|
||||||
\end{equation}
|
\end{equation}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
- **Explanation**: This term describes the field strength of the electromagnetic field. It involves the derivative of the electromagnetic potential $A_\mu$. The field strength tensor is antisymmetric, reflecting the nature of the electromagnetic force.
|
- **Explanation**: This term describes the field strength of the electromagnetic field. It involves the derivative of the electromagnetic potential $A_\mu$. The field strength tensor is antisymmetric, reflecting the nature of the electromagnetic force.
|
||||||
|
|
||||||
2. **Electron Kinetic Term** $(\bar{\psi}(i\gamma^\mu D_\mu-m)\psi)$:
|
2. **Electron Kinetic Term** $(\bar{\psi}(i\gamma^\mu D_\mu-m)\psi)$:
|
||||||
|
|
@ -270,7 +269,6 @@ $$
|
||||||
\label{eq:covariant_derivative_for_electrons}
|
\label{eq:covariant_derivative_for_electrons}
|
||||||
\end{equation}
|
\end{equation}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
- **Explanation**: This accounts for the interaction of electrons with the electromagnetic field. $e$ is the electric charge, and $A_\mu$ is the electromagnetic potential.
|
- **Explanation**: This accounts for the interaction of electrons with the electromagnetic field. $e$ is the electric charge, and $A_\mu$ is the electromagnetic potential.
|
||||||
|
|
||||||
3. **Electron Mass Term** $(-m)$:
|
3. **Electron Mass Term** $(-m)$:
|
||||||
|
|
@ -307,7 +305,6 @@ $$
|
||||||
\label{eq:w_field_strength_tensor}
|
\label{eq:w_field_strength_tensor}
|
||||||
\end{equation}
|
\end{equation}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
- **$B$ Field Strength Tensor** ($B_{\mu\nu}$):
|
- **$B$ Field Strength Tensor** ($B_{\mu\nu}$):
|
||||||
|
|
||||||
$$
|
$$
|
||||||
|
|
@ -316,7 +313,6 @@ $$
|
||||||
\label{eq:b_field_strength_tensor}
|
\label{eq:b_field_strength_tensor}
|
||||||
\end{equation}
|
\end{equation}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
- **Explanation**: These terms describe the field strengths of the $W$ and $B$ fields, which are associated with the weak and electromagnetic forces, respectively. The $W$ field strength tensor involves the non-Abelian field strength term, reflecting the non-Abelian nature of the weak force.
|
- **Explanation**: These terms describe the field strengths of the $W$ and $B$ fields, which are associated with the weak and electromagnetic forces, respectively. The $W$ field strength tensor involves the non-Abelian field strength term, reflecting the non-Abelian nature of the weak force.
|
||||||
|
|
||||||
2. **Lepton/Quark Kinetic Term** $(\bar{\psi}(i\gamma^\mu D_\mu-m)\psi)$:
|
2. **Lepton/Quark Kinetic Term** $(\bar{\psi}(i\gamma^\mu D_\mu-m)\psi)$:
|
||||||
|
|
@ -331,7 +327,6 @@ $$
|
||||||
\label{eq:covariant_derivative_for_leptons_quarks}
|
\label{eq:covariant_derivative_for_leptons_quarks}
|
||||||
\end{equation}
|
\end{equation}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
- **Explanation**: This accounts for the interaction of leptons/quarks with the $W$ and $B$ fields. $g'$ and $g$ are the coupling constants for the $U(1)$ and $SU(2)$ gauge groups, respectively. $Y$ is the weak hypercharge, and $\frac{\tau^a}{2}$ are the generators of the $SU(2)$ group.
|
- **Explanation**: This accounts for the interaction of leptons/quarks with the $W$ and $B$ fields. $g'$ and $g$ are the coupling constants for the $U(1)$ and $SU(2)$ gauge groups, respectively. $Y$ is the weak hypercharge, and $\frac{\tau^a}{2}$ are the generators of the $SU(2)$ group.
|
||||||
|
|
||||||
3. **Lepton/Quark Mass Term** $(-m)$:
|
3. **Lepton/Quark Mass Term** $(-m)$:
|
||||||
|
|
@ -366,7 +361,6 @@ $$
|
||||||
\label{eq:gluon_field_strength_tensor}
|
\label{eq:gluon_field_strength_tensor}
|
||||||
\end{equation}
|
\end{equation}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
- **Explanation**: This term describes the field strength of gluons. It involves the derivative of the gluon fields $(\partial_\mu G^a_\nu$ and $\partial_\nu G^a_\mu)$ and the non-Abelian field strength term involving the structure constants $f^{abc}$, which accounts for the interaction between gluons themselves.
|
- **Explanation**: This term describes the field strength of gluons. It involves the derivative of the gluon fields $(\partial_\mu G^a_\nu$ and $\partial_\nu G^a_\mu)$ and the non-Abelian field strength term involving the structure constants $f^{abc}$, which accounts for the interaction between gluons themselves.
|
||||||
|
|
||||||
2. **Quark Kinetic Term** $(\bar{q}(i\gamma^\mu D_\mu-m_q)q)$:
|
2. **Quark Kinetic Term** $(\bar{q}(i\gamma^\mu D_\mu-m_q)q)$:
|
||||||
|
|
@ -381,7 +375,6 @@ $$
|
||||||
\label{eq:quark_covariant_derivative_qcd}
|
\label{eq:quark_covariant_derivative_qcd}
|
||||||
\end{equation}
|
\end{equation}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
- **Explanation**: This accounts for the interaction of quarks with gluons. $g_s$ is the strong coupling constant, and $\frac{\lambda^a}{2}$ are the generators of the SU(3) color gauge group. $G^a_\mu$ are the gluon fields.
|
- **Explanation**: This accounts for the interaction of quarks with gluons. $g_s$ is the strong coupling constant, and $\frac{\lambda^a}{2}$ are the generators of the SU(3) color gauge group. $G^a_\mu$ are the gluon fields.
|
||||||
|
|
||||||
3. **Quark Mass Term** $(-m_q)$:
|
3. **Quark Mass Term** $(-m_q)$:
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ You can also use other code blocks in your Markdown, like this:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function helloWorld() {
|
function helloWorld() {
|
||||||
console.log("Hello, world!");
|
console.log('Hello, world!')
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ It will be rendered as:
|
||||||
> [!TIP] You can also use `>` to add blockquotes to your posts.
|
> [!TIP] You can also use `>` to add blockquotes to your posts.
|
||||||
|
|
||||||
| Column 1 | Column 2 | Column 3 |
|
| Column 1 | Column 2 | Column 3 |
|
||||||
|----------|----------|----------|
|
| -------- | -------- | -------- |
|
||||||
| Cell 1 | Cell 2 | Cell 3 |
|
| Cell 1 | Cell 2 | Cell 3 |
|
||||||
| Cell 4 | Cell 5 | Cell 6 |
|
| Cell 4 | Cell 5 | Cell 6 |
|
||||||
|
|
||||||
|
|
@ -91,10 +91,9 @@ Term 2
|
||||||
: Definition A for term 2.
|
: Definition A for term 2.
|
||||||
: Definition B for term 2, on a new line.
|
: Definition B for term 2, on a new line.
|
||||||
|
|
||||||
H~2~O
|
|
||||||
|
|
||||||
- [ ] Task 1
|
- [ ] Task 1
|
||||||
- [x] Task 2
|
- [x] Task 2
|
||||||
|
|
||||||
[^1]: The `src/assets/blog` ==directory== is where you can place images for your blog posts. You can reference them in your Markdown using the relative path to the image file.
|
[^1]: The `src/assets/blog` ==directory== is where you can place images for your blog posts. You can reference them in your Markdown using the relative path to the image file.
|
||||||
|
|
||||||
[^2]: You can also add footnotes to your posts. This is a footnote reference, and the footnote text is at the bottom of the post.
|
[^2]: You can also add footnotes to your posts. This is a footnote reference, and the footnote text is at the bottom of the post.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineConfig, globalIgnores } from "eslint/config";
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
import nextVitals from 'eslint-config-next/core-web-vitals'
|
||||||
import nextTs from "eslint-config-next/typescript";
|
import nextTs from 'eslint-config-next/typescript'
|
||||||
|
|
||||||
const eslintConfig = defineConfig([
|
const eslintConfig = defineConfig([
|
||||||
...nextVitals,
|
...nextVitals,
|
||||||
|
|
@ -8,11 +8,11 @@ const eslintConfig = defineConfig([
|
||||||
// Override default ignores of eslint-config-next.
|
// Override default ignores of eslint-config-next.
|
||||||
globalIgnores([
|
globalIgnores([
|
||||||
// Default ignores of eslint-config-next:
|
// Default ignores of eslint-config-next:
|
||||||
".next/**",
|
'.next/**',
|
||||||
"out/**",
|
'out/**',
|
||||||
"build/**",
|
'build/**',
|
||||||
"next-env.d.ts",
|
'next-env.d.ts',
|
||||||
]),
|
]),
|
||||||
]);
|
])
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { findSlugInArray } from "@/utils/slug"
|
import { findSlugInArray } from '@/utils/slug'
|
||||||
import { getAllPostsMeta } from "@/lib/posts"
|
import { getAllPostsMeta } from '@/lib/posts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all unique categories from the posts and counts the number of posts in each category.
|
* Retrieves all unique categories from the posts and counts the number of posts in each category.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import path from 'path'
|
||||||
import matter from 'gray-matter'
|
import matter from 'gray-matter'
|
||||||
import { remark } from 'remark'
|
import { remark } from 'remark'
|
||||||
import remarkParse from 'remark-parse'
|
import remarkParse from 'remark-parse'
|
||||||
import gfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
import remarkMath from 'remark-math'
|
import remarkMath from 'remark-math'
|
||||||
import remarkRehype from 'remark-rehype'
|
import remarkRehype from 'remark-rehype'
|
||||||
import rehypeHighlight from 'rehype-highlight'
|
import rehypeHighlight from 'rehype-highlight'
|
||||||
|
|
@ -66,7 +66,7 @@ export async function getPostBySlug(slug: string) {
|
||||||
const fullPath = path.join(postsDirectory, `${slug}.md`)
|
const fullPath = path.join(postsDirectory, `${slug}.md`)
|
||||||
const { data, content } = matter(fs.readFileSync(fullPath, 'utf8'))
|
const { data, content } = matter(fs.readFileSync(fullPath, 'utf8'))
|
||||||
|
|
||||||
let processor = remark().use(remarkParse).use(gfm)
|
let processor = remark().use(remarkParse).use(remarkGfm)
|
||||||
|
|
||||||
if (data.math) {
|
if (data.math) {
|
||||||
processor = processor.use(remarkMath)
|
processor = processor.use(remarkMath)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { cloneElement, isValidElement, type ReactNode } from "react";
|
import { cloneElement, isValidElement, type ReactNode } from 'react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively traverses a ReactNode tree and extracts the text content.
|
* Recursively traverses a ReactNode tree and extracts the text content.
|
||||||
|
|
@ -6,14 +6,14 @@ import { cloneElement, isValidElement, type ReactNode } from "react";
|
||||||
* @returns A string containing the concatenated text content of the ReactNode tree.
|
* @returns A string containing the concatenated text content of the ReactNode tree.
|
||||||
*/
|
*/
|
||||||
export function getTextContent(node: ReactNode): string {
|
export function getTextContent(node: ReactNode): string {
|
||||||
if (node === null || typeof node === "boolean") return "";
|
if (node === null || typeof node === 'boolean') return ''
|
||||||
if (typeof node === "string" || typeof node === "number") return String(node);
|
if (typeof node === 'string' || typeof node === 'number') return String(node)
|
||||||
if (Array.isArray(node)) return node.map(getTextContent).join("");
|
if (Array.isArray(node)) return node.map(getTextContent).join('')
|
||||||
if (isValidElement(node)) {
|
if (isValidElement(node)) {
|
||||||
const props = node.props as { children?: ReactNode };
|
const props = node.props as { children?: ReactNode }
|
||||||
return getTextContent(props.children);
|
return getTextContent(props.children)
|
||||||
}
|
}
|
||||||
return "";
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,36 +23,36 @@ export function getTextContent(node: ReactNode): string {
|
||||||
* @returns A new ReactNode with the leading whitespace and specified pattern removed from text nodes.
|
* @returns A new ReactNode with the leading whitespace and specified pattern removed from text nodes.
|
||||||
*/
|
*/
|
||||||
export function stripLeadingMatch(node: ReactNode, pattern: RegExp): ReactNode {
|
export function stripLeadingMatch(node: ReactNode, pattern: RegExp): ReactNode {
|
||||||
if (typeof node === "string") {
|
if (typeof node === 'string') {
|
||||||
const trimmed = node.replace(/^\s+/, "");
|
const trimmed = node.replace(/^\s+/, '')
|
||||||
if (pattern.test(trimmed)) {
|
if (pattern.test(trimmed)) {
|
||||||
return trimmed.replace(pattern, "").replace(/^\s+/, "");
|
return trimmed.replace(pattern, '').replace(/^\s+/, '')
|
||||||
}
|
}
|
||||||
return node;
|
return node
|
||||||
}
|
}
|
||||||
if (Array.isArray(node)) {
|
if (Array.isArray(node)) {
|
||||||
let stripped = false;
|
let stripped = false
|
||||||
return node.map((child) => {
|
return node.map((child) => {
|
||||||
if (stripped) return child;
|
if (stripped) return child
|
||||||
if (typeof child === "string" && child.trim() === "") return child; // skip whitespace nodes
|
if (typeof child === 'string' && child.trim() === '') return child // skip whitespace nodes
|
||||||
if (typeof child === "string") {
|
if (typeof child === 'string') {
|
||||||
const trimmed = child.replace(/^\s+/, "");
|
const trimmed = child.replace(/^\s+/, '')
|
||||||
if (pattern.test(trimmed)) {
|
if (pattern.test(trimmed)) {
|
||||||
stripped = true;
|
stripped = true
|
||||||
return trimmed.replace(pattern, "").replace(/^\s+/, "");
|
return trimmed.replace(pattern, '').replace(/^\s+/, '')
|
||||||
}
|
}
|
||||||
return child;
|
return child
|
||||||
}
|
}
|
||||||
if (isValidElement(child)) {
|
if (isValidElement(child)) {
|
||||||
stripped = true;
|
stripped = true
|
||||||
return stripLeadingMatch(child, pattern);
|
return stripLeadingMatch(child, pattern)
|
||||||
}
|
}
|
||||||
return child;
|
return child
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
if (isValidElement(node)) {
|
if (isValidElement(node)) {
|
||||||
const props = node.props as { children?: ReactNode };
|
const props = node.props as { children?: ReactNode }
|
||||||
return cloneElement(node, {}, stripLeadingMatch(props.children, pattern));
|
return cloneElement(node, {}, stripLeadingMatch(props.children, pattern))
|
||||||
}
|
}
|
||||||
return node;
|
return node
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,12 @@ export const rehypeMermaidSsg: Plugin<[], Root> = () => async (tree: Root) => {
|
||||||
const targets: { node: Element; parent: Element | Root; index: number }[] = []
|
const targets: { node: Element; parent: Element | Root; index: number }[] = []
|
||||||
|
|
||||||
visit(tree, 'element', (node: Element, index, parent) => {
|
visit(tree, 'element', (node: Element, index, parent) => {
|
||||||
if (node.tagName === 'div' && node.properties?.['data-mermaid'] && parent && typeof index === 'number') {
|
if (
|
||||||
|
node.tagName === 'div' &&
|
||||||
|
node.properties?.['data-mermaid'] &&
|
||||||
|
parent &&
|
||||||
|
typeof index === 'number'
|
||||||
|
) {
|
||||||
targets.push({ node, parent: parent as Element | Root, index })
|
targets.push({ node, parent: parent as Element | Root, index })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -25,14 +30,12 @@ export const rehypeMermaidSsg: Plugin<[], Root> = () => async (tree: Root) => {
|
||||||
const svgTree = fromHtmlIsomorphic(svg, { fragment: true })
|
const svgTree = fromHtmlIsomorphic(svg, { fragment: true })
|
||||||
const svgNode = svgTree.children.find((c) => c.type === 'element') as Element | undefined
|
const svgNode = svgTree.children.find((c) => c.type === 'element') as Element | undefined
|
||||||
if (svgNode) {
|
if (svgNode) {
|
||||||
; (parent.children as Element[])[index] = svgNode
|
;(parent.children as Element[])[index] = svgNode
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
node.tagName = 'pre'
|
node.tagName = 'pre'
|
||||||
node.properties = { className: ['text-red-500', 'text-sm'] }
|
node.properties = { className: ['text-red-500', 'text-sm'] }
|
||||||
node.children = [
|
node.children = [{ type: 'text', value: `Mermaid render error: ${(err as Error).message}` }]
|
||||||
{ type: 'text', value: `Mermaid render error: ${(err as Error).message}` },
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from 'next'
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
images: {
|
||||||
};
|
qualities: [75, 100],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,7 @@
|
||||||
"lint": "oxlint . && oxfmt --check",
|
"lint": "oxlint . && oxfmt --check",
|
||||||
"lint:fix": "oxlint . --fix && oxfmt --write",
|
"lint:fix": "oxlint . --fix && oxfmt --write",
|
||||||
"format": "oxfmt --check",
|
"format": "oxfmt --check",
|
||||||
"format:write": "oxfmt --write",
|
"format:write": "oxfmt --write"
|
||||||
"postinstall": "puppeteer browsers install chrome"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const config = {
|
const config = {
|
||||||
plugins: {
|
plugins: {
|
||||||
"@tailwindcss/postcss": {},
|
'@tailwindcss/postcss': {},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default config;
|
export default config
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { clsx, type ClassValue } from 'clsx';
|
import { clsx, type ClassValue } from 'clsx'
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to merge Tailwind CSS classes
|
* Utility function to merge Tailwind CSS classes
|
||||||
* Combines clsx for conditional classes with tailwind-merge for deduplication
|
* Combines clsx for conditional classes with tailwind-merge for deduplication
|
||||||
*/
|
*/
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ export const dateFormatter = new Intl.DateTimeFormat('en', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
});
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue