Load mdx files, make navbar good

This commit is contained in:
2025-07-15 00:54:33 +01:00
parent 813603282e
commit eb0907fdfd
13 changed files with 104 additions and 98 deletions

View File

@@ -20,6 +20,10 @@ const config = {
protocol: "https", protocol: "https",
hostname: "fly.storage.tigris.dev", hostname: "fly.storage.tigris.dev",
}, },
{
protocol: "https",
hostname: "img.daisyui.com",
},
], ],
}, },
}; };

View File

@@ -9,7 +9,7 @@ export default function CvPage(): React.JSX.Element {
Download Download
</button> </button>
</div> </div>
<div className="divider divider-primary"></div> <div className="divider divider-primary"></div>
<Cv /> <Cv />
</div> </div>
); );

View File

@@ -9,7 +9,7 @@ export default function RootLayout({
return ( return (
<> <>
<NavBar /> <NavBar />
<main className="bg-base-200 sm:border-x-2 dark:border-0 border-accent mx-auto w-full flex-1 px-6 py-4 align-middle lg:max-w-5xl"> <main className="mx-auto w-full flex-1 px-6 py-8 align-middle lg:max-w-5xl">
{children} {children}
</main> </main>
<Footer /> <Footer />

View File

@@ -1,10 +1,14 @@
import { glob } from "glob"; import { glob } from "glob";
import dynamic, { type LoaderComponent } from "next/dynamic"; import dynamic from "next/dynamic";
import type React from "react"; import type React from "react";
export const dynamicParams = false; export const dynamicParams = false;
export async function generateStaticParams(): Promise<{ slug: string[] }[]> { export async function generateStaticParams(): Promise<
{
slug: string[];
}[]
> {
const posts = await glob( const posts = await glob(
`${process.cwd()}/src/markdown/posts/[[]...slug[]]/**/*.mdx`, `${process.cwd()}/src/markdown/posts/[[]...slug[]]/**/*.mdx`,
{ {
@@ -12,21 +16,25 @@ export async function generateStaticParams(): Promise<{ slug: string[] }[]> {
}, },
); );
const slugs = posts.map((post) => ({ const postData = posts.map((post) => ({
slug: [post.split("/").at(-1)?.slice(0, -4)], slug: [post.split("/").at(-1)?.slice(0, -4) ?? ""],
})); }));
return slugs; return await Promise.all(postData);
} }
export default async function Post({ export default async function Post({
params, params,
}: { }: {
params: Promise<{ slug: string[] }>; params: {
slug: string[];
};
}): Promise<React.JSX.Element> { }): Promise<React.JSX.Element> {
const mdxFile = (await import( const Post = dynamic(
`../../../../markdown/posts/[...slug]/${(await params).slug.join("/")}.mdx` async () =>
)) as LoaderComponent<unknown>; import(
const Post = dynamic(() => mdxFile); `../../../../markdown/posts/[...slug]/${(await params).slug.join("/")}.mdx`
),
);
return <Post />; return <Post />;
} }

View File

@@ -1,16 +1,13 @@
import { glob } from "glob"; import { glob } from "glob";
import { unstable_cache } from "next/cache"; import { unstable_cache } from "next/cache";
import Link from "next/link"; import Link from "next/link";
import { getBaseUrl } from "@/lib/base-url";
type postDetails = { type postDetails = {
link: string; link: string;
metadata: { metadata: {
title: string; title: string;
date: string; date: string;
coverImage: string;
blurb: string; blurb: string;
shortBlurb: string;
tags: string[]; tags: string[];
}; };
}; };
@@ -29,13 +26,16 @@ async function loadPostDetails(): Promise<postDetails[]> {
`../../../../src/markdown/posts/[...slug]/${slug.join("/")}.mdx` `../../../../src/markdown/posts/[...slug]/${slug.join("/")}.mdx`
)) as postDetails; )) as postDetails;
return { return {
link: `${getBaseUrl()}/posts/${slug.join("/")}`, link: `/posts/${slug.join("/")}`,
metadata: mdxFile.metadata, metadata: mdxFile.metadata,
}; };
}); });
const postData = await Promise.all(loadPostData); const postData = await Promise.all(loadPostData);
return postData; return postData.sort(
(postA, postB) =>
Date.parse(postB.metadata.date) - Date.parse(postA.metadata.date),
);
} }
const getPosts = unstable_cache(loadPostDetails, ["posts"], { const getPosts = unstable_cache(loadPostDetails, ["posts"], {
@@ -45,26 +45,30 @@ const getPosts = unstable_cache(loadPostDetails, ["posts"], {
export default async function Posts(): Promise<React.JSX.Element> { export default async function Posts(): Promise<React.JSX.Element> {
const postDetails = await getPosts(); const postDetails = await getPosts();
return ( return (
<div className="flex flex-col gap-6"> <div className="flex flex-wrap sm:grid-cols-2">
{postDetails.map((post) => { {postDetails.map((post) => {
return ( return (
<div key={post.link}> <div key={post.link} className="sm:max-w-1/2 grow">
<div className="card md:card-side"> <div className="card card-border m-2 bg-base-200 shadow-sm">
<h2> <div className="card-body">
<Link href={post.link}>{post.metadata.title}</Link> <h1 className="card-title">{post.metadata.title}</h1>
</h2> <time dateTime={post.metadata.date}>{post.metadata.date}</time>
<div className="flex flex-row"> <div className="flex flex-row gap-2">
{post.metadata.tags.map((tag) => { {post.metadata.tags.map((tag) => {
return ( return (
<div key={`${post.link}_${tag}`}> <div key={`${post.link}_${tag}`}>
<span className="me-2 select-none rounded border border-dracula-pink px-2.5 py-1 text-sm dark:bg-dracula-bg-darker dark:text-dracula-pink"> <div className="badge badge-soft badge-info">{tag}</div>
{tag} </div>
</span> );
</div> })}
); </div>
})} <p>{post.metadata.blurb}</p>
<div className="card-actions justify-end pt-2">
<Link className="btn btn-primary" href={post.link}>
Read
</Link>
</div>
</div> </div>
<p>{post.metadata.blurb}</p>
</div> </div>
</div> </div>
); );

View File

@@ -25,9 +25,7 @@ export default async function LogIn(): Promise<React.JSX.Element | undefined> {
> >
<UserIcon <UserIcon
className={`h-8 w-auto transition-colors ${ className={`h-8 w-auto transition-colors ${
session?.user session?.user ? "stroke-warning" : ""
? "stroke-warning"
: ""
}`} }`}
/> />
<span className="sr-only">{session?.user ? "Log out" : "Log in"}</span> <span className="sr-only">{session?.user ? "Log out" : "Log in"}</span>

View File

@@ -152,15 +152,9 @@ export default function Cv(): React.JSX.Element {
</h1> </h1>
<div className="flex flex-col gap-2 p-2"> <div className="flex flex-col gap-2 p-2">
<div className="grid grid-cols-3 border-b-2 pb-2"> <div className="grid grid-cols-3 border-b-2 pb-2">
<span className="border-r text-left"> <span className="border-r text-left">joemonk.co.uk</span>
joemonk.co.uk <span className="border-x text-center">07757 017587</span>
</span> <span className="border-l text-right">joemonk@hotmail.co.uk</span>
<span className="border-x text-center">
07757 017587
</span>
<span className="border-l text-right">
joemonk@hotmail.co.uk
</span>
</div> </div>
<p className="text-justify"> <p className="text-justify">
As a highly motivated and adaptive developer, my enthusiasm for As a highly motivated and adaptive developer, my enthusiasm for

View File

@@ -43,7 +43,7 @@ export default function NavBarClient({
}, [pathname, navigation]); }, [pathname, navigation]);
return ( return (
<nav className="border-accent border-b-2 bg-base-300"> <nav className="border-accent border-b-2 shadow-md dark:shadow-none dark:bg-base-300">
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<div className="mx-auto max-w-5xl px-4"> <div className="mx-auto max-w-5xl px-4">
<div className="relative flex h-16 items-center justify-between"> <div className="relative flex h-16 items-center justify-between">
@@ -64,7 +64,7 @@ export default function NavBarClient({
className="btn hidden items-center rounded border-2 border-primary/75 p-1 transition-colors hover:bg-primary/25 sm:flex" className="btn hidden items-center rounded border-2 border-primary/75 p-1 transition-colors hover:bg-primary/25 sm:flex"
href="/" href="/"
> >
<HomeModernIcon className="h-8 w-auto rounded-sm" /> <HomeModernIcon className="h-8 w-auto rounded-sm" />
</Link> </Link>
<div className="ml-12 hidden gap-4 sm:flex"> <div className="ml-12 hidden gap-4 sm:flex">
{activeNavigation.map((item) => ( {activeNavigation.map((item) => (

View File

@@ -18,13 +18,13 @@ export default function PostHeader({
return ( return (
<> <>
<h1>{metadata.title}</h1> <h1>{metadata.title}</h1>
<div className="mb-2">{metadata.date}</div> <time dateTime={metadata.date}>{metadata.date}</time>
<div className="mb-6"> <div className="mb-6 flex gap-2">
{metadata.tags.map((tag) => { {metadata.tags.map((tag) => {
return ( return (
<span className="me-2 select-none rounded border border-dracula-pink px-2.5 py-1 text-sm dark:bg-dracula-bg-darker dark:text-dracula-pink"> <div key={`${metadata.title}_tag_${tag}`}>
{tag} <div className="badge badge-soft badge-info">{tag}</div>
</span> </div>
); );
})} })}
</div> </div>

View File

@@ -3,7 +3,6 @@ import PostHeader from '@/app/_components/post-header';
export const metadata = { export const metadata = {
title: "Being a Developer", title: "Being a Developer",
date: "2020-05-12", date: "2020-05-12",
coverImage: "../images/being-a-developer/being-a-developer.jpg",
blurb: "My thoughts on being a \"developer\", being a \"programmer\" and the differences between them.", blurb: "My thoughts on being a \"developer\", being a \"programmer\" and the differences between them.",
shortBlurb: "My thoughts on being a developer vs being a programmer.", shortBlurb: "My thoughts on being a developer vs being a programmer.",
tags: ["Blog", "Development"] tags: ["Blog", "Development"]

View File

@@ -4,7 +4,6 @@ export const metadata = {
title: "Learning Kubernetes", title: "Learning Kubernetes",
date: "2020-12-31", date: "2020-12-31",
path: "/posts/learning-Kubernetes", path: "/posts/learning-Kubernetes",
coverImage: "../images/learning-kubernetes/k8s.png",
blurb: "Learning how to use Kubenetes in an environment between \"Local testing\" and \"Full server deployments\".", blurb: "Learning how to use Kubenetes in an environment between \"Local testing\" and \"Full server deployments\".",
shortBlurb: "Finally getting around to \"learning\" Kubernetes.", shortBlurb: "Finally getting around to \"learning\" Kubernetes.",
tags: ["Blog", "Development"], tags: ["Blog", "Development"],

View File

@@ -3,7 +3,6 @@ import PostHeader from '@/app/_components/post-header';
export const metadata = { export const metadata = {
title: "Managing a Team Remotely", title: "Managing a Team Remotely",
date: "2020-10-05", date: "2020-10-05",
coverImage: "../images/managing-a-team-remotely/managing-a-team-remotely.jpg",
blurb: "With working remotely being a necessity at the moment, my thoughts on managing a team of developers with no physicality.", blurb: "With working remotely being a necessity at the moment, my thoughts on managing a team of developers with no physicality.",
shortBlurb: "My thoughts managing a team of developers with no physicality.", shortBlurb: "My thoughts managing a team of developers with no physicality.",
tags: ["Blog", "Development"] tags: ["Blog", "Development"]

View File

@@ -1,58 +1,59 @@
/** biome-ignore-all lint/correctness/noUnknownProperty: Biome doesn't understand DaisyUI properties */
@import "tailwindcss"; @import "tailwindcss";
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
@plugin "daisyui" { @plugin "daisyui" {
themes: nord --default; themes: nord --default;
}; }
@plugin "daisyui/theme" { @plugin "daisyui/theme" {
/* Nicked from the vscode soft theme https://github.com/dracula/visual-studio-code/blob/master/src/dracula.yml */ /* Nicked from the vscode soft theme https://github.com/dracula/visual-studio-code/blob/master/src/dracula.yml */
name: "dracula-soft"; name: "dracula-soft";
default: false; default: false;
prefersdark: false; prefersdark: false;
color-scheme: "dark"; color-scheme: "dark";
/* --color-base-50: oklch(34.02% 0.027 276.05); */ /* --color-base-50: oklch(34.02% 0.027 276.05); */
--color-base-100: oklch(28.82% 0.022 277.51); --color-base-100: oklch(28.82% 0.022 277.51);
--color-base-200: oklch(25.54% 0.019 280.49); --color-base-200: oklch(25.54% 0.019 280.49);
--color-base-300: oklch(21.99% 0.014 278.80); --color-base-300: oklch(21.99% 0.014 278.8);
--color-base-content: oklch(91% 0.020 278); --color-base-content: oklch(91% 0.02 278);
--color-primary: oklch(88.263% 0.093 212.846); --color-primary: oklch(88.263% 0.093 212.846);
--color-primary-content: oklch(17.652% 0.018 212.846); --color-primary-content: oklch(88.263% 0.093 212.846);
--color-secondary: oklch(83.392% 0.124 66.558); /* --color-primary-content: oklch(17.652% 0.018 212.846); */
--color-secondary-content: oklch(16.678% 0.024 66.558); --color-secondary: oklch(83.392% 0.124 66.558);
--color-accent: oklch(74.202% 0.148 301.883); --color-secondary-content: oklch(16.678% 0.024 66.558);
--color-accent-content: oklch(14.84% 0.029 301.883); --color-accent: oklch(74.202% 0.148 301.883);
--color-neutral: oklch(38.94% 0.020 277.93); --color-accent-content: oklch(14.84% 0.029 301.883);
--color-neutral-content: oklch(87.889% 0.006 275.524); --color-neutral: oklch(38.94% 0.02 277.93);
--color-neutral-content: oklch(87.889% 0.006 275.524);
--color-info: oklch(75.461% 0.183 346.812);
--color-info-content: oklch(15.092% 0.036 346.812);
--color-success: oklch(87.099% 0.219 148.024);
--color-success-content: oklch(17.419% 0.043 148.024);
--color-warning: oklch(95.533% 0.134 112.757);
--color-warning-content: oklch(15.106% 0.026 112.757);
--color-error: oklch(68.22% 0.206 24.43);
--color-error-content: oklch(13.644% 0.041 24.43);
--radius-selector: 0.5rem; --color-info: oklch(75.461% 0.183 346.812);
--radius-field: 0.5rem; --color-info-content: oklch(15.092% 0.036 346.812);
--radius-box: 0.5rem; --color-success: oklch(87.099% 0.219 148.024);
--color-success-content: oklch(17.419% 0.043 148.024);
--color-warning: oklch(95.533% 0.134 112.757);
--color-warning-content: oklch(15.106% 0.026 112.757);
--color-error: oklch(68.22% 0.206 24.43);
--color-error-content: oklch(13.644% 0.041 24.43);
--size-selector: 0.25rem; --radius-selector: 0.5rem;
--size-field: 0.25rem; --radius-field: 0.5rem;
--border: 1px; --radius-box: 0.5rem;
--depth: 0; --size-selector: 0.25rem;
--noise: 0; --size-field: 0.25rem;
--border: 1px;
--depth: 0;
--noise: 0;
} }
@theme { @theme {
--font-sans: --font-sans:
var(--font-inter), ui-sans-serif, system-ui, sans-serif, var(--font-inter), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
} }
@custom-variant dark (&:where([data-theme=dracula-soft], [data-theme=dracula-soft] *)); @custom-variant dark (&:where([data-theme=dracula-soft], [data-theme=dracula-soft] *));
@@ -62,5 +63,5 @@
} }
:root .prose { :root .prose {
--tw-prose-body: color-mix(in oklab, var(--color-base-content) 92%, #0000) !important; --tw-prose-body: color-mix(in oklab, var(--color-base-content) 92%, #0000) !important;
} }