JavaScript-optional public web frontend for Bluesky anartia.kelinci.net
sveltekit atcute bluesky typescript svelte
7
fork

Configure Feed

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

refactor: revamp time formatting

Mary a1c0d075 73118956

+143 -89
+2 -2
src/lib/components/embeds/quote-embed.svelte
··· 39 39 import { parseAtUri } from '$lib/types/at-uri'; 40 40 41 41 import Avatar from '$lib/components/avatar.svelte'; 42 - import RelativeTime from '$lib/components/islands/relative-time.svelte'; 42 + import Time from '$lib/components/islands/time.svelte'; 43 43 44 44 import ContentHider from '../content-hider.svelte'; 45 45 ··· 86 86 <span aria-hidden="true" class="dot">·</span> 87 87 88 88 <span class="date"> 89 - <RelativeTime date={record.createdAt} /> 89 + <Time date={record.createdAt} format="relative-time" /> 90 90 </span> 91 91 </div> 92 92
-28
src/lib/components/islands/long-date.svelte
··· 1 - <script lang="ts"> 2 - import { dev } from '$app/environment'; 3 - import { base } from '$app/paths'; 4 - 5 - import { formatLongDate } from '$lib/utils/intl/date'; 6 - 7 - import Island from '../island.svelte'; 8 - 9 - interface Props { 10 - date: string | number; 11 - } 12 - 13 - const { date: rawDate }: Props = $props(); 14 - 15 - const date = $derived(new Date(rawDate)); 16 - </script> 17 - 18 - {#if dev} 19 - <time class="isl-long-date" datetime={date.toISOString()}> 20 - {formatLongDate(date.getTime())} 21 - </time> 22 - {:else} 23 - <Island scriptUrl="{base}/_scripts/time-formatter.js" fetchPriority="low"> 24 - <time class="isl-long-date" datetime={date.toISOString()}> 25 - {formatLongDate(date.getTime())} 26 - </time> 27 - </Island> 28 - {/if}
-28
src/lib/components/islands/relative-time.svelte
··· 1 - <script lang="ts"> 2 - import { dev } from '$app/environment'; 3 - import { base } from '$app/paths'; 4 - 5 - import { formatLongDate, formatRelativeTime, formatShortDate } from '$lib/utils/intl/date'; 6 - 7 - import Island from '../island.svelte'; 8 - 9 - interface Props { 10 - date: string | number; 11 - } 12 - 13 - const { date: rawDate }: Props = $props(); 14 - 15 - const date = $derived(new Date(rawDate)); 16 - </script> 17 - 18 - {#if dev} 19 - <time class="isl-relative-time" title={formatLongDate(date.getTime())} datetime={date.toISOString()}> 20 - {formatRelativeTime(date.getTime())} 21 - </time> 22 - {:else} 23 - <Island scriptUrl="{base}/_scripts/time-formatter.js" fetchPriority="low"> 24 - <time class="isl-relative-time" title={formatLongDate(date.getTime())} datetime={date.toISOString()}> 25 - {formatShortDate(date.getTime())} 26 - </time> 27 - </Island> 28 - {/if}
+52
src/lib/components/islands/time.svelte
··· 1 + <script lang="ts"> 2 + import { dev } from '$app/environment'; 3 + import { base } from '$app/paths'; 4 + 5 + import { formatLongDate, formatRelativeTime, formatShortDate, formatTime } from '$lib/utils/intl/date'; 6 + import { assertNever } from '$lib/utils/invariant'; 7 + 8 + import Island from '../island.svelte'; 9 + 10 + interface Props { 11 + date: Date | string | number; 12 + format: 'short-date' | 'long-date' | 'time' | 'relative-time'; 13 + } 14 + 15 + const { date: rawDate, format }: Props = $props(); 16 + 17 + const date = $derived(new Date(rawDate)); 18 + 19 + const formatted = $derived.by((): { label: string; alt?: string } => { 20 + const long = formatLongDate(date); 21 + 22 + switch (format) { 23 + case 'short-date': { 24 + return { label: formatShortDate(date), alt: long }; 25 + } 26 + case 'long-date': { 27 + return { label: long }; 28 + } 29 + case 'time': { 30 + return { label: formatTime(date), alt: long }; 31 + } 32 + case 'relative-time': { 33 + return { label: formatRelativeTime(date), alt: long }; 34 + } 35 + default: { 36 + assertNever(format); 37 + } 38 + } 39 + }); 40 + </script> 41 + 42 + {#if dev} 43 + <time title={formatted.alt} datetime={date.toISOString()} data-format={format}> 44 + {formatted.label} 45 + </time> 46 + {:else} 47 + <Island scriptUrl="{base}/_scripts/time-formatter.js" fetchPriority="low"> 48 + <time title={formatted.alt} datetime={date.toISOString()} data-format={format}> 49 + {formatted.label} 50 + </time> 51 + </Island> 52 + {/if}
+3 -3
src/lib/components/timeline/post-meta.svelte
··· 1 1 <script lang="ts"> 2 2 import type { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/client/lexicons'; 3 3 4 - import RelativeTime from '$lib/components/islands/relative-time.svelte'; 4 + import Time from '$lib/components/islands/time.svelte'; 5 5 6 6 interface Props { 7 7 post: AppBskyFeedDefs.PostView; ··· 32 32 <span aria-hidden="true" class="dot"> · </span> 33 33 34 34 <a href={postUrl} class="date"> 35 - <RelativeTime date={createdAt} /> 35 + <Time date={createdAt} format="relative-time" /> 36 36 </a> 37 37 </div> 38 38 ··· 40 40 .post-meta { 41 41 display: flex; 42 42 align-items: center; 43 - color: var(--text-blurb); 44 43 min-width: 0; 44 + color: var(--text-blurb); 45 45 } 46 46 .has-bottom-gutter { 47 47 margin-bottom: 2px;
+2 -2
src/routes/(app)/[actor=didOrHandle]/[rkey=tid]/components/main-post.svelte
··· 11 11 import ThreadOutlined from '$lib/components/central-icons/thread-outlined.svelte'; 12 12 import ContentHider from '$lib/components/content-hider.svelte'; 13 13 import Embeds from '$lib/components/embeds/embeds.svelte'; 14 - import LongDate from '$lib/components/islands/long-date.svelte'; 14 + import Time from '$lib/components/islands/time.svelte'; 15 15 import OverflowMenu from '$lib/components/overflow-menu.svelte'; 16 16 import RichTextRenderer from '$lib/components/richtext-renderer.svelte'; 17 17 ··· 93 93 94 94 <div class="footer"> 95 95 <a href={postUrl} class="date"> 96 - <LongDate date={record.createdAt} /> 96 + <Time date={record.createdAt} format="long-date" /> 97 97 </a> 98 98 <span aria-hidden="true" class="dot"> • </span> 99 99 <InteractionState threadgate={post.threadgate} />
+3 -2
src/routes/(app)/[actor=did]/[rkey=tid]/unroll/+page.svelte
··· 18 18 import OverflowMenu from '$lib/components/overflow-menu.svelte'; 19 19 import PageContainer from '$lib/components/page/page-container.svelte'; 20 20 import RichtextRenderer from '$lib/components/richtext-renderer.svelte'; 21 + import Time from '$lib/components/islands/time.svelte'; 21 22 22 23 const { data }: PageProps = $props(); 23 24 ··· 104 105 <p class="meta"> 105 106 <span class="date"> 106 107 {#if !prevClusterDate || !isSameCalendarDate(date, prevClusterDate)} 107 - {formatLongDate(date)} 108 + <Time {date} format="long-date" /> 108 109 {:else} 109 - {formatTime(date)} 110 + <Time {date} format="time" /> 110 111 {/if} 111 112 </span> 112 113 </p>
+81 -24
static/_scripts/time-formatter.js
··· 3 3 let startOfYear = 0; 4 4 let endOfYear = 0; 5 5 6 - const fmtAbsoluteLong = new Intl.DateTimeFormat('en-US', { dateStyle: 'long', timeStyle: 'short' }); 7 - const fmtAbsShortWithYear = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }); 8 - const fmtAbsShort = new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }); 6 + const fmtTime = new Intl.DateTimeFormat('en-US', { 7 + timeStyle: 'short', 8 + }); 9 + const fmtDateTime = new Intl.DateTimeFormat('en-US', { 10 + dateStyle: 'long', 11 + timeStyle: 'short', 12 + }); 13 + const fmtShortDateWithYear = new Intl.DateTimeFormat('en-US', { 14 + dateStyle: 'medium', 15 + }); 16 + const fmtShortDate = new Intl.DateTimeFormat('en-US', { 17 + month: 'short', 18 + day: 'numeric', 19 + }); 9 20 10 21 /** 11 22 * @param {string | number} date ··· 15 26 const inst = new Date(date); 16 27 const time = inst.getTime(); 17 28 18 - if (isNaN(time)) { 29 + if (Number.isNaN(time)) { 19 30 return 'N/A'; 20 31 } 21 32 ··· 33 44 } 34 45 35 46 if (time >= startOfYear && time <= endOfYear) { 36 - return fmtAbsShort.format(inst); 47 + return fmtShortDate.format(inst); 48 + } 49 + 50 + return fmtShortDateWithYear.format(inst); 51 + }; 52 + 53 + /** 54 + * @param {string | number} date 55 + * @returns {string} 56 + */ 57 + const formatTime = (date) => { 58 + const inst = new Date(date); 59 + 60 + if (Number.isNaN(inst.getTime())) { 61 + return 'N/A'; 37 62 } 38 63 39 - return fmtAbsShortWithYear.format(inst); 64 + return fmtTime.format(inst); 40 65 }; 41 66 42 67 /** ··· 46 71 const formatLongDate = (date) => { 47 72 const inst = new Date(date); 48 73 49 - if (isNaN(inst.getTime())) { 74 + if (Number.isNaN(inst.getTime())) { 50 75 return 'N/A'; 51 76 } 52 77 53 - return fmtAbsoluteLong.format(inst); 78 + return fmtDateTime.format(inst); 54 79 }; 55 80 56 81 /** @type {Record<string, Intl.NumberFormat>} */ ··· 71 96 const formatRelativeTime = (date, now) => { 72 97 const time = new Date(date).getTime(); 73 98 99 + if (Number.isNaN(time)) { 100 + return 'N/A'; 101 + } 102 + 74 103 const delta = now - time; 75 104 76 105 if (delta < -NOW || delta > WEEK) { ··· 88 117 89 118 // if it happened this year, don't show the year. 90 119 if (time >= startOfYear && time <= endOfYear) { 91 - return fmtAbsShort.format(time); 120 + return fmtShortDate.format(time); 92 121 } 93 122 94 - return fmtAbsShortWithYear.format(time); 123 + return fmtShortDateWithYear.format(time); 95 124 } 96 125 97 126 if (delta < NOW) { ··· 132 161 133 162 (() => { 134 163 /** @type {NodeListOf<HTMLTimeElement>} */ 135 - const nodes = document.querySelectorAll('time.isl-relative-time'); 164 + const nodes = document.querySelectorAll('time[data-format="short-date"]'); 165 + if (nodes.length === 0) { 166 + return; 167 + } 168 + 169 + for (const node of nodes) { 170 + const dt = node.dateTime; 171 + 172 + node.textContent = formatShortDate(dt); 173 + node.title = formatLongDate(dt); 174 + } 175 + })(); 136 176 177 + (() => { 178 + /** @type {NodeListOf<HTMLTimeElement>} */ 179 + const nodes = document.querySelectorAll('time[data-format="long-date"]'); 180 + if (nodes.length === 0) { 181 + return; 182 + } 183 + 184 + for (const node of nodes) { 185 + node.textContent = formatLongDate(node.dateTime); 186 + } 187 + })(); 188 + 189 + (() => { 190 + /** @type {NodeListOf<HTMLTimeElement>} */ 191 + const nodes = document.querySelectorAll('time[data-format="time"]'); 192 + if (nodes.length === 0) { 193 + return; 194 + } 195 + 196 + for (const node of nodes) { 197 + const dt = node.dateTime; 198 + 199 + node.textContent = formatTime(dt); 200 + node.title = formatLongDate(dt); 201 + } 202 + })(); 203 + 204 + (() => { 205 + /** @type {NodeListOf<HTMLTimeElement>} */ 206 + const nodes = document.querySelectorAll('time[data-format="relative-time"]'); 137 207 if (nodes.length === 0) { 138 208 return; 139 209 } ··· 152 222 update(); 153 223 setInterval(update, 60_000); 154 224 })(); 155 - 156 - (() => { 157 - /** @type {NodeListOf<HTMLTimeElement>} */ 158 - const nodes = document.querySelectorAll('time.isl-long-date'); 159 - 160 - if (nodes.length === 0) { 161 - return; 162 - } 163 - 164 - for (const node of nodes) { 165 - node.textContent = formatLongDate(node.dateTime); 166 - } 167 - })();