diff --git a/src/app/components/message/Time.tsx b/src/app/components/message/Time.tsx index 3eab5cc2..0353ec03 100644 --- a/src/app/components/message/Time.tsx +++ b/src/app/components/message/Time.tsx @@ -1,6 +1,6 @@ -import React, { ComponentProps } from 'react'; +import React, { ComponentProps, useEffect, useState } from 'react'; import { Text, as } from 'folds'; -import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../utils/time'; +import { relativeTime, timeDayMonYear, timeHourMinute } from '../../utils/time'; export type TimeProps = { compact?: boolean; @@ -9,35 +9,37 @@ export type TimeProps = { dateFormatString: string; }; +const REFRESH_INTERVAL_MS = 60 * 1000; + /** - * Renders a formatted timestamp, supporting compact and full display modes. - * - * Displays the time in hour:minute format if the message is from today, yesterday, or if `compact` is true. - * For older messages, it shows the date and time. - * - * @param {number} ts - The timestamp to display. - * @param {boolean} [compact=false] - If true, always show only the time. - * @param {boolean} hour24Clock - Whether to use 24-hour time format. - * @param {string} dateFormatString - Format string for the date part. - * @returns {React.ReactElement} A element with the formatted date/time. + * Renders a relative timestamp (e.g. "2 minutes ago", "3 days ago"), refreshing periodically. + * The absolute date/time is available as a hover tooltip. */ export const Time = as<'span', TimeProps & ComponentProps>( ({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => { - const formattedTime = timeHourMinute(ts, hour24Clock); + const [, setTick] = useState(0); + + useEffect(() => { + const intervalId = setInterval(() => setTick((tick) => tick + 1), REFRESH_INTERVAL_MS); + return () => clearInterval(intervalId); + }, []); - let time = ''; - if (compact) { - time = formattedTime; - } else if (today(ts)) { - time = formattedTime; - } else if (yesterday(ts)) { - time = `Yesterday ${formattedTime}`; - } else { - time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`; - } + const time = relativeTime(ts, Date.now(), compact ? 'narrow' : 'long'); + const absoluteTime = `${timeDayMonYear(ts, dateFormatString)} ${timeHourMinute( + ts, + hour24Clock + )}`; return ( - + {time} ); diff --git a/src/app/features/feed/Feed.tsx b/src/app/features/feed/Feed.tsx index 03ca7d49..f7efd431 100644 --- a/src/app/features/feed/Feed.tsx +++ b/src/app/features/feed/Feed.tsx @@ -18,6 +18,8 @@ type FeedProps = { export function Feed({ rooms }: FeedProps) { const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [urlPreview] = useSetting(settingsAtom, 'urlPreview'); + const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock'); + const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString'); const [commentsTarget, setCommentsTarget] = useState<{ room: Room; event: MatrixEvent }>(); const posts = useFeedPosts(rooms); @@ -49,6 +51,8 @@ export function Feed({ rooms }: FeedProps) { events={group.posts.map((post) => post.event)} mediaAutoLoad={mediaAutoLoad} urlPreview={urlPreview} + hour24Clock={hour24Clock} + dateFormatString={dateFormatString} onOpenComments={handleOpenComments} /> ))} diff --git a/src/app/features/feed/FeedPostCard.tsx b/src/app/features/feed/FeedPostCard.tsx index 81d5d0a2..587f88d3 100644 --- a/src/app/features/feed/FeedPostCard.tsx +++ b/src/app/features/feed/FeedPostCard.tsx @@ -30,6 +30,7 @@ import { MImage, MText, RenderBody, + Time, Username, UsernameBold, } from '../../components/message'; @@ -123,6 +124,8 @@ type FeedPostCardProps = { events: MatrixEvent[]; mediaAutoLoad?: boolean; urlPreview?: boolean; + hour24Clock: boolean; + dateFormatString: string; onOpenComments?: (room: Room, event: MatrixEvent) => void; }; @@ -131,6 +134,8 @@ export function FeedPostCard({ events, mediaAutoLoad, urlPreview, + hour24Clock, + dateFormatString, onOpenComments, }: FeedPostCardProps) { const mx = useMatrixClient(); @@ -209,11 +214,16 @@ export function FeedPostCard({ renderFallback={() => } /> - + {displayName} +