tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
20
fork
atom
overview
issues
pulls
pipelines
add npmx leaderboard (hidden)
Florian
2 days ago
63e71530
6e19d606
+146
-2
4 changed files
expand all
collapse all
unified
split
src
lib
cards
index.ts
social
NpmxLikesLeaderboardCard
NpmxLikesLeaderboardCard.svelte
index.ts
routes
api
npmx-leaderboard
+server.ts
+3
-1
src/lib/cards/index.ts
···
41
import { ProductHuntCardDefinition } from './social/ProductHuntCard';
42
import { KickstarterCardDefinition } from './social/KickstarterCard';
43
import { NpmxLikesCardDefinition } from './social/NpmxLikesCard';
0
44
// import { Model3DCardDefinition } from './visual/Model3DCard';
45
46
export const AllCardDefinitions = [
···
86
GitHubContributorsCardDefinition,
87
ProductHuntCardDefinition,
88
KickstarterCardDefinition,
89
-
NpmxLikesCardDefinition
0
90
] as const;
91
92
export const CardDefinitionsByType = AllCardDefinitions.reduce(
···
41
import { ProductHuntCardDefinition } from './social/ProductHuntCard';
42
import { KickstarterCardDefinition } from './social/KickstarterCard';
43
import { NpmxLikesCardDefinition } from './social/NpmxLikesCard';
44
+
import { NpmxLikesLeaderboardCardDefinition } from './social/NpmxLikesLeaderboardCard';
45
// import { Model3DCardDefinition } from './visual/Model3DCard';
46
47
export const AllCardDefinitions = [
···
87
GitHubContributorsCardDefinition,
88
ProductHuntCardDefinition,
89
KickstarterCardDefinition,
90
+
NpmxLikesCardDefinition,
91
+
NpmxLikesLeaderboardCardDefinition
92
] as const;
93
94
export const CardDefinitionsByType = AllCardDefinitions.reduce(
+116
src/lib/cards/social/NpmxLikesLeaderboardCard/NpmxLikesLeaderboardCard.svelte
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
<script lang="ts">
2
+
import type { Item } from '$lib/types';
3
+
import { onMount } from 'svelte';
4
+
import { getAdditionalUserData, getDidContext, getHandleContext } from '$lib/website/context';
5
+
import { CardDefinitionsByType } from '../..';
6
+
7
+
interface LeaderboardEntry {
8
+
subjectRef: string;
9
+
totalLikes: number;
10
+
}
11
+
12
+
interface LeaderboardData {
13
+
totalLikes: number;
14
+
totalUniqueLikers: number;
15
+
leaderBoard: LeaderboardEntry[];
16
+
}
17
+
18
+
let { item }: { item: Item } = $props();
19
+
20
+
const data = getAdditionalUserData();
21
+
// svelte-ignore state_referenced_locally
22
+
let leaderboard = $state(data[item.cardType] as LeaderboardData | undefined);
23
+
24
+
let did = getDidContext();
25
+
let handle = getHandleContext();
26
+
27
+
onMount(async () => {
28
+
if (leaderboard) return;
29
+
30
+
leaderboard = (await CardDefinitionsByType[item.cardType]?.loadData?.([], {
31
+
did,
32
+
handle
33
+
})) as LeaderboardData | undefined;
34
+
35
+
data[item.cardType] = leaderboard;
36
+
});
37
+
38
+
function getPackageName(entry: LeaderboardEntry): string {
39
+
return entry.subjectRef.split('/package/')[1] ?? entry.subjectRef;
40
+
}
41
+
</script>
42
+
43
+
{#snippet leaderboardRow(entry: LeaderboardEntry, index: number)}
44
+
<div
45
+
class="hover:bg-base-100 dark:hover:bg-base-800 accent:hover:bg-white/10 flex w-full items-center gap-3 rounded-lg px-2 py-1.5 transition-colors"
46
+
>
47
+
<div
48
+
class="text-base-600 dark:text-base-400 accent:text-white/60 w-6 shrink-0 text-right text-xs font-medium"
49
+
>
50
+
#{index + 1}
51
+
</div>
52
+
<div class="min-w-0 flex-1">
53
+
<div class="inline-flex w-full max-w-full items-center justify-between gap-2">
54
+
<div
55
+
class="text-accent-500 accent:text-accent-50 dark:text-accent-400 min-w-0 flex-1 shrink truncate text-sm font-semibold"
56
+
>
57
+
{getPackageName(entry)}
58
+
</div>
59
+
<div
60
+
class="text-base-500 dark:text-base-400 accent:text-white/60 flex shrink-0 items-center gap-1 text-xs"
61
+
>
62
+
<svg
63
+
xmlns="http://www.w3.org/2000/svg"
64
+
viewBox="0 0 24 24"
65
+
fill="currentColor"
66
+
class="accent:text-accent-200 text-accent-400 size-3.5"
67
+
>
68
+
<path
69
+
d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z"
70
+
/>
71
+
</svg>
72
+
{entry.totalLikes}
73
+
</div>
74
+
</div>
75
+
</div>
76
+
</div>
77
+
{/snippet}
78
+
79
+
<div class="z-10 flex h-full w-full flex-col overflow-hidden">
80
+
{#if leaderboard && leaderboard.leaderBoard.length > 0}
81
+
<div class="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto p-4 pb-10">
82
+
{#each leaderboard.leaderBoard as entry, index (entry.subjectRef)}
83
+
<a
84
+
href="https://npmx.dev/package/{getPackageName(entry)}"
85
+
target="_blank"
86
+
rel="noopener noreferrer"
87
+
class="w-full"
88
+
>
89
+
{@render leaderboardRow(entry, index)}
90
+
</a>
91
+
{/each}
92
+
</div>
93
+
<div
94
+
class="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-12 bg-linear-to-t from-base-200 from-40% to-transparent dark:from-base-950 accent:from-accent-500"
95
+
></div>
96
+
<div
97
+
class="text-base-500 dark:text-base-400 accent:text-white/60 bg-base-200 dark:bg-base-950/50 accent:bg-accent-500/20 relative z-10 flex shrink-0 items-center justify-center gap-3 px-4 pb-3 text-xs"
98
+
>
99
+
<span>{leaderboard.totalLikes} likes</span>
100
+
<span class="text-base-300 dark:text-base-600 accent:text-white/20">·</span>
101
+
<span>{leaderboard.totalUniqueLikers} unique likers</span>
102
+
</div>
103
+
{:else if leaderboard}
104
+
<div
105
+
class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center p-4 text-center text-sm"
106
+
>
107
+
No leaderboard data.
108
+
</div>
109
+
{:else}
110
+
<div
111
+
class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center p-4 text-center text-sm"
112
+
>
113
+
Loading leaderboard...
114
+
</div>
115
+
{/if}
116
+
</div>
+26
src/lib/cards/social/NpmxLikesLeaderboardCard/index.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import type { CardDefinition } from '../../types';
2
+
import NpmxLikesLeaderboardCard from './NpmxLikesLeaderboardCard.svelte';
3
+
4
+
export const NpmxLikesLeaderboardCardDefinition = {
5
+
type: 'npmxLikesLeaderboard',
6
+
contentComponent: NpmxLikesLeaderboardCard,
7
+
createNew: (card) => {
8
+
card.w = 4;
9
+
card.mobileW = 8;
10
+
card.h = 4;
11
+
card.mobileH = 6;
12
+
},
13
+
loadData: async () => {
14
+
const res = await fetch('https://blento.app/api/npmx-leaderboard');
15
+
const data = await res.json();
16
+
return data;
17
+
},
18
+
minW: 3,
19
+
canHaveLabel: true,
20
+
21
+
keywords: ['npm', 'package', 'npmx', 'likes', 'leaderboard', 'ranking'],
22
+
name: 'npmx Likes Leaderboard',
23
+
24
+
//groups: ['Social'],
25
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 18.75h-9m9 0a3 3 0 0 1 3 3h-15a3 3 0 0 1 3-3m9 0v-4.5A3.375 3.375 0 0 0 13.125 10.875h-2.25A3.375 3.375 0 0 0 7.5 14.25v4.5m6-6V6.375a3.375 3.375 0 0 0-3-3.353A3.375 3.375 0 0 0 7.5 6.375v1.5" /></svg>`
26
+
} as CardDefinition & { type: 'npmxLikesLeaderboard' };
+1
-1
src/routes/api/npmx-leaderboard/+server.ts
···
2
import type { RequestHandler } from './$types';
3
4
const LEADERBOARD_API_URL =
5
-
'https://npmx-likes-leaderboard-api-production.up.railway.app/api/leaderboard/likes';
6
7
export const GET: RequestHandler = async ({ platform }) => {
8
const cacheKey = '#npmx-leaderboard:likes';
···
2
import type { RequestHandler } from './$types';
3
4
const LEADERBOARD_API_URL =
5
+
'https://npmx-likes-leaderboard-api-production.up.railway.app/api/leaderboard/likes?limit=20';
6
7
export const GET: RequestHandler = async ({ platform }) => {
8
const cacheKey = '#npmx-leaderboard:likes';