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} />
|
<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">
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 { 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>
|
||||||
|
|
Loading…
Reference in a new issue