Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import config from "~/config";

export type PageProps = Promise<{ slug: string }>;
export async function generateStaticParams() {
const posts = await getBlogPosts();
const posts = getBlogPosts();
return posts.map((post) => ({ slug: post.slug }));
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export const metadata = {
description: "A collection of insightful tutorials, guides, and various topics of interest by Jake McQuade.",
};

export default function Blog() {
const posts = await getBlogPosts();
export default async function Blog() {
const posts = getBlogPosts();

return (
<main className="relative min-h-[100dvh] items-center justify-center bg-transparent px-8 pt-8">
Expand Down
7 changes: 4 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google";
import { ReactNode } from "react";
import { Inter } from "next/font/google";
import Script from "next/script";

import { ThemeProvider } from "~/components/theme";
// import MouseEffect from "~/components/mouse";
import Footer from "~/components/footer";
import { cn } from "~/lib/utils";
import config from "~/config";

const font = Inter({ subsets: ["latin"], variable: "--font-sans" });


export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
Expand Down Expand Up @@ -75,7 +76,7 @@ export default function RootLayout({ children }: Readonly<{ children: ReactNode
return (
<html lang={"en"} data-theme={"dark"} style={{ colorScheme: "dark", scrollBehavior: "smooth" }} suppressHydrationWarning>
<head />
<body className={cn("mx-auto min-h-screen max-w-2xl bg-background font-sans antialiased", font.className)}>
<body className={`${font.variable} mx-auto min-h-screen max-w-2xl bg-background font-sans antialiased`}>
<ThemeProvider attribute={"data-theme"} defaultTheme={"dark"} disableTransitionOnChange>
{children}
<Footer />
Expand Down
22 changes: 14 additions & 8 deletions src/components/backup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import { FaArrowUp } from "react-icons/fa";
export default function BackUp() {
useEffect(() => {
const button = document.getElementById("top");
window.onscroll = function () {
if (document.documentElement.scrollTop > 80) {
button?.classList.add("opacity-100");
button?.classList.remove("opacity-0");
} else {
button?.classList.remove("opacity-100");
button?.classList.add("opacity-0");
}

const onScroll = () => {
if (!button) return;

const isVisible = document.documentElement.scrollTop > 80;
button.classList.toggle("opacity-100", isVisible);
button.classList.toggle("opacity-0", !isVisible);
};

window.addEventListener("scroll", onScroll, { passive: true });
onScroll();

return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);

Expand Down
62 changes: 28 additions & 34 deletions src/components/effects/blur-fade-text.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { cn } from "../../lib/utils";
import { AnimatePresence, motion, Variants } from "motion/react";
import { motion, Variants } from "motion/react";
import { useMemo } from "react";

interface BlurFadeTextProps {
Expand Down Expand Up @@ -37,45 +37,39 @@ const BlurFadeText = ({
if (animateByCharacter) {
return (
<div className="flex">
<AnimatePresence>
{characters.map((char, i) => (
<motion.span
key={char + i}
initial="hidden"
animate="visible"
exit="hidden"
variants={combinedVariants}
transition={{
delay: delay + i * characterDelay,
ease: "easeOut",
}}
className={cn("inline-block", className)}
style={{ width: char.trim() === "" ? "0.2em" : "auto" }}
>
{char}
</motion.span>
))}
</AnimatePresence>
{characters.map((char, i) => (
<motion.span
key={char + i}
initial="hidden"
animate="visible"
variants={combinedVariants}
transition={{
delay: delay + i * characterDelay,
ease: "easeOut",
}}
className={cn("inline-block", className)}
style={{ width: char.trim() === "" ? "0.2em" : "auto" }}
>
{char}
</motion.span>
))}
</div>
);
}

return (
<div className="flex">
<AnimatePresence>
<motion.span
initial="hidden"
animate="visible"
exit="hidden"
variants={combinedVariants}
transition={{
delay,
ease: "easeOut",
}}
className={cn("inline-block", className)}
dangerouslySetInnerHTML={{ __html: text }}
/>
</AnimatePresence>
<motion.span
initial="hidden"
animate="visible"
variants={combinedVariants}
transition={{
delay,
ease: "easeOut",
}}
className={cn("inline-block", className)}
dangerouslySetInnerHTML={{ __html: text }}
/>
</div>
);
};
Expand Down
68 changes: 34 additions & 34 deletions src/components/effects/blur-fade.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { AnimatePresence, motion, useInView, UseInViewOptions, Variants, MotionProps } from "motion/react";
import { useRef } from "react";
import { motion, useInView, UseInViewOptions, Variants, MotionProps } from "motion/react";
import { useMemo, useRef } from "react";

type MarginType = UseInViewOptions["margin"];

Expand Down Expand Up @@ -37,38 +37,38 @@ export default function BlurFade({
const ref = useRef(null);
const inViewResult = useInView(ref, { once: true, margin: inViewMargin });
const isInView = !inView || inViewResult;
const defaultVariants: Variants = {
hidden: {
[direction === "left" || direction === "right" ? "x" : "y"]:
direction === "right" || direction === "down" ? -offset : offset,
opacity: 0,
filter: `blur(${blur})`,
},
visible: {
[direction === "left" || direction === "right" ? "x" : "y"]: 0,
opacity: 1,
filter: `blur(0px)`,
},
};
const combinedVariants = variant || defaultVariants;
const defaultVariants: Variants = useMemo(
() => ({
hidden: {
[direction === "left" || direction === "right" ? "x" : "y"]:
direction === "right" || direction === "down" ? -offset : offset,
opacity: 0,
filter: `blur(${blur})`,
},
visible: {
[direction === "left" || direction === "right" ? "x" : "y"]: 0,
opacity: 1,
filter: "blur(0px)",
},
}),
[blur, direction, offset],
);

return (
<AnimatePresence>
<motion.div
ref={ref}
initial="hidden"
animate={isInView ? "visible" : "hidden"}
exit="hidden"
variants={combinedVariants}
transition={{
delay: 0.04 + delay,
duration,
ease: "easeOut",
}}
className={className}
{...props}
>
{children}
</motion.div>
</AnimatePresence>
<motion.div
ref={ref}
initial="hidden"
animate={isInView ? "visible" : "hidden"}
variants={variant || defaultVariants}
transition={{
delay: 0.04 + delay,
duration,
ease: "easeOut",
}}
className={className}
{...props}
>
{children}
</motion.div>
);
}
85 changes: 55 additions & 30 deletions src/lib/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,69 @@ function findRawBySlug(slug: string): string | null {
return null;
}

export async function getPost(slug: string) {
const metadataCache = new Map<string, Record<string, any>>();
const postCache = new Map<string, Promise<{ source: string; metadata: Record<string, any>; slug: string }>>();
let highlighterPromise: ReturnType<typeof createHighlighter> | null = null;

function getPostMetadata(slug: string) {
const cached = metadataCache.get(slug);
if (cached) return cached;

const raw = findRawBySlug(slug);
if (!raw) throw new Error(`Post not found: ${slug}`);

const { content: rawContent, data: metadata } = matter(raw);

const content = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypePrettyCode, {
// https://rehype-pretty.pages.dev/#usage
keepBackground: false,
theme: {
light: "min-light",
dark: "min-dark",
},
// IMPORTANT: avoid Shiki's default WASM Oniguruma engine on Cloudflare Workers
getHighlighter: (options) =>
createHighlighter({
...options,
engine: createJavaScriptRegexEngine({ forgiving: true }),
}),
})
.use(rehypeStringify)
.process(rawContent);

return { source: content.toString(), metadata, slug };
const { data: metadata } = matter(raw);
metadataCache.set(slug, metadata as Record<string, any>);

return metadata as Record<string, any>;
}

export async function getPost(slug: string) {
const cached = postCache.get(slug);
if (cached) return cached;

const postPromise = (async () => {
const raw = findRawBySlug(slug);
if (!raw) throw new Error(`Post not found: ${slug}`);

const { content: rawContent, data: metadata } = matter(raw);

const content = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypePrettyCode, {
// https://rehype-pretty.pages.dev/#usage
keepBackground: false,
theme: {
light: "min-light",
dark: "min-dark",
},
// IMPORTANT: avoid Shiki's default WASM Oniguruma engine on Cloudflare Workers
getHighlighter: (options) => {
if (!highlighterPromise) {
highlighterPromise = createHighlighter({
...options,
engine: createJavaScriptRegexEngine({ forgiving: true }),
});
}

return highlighterPromise;
},
})
.use(rehypeStringify)
.process(rawContent);

return { source: content.toString(), metadata, slug };
})();

postCache.set(slug, postPromise);
return postPromise;
}

export function getBlogPosts() {
const slugs = Object.keys(rawPosts).map((filePath) =>
filePath.split("/").pop()!.replace(/\.mdx?$/, ""),
);

return Promise.all(
slugs.map(async (slug) => {
const { metadata, source } = await getPost(slug);
return { metadata, slug, source };
}),
);
return slugs.map((slug) => ({ metadata: getPostMetadata(slug), slug }));
}
2 changes: 1 addition & 1 deletion src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
}

:root {
font-family: Inter, sans-serif;
font-family: var(--font-sans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-feature-settings: "liga" 1, "calt" 1;
}

Expand Down
17 changes: 8 additions & 9 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@

import { defineConfig } from "vite";
import vinext from "vinext";
import rsc from "@vitejs/plugin-rsc";
import { cloudflare } from "@cloudflare/vite-plugin";

export default defineConfig({
build: {
sourcemap: true,
// Enable sourcemaps for better error reporting
},
plugins: [
vinext(),
cloudflare({
build: {
sourcemap: false,
},
plugins: [
vinext(),
rsc(),
cloudflare({
viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] },
}),
],
})
});