clean up and improve perf

This commit is contained in:
Ren Amamiya 2023-09-28 16:18:04 +07:00
parent 4f4e2f5ccd
commit cb3c95b133
19 changed files with 246 additions and 137 deletions

View File

@ -21,8 +21,10 @@
"@dnd-kit/core": "^6.0.8",
"@getalby/sdk": "^2.4.0",
"@nostr-dev-kit/ndk": "^1.3.0",
"@nostr-dev-kit/ndk-cache-dexie": "^1.3.0",
"@nostr-fetch/adapter-ndk": "^0.12.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",

View File

@ -14,12 +14,18 @@ dependencies:
'@nostr-dev-kit/ndk':
specifier: ^1.3.0
version: 1.3.0(typescript@5.2.2)
'@nostr-dev-kit/ndk-cache-dexie':
specifier: ^1.3.0
version: 1.3.0(typescript@5.2.2)
'@nostr-fetch/adapter-ndk':
specifier: ^0.12.2
version: 0.12.2(@nostr-dev-kit/ndk@1.3.0)(nostr-fetch@0.13.0)
'@radix-ui/react-alert-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-avatar':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collapsible':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0)
@ -782,6 +788,19 @@ packages:
fastq: 1.15.0
dev: true
/@nostr-dev-kit/ndk-cache-dexie@1.3.0(typescript@5.2.2):
resolution: {integrity: sha512-T9c2d/CennLXm/VO+4oKQgJln5+4GATM/Ko+QDkjOOKryqCYldap2QkIZ27IAsfhgGqMueyiU5g3oOcZFBgejQ==}
dependencies:
'@nostr-dev-kit/ndk': 1.3.0(typescript@5.2.2)
debug: 4.3.4
dexie: 3.2.4
nostr-tools: 1.16.0(typescript@5.2.2)
typescript-lru-cache: 2.0.0
transitivePeerDependencies:
- supports-color
- typescript
dev: false
/@nostr-dev-kit/ndk@1.3.0(typescript@5.2.2):
resolution: {integrity: sha512-ZRne/TF1IRqy35ZwLq3452YD+AUux0r5A+mZgHuGNRv7GJzr2gUl1QNRjs+Db+emXdaHn82LKTRn6cFrni1oPA==}
dependencies:
@ -883,6 +902,30 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-avatar@1.0.4(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.1
'@radix-ui/react-context': 1.0.1(@types/react@18.2.23)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.23)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.23)(react@18.2.0)
'@types/react': 18.2.23
'@types/react-dom': 18.2.8
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.8)(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
peerDependencies:
@ -3225,6 +3268,11 @@ packages:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
dev: false
/dexie@3.2.4:
resolution: {integrity: sha512-VKoTQRSv7+RnffpOJ3Dh6ozknBqzWw/F3iqMdsZg958R0AS8AnY9x9d1lbwENr0gzeGJHXKcGhAMRaqys6SxqA==}
engines: {node: '>=6.0'}
dev: false
/didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true

View File

@ -1,9 +0,0 @@
-- Add migration script here
CREATE TABLE
metadata (
id TEXT NOT NULL PRIMARY KEY,
event TEXT NOT NULL,
author TEXT NOT NULL,
kind NUMBER NOT NULL DEFAULt 0,
created_at INTEGER NOT NULL
);

View File

@ -239,12 +239,6 @@ fn main() {
sql: include_str!("../migrations/20230918235335_add_uniq_to_relay.sql"),
kind: MigrationKind::Up,
},
Migration {
version: 20230921085234,
description: "add metadata",
sql: include_str!("../migrations/20230921085234_add_metadata_table.sql"),
kind: MigrationKind::Up,
},
],
)
.build(),

View File

