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} />
{/each}
{:else if letter.from}
{#key letter.from.npub}
<NostrIdentifier user={letter.from.npub} emailAddress={letter.emailAddress} />
{/key}
{/if}
</div>
<div class="letter-meta">

View file

@ -1,115 +1,70 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import { ndk } from '$lib/stores.svelte';
import { isValidNip05 } from '$lib/utils.svelte';
import { onMount } from 'svelte';
import type { NDKUser } from '@nostr-dev-kit/ndk';
import { isValidNip05, loadUserAvatar, loadUserDisplay } from '$lib/utils.svelte';
import Tooltip from './Tooltip.svelte';
type Props = {
let { user, extraButtons, emailAddress } = $props<{
user: string;
extraButtons?: () => void;
emailAddress?: string;
};
}>();
let { user, extraButtons, emailAddress }: Props = $props();
const CACHE_DURATION = 5 * 60 * 1000;
let npub = $state('');
let ndkUser = $state<NDKUser>();
let displayString = $state(emailAddress || user);
let chipType = $state(emailAddress ? 'email' : isValidNpub(user) ? 'npub' : 'nip05');
let avatarUrl = $state<string | undefined>();
let isLoading = $state(true);
let displayString = $state('');
let isNip05 = $state(false);
function isValidNpub(npub: string): boolean {
return npub.startsWith('npub');
async function getUser() {
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 {
try {
const cached = localStorage.getItem(`avatar_${identifier}`);
if (!cached) return null;
async function getAvatarForUser() {
if (!user) return;
return await loadUserAvatar(await getUser());
}
const { url, timestamp } = JSON.parse(cached);
return Date.now() - timestamp > CACHE_DURATION ? null : url;
} catch {
return null;
async function getDisplayStringForUser() {
if (!user) return;
if (emailAddress) return emailAddress;
const allDisplays = loadUserDisplay(await getUser());
for await(const display of allDisplays) {
displayString = display.display;
isNip05 = display.isNip05;
}
}
function setCachedAvatar(identifier: string, url: string): void {
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;
}
});
$effect(() => getDisplayStringForUser());
</script>
<div class="user-chip" data-type={chipType}>
{#if emailAddress}
<div class="user-chip" data-type="email">
<div class="avatar">
{#if isLoading}
<Icon icon="mdi:email" width="32" />
</div>
<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" />
{:else if avatarUrl}
{: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}>
@ -118,8 +73,10 @@
{:else}
<span class="user-text">{displayString}</span>
{/if}
</div>
{@render extraButtons?.()}
</div>
{/if}
<style>
.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 FoldersListSidebar from '../components/FoldersListSidebar.svelte';
import IconButton from '../components/IconButton.svelte';
import { type PartialLetter, sortedLetters } from '$lib/letterHandler.svelte';
import Dialog from '../components/Dialog.svelte';
import MoveFolderDialog from '../components/MoveFolderDialog.svelte';
import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
@ -37,25 +36,12 @@
activeFolderId = newFolder;
$pageTitle = folders.find((f) => f.id === newFolder)!.name;
$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 () => {
$pageTitle = 'Inbox';
$pageIcon = 'solar:inbox-bold-duotone';
await loadFolders();
changeFolder('inbox');
initialLoading = true;
});
</script>