From 0816e1d5db87067661544afb16f295f2f5c2689f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 01:50:58 +0000 Subject: [PATCH] feat: slug and date derivation Co-Authored-By: Claude --- src/slug.test.ts | 32 ++++++++++++++++++++++++++++++++ src/slug.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/slug.test.ts create mode 100644 src/slug.ts diff --git a/src/slug.test.ts b/src/slug.test.ts new file mode 100644 index 0000000..06717f4 --- /dev/null +++ b/src/slug.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, test } from "vitest"; +import { slugify, deriveSlug, deriveDate, postFilename } from "./slug"; + +describe("slugify", () => { + test("lowercases, drops apostrophes, hyphenates", () => { + expect(slugify("On Making a Game")).toBe("on-making-a-game"); + expect(slugify("It's a Test!")).toBe("its-a-test"); + expect(slugify(" Spaced out ")).toBe("spaced-out"); + }); +}); + +describe("deriveSlug", () => { + test("prefers title, falls back to filename", () => { + expect(deriveSlug({ title: "Hello World", filename: "note" })).toBe("hello-world"); + expect(deriveSlug({ title: " ", filename: "My Note" })).toBe("my-note"); + }); +}); + +describe("deriveDate", () => { + const now = new Date("2026-06-19T12:00:00Z"); + test("uses frontmatter Date or string, else now", () => { + expect(deriveDate({ frontmatterDate: new Date("2026-02-06T00:00:00Z"), now })).toBe("2026-02-06"); + expect(deriveDate({ frontmatterDate: "2026-02-18 09:00", now })).toBe("2026-02-18"); + expect(deriveDate({ now })).toBe("2026-06-19"); + }); +}); + +describe("postFilename", () => { + test("joins date and slug", () => { + expect(postFilename({ date: "2026-06-19", slug: "hello" })).toBe("2026-06-19-hello.md"); + }); +}); diff --git a/src/slug.ts b/src/slug.ts new file mode 100644 index 0000000..670813a --- /dev/null +++ b/src/slug.ts @@ -0,0 +1,29 @@ +export function slugify(s: string): string { + return s + .toLowerCase() + .trim() + .replace(/['']/g, "") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +export function deriveSlug(o: { title?: string; filename: string }): string { + const base = o.title && o.title.trim() ? o.title : o.filename; + return slugify(base); +} + +export function deriveDate(o: { frontmatterDate?: unknown; now: Date }): string { + const d = o.frontmatterDate; + if (d instanceof Date && !isNaN(d.getTime())) return fmt(d); + if (typeof d === "string" && /^\d{4}-\d{2}-\d{2}/.test(d)) return d.slice(0, 10); + return fmt(o.now); +} + +function fmt(d: Date): string { + const p = (n: number) => String(n).padStart(2, "0"); + return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}`; +} + +export function postFilename(o: { date: string; slug: string }): string { + return `${o.date}-${o.slug}.md`; +}