Barazo default frontend barazo.forum
at main 124 lines 3.4 kB view raw
1/** 2 * Formatting utilities for display values. 3 */ 4 5/** 6 * Formats an ISO date string as a relative time (e.g., "2h ago", "3d ago"). 7 */ 8export function formatRelativeTime(isoDate: string): string { 9 const date = new Date(isoDate) 10 const now = new Date() 11 const diffMs = now.getTime() - date.getTime() 12 const diffSeconds = Math.floor(diffMs / 1000) 13 14 if (diffSeconds < 60) return 'just now' 15 16 const diffMinutes = Math.floor(diffSeconds / 60) 17 if (diffMinutes < 60) return `${diffMinutes}m ago` 18 19 const diffHours = Math.floor(diffMinutes / 60) 20 if (diffHours < 24) return `${diffHours}h ago` 21 22 const diffDays = Math.floor(diffHours / 24) 23 if (diffDays < 30) return `${diffDays}d ago` 24 25 const diffMonths = Math.floor(diffDays / 30) 26 if (diffMonths < 12) return `${diffMonths}mo ago` 27 28 const diffYears = Math.floor(diffMonths / 12) 29 return `${diffYears}y ago` 30} 31 32/** 33 * Formats an ISO date string as a short date/time (e.g., "Jan 15, 3:42 PM"). 34 */ 35export function formatDate(dateStr: string): string { 36 return new Date(dateStr).toLocaleDateString('en-US', { 37 month: 'short', 38 day: 'numeric', 39 hour: 'numeric', 40 minute: '2-digit', 41 }) 42} 43 44/** 45 * Formats an ISO date string as a short date without time (e.g., "Jan 15, 2026"). 46 */ 47export function formatDateShort(dateStr: string): string { 48 return new Date(dateStr).toLocaleDateString('en-US', { 49 month: 'short', 50 day: 'numeric', 51 year: 'numeric', 52 }) 53} 54 55/** 56 * Formats an ISO date string as a long date (e.g., "January 15, 2026"). 57 */ 58export function formatDateLong(dateStr: string): string { 59 return new Date(dateStr).toLocaleDateString('en-US', { 60 year: 'numeric', 61 month: 'long', 62 day: 'numeric', 63 }) 64} 65 66/** 67 * Formats a number with locale-aware separators (e.g., 1,234). 68 */ 69export function formatNumber(n: number): string { 70 return n.toLocaleString('en-US') 71} 72 73/** 74 * Formats a number with compact notation (e.g., 1.2k, 3.4M). 75 */ 76export function formatCompactNumber(n: number): string { 77 if (n < 1000) return String(n) 78 if (n < 1_000_000) return `${(n / 1000).toFixed(1).replace(/\.0$/, '')}k` 79 return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M` 80} 81 82/** 83 * Converts a string to a URL-safe slug. 84 */ 85export function slugify(text: string): string { 86 const slug = text 87 .toLowerCase() 88 .replace(/[^a-z0-9\s-]/g, '') 89 .replace(/[\s-]+/g, '-') 90 .replace(/^-+|-+$/g, '') 91 .slice(0, 80) 92 .replace(/-+$/, '') 93 94 return slug || 'untitled' 95} 96 97/** 98 * Generates a topic URL from a topic's author handle and rkey. 99 */ 100export function getTopicUrl(topic: { authorHandle: string; rkey: string }): string { 101 return `/${topic.authorHandle}/${topic.rkey}` 102} 103 104/** 105 * Generates a reply permalink URL. 106 */ 107export function getReplyUrl(params: { 108 topicAuthorHandle: string 109 topicRkey: string 110 replyAuthorHandle: string 111 replyRkey: string 112}): string { 113 return `/${params.topicAuthorHandle}/${params.topicRkey}/${params.replyAuthorHandle}/${params.replyRkey}` 114} 115 116/** 117 * Returns true if a post was edited (indexedAt differs from createdAt by more than 30 seconds). 118 */ 119export function isEdited(createdAt: string, indexedAt: string): boolean { 120 const created = new Date(createdAt).getTime() 121 const indexed = new Date(indexedAt).getTime() 122 if (Number.isNaN(created) || Number.isNaN(indexed)) return false 123 return indexed - created > 30_000 124}