54 lines
No EOL
1.7 KiB
JavaScript
54 lines
No EOL
1.7 KiB
JavaScript
/**
|
||
* 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(' ');
|
||
} |