your personal website on atproto - mirror blento.app

Merge pull request #169 from flo-bit/copy-page

add npmx stuff pt1

authored by

Florian and committed by
GitHub
40c0a478 6f908ec6

+187 -3
+3 -1
src/lib/cards/index.ts
··· 40 40 import { GitHubContributorsCardDefinition } from './social/GitHubContributorsCard'; 41 41 import { ProductHuntCardDefinition } from './social/ProductHuntCard'; 42 42 import { KickstarterCardDefinition } from './social/KickstarterCard'; 43 + import { NpmxLikesCardDefinition } from './social/NpmxLikesCard'; 43 44 // import { Model3DCardDefinition } from './visual/Model3DCard'; 44 45 45 46 export const AllCardDefinitions = [ ··· 84 85 FriendsCardDefinition, 85 86 GitHubContributorsCardDefinition, 86 87 ProductHuntCardDefinition, 87 - KickstarterCardDefinition 88 + KickstarterCardDefinition, 89 + NpmxLikesCardDefinition 88 90 ] as const; 89 91 90 92 export const CardDefinitionsByType = AllCardDefinitions.reduce(
+103
src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte
··· 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 + import { RelativeTime } from '@foxui/time'; 7 + 8 + interface NpmxLike { 9 + uri: string; 10 + value: { 11 + subjectRef: string; 12 + createdAt: string; 13 + }; 14 + } 15 + 16 + let { item }: { item: Item } = $props(); 17 + 18 + const data = getAdditionalUserData(); 19 + // svelte-ignore state_referenced_locally 20 + let feed = $state(data[item.cardType] as NpmxLike[] | undefined); 21 + 22 + let did = getDidContext(); 23 + let handle = getHandleContext(); 24 + 25 + onMount(async () => { 26 + if (feed) return; 27 + 28 + feed = (await CardDefinitionsByType[item.cardType]?.loadData?.([], { 29 + did, 30 + handle 31 + })) as NpmxLike[] | undefined; 32 + 33 + data[item.cardType] = feed; 34 + }); 35 + 36 + function getPackageName(like: NpmxLike): string { 37 + return like.value.subjectRef.split('/package/')[1] ?? like.value.subjectRef; 38 + } 39 + </script> 40 + 41 + {#snippet likeItem(like: NpmxLike)} 42 + <div class="flex w-full items-center gap-3"> 43 + <div 44 + class="text-accent-500 accent:text-accent-950 flex size-8 shrink-0 items-center justify-center" 45 + > 46 + <svg 47 + xmlns="http://www.w3.org/2000/svg" 48 + fill="none" 49 + viewBox="0 0 24 24" 50 + stroke-width="1.5" 51 + stroke="currentColor" 52 + class="size-5" 53 + > 54 + <path 55 + stroke-linecap="round" 56 + stroke-linejoin="round" 57 + d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" 58 + /> 59 + </svg> 60 + </div> 61 + <div class="min-w-0 flex-1"> 62 + <div class="inline-flex w-full max-w-full items-baseline justify-between gap-2"> 63 + <div 64 + class="text-accent-500 accent:text-accent-950 min-w-0 flex-1 shrink truncate text-sm font-semibold" 65 + > 66 + {getPackageName(like)} 67 + </div> 68 + {#if like.value.createdAt} 69 + <div class="text-base-500 dark:text-base-400 accent:text-white/60 shrink-0 text-xs"> 70 + <RelativeTime date={new Date(like.value.createdAt)} locale="en-US" /> ago 71 + </div> 72 + {/if} 73 + </div> 74 + </div> 75 + </div> 76 + {/snippet} 77 + 78 + <div class="z-10 flex h-full w-full flex-col gap-3 overflow-y-scroll p-4"> 79 + {#if feed && feed.length > 0} 80 + {#each feed as like (like.uri)} 81 + <a 82 + href="https://npmx.dev/package/{getPackageName(like)}" 83 + target="_blank" 84 + rel="noopener noreferrer" 85 + class="w-full" 86 + > 87 + {@render likeItem(like)} 88 + </a> 89 + {/each} 90 + {:else if feed} 91 + <div 92 + class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center text-center text-sm" 93 + > 94 + No liked packages found. 95 + </div> 96 + {:else} 97 + <div 98 + class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center text-center text-sm" 99 + > 100 + Loading likes... 101 + </div> 102 + {/if} 103 + </div>
+31
src/lib/cards/social/NpmxLikesCard/index.ts
··· 1 + import type { CardDefinition } from '../../types'; 2 + import { listRecords } from '$lib/atproto'; 3 + import NpmxLikesCard from './NpmxLikesCard.svelte'; 4 + 5 + export const NpmxLikesCardDefinition = { 6 + type: 'npmxLikes', 7 + contentComponent: NpmxLikesCard, 8 + createNew: (card) => { 9 + card.w = 4; 10 + card.mobileW = 8; 11 + card.h = 3; 12 + card.mobileH = 6; 13 + }, 14 + loadData: async (items, { did }) => { 15 + const data = await listRecords({ 16 + did, 17 + collection: 'dev.npmx.feed.like', 18 + limit: 99 19 + }); 20 + 21 + return data; 22 + }, 23 + minW: 4, 24 + canHaveLabel: true, 25 + 26 + keywords: ['npm', 'package', 'npmx', 'likes'], 27 + name: 'npmx Likes', 28 + 29 + groups: ['Social'], 30 + 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="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" /></svg>` 31 + } as CardDefinition & { type: 'npmxLikes' };
+6 -2
src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte
··· 1773 1773 ? 'bg-base-50 dark:bg-base-900' 1774 1774 : 'bg-black'}" 1775 1775 > 1776 - <canvas bind:this={shadowCanvas} class="absolute inset-0.5 h-[calc(100%-4px)] w-[calc(100%-4px)]"></canvas> 1777 - <canvas bind:this={fluidCanvas} class="absolute inset-0.5 h-[calc(100%-4px)] w-[calc(100%-4px)]"></canvas> 1776 + <canvas 1777 + bind:this={shadowCanvas} 1778 + class="absolute inset-0.5 h-[calc(100%-4px)] w-[calc(100%-4px)] rounded-[inherit]" 1779 + ></canvas> 1780 + <canvas bind:this={fluidCanvas} class="absolute inset-0.5 h-[calc(100%-4px)] w-[calc(100%-4px)]" 1781 + ></canvas> 1778 1782 <canvas bind:this={maskCanvas} class="absolute h-full w-full"></canvas> 1779 1783 </div>
+44
src/routes/api/npmx-leaderboard/+server.ts
··· 1 + import { json } from '@sveltejs/kit'; 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'; 9 + const cachedData = await platform?.env?.USER_DATA_CACHE?.get(cacheKey); 10 + 11 + if (cachedData) { 12 + const parsedCache = JSON.parse(cachedData); 13 + 14 + const TWELVE_HOURS = 12 * 60 * 60 * 1000; 15 + const now = Date.now(); 16 + 17 + if (now - (parsedCache.updatedAt || 0) < TWELVE_HOURS) { 18 + return json(parsedCache.data); 19 + } 20 + } 21 + 22 + try { 23 + const response = await fetch(LEADERBOARD_API_URL); 24 + 25 + if (!response.ok) { 26 + return json( 27 + { error: 'Failed to fetch npmx leaderboard ' + response.statusText }, 28 + { status: response.status } 29 + ); 30 + } 31 + 32 + const data = await response.json(); 33 + 34 + await platform?.env?.USER_DATA_CACHE?.put( 35 + cacheKey, 36 + JSON.stringify({ data, updatedAt: Date.now() }) 37 + ); 38 + 39 + return json(data); 40 + } catch (error) { 41 + console.error('Error fetching npmx leaderboard:', error); 42 + return json({ error: 'Failed to fetch npmx leaderboard' }, { status: 500 }); 43 + } 44 + };