import type { DesktopBridge, DesktopUpdateState } from "@t3tools/contracts"; import { ExternalLinkIcon } from "lucide-react"; import { getDesktopUpdateReleaseHistoryUrl, getDesktopUpdateReleaseUrl, } from "../desktopUpdate.logic"; import { formatRelativeTimeLabel } from "../../timestampFormat"; import { openDesktopUpdateReleaseNotes } from "../desktopUpdate.toast"; type DesktopUpdateShell = Pick; function keyReleaseNoteItems(items: ReadonlyArray) { const occurrences = new Map(); return items.map((item) => { const occurrence = occurrences.get(item) ?? 0; occurrences.set(item, occurrence + 1); return { item, key: JSON.stringify([item, occurrence]) }; }); } /** Feed times read "3h ago"; build days from a prerelease version read "today" or "2d ago". */ function formatReleaseAge(releasedAt: string): string { const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(releasedAt); if (!day) return formatRelativeTimeLabel(releasedAt); const now = new Date(); const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()); const days = Math.round( (today - Date.UTC(Number(day[1]), Number(day[2]) - 1, Number(day[3]))) / 86_400_000, ); return days <= 0 ? "today" : `${days}d ago`; } function ReleaseLink({ children, releaseUrl, shell, }: { readonly children: string; readonly releaseUrl: string; readonly shell: DesktopUpdateShell | undefined; }) { return ( { event.preventDefault(); void openDesktopUpdateReleaseNotes(shell, releaseUrl); }} > {children} ); } export function SidebarUpdateReleaseNotes({ shell, state, tooltip, }: { readonly shell: DesktopUpdateShell | undefined; readonly state: DesktopUpdateState; readonly tooltip: string; }) { if (state.releaseNotes.length === 0) { return <>{tooltip}; } const releasedAt = state.releaseNotes.find( (releaseNote) => releaseNote.version === state.availableVersion, )?.releasedAt; return (
{state.status === "available" ? (
Update ready to download
{state.availableVersion ? (
{releasedAt ? `${state.availableVersion} · ${formatReleaseAge(releasedAt)}` : state.availableVersion}
) : null}
) : (
{tooltip}
)}
{state.releaseNotes.map((releaseNote, index) => { const releaseUrl = getDesktopUpdateReleaseUrl(releaseNote.version); const omittedItemCount = Math.max(0, releaseNote.totalItems - releaseNote.items.length); const linkLabel = releaseNote.stats ?? (omittedItemCount === 0 ? "View release on GitHub" : `${omittedItemCount} more ${omittedItemCount === 1 ? "change" : "changes"} on GitHub`); const generated = releaseNote.stats !== undefined || releaseNote.intro !== undefined; return (
0 ? "mt-4" : undefined} key={releaseNote.version}> {generated ? ( releaseNote.version !== state.availableVersion && (

{releaseNote.releasedAt ? `${releaseNote.version} · ${formatReleaseAge(releaseNote.releasedAt)}` : releaseNote.version}

) ) : (

{index === 0 ? "What's changed" : `Changes in ${releaseNote.version}`}

)} {releaseNote.intro ? (

{releaseNote.intro}

) : releaseNote.items.length > 0 ? (
    {keyReleaseNoteItems(releaseNote.items).map(({ item, key }) => { const highlight = /^\*\*([^*]+)\*\* (.+)$/.exec(item); return (
  • {highlight ? ( <> {highlight[1]} {highlight[2]} ) : ( item )}
  • ); })}
) : null} {releaseNote.footer && (

{releaseNote.footer}

)} {releaseUrl && ( {linkLabel} )}
); })} {state.omittedReleaseCount > 0 ? (
{`${state.omittedReleaseCount} older ${state.omittedReleaseCount === 1 ? "release" : "releases"} on GitHub`}
) : null}
); }