note to self: get rid of svelte and other frameworks they always cause more problems than they solve
This commit is contained in:
Danny Morabito 2024-12-05 21:51:12 +01:00
parent 801138ee12
commit 4966a3ac45
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
4 changed files with 125 additions and 114 deletions

View file

@ -51,7 +51,9 @@
<NostrIdentifier user={recipient.npub} /> <NostrIdentifier user={recipient.npub} />
{/each} {/each}
{:else if letter.from} {:else if letter.from}
<NostrIdentifier user={letter.from.npub} emailAddress={letter.emailAddress} /> {#key letter.from.npub}
<NostrIdentifier user={letter.from.npub} emailAddress={letter.emailAddress} />
{/key}
{/if} {/if}
</div> </div>
<div class="letter-meta"> <div class="letter-meta">

View file

@ -1,125 +1,82 @@
<script lang="ts"> <script lang="ts">
import Icon from '@iconify/svelte'; import Icon from '@iconify/svelte';
import { ndk } from '$lib/stores.svelte'; import { ndk } from '$lib/stores.svelte';
import { isValidNip05 } from '$lib/utils.svelte'; import { isValidNip05, loadUserAvatar, loadUserDisplay } from '$lib/utils.svelte';
import { onMount } from 'svelte';
import type { NDKUser } from '@nostr-dev-kit/ndk';
import Tooltip from './Tooltip.svelte'; import Tooltip from './Tooltip.svelte';
type Props = { let { user, extraButtons, emailAddress } = $props<{
user: string; user: string;
extraButtons?: () => void; extraButtons?: () => void;
emailAddress?: string; emailAddress?: string;
}; }>();
let { user, extraButtons, emailAddress }: Props = $props();
const CACHE_DURATION = 5 * 60 * 1000;
let npub = $state(''); let npub = $state('');
let ndkUser = $state<NDKUser>(); let displayString = $state('');
let displayString = $state(emailAddress || user); let isNip05 = $state(false);
let chipType = $state(emailAddress ? 'email' : isValidNpub(user) ? 'npub' : 'nip05');
let avatarUrl = $state<string | undefined>();
let isLoading = $state(true);
function isValidNpub(npub: string): boolean { async function getUser() {
return npub.startsWith('npub'); let u;
if (isValidNip05(user))
u = await $ndk.getUserFromNip05(user);
else if (user.startsWith('npub'))
u = await $ndk.getUser({ npub: user });
else
u = await $ndk.getUser({ pubkey: user });
npub = u.npub;
return u;
} }
function getCachedAvatar(identifier: string): string | null { async function getAvatarForUser() {
try { if (!user) return;
const cached = localStorage.getItem(`avatar_${identifier}`); return await loadUserAvatar(await getUser());
if (!cached) return null; }
const { url, timestamp } = JSON.parse(cached); async function getDisplayStringForUser() {
return Date.now() - timestamp > CACHE_DURATION ? null : url; if (!user) return;
} catch { if (emailAddress) return emailAddress;
return null; const allDisplays = loadUserDisplay(await getUser());
for await(const display of allDisplays) {
displayString = display.display;
isNip05 = display.isNip05;
} }
} }
function setCachedAvatar(identifier: string, url: string): void { $effect(() => getDisplayStringForUser());
try {
localStorage.setItem(
`avatar_${identifier}`,
JSON.stringify({ url, timestamp: Date.now() })
);
} catch (error) {
console.error('Cache write error:', error);
}
}
onMount(async () => {
if (!user) {
isLoading = false;
return;
}
try {
ndkUser = isValidNpub(user)
? $ndk.getUser({ npub: user })
: await $ndk.getUserFromNip05(user);
if (!ndkUser) {
isLoading = false;
return;
}
npub = ndkUser.npub;
await ndkUser.fetchProfile();
if (ndkUser.profile?.nip05) {
const fetchFromNip05 = await $ndk.getUserFromNip05(ndkUser.profile.nip05);
if (fetchFromNip05?.pubkey === ndkUser.pubkey) {
displayString = ndkUser.profile.nip05;
chipType = 'nip05';
}
}
const cachedUrl = getCachedAvatar(user);
if (cachedUrl) {
avatarUrl = cachedUrl;
} else {
let avatarUser;
if (isValidNip05(user)) {
avatarUser = await $ndk.getUserFromNip05(user);
} else if (isValidNpub(user)) {
avatarUser = $ndk.getUser({ npub: user });
}
if (avatarUser) {
await avatarUser.fetchProfile();
avatarUrl = avatarUser.profile?.image;
if (avatarUrl) setCachedAvatar(user, avatarUrl);
}
}
} catch (error) {
console.error('Error loading user data:', error);
} finally {
isLoading = false;
}
});
</script> </script>
<div class="user-chip" data-type={chipType}> {#if emailAddress}
<div class="avatar"> <div class="user-chip" data-type="email">
{#if isLoading} <div class="avatar">
<Icon icon="eos-icons:loading" /> <Icon icon="mdi:email" width="32" />
{:else if avatarUrl} </div>
<img alt="User avatar" src={avatarUrl} loading="lazy" /> <span class="user-text">
{emailAddress}
</span>
</div>
{:else}
<div class="user-chip" data-type={isNip05 ? 'nip05' : 'npub'}>
<div class="avatar">
{#await getAvatarForUser()}
<Icon icon="eos-icons:loading" />
{:then avatarUrl}
{#if avatarUrl}
<img alt="User avatar" src={avatarUrl} loading="lazy" />
{:else}
<Icon icon="ph:user" />
{/if}
{/await}
</div>
{#if npub !== displayString}
<Tooltip position="bottom" content={npub}>
<span class="user-text">{displayString}</span>
</Tooltip>
{:else} {:else}
<Icon icon="ph:user" /> <span class="user-text">{displayString}</span>
{/if} {/if}
</div> </div>
{#if npub !== displayString}
<Tooltip position="bottom" content={npub}>
<span class="user-text">{displayString}</span>
</Tooltip>
{:else}
<span class="user-text">{displayString}</span>
{/if}
{@render extraButtons?.()} {@render extraButtons?.()}
</div> {/if}
<style> <style>
.user-chip { .user-chip {

View file

@ -205,3 +205,69 @@ export function appendToBody(node: HTMLElement) {
} }
}; };
} }
const userAvatarCache = new Map<string, string | undefined>();
export async function loadUserAvatar(user: NDKUser) {
if (userAvatarCache.has(user.npub)) return userAvatarCache.get(user.npub);
await user.fetchProfile();
if (!user.profile) {
userAvatarCache.set(user.npub, undefined);
return;
}
const avatar = user.profile.image! as string;
userAvatarCache.set(user.npub, avatar);
return avatar;
}
const userDisplayCache = new Map<
string,
{
display: string;
isNip05: boolean;
}
>();
export async function* loadUserDisplay(user: NDKUser) {
await waitForNDK();
if (userDisplayCache.has(user.npub)) {
yield userDisplayCache.get(user.npub)!;
return;
}
yield {
display: user.npub,
isNip05: false
};
await user.fetchProfile();
if (!user.profile?.nip05) {
userDisplayCache.set(user.npub, {
display: user.npub,
isNip05: false
});
yield {
display: user.npub,
isNip05: false
};
return;
}
const userWithNip05 = await ndk.getUserFromNip05(user.profile.nip05);
if (userWithNip05?.npub == user.npub) {
userDisplayCache.set(user.npub, {
display: user.profile.nip05,
isNip05: true
});
yield {
display: user.profile.nip05,
isNip05: true
};
} else {
userDisplayCache.set(user.npub, {
display: user.npub,
isNip05: false
});
yield {
display: user.npub,
isNip05: false
};
}
}

View file

@ -5,7 +5,6 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import FoldersListSidebar from '../components/FoldersListSidebar.svelte'; import FoldersListSidebar from '../components/FoldersListSidebar.svelte';
import IconButton from '../components/IconButton.svelte'; import IconButton from '../components/IconButton.svelte';
import { type PartialLetter, sortedLetters } from '$lib/letterHandler.svelte';
import Dialog from '../components/Dialog.svelte'; import Dialog from '../components/Dialog.svelte';
import MoveFolderDialog from '../components/MoveFolderDialog.svelte'; import MoveFolderDialog from '../components/MoveFolderDialog.svelte';
import MailboxFolderItems from '../components/MailboxFolderItems.svelte'; import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
@ -37,25 +36,12 @@
activeFolderId = newFolder; activeFolderId = newFolder;
$pageTitle = folders.find((f) => f.id === newFolder)!.name; $pageTitle = folders.find((f) => f.id === newFolder)!.name;
$pageIcon = folders.find((f) => f.id === newFolder)!.icon; $pageIcon = folders.find((f) => f.id === newFolder)!.icon;
reloadLetters();
}
let letters = $state<PartialLetter[]>([]);
async function reloadLetters() {
letters = [];
const ignoredIDs =
activeFolderId == 'inbox' || activeFolderId == 'sent'
? folders.flatMap((f) => f.letters)
: [];
for await (const newLetters of sortedLetters(activeFolder, ignoredIDs)) letters = newLetters;
} }
onMount(async () => { onMount(async () => {
$pageTitle = 'Inbox'; $pageTitle = 'Inbox';
$pageIcon = 'solar:inbox-bold-duotone'; $pageIcon = 'solar:inbox-bold-duotone';
await loadFolders(); await loadFolders();
changeFolder('inbox');
initialLoading = true; initialLoading = true;
}); });
</script> </script>