slack status without the slack status.zzstoatzz.io
hatk statusphere
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at cfd09b95e8a6ce6bdd9e0728648eeeea8a95ebc8 249 lines 8.6 kB view raw
1// Beautiful timestamp formatting with hover tooltips 2// Provides minute-resolution display by default with full timestamp on hover 3 4const TimestampFormatter = { 5 // Format a timestamp with appropriate granularity 6 formatRelative(date, now = new Date()) { 7 const diffMs = now - date; 8 const diffSecs = Math.floor(diffMs / 1000); 9 const diffMins = Math.floor(diffMs / 60000); 10 const diffHours = Math.floor(diffMs / 3600000); 11 const diffDays = Math.floor(diffMs / 86400000); 12 13 // For very recent times, show "just now" 14 if (diffSecs < 30) { 15 return 'just now'; 16 } 17 18 // Under 1 hour: show minutes 19 if (diffMins < 60) { 20 return `${diffMins}m ago`; 21 } 22 23 // Under 24 hours: show hours and minutes 24 if (diffHours < 24) { 25 const remainingMins = diffMins % 60; 26 if (remainingMins === 0) { 27 return `${diffHours}h ago`; 28 } 29 return `${diffHours}h ${remainingMins}m ago`; 30 } 31 32 // Under 7 days: show days and hours 33 if (diffDays < 7) { 34 const remainingHours = diffHours % 24; 35 if (remainingHours === 0) { 36 return `${diffDays}d ago`; 37 } 38 return `${diffDays}d ${remainingHours}h ago`; 39 } 40 41 // Over a week: show date with time 42 const timeStr = date.toLocaleTimeString('en-US', { 43 hour: 'numeric', 44 minute: '2-digit', 45 hour12: true 46 }).toLowerCase(); 47 48 // If same year, don't show year 49 if (date.getFullYear() === now.getFullYear()) { 50 return date.toLocaleDateString('en-US', { 51 month: 'short', 52 day: 'numeric' 53 }) + ', ' + timeStr; 54 } 55 56 // Different year: show full date 57 return date.toLocaleDateString('en-US', { 58 month: 'short', 59 day: 'numeric', 60 year: 'numeric' 61 }) + ', ' + timeStr; 62 }, 63 64 // Format future timestamps (for expiry times) 65 formatFuture(date, now = new Date()) { 66 const diffMs = date - now; 67 const diffSecs = Math.floor(diffMs / 1000); 68 const diffMins = Math.floor(diffMs / 60000); 69 const diffHours = Math.floor(diffMs / 3600000); 70 const diffDays = Math.floor(diffMs / 86400000); 71 72 if (diffSecs < 60) { 73 return 'expires soon'; 74 } 75 76 if (diffMins < 60) { 77 return `expires in ${diffMins}m`; 78 } 79 80 if (diffHours < 24) { 81 const remainingMins = diffMins % 60; 82 if (remainingMins === 0) { 83 return `expires in ${diffHours}h`; 84 } 85 return `expires in ${diffHours}h ${remainingMins}m`; 86 } 87 88 if (diffDays < 7) { 89 const remainingHours = diffHours % 24; 90 if (remainingHours === 0) { 91 return `expires in ${diffDays}d`; 92 } 93 return `expires in ${diffDays}d ${remainingHours}h`; 94 } 95 96 // Over a week: show date 97 return 'expires ' + date.toLocaleDateString('en-US', { 98 month: 'short', 99 day: 'numeric', 100 hour: 'numeric', 101 minute: '2-digit', 102 hour12: true 103 }).toLowerCase(); 104 }, 105 106 // Format for history view (compact but informative) 107 formatCompact(date, now = new Date()) { 108 const diffMs = now - date; 109 const diffMins = Math.floor(diffMs / 60000); 110 const diffHours = Math.floor(diffMs / 3600000); 111 const diffDays = Math.floor(diffMs / 86400000); 112 113 // Today: show time only 114 if (date.toDateString() === now.toDateString()) { 115 return date.toLocaleTimeString('en-US', { 116 hour: 'numeric', 117 minute: '2-digit', 118 hour12: true 119 }).toLowerCase(); 120 } 121 122 // Yesterday: show "yesterday" + time 123 const yesterday = new Date(now); 124 yesterday.setDate(yesterday.getDate() - 1); 125 if (date.toDateString() === yesterday.toDateString()) { 126 return 'yesterday, ' + date.toLocaleTimeString('en-US', { 127 hour: 'numeric', 128 minute: '2-digit', 129 hour12: true 130 }).toLowerCase(); 131 } 132 133 // Within 7 days: show day of week + time 134 if (diffDays < 7) { 135 const dayName = date.toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase(); 136 const time = date.toLocaleTimeString('en-US', { 137 hour: 'numeric', 138 minute: '2-digit', 139 hour12: true 140 }).toLowerCase(); 141 return `${dayName}, ${time}`; 142 } 143 144 // Same year: show month, day, time 145 if (date.getFullYear() === now.getFullYear()) { 146 return date.toLocaleDateString('en-US', { 147 month: 'short', 148 day: 'numeric', 149 hour: 'numeric', 150 minute: '2-digit', 151 hour12: true 152 }).toLowerCase(); 153 } 154 155 // Different year: show full date 156 return date.toLocaleDateString('en-US', { 157 month: 'short', 158 day: 'numeric', 159 year: 'numeric', 160 hour: 'numeric', 161 minute: '2-digit', 162 hour12: true 163 }).toLowerCase(); 164 }, 165 166 // Get full timestamp for tooltip 167 getFullTimestamp(date) { 168 // Get day of week 169 const dayName = date.toLocaleDateString('en-US', { weekday: 'long' }); 170 171 // Get month and day 172 const monthDay = date.toLocaleDateString('en-US', { 173 month: 'long', 174 day: 'numeric', 175 year: 'numeric' 176 }); 177 178 // Get time with seconds 179 const time = date.toLocaleTimeString('en-US', { 180 hour: 'numeric', 181 minute: '2-digit', 182 second: '2-digit', 183 hour12: true 184 }); 185 186 // Get timezone 187 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 188 const tzAbbr = date.toLocaleTimeString('en-US', { 189 timeZoneName: 'short' 190 }).split(' ').pop(); 191 192 return `${dayName}, ${monthDay} at ${time} ${tzAbbr}`; 193 }, 194 195 // Initialize all timestamps on the page 196 initialize() { 197 const updateTimestamps = () => { 198 const now = new Date(); 199 200 document.querySelectorAll('.local-time').forEach(el => { 201 const timestamp = el.getAttribute('data-timestamp'); 202 if (!timestamp) return; 203 204 const date = new Date(timestamp); 205 const format = el.getAttribute('data-format'); 206 const prefix = el.getAttribute('data-prefix'); 207 208 let text = ''; 209 210 // Determine format type 211 if (prefix === 'expires' || prefix === 'clears') { 212 text = this.formatFuture(date, now); 213 } else if (format === 'compact' || format === 'short') { 214 text = this.formatCompact(date, now); 215 } else if (prefix === 'since') { 216 const relativeText = this.formatRelative(date, now); 217 text = `since ${relativeText}`.replace('since just now', 'just started'); 218 } else { 219 text = this.formatRelative(date, now); 220 } 221 222 // Update text content 223 el.textContent = text; 224 225 // Add tooltip with full timestamp 226 const fullTimestamp = this.getFullTimestamp(date); 227 el.setAttribute('title', fullTimestamp); 228 el.style.cursor = 'help'; 229 el.style.display = 'inline-block'; 230 el.style.lineHeight = '1.2'; 231 el.style.alignSelf = 'flex-start'; 232 el.style.width = 'auto'; 233 }); 234 }; 235 236 // Initial update 237 updateTimestamps(); 238 239 // Update every 30 seconds for better granularity 240 setInterval(updateTimestamps, 30000); 241 } 242}; 243 244// Auto-initialize when DOM is ready 245if (document.readyState === 'loading') { 246 document.addEventListener('DOMContentLoaded', () => TimestampFormatter.initialize()); 247} else { 248 TimestampFormatter.initialize(); 249}