the home site for me: also iteration 3 or 4 of my site
at main 155 lines 4.2 kB view raw
1class RelativeTimeElement extends HTMLElement { 2 static get observedAttributes() { 3 return ['datetime', 'threshold', 'prefix', 'format']; 4 } 5 6 connectedCallback() { 7 this.update(); 8 } 9 10 disconnectedCallback() { 11 this.stopTimer(); 12 } 13 14 attributeChangedCallback() { 15 this.update(); 16 } 17 18 scheduleUpdate(ms) { 19 this.stopTimer(); 20 this.timer = setTimeout(() => this.update(), ms); 21 } 22 23 stopTimer() { 24 if (this.timer) { 25 clearTimeout(this.timer); 26 this.timer = null; 27 } 28 } 29 30 get datetime() { 31 return this.getAttribute('datetime') || ''; 32 } 33 34 get threshold() { 35 return this.getAttribute('threshold') || 'P14D'; 36 } 37 38 get prefix() { 39 return this.getAttribute('prefix') || 'on'; 40 } 41 42 get format() { 43 return this.getAttribute('format') || 'relative'; 44 } 45 46 parseThreshold(iso) { 47 const match = iso.match(/^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/); 48 if (!match) return 30 * 24 * 60 * 60 * 1000; 49 const days = parseInt(match[1] || 0, 10); 50 const hours = parseInt(match[2] || 0, 10); 51 const minutes = parseInt(match[3] || 0, 10); 52 const seconds = parseInt(match[4] || 0, 10); 53 return ((days * 24 + hours) * 60 + minutes) * 60 * 1000 + seconds * 1000; 54 } 55 56 update() { 57 const datetime = this.datetime; 58 if (!datetime) return; 59 60 const date = new Date(datetime); 61 if (isNaN(date.getTime())) return; 62 63 const now = Date.now(); 64 const diff = now - date.getTime(); 65 const absDiff = Math.abs(diff); 66 const thresholdMs = this.parseThreshold(this.threshold); 67 68 if (this.format === 'datetime' || absDiff > thresholdMs) { 69 this.textContent = this.formatDatetime(date); 70 this.scheduleUpdate(3600000); 71 } else { 72 this.textContent = this.formatRelative(diff); 73 const delay = this.getNextUpdateDelay(absDiff); 74 if (delay !== null) this.scheduleUpdate(delay); 75 } 76 } 77 78 getNextUpdateDelay(absDiff) { 79 const seconds = Math.floor(absDiff / 1000); 80 const minutes = Math.floor(seconds / 60); 81 const hours = Math.floor(minutes / 60); 82 const days = Math.floor(hours / 24); 83 84 if (seconds < 60) { 85 return 1000; 86 } else if (minutes < 60) { 87 return 60000; 88 } else if (hours < 24) { 89 return 60000 * 5; 90 } else if (days < 7) { 91 return 3600000; 92 } else if (days < 30) { 93 return 3600000 * 6; 94 } else { 95 return null; 96 } 97 } 98 99 getRtf() { 100 if (!this._rtf || this._rtfLang !== navigator.language) { 101 this._rtfLang = navigator.language; 102 this._rtf = new Intl.RelativeTimeFormat(navigator.language, { 103 numeric: 'auto', 104 style: 'long' 105 }); 106 } 107 return this._rtf; 108 } 109 110 formatRelative(diff) { 111 const rtf = this.getRtf(); 112 113 const absDiff = Math.abs(diff); 114 const sign = diff > 0 ? -1 : 1; 115 const seconds = Math.floor(absDiff / 1000); 116 const minutes = Math.floor(seconds / 60); 117 const hours = Math.floor(minutes / 60); 118 const days = Math.floor(hours / 24); 119 const date = new Date(Date.now() - diff); 120 const now = new Date(); 121 const months = (now.getFullYear() - date.getFullYear()) * 12 + (now.getMonth() - date.getMonth()); 122 const years = Math.floor(Math.abs(months) / 12) * sign; 123 124 if (seconds < 60) { 125 return rtf.format(sign * seconds, 'second'); 126 } else if (minutes < 60) { 127 return rtf.format(sign * minutes, 'minute'); 128 } else if (hours < 24) { 129 return rtf.format(sign * hours, 'hour'); 130 } else if (days < 30) { 131 return rtf.format(sign * days, 'day'); 132 } else if (Math.abs(months) < 12) { 133 return rtf.format(months, 'month'); 134 } else { 135 return rtf.format(years, 'year'); 136 } 137 } 138 139 formatDatetime(date) { 140 const now = new Date(); 141 const sameYear = date.getFullYear() === now.getFullYear(); 142 143 const options = { 144 month: 'short', 145 day: 'numeric', 146 ...(sameYear ? {} : { year: 'numeric' }) 147 }; 148 149 const prefix = this.prefix; 150 const formatted = new Intl.DateTimeFormat(navigator.language, options).format(date); 151 return prefix ? `${prefix} ${formatted}` : formatted; 152 } 153} 154 155customElements.define('relative-time', RelativeTimeElement);