mirror of https://github.com/Squidex/squidex.git
18 changed files with 784 additions and 80 deletions
@ -0,0 +1,87 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { generateSlug } from './slug'; |
|||
|
|||
describe('generateSlug', () => { |
|||
describe('Should replace special characters with separator', () => { |
|||
const cases: Array<[string, string, string]> = [ |
|||
['Hello World', '-', 'hello-world'], |
|||
['Hello/World', '-', 'hello-world'], |
|||
['Hello World', '_', 'hello_world'], |
|||
['Hello/World', '_', 'hello_world'], |
|||
['Hello World ', '_', 'hello_world'], |
|||
['Hello World-', '_', 'hello_world'], |
|||
['Hello/World_', '_', 'hello_world'], |
|||
]; |
|||
|
|||
cases.forEach(([input, separator, expected]) => { |
|||
it(`slugifies "${input}" with separator "${separator}" to "${expected}"`, () => { |
|||
expect(generateSlug(input, { separator })).toEqual(expected); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe('Should replace multi-char diacritics', () => { |
|||
const cases: Array<[string, string]> = [ |
|||
['ö', 'oe'], |
|||
['ü', 'ue'], |
|||
['ä', 'ae'], |
|||
]; |
|||
|
|||
cases.forEach(([input, expected]) => { |
|||
it(`slugifies "${input}" to "${expected}"`, () => { |
|||
expect(generateSlug(input)).toEqual(expected); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe('Should not replace multi-char diacritics', () => { |
|||
const cases: Array<[string, string]> = [ |
|||
['ö', 'o'], |
|||
['ü', 'u'], |
|||
['ä', 'a'], |
|||
]; |
|||
|
|||
cases.forEach(([input, expected]) => { |
|||
it(`slugifies "${input}" to "${expected}" with singleCharDiacritic=true`, () => { |
|||
expect(generateSlug(input, { singleCharDiacritic: true })).toEqual(expected); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe('Should replace single-char diacritics', () => { |
|||
const cases: Array<[string, string]> = [ |
|||
['Físh', 'fish'], |
|||
['źish', 'zish'], |
|||
['żish', 'zish'], |
|||
['fórm', 'form'], |
|||
['fòrm', 'form'], |
|||
['fårt', 'fart'], |
|||
]; |
|||
|
|||
cases.forEach(([input, expected]) => { |
|||
it(`slugifies "${input}" to "${expected}"`, () => { |
|||
expect(generateSlug(input)).toEqual(expected); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe('Should keep characters', () => { |
|||
const cases: Array<[string, string, string]> = [ |
|||
['Hello my&World ', '_', 'hello_my&world'], |
|||
['Hello my&World-', '_', 'hello_my&world'], |
|||
['Hello my/World_', '_', 'hello_my/world'], |
|||
]; |
|||
|
|||
cases.forEach(([input, separator, expected]) => { |
|||
it(`slugifies "${input}" with separator "${separator}" keeping chars`, () => { |
|||
expect(generateSlug(input, { allowed: ['&', '/'], separator })).toEqual(expected); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,540 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import slugify from '@sindresorhus/slugify'; |
|||
|
|||
const DIACRITICS: Record<string, string> = { |
|||
'$': 'dollar', |
|||
'%': 'percent', |
|||
'&': 'and', |
|||
'<': 'less', |
|||
'>': 'greater', |
|||
'|': 'or', |
|||
'¢': 'cent', |
|||
'£': 'pound', |
|||
'¤': 'currency', |
|||
'¥': 'yen', |
|||
'©': '(c)', |
|||
'ª': 'a', |
|||
'®': '(r)', |
|||
'º': 'o', |
|||
'À': 'A', |
|||
'Á': 'A', |
|||
'Â': 'A', |
|||
'Ã': 'A', |
|||
'Ä': 'AE', |
|||
'Å': 'A', |
|||
'Æ': 'AE', |
|||
'Ç': 'C', |
|||
'Ə': 'E', |
|||
'È': 'E', |
|||
'É': 'E', |
|||
'Ê': 'E', |
|||
'Ë': 'E', |
|||
'Ì': 'I', |
|||
'Í': 'I', |
|||
'Î': 'I', |
|||
'Ï': 'I', |
|||
'Ð': 'D', |
|||
'Ñ': 'N', |
|||
'Ò': 'O', |
|||
'Ó': 'O', |
|||
'Ô': 'O', |
|||
'Õ': 'O', |
|||
'Ö': 'OE', |
|||
'Ø': 'O', |
|||
'Ù': 'U', |
|||
'Ú': 'U', |
|||
'Û': 'U', |
|||
'Ü': 'UE', |
|||
'Ý': 'Y', |
|||
'Þ': 'TH', |
|||
'ß': 'ss', |
|||
'à': 'a', |
|||
'á': 'a', |
|||
'â': 'a', |
|||
'ã': 'a', |
|||
'ä': 'ae', |
|||
'å': 'a', |
|||
'æ': 'ae', |
|||
'ç': 'c', |
|||
'ə': 'e', |
|||
'è': 'e', |
|||
'é': 'e', |
|||
'ê': 'e', |
|||
'ë': 'e', |
|||
'ì': 'i', |
|||
'í': 'i', |
|||
'î': 'i', |
|||
'ï': 'i', |
|||
'ð': 'd', |
|||
'ñ': 'n', |
|||
'ò': 'o', |
|||
'ó': 'o', |
|||
'ô': 'o', |
|||
'õ': 'o', |
|||
'ö': 'oe', |
|||
'ø': 'o', |
|||
'ù': 'u', |
|||
'ú': 'u', |
|||
'û': 'u', |
|||
'ü': 'ue', |
|||
'ý': 'y', |
|||
'þ': 'th', |
|||
'ÿ': 'y', |
|||
'Ā': 'A', |
|||
'ā': 'a', |
|||
'Ă': 'A', |
|||
'ă': 'a', |
|||
'Ą': 'A', |
|||
'ą': 'a', |
|||
'Ć': 'C', |
|||
'ć': 'c', |
|||
'Č': 'C', |
|||
'č': 'c', |
|||
'Ď': 'D', |
|||
'ď': 'd', |
|||
'Đ': 'DJ', |
|||
'đ': 'dj', |
|||
'Ē': 'E', |
|||
'ē': 'e', |
|||
'Ė': 'E', |
|||
'ė': 'e', |
|||
'Ę': 'e', |
|||
'ę': 'e', |
|||
'Ě': 'E', |
|||
'ě': 'e', |
|||
'Ğ': 'G', |
|||
'ğ': 'g', |
|||
'Ģ': 'G', |
|||
'ģ': 'g', |
|||
'Ĩ': 'I', |
|||
'ĩ': 'i', |
|||
'Ī': 'i', |
|||
'ī': 'i', |
|||
'Į': 'I', |
|||
'į': 'i', |
|||
'İ': 'I', |
|||
'ı': 'i', |
|||
'Ķ': 'k', |
|||
'ķ': 'k', |
|||
'Ļ': 'L', |
|||
'ļ': 'l', |
|||
'Ľ': 'L', |
|||
'ľ': 'l', |
|||
'Ł': 'L', |
|||
'ł': 'l', |
|||
'Ń': 'N', |
|||
'ń': 'n', |
|||
'Ņ': 'N', |
|||
'ņ': 'n', |
|||
'Ň': 'N', |
|||
'ň': 'n', |
|||
'Ő': 'O', |
|||
'ő': 'o', |
|||
'Œ': 'OE', |
|||
'œ': 'oe', |
|||
'Ŕ': 'R', |
|||
'ŕ': 'r', |
|||
'Ř': 'R', |
|||
'ř': 'r', |
|||
'Ś': 'S', |
|||
'ś': 's', |
|||
'Ş': 'S', |
|||
'ş': 's', |
|||
'Š': 'S', |
|||
'š': 's', |
|||
'Ţ': 'T', |
|||
'ţ': 't', |
|||
'Ť': 'T', |
|||
'ť': 't', |
|||
'Ũ': 'U', |
|||
'ũ': 'u', |
|||
'Ū': 'u', |
|||
'ū': 'u', |
|||
'Ů': 'U', |
|||
'ů': 'u', |
|||
'Ű': 'U', |
|||
'ű': 'u', |
|||
'Ų': 'U', |
|||
'ų': 'u', |
|||
'Ź': 'Z', |
|||
'ź': 'z', |
|||
'Ż': 'Z', |
|||
'ż': 'z', |
|||
'Ž': 'Z', |
|||
'ž': 'z', |
|||
'ƒ': 'f', |
|||
'Ơ': 'O', |
|||
'ơ': 'o', |
|||
'Ư': 'U', |
|||
'ư': 'u', |
|||
'Lj': 'LJ', |
|||
'lj': 'lj', |
|||
'Nj': 'NJ', |
|||
'nj': 'nj', |
|||
'Ș': 'S', |
|||
'ș': 's', |
|||
'Ț': 'T', |
|||
'ț': 't', |
|||
'˚': 'o', |
|||
'Ά': 'A', |
|||
'Έ': 'E', |
|||
'Ή': 'H', |
|||
'Ί': 'I', |
|||
'Ό': 'O', |
|||
'Ύ': 'Y', |
|||
'Ώ': 'W', |
|||
'ΐ': 'i', |
|||
'Α': 'A', |
|||
'Β': 'B', |
|||
'Γ': 'G', |
|||
'Δ': 'D', |
|||
'Ε': 'E', |
|||
'Ζ': 'Z', |
|||
'Η': 'H', |
|||
'Θ': '8', |
|||
'Ι': 'I', |
|||
'Κ': 'K', |
|||
'Λ': 'L', |
|||
'Μ': 'M', |
|||
'Ν': 'N', |
|||
'Ξ': '3', |
|||
'Ο': 'O', |
|||
'Π': 'P', |
|||
'Ρ': 'R', |
|||
'Σ': 'S', |
|||
'Τ': 'T', |
|||
'Υ': 'Y', |
|||
'Φ': 'F', |
|||
'Χ': 'X', |
|||
'Ψ': 'PS', |
|||
'Ω': 'W', |
|||
'Ϊ': 'I', |
|||
'Ϋ': 'Y', |
|||
'ά': 'a', |
|||
'έ': 'e', |
|||
'ή': 'h', |
|||
'ί': 'i', |
|||
'ΰ': 'y', |
|||
'α': 'a', |
|||
'β': 'b', |
|||
'γ': 'g', |
|||
'δ': 'd', |
|||
'ε': 'e', |
|||
'ζ': 'z', |
|||
'η': 'h', |
|||
'θ': '8', |
|||
'ι': 'i', |
|||
'κ': 'k', |
|||
'λ': 'l', |
|||
'μ': 'm', |
|||
'ν': 'n', |
|||
'ξ': '3', |
|||
'ο': 'o', |
|||
'π': 'p', |
|||
'ρ': 'r', |
|||
'ς': 's', |
|||
'σ': 's', |
|||
'τ': 't', |
|||
'υ': 'y', |
|||
'φ': 'f', |
|||
'χ': 'x', |
|||
'ψ': 'ps', |
|||
'ω': 'w', |
|||
'ϊ': 'i', |
|||
'ϋ': 'y', |
|||
'ό': 'o', |
|||
'ύ': 'y', |
|||
'ώ': 'w', |
|||
'Ё': 'Yo', |
|||
'Ђ': 'DJ', |
|||
'Є': 'Ye', |
|||
'І': 'I', |
|||
'Ї': 'Yi', |
|||
'Ј': 'J', |
|||
'Љ': 'LJ', |
|||
'Њ': 'NJ', |
|||
'Ћ': 'C', |
|||
'Џ': 'DZ', |
|||
'А': 'A', |
|||
'Б': 'B', |
|||
'В': 'V', |
|||
'Г': 'G', |
|||
'Д': 'D', |
|||
'Е': 'E', |
|||
'Ж': 'Zh', |
|||
'З': 'Z', |
|||
'И': 'I', |
|||
'Й': 'J', |
|||
'К': 'K', |
|||
'Л': 'L', |
|||
'М': 'M', |
|||
'Н': 'N', |
|||
'О': 'O', |
|||
'П': 'P', |
|||
'Р': 'R', |
|||
'С': 'S', |
|||
'Т': 'T', |
|||
'У': 'U', |
|||
'Ф': 'F', |
|||
'Х': 'H', |
|||
'Ц': 'C', |
|||
'Ч': 'Ch', |
|||
'Ш': 'Sh', |
|||
'Щ': 'Sh', |
|||
'Ъ': 'U', |
|||
'Ы': 'Y', |
|||
'Ь': 'b', |
|||
'Э': 'E', |
|||
'Ю': 'Yu', |
|||
'Я': 'Ya', |
|||
'а': 'a', |
|||
'б': 'b', |
|||
'в': 'v', |
|||
'г': 'g', |
|||
'д': 'd', |
|||
'е': 'e', |
|||
'ж': 'zh', |
|||
'з': 'z', |
|||
'и': 'i', |
|||
'й': 'j', |
|||
'к': 'k', |
|||
'л': 'l', |
|||
'м': 'm', |
|||
'н': 'n', |
|||
'о': 'o', |
|||
'п': 'p', |
|||
'р': 'r', |
|||
'с': 's', |
|||
'т': 't', |
|||
'у': 'u', |
|||
'ф': 'f', |
|||
'х': 'h', |
|||
'ц': 'c', |
|||
'ч': 'ch', |
|||
'ш': 'sh', |
|||
'щ': 'sh', |
|||
'ъ': 'u', |
|||
'ы': 'y', |
|||
'ь': 's', |
|||
'э': 'e', |
|||
'ю': 'yu', |
|||
'я': 'ya', |
|||
'ё': 'yo', |
|||
'ђ': 'dj', |
|||
'є': 'ye', |
|||
'і': 'i', |
|||
'ї': 'yi', |
|||
'ј': 'j', |
|||
'љ': 'lj', |
|||
'њ': 'nj', |
|||
'ћ': 'c', |
|||
'џ': 'dz', |
|||
'Ґ': 'G', |
|||
'ґ': 'g', |
|||
'฿': 'baht', |
|||
'ა': 'a', |
|||
'ბ': 'b', |
|||
'გ': 'g', |
|||
'დ': 'd', |
|||
'ე': 'e', |
|||
'ვ': 'v', |
|||
'ზ': 'z', |
|||
'თ': 't', |
|||
'ი': 'i', |
|||
'კ': 'k', |
|||
'ლ': 'l', |
|||
'მ': 'm', |
|||
'ნ': 'n', |
|||
'ო': 'o', |
|||
'პ': 'p', |
|||
'ჟ': 'zh', |
|||
'რ': 'r', |
|||
'ს': 's', |
|||
'ტ': 't', |
|||
'უ': 'u', |
|||
'ფ': 'f', |
|||
'ქ': 'k', |
|||
'ღ': 'gh', |
|||
'ყ': 'q', |
|||
'შ': 'sh', |
|||
'ჩ': 'ch', |
|||
'ც': 'ts', |
|||
'ძ': 'dz', |
|||
'წ': 'ts', |
|||
'ჭ': 'ch', |
|||
'ხ': 'kh', |
|||
'ჯ': 'j', |
|||
'ჰ': 'h', |
|||
'ẞ': 'SS', |
|||
'Ạ': 'A', |
|||
'ạ': 'a', |
|||
'Ả': 'A', |
|||
'ả': 'a', |
|||
'Ấ': 'A', |
|||
'ấ': 'a', |
|||
'Ầ': 'A', |
|||
'ầ': 'a', |
|||
'Ẩ': 'A', |
|||
'ẩ': 'a', |
|||
'Ẫ': 'A', |
|||
'ẫ': 'a', |
|||
'Ậ': 'A', |
|||
'ậ': 'a', |
|||
'Ắ': 'A', |
|||
'ắ': 'a', |
|||
'Ằ': 'A', |
|||
'ằ': 'a', |
|||
'Ẳ': 'A', |
|||
'ẳ': 'a', |
|||
'Ẵ': 'A', |
|||
'ẵ': 'a', |
|||
'Ặ': 'A', |
|||
'ặ': 'a', |
|||
'Ẹ': 'E', |
|||
'ẹ': 'e', |
|||
'Ẻ': 'E', |
|||
'ẻ': 'e', |
|||
'Ẽ': 'E', |
|||
'ẽ': 'e', |
|||
'Ế': 'E', |
|||
'ế': 'e', |
|||
'Ề': 'E', |
|||
'ề': 'e', |
|||
'Ể': 'E', |
|||
'ể': 'e', |
|||
'Ễ': 'E', |
|||
'ễ': 'e', |
|||
'Ệ': 'E', |
|||
'ệ': 'e', |
|||
'Ỉ': 'I', |
|||
'ỉ': 'i', |
|||
'Ị': 'I', |
|||
'ị': 'i', |
|||
'Ọ': 'O', |
|||
'ọ': 'o', |
|||
'Ỏ': 'O', |
|||
'ỏ': 'o', |
|||
'Ố': 'O', |
|||
'ố': 'o', |
|||
'Ồ': 'O', |
|||
'ồ': 'o', |
|||
'Ổ': 'O', |
|||
'ổ': 'o', |
|||
'Ỗ': 'O', |
|||
'ỗ': 'o', |
|||
'Ộ': 'O', |
|||
'ộ': 'o', |
|||
'Ớ': 'O', |
|||
'ớ': 'o', |
|||
'Ờ': 'O', |
|||
'ờ': 'o', |
|||
'Ở': 'O', |
|||
'ở': 'o', |
|||
'Ỡ': 'O', |
|||
'ỡ': 'o', |
|||
'Ợ': 'O', |
|||
'ợ': 'o', |
|||
'Ụ': 'U', |
|||
'ụ': 'u', |
|||
'Ủ': 'U', |
|||
'ủ': 'u', |
|||
'Ứ': 'U', |
|||
'ứ': 'u', |
|||
'Ừ': 'U', |
|||
'ừ': 'u', |
|||
'Ử': 'U', |
|||
'ử': 'u', |
|||
'Ữ': 'U', |
|||
'ữ': 'u', |
|||
'Ự': 'U', |
|||
'ự': 'u', |
|||
'Ỳ': 'Y', |
|||
'ỳ': 'y', |
|||
'Ỵ': 'Y', |
|||
'ỵ': 'y', |
|||
'Ỷ': 'Y', |
|||
'ỷ': 'y', |
|||
'Ỹ': 'Y', |
|||
'ỹ': 'y', |
|||
'‘': '\'', |
|||
'’': '\'', |
|||
'“': '\\\"', |
|||
'”': '\\\"', |
|||
'†': '+', |
|||
'•': '*', |
|||
'…': '...', |
|||
'₠': 'ecu', |
|||
'₢': 'cruzeiro', |
|||
'₣': 'french franc', |
|||
'₤': 'lira', |
|||
'₥': 'mill', |
|||
'₦': 'naira', |
|||
'₧': 'peseta', |
|||
'₨': 'rupee', |
|||
'₩': 'won', |
|||
'₪': 'new shequel', |
|||
'₫': 'dong', |
|||
'€': 'euro', |
|||
'₭': 'kip', |
|||
'₮': 'tugrik', |
|||
'₯': 'drachma', |
|||
'₰': 'penny', |
|||
'₱': 'peso', |
|||
'₲': 'guarani', |
|||
'₳': 'austral', |
|||
'₴': 'hryvnia', |
|||
'₵': 'cedi', |
|||
'₹': 'indian rupee', |
|||
'₽': 'russian ruble', |
|||
'₿': 'bitcoin', |
|||
'℠': 'sm', |
|||
'™': 'tm', |
|||
'∂': 'd', |
|||
'∆': 'delta', |
|||
'∑': 'sum', |
|||
'∞': 'infinity', |
|||
'♥': 'love', |
|||
'元': 'yuan', |
|||
'円': 'yen', |
|||
'﷼': 'rial', |
|||
}; |
|||
|
|||
export const LOWER_CASE_DIACRITICS: ReadonlyArray<[string, string]> = |
|||
Object.entries(DIACRITICS).map(([key, value]) => [key, value.toLowerCase()]); |
|||
|
|||
export const LOWER_CASE_SINGLE_DIACRITICS: ReadonlyArray<[string, string]> = |
|||
Object.entries(DIACRITICS).map(([key, value]) => [key, value.toLowerCase()[0]]); |
|||
|
|||
export function generateSlug(input: string, options?: { separator?: string; singleCharDiacritic?: boolean; allowed?: string[]; preserveTrailingDash?: boolean }) { |
|||
if (!input) { |
|||
return input?.trim(); |
|||
} |
|||
|
|||
const separator = options?.separator || '-'; |
|||
|
|||
let customReplacements = |
|||
options?.singleCharDiacritic ? |
|||
LOWER_CASE_SINGLE_DIACRITICS : |
|||
LOWER_CASE_DIACRITICS; |
|||
|
|||
if (options?.allowed) { |
|||
customReplacements = [...customReplacements]; |
|||
for (const key of options.allowed) { |
|||
customReplacements = customReplacements.filter(x => x[0] !== key); |
|||
} |
|||
} |
|||
|
|||
return slugify(input, { |
|||
customReplacements, |
|||
preserveCharacters: options?.allowed || [], |
|||
preserveTrailingDash: options?.preserveTrailingDash, |
|||
separator, |
|||
transliterate: false, |
|||
}); |
|||
} |
|||
Loading…
Reference in new issue