feat: richer link preview cards and user local time display
P2-7: Domain-specific URL preview cards — YouTube shows mqdefault.jpg thumbnail with ▶ play overlay + title; GitHub shows inline SVG icon + owner/repo parsed from og:title + star/language info from og:description; generic cards get a Google favicon when og:image is absent; empty cards (no title or description) are suppressed entirely P2-9: Live local time in user profiles — useLocalTime(timezone, hour12) uses Intl.DateTimeFormat with the user's m.tz IANA zone; updates every 60s; shows clock icon + formatted time + timezone abbreviation (EST/JST etc.) in dim text; respects existing hour24Clock setting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
function formatLocalTime(timezone: string, hour12: boolean): string | undefined {
|
||||
try {
|
||||
return new Intl.DateTimeFormat('en', {
|
||||
timeZone: timezone,
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12,
|
||||
}).format(new Date());
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getTimezoneAbbr(timezone: string): string | undefined {
|
||||
try {
|
||||
return new Intl.DateTimeFormat('en', {
|
||||
timeZone: timezone,
|
||||
timeZoneName: 'short',
|
||||
})
|
||||
.formatToParts(new Date())
|
||||
.find((p) => p.type === 'timeZoneName')?.value;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export type LocalTimeInfo = {
|
||||
time: string;
|
||||
abbr: string | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current time (and timezone abbreviation) in the given IANA
|
||||
* timezone, updated every minute. Returns undefined when timezone is
|
||||
* undefined or invalid.
|
||||
*/
|
||||
export function useLocalTime(
|
||||
timezone: string | undefined,
|
||||
hour12: boolean = true,
|
||||
): LocalTimeInfo | undefined {
|
||||
const compute = useCallback((): LocalTimeInfo | undefined => {
|
||||
if (!timezone) return undefined;
|
||||
const time = formatLocalTime(timezone, hour12);
|
||||
if (!time) return undefined;
|
||||
return { time, abbr: getTimezoneAbbr(timezone) };
|
||||
}, [timezone, hour12]);
|
||||
|
||||
const [info, setInfo] = useState<LocalTimeInfo | undefined>(compute);
|
||||
|
||||
useEffect(() => {
|
||||
setInfo(compute());
|
||||
const id = window.setInterval(() => setInfo(compute()), 60_000);
|
||||
return () => window.clearInterval(id);
|
||||
}, [compute]);
|
||||
|
||||
return info;
|
||||
}
|
||||
Reference in New Issue
Block a user