add notification widget
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { NotiUser } from '@app/notifications/components/user';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
|
||||
export function NotiMention({ event }: { event: NDKEvent }) {
|
||||
const createdAt = formatCreatedAt(event.created_at);
|
||||
const rootId = event.tags.find((el) => el[0])?.[1];
|
||||
|
||||
return (
|
||||
<Link to={`/notes/text/${rootId}`} className="h-min w-full px-3">
|
||||
<div className="group flex items-center justify-between rounded-xl px-3 py-3 hover:bg-white/10">
|
||||
<div className="flex items-center gap-2">
|
||||
<NotiUser pubkey={event.pubkey} />
|
||||
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
||||
has mention you · {createdAt}
|
||||
</p>
|
||||
</div>
|
||||
<span className="hidden text-sm font-semibold text-blue-500 group-hover:block">
|
||||
View
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { NotiUser } from '@app/notifications/components/user';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
|
||||
export function NotiReaction({ event }: { event: NDKEvent }) {
|
||||
const createdAt = formatCreatedAt(event.created_at);
|
||||
const rootId = event.tags.find((el) => el[0])?.[1];
|
||||
|
||||
return (
|
||||
<Link to={`/notes/text/${rootId}`} className="h-min w-full px-3">
|
||||
<div className="group flex items-center justify-between rounded-xl px-3 py-3 hover:bg-white/10">
|
||||
<div className="flex items-center gap-2">
|
||||
<NotiUser pubkey={event.pubkey} />
|
||||
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
||||
reacted {event.content} · {createdAt}
|
||||
</p>
|
||||
</div>
|
||||
<span className="hidden text-sm font-semibold text-blue-500 group-hover:block">
|
||||
View
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
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 createdAt = formatCreatedAt(event.created_at);
|
||||
const rootId = event.tags.find((el) => el[0])?.[1];
|
||||
|
||||
return (
|
||||
<Link to={`/notes/text/${rootId}`} className="h-min w-full px-3">
|
||||
<div className="group flex items-center justify-between rounded-xl px-3 py-3 hover:bg-white/10">
|
||||
<div className="flex items-center gap-2">
|
||||
<NotiUser pubkey={event.pubkey} />
|
||||
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
||||
repost{' '}
|
||||
{event.pubkey !== db.account.pubkey ? 'a post that mention you' : 'your post'}{' '}
|
||||
· {createdAt}
|
||||
</p>
|
||||
</div>
|
||||
<span className="hidden text-sm font-semibold text-blue-500 group-hover:block">
|
||||
View
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
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.local.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 backdrop-blur-xl">
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'error') {
|
||||
return (
|
||||
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||
<p>Can'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 backdrop-blur-xl"
|
||||
>
|
||||
<User pubkey={data.pubkey} time={data.created_at} variant="mention" />
|
||||
<div className="markdown">
|
||||
<p>
|
||||
{data.content.length > 200
|
||||
? data.content.substring(0, 200) + '...'
|
||||
: data.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
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-white/10" />
|
||||
<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-white/10" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex shrink-0 items-center justify-start gap-2">
|
||||
<Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 shrink-0 rounded-md object-cover"
|
||||
/>
|
||||
<span className="max-w-[10rem] truncate font-medium leading-none text-white">
|
||||
{user?.name || user?.display_name || user?.displayName || displayNpub(pubkey, 16)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useCallback, useEffect } 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 { useActivities } from '@stores/activities';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
|
||||
export function NotificationScreen() {
|
||||
const { db } = useStorage();
|
||||
const { fetchActivities } = useNostr();
|
||||
|
||||
const [activities, setActivities, clearTotalNewActivities] = useActivities((state) => [
|
||||
state.activities,
|
||||
state.setActivities,
|
||||
state.clearTotalNewActivities,
|
||||
]);
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
[activities]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function getActivities() {
|
||||
const events = await fetchActivities();
|
||||
setActivities(events, db.account.last_login_at);
|
||||
}
|
||||
|
||||
getActivities();
|
||||
|
||||
// clear total new activities
|
||||
clearTotalNewActivities();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-y-auto bg-neutral-400 scrollbar-none dark:bg-neutral-600">
|
||||
<div className="grid h-full 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">
|
||||
{!activities ? (
|
||||
<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-neutral-600 dark:text-neutral-400">
|
||||
Loading
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : activities.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-neutral-600 dark:text-neutral-400">
|
||||
Yo!, no new activities around you in the last 24 hours
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
activities.map((event) => renderItem(event))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
LocalFilesWidget,
|
||||
LocalFollowsWidget,
|
||||
LocalNetworkWidget,
|
||||
LocalNotificationWidget,
|
||||
LocalThreadWidget,
|
||||
LocalUserWidget,
|
||||
TrendingAccountsWidget,
|
||||
@@ -74,6 +75,8 @@ export function SpaceScreen() {
|
||||
return <WidgetList key={widget.id} params={widget} />;
|
||||
case WidgetKinds.other.learnNostr:
|
||||
return <LearnNostrWidget key={widget.id} params={widget} />;
|
||||
case WidgetKinds.local.notification:
|
||||
return <LocalNotificationWidget key={widget.id} params={widget} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -83,7 +86,7 @@ export function SpaceScreen() {
|
||||
|
||||
useEffect(() => {
|
||||
fetchWidgets(db);
|
||||
}, [fetchWidgets]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<VList
|
||||
|
||||
Reference in New Issue
Block a user