From 10ca2e906f3e238be4e53a062787ed7e8158a170 Mon Sep 17 00:00:00 2001 From: dawn <90008@gaze.systems> Date: Fri, 26 Dec 2025 21:35:31 +0000 Subject: [PATCH] virtual list --- deno.lock | 8 ++++++++ package.json | 1 + src/app.html | 3 ++- src/components/FollowingView.svelte | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------- src/components/SettingsView.svelte | 4 ++-- src/lib/settings.ts | 21 +++++++++++++++++---- src/routes/+page.svelte | 6 ++++-- 7 file(s) changed, 89 insertion(s)(+), 65 deletion(s)(-) diff --git a/deno.lock b/deno.lock --- a/deno.lock +++ b/deno.lock @@ -22,6 +22,7 @@ "npm:@sveltejs/vite-plugin-svelte@^6.2.1": "6.2.1_svelte@5.46.1__acorn@8.15.0_vite@7.3.0__@types+node@25.0.3__picomatch@4.0.3_@types+node@25.0.3", "npm:@tailwindcss/forms@~0.5.11": "0.5.11_tailwindcss@4.1.18", "npm:@tailwindcss/vite@^4.1.18": "4.1.18_vite@7.3.0__@types+node@25.0.3__picomatch@4.0.3_@types+node@25.0.3", + "npm:@tutorlatin/svelte-tiny-virtual-list@^3.0.17": "3.0.17_svelte@5.46.1__acorn@8.15.0", "npm:@types/node@^25.0.3": "25.0.3", "npm:@wora/cache-persist@^2.2.1": "2.2.1", "npm:async-cache-dedupe@^3.4.0": "3.4.0", @@ -714,6 +715,12 @@ "@tailwindcss/oxide", "tailwindcss", "vite" + ] + }, + "@tutorlatin/svelte-tiny-virtual-list@3.0.17_svelte@5.46.1__acorn@8.15.0": { + "integrity": "sha512-OvFRITfbWdsFk7VR2FKVJiBMPlgbyc81hqbFORXdEcBXcT91XRdLXfhSbS8o14ntUBMFPWVv19fti+Ez50q45g==", + "dependencies": [ + "svelte" ] }, "@types/cookie@0.6.0": { @@ -1814,6 +1821,7 @@ "npm:@sveltejs/vite-plugin-svelte@^6.2.1", "npm:@tailwindcss/forms@~0.5.11", "npm:@tailwindcss/vite@^4.1.18", + "npm:@tutorlatin/svelte-tiny-virtual-list@^3.0.17", "npm:@types/node@^25.0.3", "npm:@wora/cache-persist@^2.2.1", "npm:async-cache-dedupe@^3.4.0", diff --git a/package.json b/package.json --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@atcute/tid": "^1.0.3", "@floating-ui/dom": "^1.7.4", "@soffinal/websocket": "^0.2.1", + "@tutorlatin/svelte-tiny-virtual-list": "^3.0.17", "@wora/cache-persist": "^2.2.1", "async-cache-dedupe": "^3.4.0", "hash-wasm": "^4.12.0", diff --git a/src/app.html b/src/app.html --- a/src/app.html +++ b/src/app.html @@ -2,7 +2,8 @@ - + + %sveltekit.head% diff --git a/src/components/FollowingView.svelte b/src/components/FollowingView.svelte --- a/src/components/FollowingView.svelte +++ b/src/components/FollowingView.svelte @@ -6,8 +6,7 @@ import { getRelativeTime } from '$lib/date'; import { generateColorForDid } from '$lib/accounts'; import { type AtprotoDid } from '@atcute/lexicons/syntax'; - import { flip } from 'svelte/animate'; - import { cubicOut } from 'svelte/easing'; + import VirtualList from '@tutorlatin/svelte-tiny-virtual-list'; interface Props { selectedDid: Did; @@ -194,64 +193,16 @@ ); -{#snippet followingItems()} - {#each sortedFollowing as user (user.did)} - {@const stats = user.data!} - {@const lastPostAt = stats.lastPostAt} - {@const relTime = getRelativeTime(lastPostAt, currentTime)} - {@const color = generateColorForDid(user.did)} -
-
- -
-
- {#await Promise.all([user.profile, user.handle]) then [displayName, handle]} - {displayName || handle} - @{handle} - {/await} -
-
- - posted {relTime} - {relTime !== 'now' ? 'ago' : ''} - - {#if stats.recentPostCount > 0} - - {stats.recentPostCount} posts / 6h - - {/if} - {#if followingSort === 'conversational' && stats.conversationalScore > 0} - - ★ {stats.conversationalScore.toFixed(1)} - - {/if} -
-
-
-
- {/each} -{/snippet} - -
-
+
+
-

following

+

following

-
+
{#each ['recent', 'active', 'conversational'] as type (type)}
diff --git a/src/components/SettingsView.svelte b/src/components/SettingsView.svelte --- a/src/components/SettingsView.svelte +++ b/src/components/SettingsView.svelte @@ -155,9 +155,9 @@
{ - const stored = localStorage.getItem('settings'); + // Prevent SSR crash if localStorage is missing + const stored = typeof localStorage !== 'undefined' ? localStorage.getItem('settings') : null; const initial: Partial = stored ? JSON.parse(stored) : defaultSettings; initial.endpoints = { ...defaultSettings.endpoints, ...initial.endpoints }; @@ -35,28 +36,40 @@ const { subscribe, set, update } = writable(initial as Settings); subscribe((settings) => { + if (typeof document === 'undefined') return; const theme = settings.theme; document.documentElement.style.setProperty('--nucleus-bg', theme.bg); document.documentElement.style.setProperty('--nucleus-fg', theme.fg); document.documentElement.style.setProperty('--nucleus-accent', theme.accent); document.documentElement.style.setProperty('--nucleus-accent2', theme.accent2); + + const oldMeta = document.querySelector('meta[name="theme-color"]'); + if (oldMeta) oldMeta.remove(); + + const metaThemeColor = document.createElement('meta'); + metaThemeColor.setAttribute('name', 'theme-color'); + metaThemeColor.setAttribute('content', theme.bg); + document.head.appendChild(metaThemeColor); }); return { subscribe, set: (value: Settings) => { - localStorage.setItem('settings', JSON.stringify(value)); + if (typeof localStorage !== 'undefined') + localStorage.setItem('settings', JSON.stringify(value)); set(value); }, update: (fn: (value: Settings) => Settings) => { update((value) => { const newValue = fn(value); - localStorage.setItem('settings', JSON.stringify(newValue)); + if (typeof localStorage !== 'undefined') + localStorage.setItem('settings', JSON.stringify(newValue)); return newValue; }); }, reset: () => { - localStorage.setItem('settings', JSON.stringify(defaultSettings)); + if (typeof localStorage !== 'undefined') + localStorage.setItem('settings', JSON.stringify(defaultSettings)); set(defaultSettings); } }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -352,8 +352,8 @@
@@ -389,6 +389,8 @@
+ +