fix for #3
note to self: get rid of svelte and other frameworks they always cause more problems than they solve
This commit is contained in:
parent
801138ee12
commit
4966a3ac45
4 changed files with 125 additions and 114 deletions
|
@ -51,7 +51,9 @@
|
|||
<NostrIdentifier user={recipient.npub} />
|
||||
{/each}
|
||||
{: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}
|
||||
</div>
|
||||
<div class="letter-meta">
|
||||
|
|
|
@ -1,125 +1,82 @@
|
|||
<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}>
|
||||
<div class="avatar">
|
||||
{#if isLoading}
|
||||
<Icon icon="eos-icons:loading" />
|
||||
{:else if avatarUrl}
|
||||
<img alt="User avatar" src={avatarUrl} loading="lazy" />
|
||||
{#if emailAddress}
|
||||
<div class="user-chip" data-type="email">
|
||||
<div class="avatar">
|
||||
<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" />
|
||||
{: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}
|
||||
<Icon icon="ph:user" />
|
||||
<span class="user-text">{displayString}</span>
|
||||
{/if}
|
||||
</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?.()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.user-chip {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue