mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-02 18:00:47 +00:00
clean up and improve perf
This commit is contained in:
parent
4f4e2f5ccd
commit
cb3c95b133
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
);
|
@ -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(),
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -12,14 +12,20 @@ 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>
|
||||
<div className="pl-14">
|
||||
{event.replies ? (
|
||||
event.replies.map((sub) => <SubReply key={sub.id} event={sub} />)
|
||||
) : (
|
||||
@ -27,5 +33,6 @@ export function Reply({ event, root }: { event: NDKEventWithReplies; root?: stri
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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
|
||||
<Avatar.Root className="shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-14 w-14 shrink-0 rounded-lg object-cover"
|
||||
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
|
||||
<Avatar.Root className="shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-12 w-12 shrink-0 rounded-lg object-cover"
|
||||
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
|
||||
<Avatar.Root>
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-12 w-12 shrink-0 rounded-lg object-cover"
|
||||
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
|
||||
<Avatar.Root className="shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="relative z-20 inline-block h-6 w-6 rounded"
|
||||
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
|
||||
<Avatar.Root className="shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="relative z-20 inline-block h-10 w-10 rounded-lg"
|
||||
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
|
||||
<Avatar.Root className="shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 shrink-0 rounded-lg object-cover"
|
||||
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">
|
||||
|
@ -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))
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -20,6 +20,8 @@ function isURL(string: string) {
|
||||
}
|
||||
|
||||
export function parser(eventContent: string) {
|
||||
if (!eventContent) return '';
|
||||
|
||||
try {
|
||||
const content: RichContent = {
|
||||
parsed: null,
|
||||
|
Loading…
Reference in New Issue
Block a user