Barazo default frontend
barazo.forum
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}