slugify.js 1.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$|^[a-z0-9]{3}$/
  2. function simpleHash(str) {
  3. let h = 0
  4. for (let i = 0; i < str.length; i++) {
  5. h = ((h << 5) - h) + str.charCodeAt(i)
  6. h |= 0
  7. }
  8. return Math.abs(h).toString(36)
  9. }
  10. function slugify(title) {
  11. let base = String(title || '').trim().toLowerCase()
  12. .replace(/\s+/g, '-')
  13. .replace(/[^a-z0-9-]/g, '')
  14. .replace(/-+/g, '-')
  15. .replace(/^-|-$/g, '')
  16. if (base.length < 3) {
  17. base = 'article-' + simpleHash(String(title || Date.now()))
  18. }
  19. return base.slice(0, 58)
  20. }
  21. function isValidSlug(slug) {
  22. return SLUG_PATTERN.test(String(slug || ''))
  23. }
  24. async function ensureUniqueSlug(db, slug, excludeId = null) {
  25. let candidate = slug
  26. let n = 2
  27. while (true) {
  28. const sql = excludeId
  29. ? 'SELECT id FROM article WHERE slug = ? AND id != ? LIMIT 1'
  30. : 'SELECT id FROM article WHERE slug = ? LIMIT 1'
  31. const params = excludeId ? [candidate, excludeId] : [candidate]
  32. const rows = await db.query(sql, params)
  33. if (!rows || rows.length === 0) return candidate
  34. const suffix = `-${n}`
  35. candidate = `${slug.slice(0, 64 - suffix.length)}${suffix}`
  36. n++
  37. }
  38. }
  39. module.exports = { slugify, isValidSlug, ensureUniqueSlug, SLUG_PATTERN }