@ -1,20 +1,18 @@
import NDK from '@nostr-dev-kit/ndk';
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie';
import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
import { message } from '@tauri-apps/api/dialog';
import { fetch } from '@tauri-apps/api/http';
import { NostrFetcher } from 'nostr-fetch';
import { useEffect, useMemo, useState } from 'react';
import TauriAdapter from '@libs/ndk/cache';
import { useStorage } from '@libs/storage/provider';
export const NDKInstance = () => {
const { db } = useStorage();
const [ndk, setNDK] = useState<NDK | undefined>(undefined);
const [relayUrls, setRelayUrls] = useState<string[]>([]);
const cacheAdapter = useMemo(() => new TauriAdapter(), []);
const { db } = useStorage();
const fetcher = useMemo(
() => (ndk ? NostrFetcher.withCustomPool(ndkAdapter(ndk)) : null),
[ndk]
@ -57,9 +55,10 @@ export const NDKInstance = () => {
async function initNDK() {
const explicitRelayUrls = await getExplicitRelays();
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'lume_ndkcache' });
const instance = new NDK({
explicitRelayUrls,
cacheAdapter,
cacheAdapter: dexieAdapter,
});
try {
@ -77,10 +76,6 @@ export const NDKInstance = () => {
useEffect(() => {
if (!ndk) initNDK();
return () => {
cacheAdapter.saveCache();
};
}, []);
return {

View File

@ -1,4 +1,4 @@
import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { BaseDirectory, removeFile } from '@tauri-apps/api/fs';
import { Platform } from '@tauri-apps/api/os';
import Database from 'tauri-plugin-sql-api';
@ -6,7 +6,6 @@ import { Stronghold } from 'tauri-plugin-stronghold-api';
import { FULL_RELAYS } from '@stores/constants';
import { toRawEvent } from '@utils/rawEvent';
import { Account, DBEvent, Relays, Widget } from '@utils/types';
export class LumeStorage {
@ -124,6 +123,7 @@ export class LumeStorage {
public async updateLastLogin() {
const now = Math.floor(Date.now() / 1000);
this.account.last_login_at = now;
return await this.db.execute(
'UPDATE accounts SET last_login_at = $1 WHERE id = $2;',
[now, this.account.id]
@ -298,38 +298,6 @@ export class LumeStorage {
return results.length < 1;
}
public async createMetadata(event: NDKEvent) {
const rawEvent = toRawEvent(event);
return await this.db.execute(
'INSERT OR IGNORE INTO metadata (id, event, author, kind, created_at) VALUES ($1, $2, $3, $4, $5);',
[
rawEvent.id,
JSON.stringify(rawEvent),
rawEvent.pubkey,
rawEvent.kind,
rawEvent.created_at,
]
);
}
public async createProfile(pubkey: string, profile: NDKUserProfile) {
return await this.db.execute(
'INSERT OR REPLACE INTO metadata (id, event, author, kind, created_at) VALUES ($1, $2, $3, $4, $5);',
[pubkey, JSON.stringify(profile), pubkey, 0, Math.round(Date.now() / 1000)]
);
}
public async getMetadataByPubkey(pubkey: string) {
const results: DBEvent[] = await this.db.select(
'SELECT * FROM metadata WHERE author = $1 AND kind = "0" LIMIT 1;',
[pubkey]
);
if (results.length < 1) return null;
return JSON.parse(results[0].event as string) as NDKEvent;
}
public async getExplicitRelayUrls() {
if (!this.account) return FULL_RELAYS;

View File

@ -34,7 +34,7 @@ export function ComposerModal() {
<ComposeIcon className="h-4 w-4 text-white" />
</button>
</Dialog.Trigger>
<Dialog.Portal className="relative z-10">
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-2xl" />
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<div

View File

@ -1,7 +1,10 @@
import { minidenticon } from 'minidenticons';
import { ImgHTMLAttributes, useState } from 'react';
import { ImgHTMLAttributes, memo, useState } from 'react';
export function Image({ src, ...props }: ImgHTMLAttributes<HTMLImageElement>) {
export const Image = memo(function Image({
src,
...props
}: ImgHTMLAttributes<HTMLImageElement>) {
const [isError, setIsError] = useState(false);
if (isError || !src) {
@ -20,9 +23,10 @@ export function Image({ src, ...props }: ImgHTMLAttributes<HTMLImageElement>) {
currentTarget.onerror = null;
setIsError(true);
}}
loading="lazy"
decoding="async"
alt="lume default img"
style={{ contentVisibility: 'auto' }}
/>
);
}
});

View File

@ -1,13 +1,23 @@
import * as Collapsible from '@radix-ui/react-collapsible';
import { NavLink } from 'react-router-dom';
import { NavLink, useNavigate } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { ChatsList } from '@app/chats/components/list';
import { useStorage } from '@libs/storage/provider';
import { ActiveAccount } from '@shared/accounts/active';
import { ComposerModal } from '@shared/composer';
import { Frame } from '@shared/frame';
import { BellIcon, NavArrowDownIcon, NwcIcon, SpaceIcon, WorldIcon } from '@shared/icons';
import {
ArrowLeftIcon,
ArrowRightIcon,
BellIcon,
NavArrowDownIcon,
NwcIcon,
SpaceIcon,
WorldIcon,
} from '@shared/icons';
import { useActivities } from '@stores/activities';
import { useSidebar } from '@stores/sidebar';
@ -15,6 +25,9 @@ import { useSidebar } from '@stores/sidebar';
import { compactNumber } from '@utils/number';
export function Navigation() {
const { db } = useStorage();
const navigate = useNavigate();
const totalNewActivities = useActivities((state) => state.totalNewActivities);
const [chats, toggleChats] = useSidebar((state) => [state.chats, state.toggleChats]);
@ -30,8 +43,28 @@ export function Navigation() {
>
<div
data-tauri-drag-region
className="inline-flex h-16 w-full items-center justify-end px-3"
className="inline-flex h-16 w-full items-center justify-between px-3"
>
{db.platform !== 'darwin' ? (
<div className="inline-flex items-center gap-4 pl-2">
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex h-9 items-center justify-center"
>
<ArrowLeftIcon className="h-5 w-5 text-white/50" />
</button>
<button
type="button"
onClick={() => navigate(1)}
className="inline-flex h-9 items-center justify-center"
>
<ArrowRightIcon className="h-5 w-5 text-white/50" />
</button>
</div>
) : (
<div />
)}
<ComposerModal />
</div>
<div

View File

@ -49,7 +49,7 @@ export function ChildNote({ id, root }: { id: string; root?: string }) {
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.4rem)] w-0.5 bg-gradient-to-t from-white/20 to-white/10" />
<div className="relative mb-5 flex flex-col">
<div className="relative z-10 flex items-start gap-3">
<div className="inline-flex h-11 w-11 items-end justify-center rounded-lg bg-black pb-1">
<div className="inline-flex h-10 w-10 items-end justify-center rounded-lg bg-black pb-1">
<img src="/lume.png" alt="lume" className="h-auto w-1/3" />
</div>
<h5 className="truncate font-semibold leading-none text-white">

View File

@ -12,19 +12,26 @@ export function Reply({ event, root }: { event: NDKEventWithReplies; root?: stri
<div className="relative z-10">
<div className="relative flex flex-col">
<User pubkey={event.pubkey} time={event.created_at} />
<div className="-mt-6 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="-mt-5 flex items-start gap-3">
<div className="w-10 shrink-0" />
<div className="flex-1">
<TextNote content={event.content} />
<NoteActions id={event.id} pubkey={event.pubkey} root={root} />
<NoteActions
id={event.id}
pubkey={event.pubkey}
root={root}
extraButtons={false}
/>
</div>
</div>
</div>
{event.replies ? (
event.replies.map((sub) => <SubReply key={sub.id} event={sub} />)
) : (
<div className="pb-3" />
)}
<div className="pl-14">
{event.replies ? (
event.replies.map((sub) => <SubReply key={sub.id} event={sub} />)
) : (
<div className="pb-3" />
)}
</div>
</div>
</div>
);

View File

@ -51,7 +51,7 @@ export function RepliesList({ id }: { id: string }) {
<h5 className="mb-2 text-lg font-semibold text-white">
{data?.length || 0} replies
</h5>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-5">
{data?.length === 0 ? (
<div className="mt-2 flex w-full items-center justify-center rounded-xl bg-white/10 backdrop-blur-xl">
<div className="flex flex-col items-center justify-center gap-2 py-6">

View File

@ -7,11 +7,11 @@ export function SubReply({ event }: { event: NDKEvent }) {
return (
<div className="relative z-10 mb-3 mt-5 flex flex-col">
<User pubkey={event.pubkey} time={event.created_at} />
<div className="-mt-6 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div className="-mt-5 flex items-start gap-3">
<div className="w-10 shrink-0" />
<div className="flex-1">
<TextNote />
<NoteActions id={event.id} pubkey={event.pubkey} />
<TextNote content={event.content} />
<NoteActions id={event.id} pubkey={event.pubkey} extraButtons={false} />
</div>
</div>
</div>

View File

@ -5,7 +5,8 @@ export function NoteSkeleton() {
<div className="relative h-11 w-11 shrink overflow-hidden rounded-lg bg-white/10 backdrop-blur-xl" />
<div className="h-3 w-20 rounded bg-white/10 backdrop-blur-xl" />
</div>
<div className="-mt-6 animate-pulse pl-[49px]">
<div className="-mt-5 flex animate-pulse gap-3">
<div className="w-10 shrink-0" />
<div className="flex flex-col gap-1">
<div className="h-3 w-full rounded bg-white/10 backdrop-blur-xl" />
<div className="h-3 w-2/3 rounded bg-white/10 backdrop-blur-xl" />

View File

@ -1,11 +1,12 @@
import * as Avatar from '@radix-ui/react-avatar';
import * as HoverCard from '@radix-ui/react-hover-card';
import { minidenticon } from 'minidenticons';
import { memo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Link } from 'react-router-dom';
import remarkGfm from 'remark-gfm';
import { RepostIcon, WorldIcon } from '@shared/icons';
import { Image } from '@shared/image';
import { NIP05 } from '@shared/nip05';
import { formatCreatedAt } from '@utils/createdAt';
@ -32,7 +33,10 @@ export const User = memo(function User({
embedProfile?: string;
}) {
const { status, user } = useProfile(pubkey, embedProfile);
const createdAt = time ? formatCreatedAt(time, variant === 'chat') : 0;
const createdAt = formatCreatedAt(time, variant === 'chat');
const svgURI =
'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(pubkey, 90, 50));
if (status === 'loading') {
if (variant === 'avatar') {
@ -51,8 +55,8 @@ export const User = memo(function User({
}
return (
<div className="relative flex items-start gap-3">
<div className="relative z-10 h-10 w-10 shrink-0 animate-pulse overflow-hidden rounded-lg bg-white/10 backdrop-blur-xl" />
<div className="flex items-start gap-3">
<div className="h-10 w-10 shrink-0 animate-pulse overflow-hidden rounded-lg bg-white/10 backdrop-blur-xl" />
<div className="h-3.5 w-36 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
</div>
);
@ -60,14 +64,20 @@ export const User = memo(function User({
if (variant === 'mention') {
return (
<div className="relative z-10 flex items-center gap-2">
<button type="button" className="relative z-40 h-6 w-6 shrink-0 overflow-hidden">
<Image
<div className="flex items-center gap-2">
<Avatar.Root className="shrink-0">
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
className="h-6 w-6 rounded object-cover"
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-6 w-6 rounded"
/>
</button>
<Avatar.Fallback delayMs={300}>
<img src={svgURI} alt={pubkey} className="h-6 w-6 rounded bg-black" />
</Avatar.Fallback>
</Avatar.Root>
<div className="flex flex-1 items-baseline gap-2">
<h5 className="max-w-[10rem] truncate font-semibold leading-none text-white">
{user?.name ||
@ -85,11 +95,19 @@ export const User = memo(function User({
if (variant === 'large') {
return (
<div className="flex h-full w-full flex-col gap-2.5">
<Image
src={user?.picture || user?.image}
alt={pubkey}
className="h-14 w-14 shrink-0 rounded-lg object-cover"
/>
<Avatar.Root className="shrink-0">
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-14 w-14 rounded-lg"
/>
<Avatar.Fallback delayMs={300}>
<img src={svgURI} alt={pubkey} className="h-14 w-14 rounded-lg bg-black" />
</Avatar.Fallback>
</Avatar.Root>
<div className="flex h-full flex-col items-start justify-between">
<div className="flex flex-col items-start gap-1 text-start">
<p className="max-w-[15rem] truncate text-lg font-semibold leading-none text-white">
@ -125,11 +143,19 @@ export const User = memo(function User({
if (variant === 'simple') {
return (
<div className="flex items-center gap-2.5">
<Image
src={user?.picture || user?.image}
alt={pubkey}
className="h-12 w-12 shrink-0 rounded-lg object-cover"
/>
<Avatar.Root className="shrink-0">
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-12 w-12 rounded-lg"
/>
<Avatar.Fallback delayMs={300}>
<img src={svgURI} alt={pubkey} className="h-12 w-12 rounded-lg bg-black" />
</Avatar.Fallback>
</Avatar.Root>
<div className="flex w-full flex-col items-start gap-1">
<h3 className="max-w-[15rem] truncate font-medium leading-none text-white">
{user?.name || user?.display_name || user?.displayName}
@ -144,11 +170,19 @@ export const User = memo(function User({
if (variant === 'avatar') {
return (
<Image
src={user?.picture || user?.image}
alt={pubkey}
className="h-12 w-12 shrink-0 rounded-lg object-cover"
/>
<Avatar.Root>
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-12 w-12 rounded-lg"
/>
<Avatar.Fallback delayMs={300}>
<img src={svgURI} alt={pubkey} className="h-12 w-12 rounded-lg bg-black" />
</Avatar.Fallback>
</Avatar.Root>
);
}
@ -159,11 +193,19 @@ export const User = memo(function User({
<RepostIcon className="h-6 w-6 text-blue-500" />
</div>
<div className="inline-flex items-center gap-2">
<Image
src={user?.picture || user?.image}
alt={pubkey}
className="relative z-20 inline-block h-6 w-6 rounded"
/>
<Avatar.Root className="shrink-0">
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-6 w-6 rounded"
/>
<Avatar.Fallback delayMs={300}>
<img src={svgURI} alt={pubkey} className="h-6 w-6 rounded bg-black" />
</Avatar.Fallback>
</Avatar.Root>
<div className="inline-flex items-baseline gap-1">
<h5 className="max-w-[10rem] truncate font-medium leading-none text-white/80">
{user?.name ||
@ -181,14 +223,22 @@ export const User = memo(function User({
if (variant === 'thread') {
return (
<div className="flex items-center gap-3">
<Image
src={user?.picture || user?.image}
alt={pubkey}
className="relative z-20 inline-block h-10 w-10 rounded-lg"
/>
<Avatar.Root className="shrink-0">
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-10 w-10 rounded-lg"
/>
<Avatar.Fallback delayMs={300}>
<img src={svgURI} alt={pubkey} className="h-10 w-10 rounded-lg bg-black" />
</Avatar.Fallback>
</Avatar.Root>
<div className="flex flex-1 flex-col gap-2">
<h5 className="max-w-[15rem] truncate font-semibold leading-none text-white">
{user?.name || user?.display_name || user?.displayName}
{user?.name || user?.display_name || user?.displayName || 'Anon'}
</h5>
<div className="inline-flex items-center gap-2">
<span className="leading-none text-white/50">{createdAt}</span>
@ -204,16 +254,23 @@ export const User = memo(function User({
<HoverCard.Root>
<div className="relative z-10 flex items-start gap-3">
<HoverCard.Trigger asChild>
<button
type="button"
className="relative z-40 h-10 w-10 shrink-0 overflow-hidden"
>
<Image
<Avatar.Root className="shrink-0">
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
className="h-10 w-10 rounded-lg object-cover"
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-10 w-10 rounded-lg border border-white/5"
/>
</button>
<Avatar.Fallback delayMs={300}>
<img
src={svgURI}
alt={pubkey}
className="h-10 w-10 rounded-lg border border-white/5 bg-black"
/>
</Avatar.Fallback>
</Avatar.Root>
</HoverCard.Trigger>
<div className="flex flex-1 items-baseline gap-2">
<h5 className="max-w-[15rem] truncate font-semibold leading-none text-white">
@ -232,11 +289,23 @@ export const User = memo(function User({
sideOffset={5}
>
<div className="flex gap-2.5 border-b border-white/5 px-3 py-3">
<Image
src={user?.picture || user?.image}
alt={pubkey}
className="h-10 w-10 shrink-0 rounded-lg object-cover"
/>
<Avatar.Root className="shrink-0">
<Avatar.Image
src={user?.picture || user?.image}
alt={pubkey}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-10 w-10 rounded-lg border border-white/5"
/>
<Avatar.Fallback delayMs={300}>
<img
src={svgURI}
alt={pubkey}
className="h-10 w-10 rounded-lg border border-white/5 bg-black"
/>
</Avatar.Fallback>
</Avatar.Root>
<div className="flex flex-1 flex-col gap-2">
<div className="inline-flex flex-col gap-1">
<h5 className="text-sm font-semibold leading-none">

View File

@ -19,6 +19,8 @@ export function EventLoader({ firstTime }: { firstTime: boolean }) {
useEffect(() => {
async function getEvents() {
const events = await getAllEventsSinceLastLogin();
console.log('total event found: ', events.data.length);
const promises = await Promise.all(
events.data.map(async (event) => await db.createEvent(event))
);

View File

@ -265,7 +265,7 @@ export function useNostr() {
if (!customSince) {
if (dbEventsEmpty || db.account.last_login_at === 0) {
since = db.account.network.length > 400 ? nHoursAgo(12) : nHoursAgo(24);
since = db.account.network.length > 500 ? nHoursAgo(12) : nHoursAgo(24);
} else {
since = db.account.last_login_at;
}

View File

@ -2,10 +2,8 @@ import { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
export function useProfile(pubkey: string, embed?: string) {
const { db } = useStorage();
const { ndk } = useNDK();
const {
status,
@ -21,13 +19,7 @@ export function useProfile(pubkey: string, embed?: string) {
const cleanPubkey = pubkey.replace('-', '');
const user = ndk.getUser({ hexpubkey: cleanPubkey });
const profile = await user.fetchProfile({ closeOnEose: true });
if (!user.profile) return Promise.reject(new Error('profile not found'));
await db.createProfile(cleanPubkey, profile);
return user.profile;
return await user.fetchProfile();
},
{
enabled: !!ndk,
@ -35,6 +27,7 @@ export function useProfile(pubkey: string, embed?: string) {
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
retry: 2,
}
);

View File

@ -20,6 +20,8 @@ function isURL(string: string) {
}
export function parser(eventContent: string) {
if (!eventContent) return '';
try {
const content: RichContent = {
parsed: null,