|
1 | 1 | <script lang="ts"> |
| 2 | + import { browser } from "$app/environment"; |
2 | 3 | import { ChevronRight } from "@lucide/svelte"; |
| 4 | + import type { GitHubRelease } from "$lib/server/github-cache"; |
| 5 | + import { Badge } from "$lib/components/ui/badge"; |
3 | 6 | import { Separator } from "$lib/components/ui/separator"; |
4 | 7 |
|
5 | 8 | let { data } = $props(); |
| 9 | +
|
| 10 | + /** |
| 11 | + * Extract the data from the {@link import('./$types').data.otherReleases|otherReleases} |
| 12 | + * props. |
| 13 | + * |
| 14 | + * @param pkgName the package name to extract releases fo |
| 15 | + * @returns the {@link Promise} of releases, or `undefined` |
| 16 | + */ |
| 17 | + function getBadgeDataFromOther(pkgName: string) { |
| 18 | + const releases = Object.entries(data.allReleases).find( |
| 19 | + ([k]) => k.localeCompare(pkgName, undefined, { sensitivity: "base" }) === 0 |
| 20 | + ); |
| 21 | + if (!releases) return undefined; |
| 22 | + const [, v] = releases; |
| 23 | + return v; |
| 24 | + } |
| 25 | +
|
| 26 | + /** |
| 27 | + * Filter the releases to exclude those that have already been seen |
| 28 | + * |
| 29 | + * @param pkgName the package name for the releases |
| 30 | + * @param releases the releases to filter |
| 31 | + * @returns the filtered releases |
| 32 | + */ |
| 33 | + function getUnvisitedReleases(pkgName: string, releases: GitHubRelease[] | undefined) { |
| 34 | + if (!releases || !browser) return []; |
| 35 | +
|
| 36 | + const lastVisitedItem = localStorage.getItem(`last-visited-${pkgName}`); |
| 37 | + if (!lastVisitedItem) { |
| 38 | + return releases.filter( |
| 39 | + ({ created_at, published_at }) => |
| 40 | + new Date(published_at ?? created_at).getTime() > Date.now() - 1000 * 60 * 60 * 24 * 7 |
| 41 | + ); |
| 42 | + } |
| 43 | + const lastVisitedDate = new Date(lastVisitedItem); |
| 44 | +
|
| 45 | + return releases.filter( |
| 46 | + ({ created_at, published_at }) => new Date(published_at ?? created_at) > lastVisitedDate |
| 47 | + ); |
| 48 | + } |
6 | 49 | </script> |
7 | 50 |
|
| 51 | +{#snippet newBadge(count: number)} |
| 52 | + {#if count > 0} |
| 53 | + <Badge>{count} new</Badge> |
| 54 | + {/if} |
| 55 | +{/snippet} |
| 56 | + |
8 | 57 | <ul class="space-y-8"> |
9 | 58 | {#each data.displayablePackages as { category, packages } (category)} |
10 | 59 | <li> |
11 | 60 | <h3 class="font-display text-3xl text-primary text-shadow-sm">{category.name}</h3> |
12 | 61 | <ul class="mt-2"> |
13 | 62 | {#each packages as { repoOwner, repoName, pkg }, index (pkg.name)} |
| 63 | + {@const linkedBadgeData = getBadgeDataFromOther(pkg.name)} |
14 | 64 | {#if index > 0} |
15 | 65 | <Separator class="mx-auto my-1 w-[95%]" /> |
16 | 66 | {/if} |
17 | 67 | <li> |
18 | 68 | <a |
19 | 69 | href="/package/{pkg.name}" |
20 | | - class="group flex items-center rounded-xl px-4 py-3 transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-800" |
| 70 | + class="group flex items-center gap-4 rounded-xl px-4 py-3 transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-800" |
21 | 71 | > |
22 | 72 | <div class="flex flex-col"> |
23 | 73 | <h4 class="font-medium">{pkg.name}</h4> |
|
31 | 81 | </span> |
32 | 82 | </span> |
33 | 83 | </div> |
34 | | - <ChevronRight class="mr-1 ml-auto transition-transform group-hover:translate-x-1" /> |
| 84 | + <span class="ml-auto mr-1 shrink-0 flex items-center gap-1"> |
| 85 | + {#if linkedBadgeData} |
| 86 | + {#await linkedBadgeData then d} |
| 87 | + {@render newBadge(getUnvisitedReleases(pkg.name, d?.releases).length)} |
| 88 | + {/await} |
| 89 | + {/if} |
| 90 | + <ChevronRight class="transition-transform group-hover:translate-x-1" /> |
| 91 | + </span> |
35 | 92 | </a> |
36 | 93 | </li> |
37 | 94 | {/each} |
|
0 commit comments