Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/lib/markdown/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

Hey, there! Thanks for checking out my site. I'm a full-stack software engineer, currently employed at Capital One and living in Richmond, Virgina with my wife, dog, and two cats.

My technical interests include systems programming and embedded systems (although, I don't get to work on that sort of technology much in a professional capacity these days). For work, I work with TypeScript and Node.js quite a lot, and also work on some vanilla JS web frontend stuff. I don't do a whole lot of frontend application development or design work, but I'll do it if you twist my arm - I always try to find tooling that makes it as easy as possible though. For example, this site is mostly written in markdown, and almost all of the styles come from [Tailwind](https://tailwindcss.com/). More about how this site is built over on my [blog](./blog).
My technical interests include systems programming and embedded systems (although, I don't get to work on that sort of technology much in a professional capacity these days). For work, I work with TypeScript and Node.js quite a lot, and also work on some vanilla JS web frontend stuff. I don't do a whole lot of frontend application development or design work, but I'll do it if you twist my arm - I always try to find tooling that makes it as easy as possible though. For example, this site is mostly written in markdown, and almost all of the styles come from [Tailwind](https://tailwindcss.com/). More about how this site is built over on my [blog](/blog/).

When I'm not writing code, I can usually be found sipping on a cup of coffee or pursuing one of my many hobbies. I enjoy camping, backpacking, fishing, and just generally anything that gets me outdoors - bonus points if I can bring my pup with me! I'm also an avid woodworker, motorcyclist, home mechanic, and general tinkerer. I always have a project going on (or more realisitically, several), so you'll never find me without something to work on - lately I've been juggling a few home renovation projects. You can check out my [blog pages](./blog) to find our more about my projects and hobbies.
When I'm not writing code, I can usually be found sipping on a cup of coffee or pursuing one of my many hobbies. I enjoy camping, backpacking, fishing, and just generally anything that gets me outdoors - bonus points if I can bring my pup with me! I'm also an avid woodworker, motorcyclist, home mechanic, and general tinkerer. I always have a project going on (or more realisitically, several), so you'll never find me without something to work on - lately I've been juggling a few home renovation projects. You can check out my [blog pages](/blog/) to find our more about my projects and hobbies.
4 changes: 1 addition & 3 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { render } from "svelte/server";

export type TagMetadata = { count: number; selected: boolean };

export type PostMetadata = {
Expand All @@ -14,7 +12,7 @@ export type PostPreview = {
};

export type Post = PostPreview & {
content: ReturnType<typeof render>;
content: string;
};

export type GetPostRequest = {
Expand Down
1 change: 1 addition & 0 deletions src/routes/+layout.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const prerender = true;
export const trailingSlash = "always";
2 changes: 1 addition & 1 deletion src/routes/api/post/[postId]/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ export const GET: RequestHandler<GetPostRequest["params"]> = async ({
return json(<Post>{
id: params.postId,
metadata: post.metadata,
content: render(post.default),
content: render(post.default).body,
});
};
7 changes: 3 additions & 4 deletions src/routes/blog/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { setContext } from "svelte";
import { setContext, untrack } from "svelte";
import { readable, type Subscriber } from "svelte/store";
import { page } from "$app/stores";

Expand All @@ -13,8 +13,7 @@

let { data, children }: Props = $props();

let posts = $derived(data.posts);

const posts = untrack(() => data.posts);
const tags: { [tagName: string]: TagMetadata } = $state({});
posts.forEach((p) => {
p.metadata.tags.forEach((tag) => {
Expand Down Expand Up @@ -47,7 +46,7 @@
}
}

let isRoot = $derived($page.url.pathname === "/blog");
let isRoot = $derived($page.url.pathname === "/blog/");
</script>

<div class="flex flex-row divide-x divide-slate-500">
Expand Down
12 changes: 11 additions & 1 deletion src/routes/blog/[postId]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
<script lang="ts">
import type { Component } from "svelte";
import type { PageData } from "./$types";

interface Props {
data: PageData;
}

const postComponents = import.meta.glob<{
default: Component;
}>("/src/lib/markdown/blog/*.md", { eager: true });

let { data }: Props = $props();
let post = $derived(data.post);
let PostContent = $derived(
postComponents[`/src/lib/markdown/blog/${post.id}.md`]?.default,
);
</script>

<h1>{post.metadata.title}</h1>
{@html post.content.html}
{#if PostContent}
<PostContent />
{/if}
30 changes: 25 additions & 5 deletions tests/blog-entry.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import { test, expect } from "@playwright/test";

test.beforeEach(async ({ page }) => {
await page.goto("/blog/2-another-post");
test("blog entries render correctly", async ({ page }) => {
await page.goto("/blog/2-another-post/");

await expect(
page.getByRole("heading", { name: "Just Messin' Around" }),
).toBeVisible();
await expect(
page.getByText("This is really just here for testing purposes"),
).toBeVisible();
});

test("blog entries render correctly", async ({ page }) => {
expect(await page.getByRole("heading").textContent()).toMatch(/.+/);
expect(await page.getByRole("paragraph").textContent()).toMatch(/.+/);
test("blog entries include prerendered content without JavaScript", async ({
browser,
baseURL,
}) => {
const context = await browser.newContext({ javaScriptEnabled: false });
const page = await context.newPage();

await page.goto(`${baseURL}/blog/2-another-post/`);
await expect(
page.getByRole("heading", { name: "Just Messin' Around" }),
).toBeVisible();
await expect(
page.getByText("This is really just here for testing purposes"),
).toBeVisible();

await context.close();
});
3 changes: 2 additions & 1 deletion tests/blog.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from "@playwright/test";

test.beforeEach(async ({ page }) => {
await page.goto("/blog");
await page.goto("/blog/");
});

test("the Posts heading exists and has the appropriate aria role and level", async ({
Expand Down Expand Up @@ -43,6 +43,7 @@ test("filtering by tags works", async ({ page }) => {
const tag = page.getByRole("button").filter({ hasText: "something else" });
const before = await page.getByRole("heading").count();

await expect(tag).toBeVisible();
await tag.click();

const after = await page.getByRole("heading").count();
Expand Down
10 changes: 5 additions & 5 deletions tests/layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ test.describe("header navigation", () => {
await Promise.all([
page.getByRole("link").filter({ hasText: "About 📕" }).click(),
]);
await expect(page).toHaveURL(new RegExp(`^${baseURL}/about$`));
await expect(page).toHaveURL(new RegExp(`^${baseURL}/about/$`));
});

test("blog link", async ({ page, baseURL }) => {
await Promise.all([
page.getByRole("link").filter({ hasText: "Blog ✍️" }).click(),
page.waitForURL(`${baseURL}/blog`),
page.waitForURL(`${baseURL}/blog/`),
]);
await expect(page).toHaveURL(new RegExp(`^${baseURL}/blog$`));
await expect(page).toHaveURL(new RegExp(`^${baseURL}/blog/$`));
});

test("home link", async ({ page, baseURL }) => {
Expand All @@ -76,9 +76,9 @@ test.describe("header navigation", () => {
});

test("blog navigation works", async ({ page, baseURL }) => {
await page.goto("/blog");
await page.goto("/blog/");
await Promise.all([page.getByRole("heading", { level: 2 }).first().click()]);
await expect(page).toHaveURL(new RegExp(`^${baseURL}/blog/.+$`));
await expect(page).toHaveURL(new RegExp(`^${baseURL}/blog/.+/$`));
});

test("github navigation works", async ({ page }) => {
Expand Down
8 changes: 1 addition & 7 deletions vercel.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
{
"trailingSlash": false,
"rewrites": [
{ "source": "/blog", "destination": "/blog.html" },
{ "source": "/blog/:id", "destination": "/blog/:id.html" },
{ "source": "/about", "destination": "/about.html" },
{ "source": "/(.*)", "destination": "/index.html" }
]
"trailingSlash": true
}
Loading