yap-blog/src/lib/slug.ts
2026-07-02 21:32:33 -04:00

37 lines
1 KiB
TypeScript

const MAX_SLUG_LENGTH = 96;
/**
* Turns arbitrary text into a URL-safe slug: lowercase ASCII words joined
* by single hyphens. Returns "" when nothing usable remains, so callers
* can decide on a fallback.
*/
export function slugify(input: string): string {
return input
.normalize("NFKD")
.replace(/[̀-ͯ]/g, "") // strip combining diacritics
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, MAX_SLUG_LENGTH)
.replace(/-+$/g, "");
}
/**
* Finds a free slug by appending -2, -3, ... to the base while `isTaken`
* reports a conflict. The base falls back to "untitled" when empty.
*/
export async function ensureUniqueSlug(
base: string,
isTaken: (slug: string) => Promise<boolean>,
): Promise<string> {
const root = base || "untitled";
let candidate = root;
for (let suffix = 2; await isTaken(candidate); suffix++) {
if (suffix > 500) {
throw new Error(`Could not find a free slug for "${root}"`);
}
candidate = `${root}-${suffix}`;
}
return candidate;
}