go-doc/frontend/src/lib/slug.js

54 lines
1.7 KiB
JavaScript
Raw Normal View History

2026-10-07 15:03:50 +02:00
/**
* Client-side mirror of storage.Slugify in the Go engine, used to preview the
* filename a new title will produce before the request is sent. The backend
* remains authoritative and re-normalises every slug it receives.
*/
const LATIN_FOLD = {
á: 'a', à: 'a', â: 'a', ä: 'a', ã: 'a', å: 'a', ā: 'a', ă: 'a', ą: 'a',
ç: 'c', ć: 'c', č: 'c', ĉ: 'c', ċ: 'c',
ď: 'd', đ: 'd',
é: 'e', è: 'e', ê: 'e', ë: 'e', ē: 'e', ė: 'e', ę: 'e', ě: 'e',
ğ: 'g', ĝ: 'g', ġ: 'g', ģ: 'g',
í: 'i', ì: 'i', î: 'i', ï: 'i', ī: 'i', į: 'i', ı: 'i',
ĵ: 'j', ķ: 'k', ł: 'l', ľ: 'l', ĺ: 'l',
ñ: 'n', ń: 'n', ň: 'n',
ó: 'o', ò: 'o', ô: 'o', ö: 'o', õ: 'o', ø: 'o', ō: 'o', ő: 'o',
ŕ: 'r', ř: 'r',
ś: 's', š: 's', ş: 's', ș: 's', ß: 's', þ: 't', ť: 't',
ú: 'u', ù: 'u', û: 'u', ü: 'u', ū: 'u', ů: 'u', ű: 'u',
ŵ: 'w', ý: 'y', ÿ: 'y', ŷ: 'y',
ź: 'z', ż: 'z', ž: 'z',
};
const MAX_SLUG_LEN = 120;
export function slugify(title) {
let out = '';
let pendingSeparator = true;
for (const raw of (title ?? '').toLowerCase()) {
const char = LATIN_FOLD[raw] ?? raw;
if (/[a-z0-9]/.test(char)) {
out += char;
pendingSeparator = false;
} else if (!pendingSeparator) {
out += '-';
pendingSeparator = true;
}
}
let slug = out.replace(/^-+|-+$/g, '');
if ([...slug].length > MAX_SLUG_LEN) {
slug = [...slug].slice(0, MAX_SLUG_LEN).join('').replace(/-+$/, '');
}
return slug;
}
/** Render a slug back into a human title: core-service-api -> Core Service Api. */
export function titleize(slug) {
return (slug ?? '')
.split(/[-_]/)
.filter(Boolean)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
}