update notification screen

This commit is contained in:
Ren Amamiya
2023-08-27 14:46:48 +07:00
parent 963328e064
commit bf91187c1f
12 changed files with 62 additions and 37 deletions

View File

@@ -0,0 +1,27 @@
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Hashtag, MentionUser } from '@shared/notes';
import { RichContent } from '@utils/types';
export function NotiContent({ content }: { content: RichContent }) {
return (
<>
<ReactMarkdown
className="markdown"
remarkPlugins={[remarkGfm]}
components={{
del: ({ children }) => {
const key = children[0] as string;
if (key.startsWith('pub') && key.length > 50 && key.length < 100)
return <MentionUser pubkey={key.replace('pub-', '')} />;
if (key.startsWith('tag')) return <Hashtag tag={key.replace('tag-', '')} />;
},
}}
>
{content?.parsed}
</ReactMarkdown>
</>
);
}

View File

@@ -0,0 +1,32 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import { NotiContent } from '@app/notifications/components/content';
import { NotiUser } from '@app/notifications/components/user';
import { formatCreatedAt } from '@utils/createdAt';
import { parser } from '@utils/parser';
export function NotiMention({ event }: { event: NDKEvent }) {
const createdAt = formatCreatedAt(event.created_at);
const content = useMemo(() => parser(event), [event]);
return (
<div className="h-min w-full px-3 py-1.5">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="flex items-start justify-between">
<div className="flex items-start gap-1">
<NotiUser pubkey={event.pubkey} />
<p className="leading-none text-white/50">has reply you post · {createdAt}</p>
</div>
</div>
<div className="f- relative z-10 -mt-6 flex gap-3">
<div className="h-11 w-11 shrink-0" />
<div className="mb-2 mt-3 w-full cursor-default rounded-lg bg-white/10 px-3 py-3">
<NotiContent content={content} />
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,30 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { SimpleNote } from '@app/notifications/components/simpleNote';
import { NotiUser } from '@app/notifications/components/user';
import { formatCreatedAt } from '@utils/createdAt';
export function NotiReaction({ event }: { event: NDKEvent }) {
const root = event.tags.find((e) => e[0] === 'e')?.[1];
const createdAt = formatCreatedAt(event.created_at);
return (
<div className="h-min w-full px-3 py-1.5">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="flex items-start justify-between">
<div className="flex items-start gap-1">
<NotiUser pubkey={event.pubkey} />
<p className="leading-none text-white/50">
reacted {event.content} · {createdAt}
</p>
</div>
</div>
<div className="relative z-10 -mt-6 flex gap-3">
<div className="h-11 w-11 shrink-0" />
<div className="flex-1">{root && <SimpleNote id={root} />}</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,38 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { SimpleNote } from '@app/notifications/components/simpleNote';
import { NotiUser } from '@app/notifications/components/user';
import { useStorage } from '@libs/storage/provider';
import { formatCreatedAt } from '@utils/createdAt';
export function NotiRepost({ event }: { event: NDKEvent }) {
const { db } = useStorage();
const root = event.tags.find((e) => e[0] === 'e')?.[1];
const createdAt = formatCreatedAt(event.created_at);
return (
<div className="h-min w-full px-3 py-1.5">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3">
<div className="flex items-start justify-between">
<div className="flex items-start gap-1">
<NotiUser pubkey={event.pubkey} />
<p className="leading-none text-white/50">
repost{' '}
{event.pubkey !== db.account.pubkey
? 'a post that mention you'
: 'your post'}{' '}
· {createdAt}
</p>
</div>
</div>
<div className="relative z-10 -mt-6 flex gap-3">
<div className="h-11 w-11 shrink-0" />
<div className="flex-1">{root && <SimpleNote id={root} />}</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,61 @@
import { memo } from 'react';
import { useStorage } from '@libs/storage/provider';
import { NoteSkeleton } from '@shared/notes';
import { User } from '@shared/user';
import { WidgetKinds, useWidgets } from '@stores/widgets';
import { useEvent } from '@utils/hooks/useEvent';
export const SimpleNote = memo(function SimpleNote({ id }: { id: string }) {
const { db } = useStorage();
const { status, data } = useEvent(id);
const setWidget = useWidgets((state) => state.setWidget);
const openThread = (event, thread: string) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
setWidget(db, { kind: WidgetKinds.thread, title: 'Thread', content: thread });
} else {
event.stopPropagation();
}
};
if (status === 'loading') {
return (
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3">
<NoteSkeleton />
</div>
);
}
if (status === 'error') {
return (
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3">
<p>Can&apos;t get event from relay</p>
</div>
);
}
return (
<div
onClick={(e) => openThread(e, id)}
onKeyDown={(e) => openThread(e, id)}
role="button"
tabIndex={0}
className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3"
>
<User pubkey={data.pubkey} time={data.created_at} size="small" />
<div className="markdown">
<p>
{data.content.length > 200
? data.content.substring(0, 200) + '...'
: data.content}
</p>
</div>
</div>
);
});

View File

@@ -0,0 +1,32 @@
import { Image } from '@shared/image';
import { useProfile } from '@utils/hooks/useProfile';
import { displayNpub } from '@utils/shortenKey';
export function NotiUser({ pubkey }: { pubkey: string }) {
const { status, user } = useProfile(pubkey);
if (status === 'loading') {
return (
<div className="flex items-start gap-2">
<div className="relative h-8 w-8 shrink-0 animate-pulse rounded-md bg-zinc-800" />
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
<span className="h-4 w-1/2 animate-pulse rounded bg-zinc-800" />
</div>
</div>
);
}
return (
<div className="flex shrink-0 items-start justify-start gap-3">
<Image
src={user?.picture || user?.image}
alt={pubkey}
className="h-11 w-11 shrink-0 rounded-lg object-cover"
/>
<span className="max-w-[10rem] flex-1 truncate font-medium leading-none text-white">
{user?.nip05 || user?.name || user?.display_name || displayNpub(pubkey, 16)}
</span>
</div>
);
}

View File

@@ -0,0 +1,73 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react';
import { NotiMention } from '@app/notifications/components/mention';
import { NotiReaction } from '@app/notifications/components/reaction';
import { NotiRepost } from '@app/notifications/components/repost';
import { useStorage } from '@libs/storage/provider';
import { LoaderIcon } from '@shared/icons';
import { TitleBar } from '@shared/titleBar';
import { useNostr } from '@utils/hooks/useNostr';
export function NotificationScreen() {
const { db } = useStorage();
const { fetchActivities } = useNostr();
const { status, data } = useQuery(
['notifications', db.account.pubkey],
async () => {
return await fetchActivities();
},
{ refetchOnWindowFocus: false }
);
const renderItem = useCallback(
(event: NDKEvent) => {
switch (event.kind) {
case 1:
return <NotiMention key={event.id} event={event} />;
case 6:
return <NotiRepost key={event.id} event={event} />;
case 7:
return <NotiReaction key={event.id} event={event} />;
default:
return null;
}
},
[data]
);
return (
<div className="scrollbar-hide h-full w-full overflow-y-auto bg-white/10">
<div className="grid grid-cols-3">
<div className="col-span-2 flex flex-col border-r border-white/5">
<TitleBar title="Activities in the last 24 hours" />
<div className="flex h-full flex-col gap-1.5">
<div className="flex flex-col">
{status === 'loading' ? (
<div className="flex h-full w-full items-center justify-center">
<div className="flex flex-col items-center gap-1.5">
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
<p className="text-sm font-medium text-white/50">Loading</p>
</div>
</div>
) : data?.length < 1 ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<p className="mb-1 text-4xl">🎉</p>
<p className="font-medium text-white/50">
Yo!, you&apos;ve no new activities
</p>
</div>
) : (
data.map((event) => renderItem(event))
)}
</div>
</div>
</div>
</div>
</div>
);
}