improve notification and performance
This commit is contained in:
@@ -35,18 +35,18 @@ export function HomeScreen() {
|
||||
queryFn: async () => {
|
||||
const dbWidgets = await db.getWidgets();
|
||||
const defaultWidgets = [
|
||||
{
|
||||
id: '9998',
|
||||
title: 'Notification',
|
||||
content: '',
|
||||
kind: WIDGET_KIND.notification,
|
||||
},
|
||||
{
|
||||
id: '9999',
|
||||
title: 'Newsfeed',
|
||||
content: '',
|
||||
kind: WIDGET_KIND.newsfeed,
|
||||
},
|
||||
{
|
||||
id: '9998',
|
||||
title: 'Notification',
|
||||
content: '',
|
||||
kind: WIDGET_KIND.notification,
|
||||
},
|
||||
];
|
||||
|
||||
return [...defaultWidgets, ...dbWidgets];
|
||||
|
||||
@@ -66,7 +66,7 @@ export const NDKInstance = () => {
|
||||
|
||||
// connect
|
||||
await instance.connect();
|
||||
const tmpFetcher = NostrFetcher.withCustomPool(ndkAdapter(instance));
|
||||
const _fetcher = NostrFetcher.withCustomPool(ndkAdapter(instance));
|
||||
|
||||
// update account's metadata
|
||||
if (db.account) {
|
||||
@@ -88,7 +88,7 @@ export const NDKInstance = () => {
|
||||
const rootIds = new Set();
|
||||
const dedupQueue = new Set();
|
||||
|
||||
const events = await tmpFetcher.fetchLatestEvents(
|
||||
const events = await _fetcher.fetchLatestEvents(
|
||||
explicitRelayUrls,
|
||||
{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
@@ -127,7 +127,7 @@ export const NDKInstance = () => {
|
||||
signal: AbortSignal;
|
||||
pageParam: number;
|
||||
}) => {
|
||||
const events = await tmpFetcher.fetchLatestEvents(
|
||||
const events = await _fetcher.fetchLatestEvents(
|
||||
explicitRelayUrls,
|
||||
{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
||||
@@ -147,7 +147,7 @@ export const NDKInstance = () => {
|
||||
}
|
||||
|
||||
setNDK(instance);
|
||||
setFetcher(tmpFetcher);
|
||||
setFetcher(_fetcher);
|
||||
setRelayUrls(explicitRelayUrls);
|
||||
} catch (e) {
|
||||
const yes = await ask(
|
||||
|
||||
@@ -73,7 +73,7 @@ export class LumeStorage {
|
||||
[pubkey]
|
||||
);
|
||||
|
||||
if (results.length < 1) return null;
|
||||
if (!results.length) return null;
|
||||
|
||||
if (typeof results[0].profile === 'string')
|
||||
results[0].profile = JSON.parse(results[0].profile);
|
||||
@@ -87,7 +87,7 @@ export class LumeStorage {
|
||||
[id]
|
||||
);
|
||||
|
||||
if (results.length < 1) return null;
|
||||
if (!results.length) return null;
|
||||
return results[0];
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class LumeStorage {
|
||||
`SELECT * FROM ndk_events WHERE id IN (${idsArr}) ORDER BY id;`
|
||||
);
|
||||
|
||||
if (results.length < 1) return [];
|
||||
if (!results.length) return [];
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export class LumeStorage {
|
||||
[pubkey]
|
||||
);
|
||||
|
||||
if (results.length < 1) return [];
|
||||
if (!results.length) return [];
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export class LumeStorage {
|
||||
[kind]
|
||||
);
|
||||
|
||||
if (results.length < 1) return [];
|
||||
if (!results.length) return [];
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export class LumeStorage {
|
||||
[kind, pubkey]
|
||||
);
|
||||
|
||||
if (results.length < 1) return [];
|
||||
if (!results.length) return [];
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ export class LumeStorage {
|
||||
[tagValue]
|
||||
);
|
||||
|
||||
if (results.length < 1) return [];
|
||||
if (!results.length) return [];
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
queries: {
|
||||
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), // 10 seconds
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -22,7 +25,7 @@ const root = createRoot(container);
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
<Toaster position="top-center" closeButton theme="system" />
|
||||
<Toaster position="top-center" theme="system" closeButton />
|
||||
<StorageProvider>
|
||||
<NDKProvider>
|
||||
<App />
|
||||
|
||||
@@ -7,14 +7,14 @@ export function TextKind({ content, textmode }: { content: string; textmode?: bo
|
||||
|
||||
if (textmode) {
|
||||
return (
|
||||
<div className="break-p line-clamp-3 select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
<div className="line-clamp-3 select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
{parsedContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'min-w-0 px-3'}>
|
||||
<div className="min-w-0 px-3">
|
||||
<div className="break-p select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
{parsedContent}
|
||||
</div>
|
||||
|
||||
@@ -1,109 +1,158 @@
|
||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { ShareIcon } from '@shared/icons';
|
||||
import {
|
||||
MemoizedArticleKind,
|
||||
MemoizedFileKind,
|
||||
MemoizedTextKind,
|
||||
NoteSkeleton,
|
||||
} from '@shared/notes';
|
||||
import { ReplyIcon, RepostIcon } from '@shared/icons';
|
||||
import { ChildNote, TextKind } from '@shared/notes';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { WIDGET_KIND } from '@stores/constants';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
||||
export function NotifyNote({ event }: { event: NDKEvent }) {
|
||||
const createdAt = formatCreatedAt(event.created_at, false);
|
||||
const rootEventId = event.tags.find((el) => el[0] === 'e')?.[1];
|
||||
|
||||
const { status, data } = useEvent(rootEventId);
|
||||
const { getEventThread } = useNostr();
|
||||
const { addWidget } = useWidget();
|
||||
|
||||
const renderKind = (event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return <MemoizedTextKind key={event.id} content={event.content} textmode />;
|
||||
case NDKKind.Article:
|
||||
return <MemoizedArticleKind key={event.id} id={event.id} tags={event.tags} />;
|
||||
case 1063:
|
||||
return <MemoizedFileKind key={event.id} tags={event.tags} />;
|
||||
default:
|
||||
return (
|
||||
<div className="break-p line-clamp-3 select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
{event.content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
const thread = getEventThread(event.tags);
|
||||
const createdAt = formatCreatedAt(event.created_at, false);
|
||||
|
||||
const renderText = (kind: number) => {
|
||||
switch (kind) {
|
||||
case NDKKind.Text:
|
||||
return 'replied';
|
||||
case NDKKind.Reaction: {
|
||||
return `reacted your post`;
|
||||
}
|
||||
case NDKKind.Repost:
|
||||
return 'reposted your post';
|
||||
case NDKKind.Zap:
|
||||
return 'zapped your post';
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
};
|
||||
|
||||
if (status === 'pending') {
|
||||
if (event.kind === NDKKind.Reaction) {
|
||||
return (
|
||||
<div className="h-min w-full px-3 pb-3">
|
||||
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||
<NoteSkeleton />
|
||||
<div className="mb-3 h-min w-full px-3">
|
||||
<div className="flex flex-col gap-2 rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950">
|
||||
<div className="flex h-10 items-center justify-between">
|
||||
<div className="relative flex w-full items-center gap-2 px-3 pt-2">
|
||||
<div className="absolute -left-0.5 -top-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-blue-100 text-xs ring-2 ring-neutral-50 dark:bg-blue-900 dark:ring-neutral-950">
|
||||
{event.content === '+' ? '👍' : event.content}
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<User pubkey={event.pubkey} variant="notify" />
|
||||
<p className="text-neutral-700 dark:text-neutral-300">reacted</p>
|
||||
</div>
|
||||
<div className="text-neutral-500 dark:text-neutral-400">{createdAt}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="w-full px-3">
|
||||
<div className="flex h-min w-full flex-col gap-3 rounded-lg bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
{thread.rootEventId ? <ChildNote id={thread.rootEventId} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
addWidget.mutate({
|
||||
kind: WIDGET_KIND.thread,
|
||||
title: 'Thread',
|
||||
content: thread.rootEventId,
|
||||
})
|
||||
}
|
||||
className="self-start text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
Show original post
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-3 h-min w-full px-3">
|
||||
<div className="flex flex-col gap-2 rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950">
|
||||
<div className="flex h-10 items-center justify-between">
|
||||
<div className="relative flex w-full items-center gap-2 px-3 pt-2">
|
||||
<div className="absolute -left-0.5 -top-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-blue-100 text-xs ring-2 ring-neutral-50 dark:bg-blue-900 dark:ring-neutral-950">
|
||||
{event.kind === 7 ? (event.content === '+' ? '👍' : event.content) : '⚡️'}
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<User pubkey={event.pubkey} variant="notify" />
|
||||
<p className="text-neutral-900 dark:text-neutral-100">
|
||||
{renderText(event.kind)}
|
||||
</p>
|
||||
if (event.kind === NDKKind.Repost) {
|
||||
return (
|
||||
<div className="mb-3 h-min w-full px-3">
|
||||
<div className="flex flex-col gap-2 rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950">
|
||||
<div className="flex h-10 items-center justify-between">
|
||||
<div className="relative flex w-full items-center gap-2 px-3 pt-2">
|
||||
<div className="absolute -left-0.5 -top-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-teal-500 text-xs ring-2 ring-neutral-50 dark:ring-neutral-950">
|
||||
<RepostIcon className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<User pubkey={event.pubkey} variant="notify" />
|
||||
<p className="text-neutral-700 dark:text-neutral-300">reposted</p>
|
||||
</div>
|
||||
<div className="text-neutral-500 dark:text-neutral-400">{createdAt}</div>
|
||||
</div>
|
||||
<div className="text-neutral-500 dark:text-neutral-400">{createdAt}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">{data ? renderKind(data) : <p>Loading...</p>}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
addWidget.mutate({
|
||||
kind: WIDGET_KIND.thread,
|
||||
title: 'Thread',
|
||||
content: data.id,
|
||||
})
|
||||
}
|
||||
className="inline-flex min-h-full w-10 shrink-0 items-center justify-center rounded-lg text-neutral-600 hover:text-blue-500 dark:text-neutral-400"
|
||||
>
|
||||
<ShareIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="w-full px-3">
|
||||
<div className="flex h-min w-full flex-col gap-3 rounded-lg bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
{thread.rootEventId ? <ChildNote id={thread.rootEventId} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
addWidget.mutate({
|
||||
kind: WIDGET_KIND.thread,
|
||||
title: 'Thread',
|
||||
content: thread.rootEventId,
|
||||
})
|
||||
}
|
||||
className="self-start text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
Show original post
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if (event.kind === NDKKind.Text) {
|
||||
return (
|
||||
<div className="mb-3 h-min w-full px-3">
|
||||
<div className="flex flex-col gap-2 rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950">
|
||||
<div className="flex h-10 items-center justify-between">
|
||||
<div className="relative flex w-full items-center gap-2 px-3 pt-2">
|
||||
<div className="absolute -left-0.5 -top-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-blue-500 text-xs ring-2 ring-neutral-50 dark:ring-neutral-950">
|
||||
<ReplyIcon className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<User pubkey={event.pubkey} variant="notify" />
|
||||
<p className="text-neutral-700 dark:text-neutral-300">replied</p>
|
||||
</div>
|
||||
<div className="text-neutral-500 dark:text-neutral-400">{createdAt}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="w-full px-3">
|
||||
<div className="flex h-min w-full flex-col gap-3 rounded-lg bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
{thread.replyEventId ? (
|
||||
<ChildNote id={thread.replyEventId} />
|
||||
) : thread.rootEventId ? (
|
||||
<ChildNote id={thread.rootEventId} isRoot />
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
addWidget.mutate({
|
||||
kind: WIDGET_KIND.thread,
|
||||
title: 'Thread',
|
||||
content: thread.replyEventId
|
||||
? thread.replyEventId
|
||||
: thread.rootEventId,
|
||||
})
|
||||
}
|
||||
className="self-start text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
Show full thread
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<TextKind content={event.content} textmode />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const MemoizedNotifyNote = memo(NotifyNote);
|
||||
|
||||
@@ -50,7 +50,7 @@ export function NoteReplyForm({ rootEvent }: { rootEvent: NDKEvent }) {
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Reply to this post..."
|
||||
className="h-28 w-full resize-none rounded-t-xl bg-neutral-100 px-5 py-4 text-neutral-900 !outline-none placeholder:text-neutral-600 dark:bg-neutral-900 dark:text-neutral-100 dark:placeholder:text-neutral-400"
|
||||
className="h-28 w-full resize-none rounded-t-xl border-transparent bg-neutral-100 px-5 py-4 text-neutral-900 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:text-neutral-100 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<div className="inline-flex items-center justify-end gap-2 rounded-b-xl p-2">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
|
||||
export function useProfile(pubkey: string, embed?: string) {
|
||||
const queryClient = useQueryClient();
|
||||
const { ndk } = useNDK();
|
||||
const {
|
||||
status,
|
||||
@@ -13,24 +14,34 @@ export function useProfile(pubkey: string, embed?: string) {
|
||||
} = useQuery({
|
||||
queryKey: ['user', pubkey],
|
||||
queryFn: async () => {
|
||||
// parse data from nostr.band api
|
||||
if (embed) {
|
||||
const profile: NDKUserProfile = JSON.parse(embed);
|
||||
return profile;
|
||||
}
|
||||
|
||||
// get clean pubkey without any special characters
|
||||
let hexstring = pubkey.replace(/[^a-zA-Z0-9]/g, '');
|
||||
if (hexstring.startsWith('npub1'))
|
||||
hexstring = nip19.decode(hexstring).data as string;
|
||||
|
||||
if (hexstring.startsWith('npub1') || hexstring.startsWith('nprofile1')) {
|
||||
const decoded = nip19.decode(hexstring);
|
||||
if (decoded.type === 'nprofile') hexstring = decoded.data.pubkey;
|
||||
if (decoded.type === 'npub') hexstring = decoded.data;
|
||||
}
|
||||
|
||||
const user = ndk.getUser({ pubkey: hexstring });
|
||||
if (!user) return Promise.reject(new Error("user's profile not found"));
|
||||
const profile = await user.fetchProfile();
|
||||
|
||||
return await user.fetchProfile();
|
||||
if (!profile)
|
||||
throw new Error(
|
||||
`Cannot get metadata for ${pubkey}, will be retry after 10 seconds`
|
||||
);
|
||||
return profile;
|
||||
},
|
||||
staleTime: Infinity,
|
||||
refetchOnMount: false,
|
||||
initialData: () => queryClient.getQueryData(['user', pubkey]),
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
return { status, user, error };
|
||||
|
||||
Reference in New Issue
Block a user