|
19 | 19 | import * as Accordion from "$lib/components/ui/accordion";
|
20 | 20 | import * as Tabs from "$lib/components/ui/tabs";
|
21 | 21 | import * as Tooltip from "$lib/components/ui/tooltip";
|
22 |
| - import ListElementRenderer from "./renderers/ListElementRenderer.svelte"; |
| 22 | + import BlinkingBadge from "$lib/components/BlinkingBadge.svelte"; |
| 23 | + import ListElementRenderer from "$lib/renderers/ListElementRenderer.svelte"; |
23 | 24 |
|
24 | 25 | // Repositories to fetch releases from
|
25 | 26 | type Repo = {
|
|
42 | 43 | */
|
43 | 44 | versionFromTag: (tag: string) => string;
|
44 | 45 | };
|
45 |
| - type Tabs = "svelte" | "kit" | "others"; |
46 |
| - const repos: Record<Tabs, { name: string; repos: Repo[] }> = { |
| 46 | + type Tab = "svelte" | "kit" | "others"; |
| 47 | + const repos: Record<Tab, { name: string; repos: Repo[] }> = { |
47 | 48 | svelte: {
|
48 | 49 | name: "Svelte",
|
49 | 50 | repos: [
|
|
120 | 121 | ).then(responses => responses.flat());
|
121 | 122 | }
|
122 | 123 |
|
| 124 | + // Badges |
| 125 | + let previousTab: string = currentRepo; |
| 126 | + let visitedTabs: string[] = []; |
| 127 | + const lastVisitKey = "lastVisit"; |
| 128 | + let lastVisitDateString = ""; |
| 129 | +
|
123 | 130 | // Settings
|
124 | 131 | let displaySvelteBetaReleases = localStorageStore("displaySvelteBetaReleases", true);
|
125 | 132 | let displayKitBetaReleases = localStorageStore("displayKitBetaReleases", true);
|
|
161 | 168 | }).format(-Math.ceil(dateDiff), relevantUnit);
|
162 | 169 | }
|
163 | 170 |
|
164 |
| - // Misc |
| 171 | + // Types |
165 | 172 | type Entries<T> = {
|
166 | 173 | [K in keyof T]: [K, T[K]];
|
167 | 174 | }[keyof T][];
|
|
170 | 177 | return Object.entries(obj) as Entries<T>;
|
171 | 178 | }
|
172 | 179 |
|
173 |
| - // Remove previous settings (will be removed in a future update) |
174 | 180 | onMount(() => {
|
| 181 | + // Remove previous settings (will be removed in a future update) |
175 | 182 | localStorage.removeItem("displayBetaReleases");
|
176 | 183 | localStorage.removeItem("nonKitReleasesDisplay");
|
| 184 | +
|
| 185 | + const localItem = localStorage.getItem(lastVisitKey); |
| 186 | + const nowDate = new Date().toISOString(); |
| 187 | + lastVisitDateString = localItem ?? nowDate; |
| 188 | + localStorage.setItem(lastVisitKey, nowDate); |
177 | 189 | });
|
178 | 190 | </script>
|
179 | 191 |
|
|
211 | 223 | <span class="text-primary">{repos[currentRepo].name}</span>
|
212 | 224 | Releases
|
213 | 225 | </h2>
|
214 |
| - <Tabs.Root bind:value={currentRepo} class="mt-8"> |
| 226 | + <Tabs.Root |
| 227 | + bind:value={currentRepo} |
| 228 | + class="mt-8" |
| 229 | + onValueChange={newValue => { |
| 230 | + const toSet = new Set(visitedTabs); |
| 231 | + toSet.add(previousTab); |
| 232 | + visitedTabs = [...toSet]; |
| 233 | + |
| 234 | + // I have no clue how this can be undefined |
| 235 | + if (newValue) { |
| 236 | + previousTab = newValue; |
| 237 | + } |
| 238 | + }} |
| 239 | + > |
215 | 240 | <div
|
216 | 241 | class="flex flex-col items-start gap-4 xs:flex-row xs:items-center xs:justify-between xs:gap-0"
|
217 | 242 | >
|
218 | 243 | <Tabs.List class="bg-input dark:bg-muted">
|
219 | 244 | {#each typedEntries(repos) as [id, { name }]}
|
220 |
| - <Tabs.Trigger |
221 |
| - class="data-[state=inactive]:text-foreground/60 data-[state=inactive]:hover:bg-background/50 data-[state=active]:hover:text-foreground/75 data-[state=inactive]:hover:text-foreground dark:data-[state=inactive]:hover:bg-background/25" |
222 |
| - value={id} |
223 |
| - > |
224 |
| - {name} |
225 |
| - </Tabs.Trigger> |
| 245 | + {#if !visitedTabs.includes(id) && id !== currentRepo} |
| 246 | + <BlinkingBadge storedDateItem="{id.toLowerCase()}MostRecentUpdate"> |
| 247 | + <Tabs.Trigger |
| 248 | + class="data-[state=inactive]:text-foreground/60 data-[state=inactive]:hover:bg-background/50 data-[state=active]:hover:text-foreground/75 data-[state=inactive]:hover:text-foreground dark:data-[state=inactive]:hover:bg-background/25" |
| 249 | + value={id} |
| 250 | + > |
| 251 | + {name} |
| 252 | + </Tabs.Trigger> |
| 253 | + </BlinkingBadge> |
| 254 | + {:else} |
| 255 | + <Tabs.Trigger |
| 256 | + class="data-[state=inactive]:text-foreground/60 data-[state=inactive]:hover:bg-background/50 data-[state=active]:hover:text-foreground/75 data-[state=inactive]:hover:text-foreground dark:data-[state=inactive]:hover:bg-background/25" |
| 257 | + value={id} |
| 258 | + > |
| 259 | + {name} |
| 260 | + </Tabs.Trigger> |
| 261 | + {/if} |
226 | 262 | {/each}
|
227 | 263 | </Tabs.List>
|
228 | 264 | <div class="ml-auto flex items-center space-x-2 xs:ml-0">
|
|
273 | 309 | <Skeleton class="h-80 w-full" />
|
274 | 310 | </div>
|
275 | 311 | {:then releases}
|
| 312 | + <!-- eslint-disable-next-line @typescript-eslint/no-unused-vars --> |
| 313 | + {@const _ = (() => { |
| 314 | + // Update the most recent date of a release of the list |
| 315 | + const latestRelease = releases.sort( |
| 316 | + (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() |
| 317 | + )[0]; |
| 318 | + if (!latestRelease) return false; // boolean because cannot store void in a const |
| 319 | + const storedDate = localStorage.getItem(`${id.toLowerCase()}MostRecentUpdate`); |
| 320 | + const latestReleaseDate = new Date(latestRelease.created_at); |
| 321 | + if (storedDate) { |
| 322 | + const storedDateObj = new Date(storedDate); |
| 323 | + if (latestReleaseDate > storedDateObj) { |
| 324 | + localStorage.setItem( |
| 325 | + `${id.toLowerCase()}MostRecentUpdate`, |
| 326 | + `"${latestReleaseDate.toISOString()}"` |
| 327 | + ); |
| 328 | + } |
| 329 | + } else { |
| 330 | + localStorage.setItem( |
| 331 | + `${id.toLowerCase()}MostRecentUpdate`, |
| 332 | + `"${latestReleaseDate.toISOString()}"` |
| 333 | + ); |
| 334 | + } |
| 335 | + })()} |
276 | 336 | <!-- The latest releases for each package of the repoList -->
|
277 | 337 | {@const latestReleases = (
|
278 | 338 | id === "others"
|
|
442 | 502 | ? !isMajorRelease &&
|
443 | 503 | semver.major(releaseRepo.versionFromTag(release.tag_name)) <
|
444 | 504 | semver.major(releaseRepo.versionFromTag(matchingLatestRelease.tag_name)) &&
|
445 |
| - releaseDate.getTime() > |
446 |
| - new Date(matchingEarliestOfLatestMajor.created_at).getTime() |
| 505 | + releaseDate > new Date(matchingEarliestOfLatestMajor.created_at) |
447 | 506 | : false}
|
448 | 507 | {@const releaseBody = (() => {
|
449 | 508 | const body = release.body ?? "";
|
|
461 | 520 | <!-- Trigger with release name, date and optional prerelease badge -->
|
462 | 521 | <Accordion.Trigger class="group hover:no-underline">
|
463 | 522 | <div class="flex w-full items-center gap-2 xs:items-baseline xs:gap-1">
|
| 523 | + {#if new Date(release.created_at) > new Date(lastVisitDateString) && !visitedTabs.includes(id)} |
| 524 | + <div class="relative ml-1 mr-2 inline-flex"> |
| 525 | + <span |
| 526 | + class="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" |
| 527 | + /> |
| 528 | + <span class="inline-flex size-2.5 rounded-full bg-primary" /> |
| 529 | + </div> |
| 530 | + {/if} |
464 | 531 | <div class="flex flex-col items-start gap-1">
|
465 | 532 | <span class="text-left text-lg group-hover:underline">
|
466 | 533 | {release.name}
|
|
0 commit comments