fix some bugs

This commit is contained in:
Ren Amamiya 2023-09-08 15:29:41 +07:00
parent 10036500cb
commit 177e4c1ff7
14 changed files with 142 additions and 119 deletions

View File

@ -1,32 +1,25 @@
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react'; import { Link } from 'react-router-dom';
import { NotiContent } from '@app/notifications/components/content';
import { NotiUser } from '@app/notifications/components/user'; import { NotiUser } from '@app/notifications/components/user';
import { formatCreatedAt } from '@utils/createdAt'; import { formatCreatedAt } from '@utils/createdAt';
import { parser } from '@utils/parser';
export function NotiMention({ event }: { event: NDKEvent }) { export function NotiMention({ event }: { event: NDKEvent }) {
const createdAt = formatCreatedAt(event.created_at); const createdAt = formatCreatedAt(event.created_at);
const content = useMemo(() => parser(event), [event]); const rootId = event.tags.find((el) => el[0])?.[1];
return ( return (
<div className="h-min w-full px-3 py-1.5"> <Link to={`/notes/text/${rootId}`} className="h-min w-full px-3">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3 backdrop-blur-xl"> <div className="group flex items-center justify-between rounded-xl px-3 py-3 hover:bg-white/10">
<div className="flex items-start justify-between"> <div className="flex items-center gap-2">
<div className="flex items-start gap-1"> <NotiUser pubkey={event.pubkey} />
<NotiUser pubkey={event.pubkey} /> <p className="leading-none text-white/50">has mention you · {createdAt}</p>
<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 backdrop-blur-xl">
<NotiContent content={content} />
</div>
</div> </div>
<span className="hidden text-sm font-semibold text-fuchsia-500 group-hover:block">
View
</span>
</div> </div>
</div> </Link>
); );
} }

View File

@ -1,30 +1,27 @@
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import { Link } from 'react-router-dom';
import { SimpleNote } from '@app/notifications/components/simpleNote';
import { NotiUser } from '@app/notifications/components/user'; import { NotiUser } from '@app/notifications/components/user';
import { formatCreatedAt } from '@utils/createdAt'; import { formatCreatedAt } from '@utils/createdAt';
export function NotiReaction({ event }: { event: NDKEvent }) { export function NotiReaction({ event }: { event: NDKEvent }) {
const root = event.tags.find((e) => e[0] === 'e')?.[1];
const createdAt = formatCreatedAt(event.created_at); const createdAt = formatCreatedAt(event.created_at);
const rootId = event.tags.find((el) => el[0])?.[1];
return ( return (
<div className="h-min w-full px-3 py-1.5"> <Link to={`/notes/text/${rootId}`} className="h-min w-full px-3">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3 backdrop-blur-xl"> <div className="group flex items-center justify-between rounded-xl px-3 py-3 hover:bg-white/10">
<div className="flex items-start justify-between"> <div className="flex items-center gap-2">
<div className="flex items-start gap-1"> <NotiUser pubkey={event.pubkey} />
<NotiUser pubkey={event.pubkey} /> <p className="leading-none text-white/50">
<p className="leading-none text-white/50"> reacted {event.content} · {createdAt}
reacted {event.content} · {createdAt} </p>
</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>
<span className="hidden text-sm font-semibold text-fuchsia-500 group-hover:block">
View
</span>
</div> </div>
</div> </Link>
); );
} }

View File

@ -1,6 +1,6 @@
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import { Link } from 'react-router-dom';
import { SimpleNote } from '@app/notifications/components/simpleNote';
import { NotiUser } from '@app/notifications/components/user'; import { NotiUser } from '@app/notifications/components/user';
import { useStorage } from '@libs/storage/provider'; import { useStorage } from '@libs/storage/provider';
@ -10,29 +10,24 @@ import { formatCreatedAt } from '@utils/createdAt';
export function NotiRepost({ event }: { event: NDKEvent }) { export function NotiRepost({ event }: { event: NDKEvent }) {
const { db } = useStorage(); const { db } = useStorage();
const root = event.tags.find((e) => e[0] === 'e')?.[1];
const createdAt = formatCreatedAt(event.created_at); const createdAt = formatCreatedAt(event.created_at);
const rootId = event.tags.find((el) => el[0])?.[1];
return ( return (
<div className="h-min w-full px-3 py-1.5"> <Link to={`/notes/text/${rootId}`} className="h-min w-full px-3">
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3 backdrop-blur-xl"> <div className="group flex items-center justify-between rounded-xl px-3 py-3 hover:bg-white/10">
<div className="flex items-start justify-between"> <div className="flex items-center gap-2">
<div className="flex items-start gap-1"> <NotiUser pubkey={event.pubkey} />
<NotiUser pubkey={event.pubkey} /> <p className="leading-none text-white/50">
<p className="leading-none text-white/50"> repost{' '}
repost{' '} {event.pubkey !== db.account.pubkey ? 'a post that mention you' : 'your post'}{' '}
{event.pubkey !== db.account.pubkey · {createdAt}
? 'a post that mention you' </p>
: '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>
<span className="hidden text-sm font-semibold text-fuchsia-500 group-hover:block">
View
</span>
</div> </div>
</div> </Link>
); );
} }

View File

@ -18,13 +18,13 @@ export function NotiUser({ pubkey }: { pubkey: string }) {
} }
return ( return (
<div className="flex shrink-0 items-start justify-start gap-3"> <div className="flex shrink-0 items-center justify-start gap-2">
<Image <Image
src={user?.picture || user?.image} src={user?.picture || user?.image}
alt={pubkey} alt={pubkey}
className="h-11 w-11 shrink-0 rounded-lg object-cover" className="h-8 w-8 shrink-0 rounded-md object-cover"
/> />
<span className="max-w-[10rem] flex-1 truncate font-medium leading-none text-white"> <span className="max-w-[10rem] truncate font-medium leading-none text-white">
{user?.name || user?.display_name || displayNpub(pubkey, 16)} {user?.name || user?.display_name || displayNpub(pubkey, 16)}
</span> </span>
</div> </div>

View File

@ -1,16 +1,24 @@
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useCallback } from 'react'; import { useCallback, useEffect } from 'react';
import { NotiMention } from '@app/notifications/components/mention'; import { NotiMention } from '@app/notifications/components/mention';
import { NotiReaction } from '@app/notifications/components/reaction'; import { NotiReaction } from '@app/notifications/components/reaction';
import { NotiRepost } from '@app/notifications/components/repost'; import { NotiRepost } from '@app/notifications/components/repost';
import { LoaderIcon } from '@shared/icons';
import { TitleBar } from '@shared/titleBar'; import { TitleBar } from '@shared/titleBar';
import { useActivities } from '@stores/activities'; import { useActivities } from '@stores/activities';
import { useNostr } from '@utils/hooks/useNostr';
export function NotificationScreen() { export function NotificationScreen() {
const activities = useActivities((state) => state.activities); const { fetchActivities } = useNostr();
const [activities, setActivities, clearTotalNewActivities] = useActivities((state) => [
state.activities,
state.setActivities,
state.clearTotalNewActivities,
]);
const renderItem = useCallback( const renderItem = useCallback(
(event: NDKEvent) => { (event: NDKEvent) => {
@ -28,24 +36,40 @@ export function NotificationScreen() {
[activities] [activities]
); );
useEffect(() => {
async function getActivities() {
const events = await fetchActivities();
setActivities(events);
// clear total new activities
clearTotalNewActivities();
}
getActivities();
}, []);
return ( return (
<div className="scrollbar-hide h-full w-full overflow-y-auto bg-white/10 backdrop-blur-xl"> <div className="scrollbar-hide h-full w-full overflow-y-auto bg-white/10 backdrop-blur-xl">
<div className="grid h-full grid-cols-3"> <div className="grid h-full grid-cols-3">
<div className="col-span-2 flex flex-col border-r border-white/5"> <div className="col-span-2 flex flex-col border-r border-white/5">
<TitleBar title="Activities in the last 24 hours" /> <TitleBar title="Activities in the last 24 hours" />
<div className="flex h-full flex-col gap-1.5"> <div className="flex h-full flex-col">
<div className="flex h-full flex-col"> {!activities ? (
{activities?.length < 1 ? ( <div className="flex h-full w-full items-center justify-center">
<div className="flex h-full w-full flex-col items-center justify-center"> <div className="flex flex-col items-center gap-1.5">
<p className="mb-1 text-4xl">🎉</p> <LoaderIcon className="h-5 w-5 animate-spin text-white" />
<p className="font-medium text-white/50"> <p className="text-sm font-medium text-white/50">Loading</p>
Yo!, no new activities around you in the last 24 hours
</p>
</div> </div>
) : ( </div>
activities.map((event) => renderItem(event)) ) : activities.length < 1 ? (
)} <div className="flex h-full w-full flex-col items-center justify-center">
</div> <p className="mb-1 text-4xl">🎉</p>
<p className="font-medium text-white/50">
Yo!, no new activities around you in the last 24 hours
</p>
</div>
) : (
activities.map((event) => renderItem(event))
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -21,6 +21,7 @@ import {
XfeedsWidget, XfeedsWidget,
XhashtagWidget, XhashtagWidget,
} from '@shared/widgets'; } from '@shared/widgets';
import { LocalFollowsWidget } from '@shared/widgets/local/follows';
import { WidgetKinds, useWidgets } from '@stores/widgets'; import { WidgetKinds, useWidgets } from '@stores/widgets';
@ -40,6 +41,8 @@ export function SpaceScreen() {
switch (widget.kind) { switch (widget.kind) {
case WidgetKinds.local.network: case WidgetKinds.local.network:
return <LocalNetworkWidget key={widget.id} />; return <LocalNetworkWidget key={widget.id} />;
case WidgetKinds.local.follows:
return <LocalFollowsWidget key={widget.id} params={widget} />;
case WidgetKinds.local.feeds: case WidgetKinds.local.feeds:
return <LocalFeedsWidget key={widget.id} params={widget} />; return <LocalFeedsWidget key={widget.id} params={widget} />;
case WidgetKinds.local.files: case WidgetKinds.local.files:

View File

@ -7,17 +7,14 @@ import { useStorage } from '@libs/storage/provider';
import { LoaderIcon } from '@shared/icons'; import { LoaderIcon } from '@shared/icons';
import { useActivities } from '@stores/activities';
import { useNostr } from '@utils/hooks/useNostr'; import { useNostr } from '@utils/hooks/useNostr';
export function SplashScreen() { export function SplashScreen() {
const { db } = useStorage(); const { db } = useStorage();
const { ndk } = useNDK(); const { ndk } = useNDK();
const { fetchUserData, fetchActivities, prefetchEvents } = useNostr(); const { fetchUserData, prefetchEvents } = useNostr();
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const setActivities = useActivities((state) => state.setActivities);
const skip = async () => { const skip = async () => {
await invoke('close_splashscreen'); await invoke('close_splashscreen');
@ -27,11 +24,8 @@ export function SplashScreen() {
try { try {
const user = await fetchUserData(); const user = await fetchUserData();
const data = await prefetchEvents(); const data = await prefetchEvents();
const activities = await fetchActivities();
if (user.status === 'ok' && data.status === 'ok') { if (user.status === 'ok' && data.status === 'ok') {
// set activities
setActivities(activities);
// update last login = current time // update last login = current time
await db.updateLastLogin(); await db.updateLastLogin();
// close splash screen and open main app screen // close splash screen and open main app screen

View File

@ -11,10 +11,15 @@ import { ComposerModal } from '@shared/composer';
import { Frame } from '@shared/frame'; import { Frame } from '@shared/frame';
import { BellIcon, NavArrowDownIcon, SpaceIcon } from '@shared/icons'; import { BellIcon, NavArrowDownIcon, SpaceIcon } from '@shared/icons';
import { useActivities } from '@stores/activities';
import { useSidebar } from '@stores/sidebar'; import { useSidebar } from '@stores/sidebar';
import { compactNumber } from '@utils/number';
export function Navigation() { export function Navigation() {
const { db } = useStorage(); const { db } = useStorage();
const [totalNewActivities] = useActivities((state) => [state.totalNewActivities]);
const [chats, toggleChats] = useSidebar((state) => [state.chats, state.toggleChats]); const [chats, toggleChats] = useSidebar((state) => [state.chats, state.toggleChats]);
return ( return (
@ -47,17 +52,26 @@ export function Navigation() {
preventScrollReset={true} preventScrollReset={true}
className={({ isActive }) => className={({ isActive }) =>
twMerge( twMerge(
'flex h-10 items-center gap-2.5 rounded-r-lg border-l-2 pl-4 pr-2', 'flex h-10 items-center justify-between rounded-r-lg border-l-2 pl-4 pr-2',
isActive isActive
? 'border-fuchsia-500 bg-white/5 text-white' ? 'border-fuchsia-500 bg-white/5 text-white'
: 'border-transparent text-white/80' : 'border-transparent text-white/80'
) )
} }
> >
<span className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl"> <div className="flex items-center gap-2.5">
<BellIcon className="h-4 w-4 text-white" /> <span className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded bg-white/10 backdrop-blur-xl">
</span> <BellIcon className="h-4 w-4 text-white" />
Notifications </span>
Notifications
</div>
{totalNewActivities > 0 ? (
<div className="inline-flex h-5 w-8 items-center justify-center rounded bg-fuchsia-500">
<span className="text-xs font-medium text-white">
{compactNumber.format(totalNewActivities)}
</span>
</div>
) : null}
</NavLink> </NavLink>
</div> </div>
<Collapsible.Root open={chats} onOpenChange={toggleChats}> <Collapsible.Root open={chats} onOpenChange={toggleChats}>

View File

@ -19,7 +19,7 @@ import { useEvent } from '@utils/hooks/useEvent';
export function Repost({ event }: { event: NDKEvent }) { export function Repost({ event }: { event: NDKEvent }) {
// @ts-expect-error, root_id isn't exist on NDKEvent // @ts-expect-error, root_id isn't exist on NDKEvent
const { status, data } = useEvent(event.root_id, event.content); const { status, data } = useEvent(event.root_id ?? event.id, event.content);
const renderKind = useCallback( const renderKind = useCallback(
(repostEvent: NDKEvent) => { (repostEvent: NDKEvent) => {

View File

@ -42,7 +42,7 @@ export function TextNote({ content }: { content: string }) {
const cleanURL = new URL(href); const cleanURL = new URL(href);
cleanURL.search = ''; cleanURL.search = '';
return ( return (
<Link to={href} target="_blank"> <Link to={href} target="_blank" className="line-clamp-1">
{cleanURL.hostname + cleanURL.pathname} {cleanURL.hostname + cleanURL.pathname}
</Link> </Link>
); );

View File

@ -17,9 +17,9 @@ import {
import { NoteSkeleton } from '@shared/notes/skeleton'; import { NoteSkeleton } from '@shared/notes/skeleton';
import { TitleBar } from '@shared/titleBar'; import { TitleBar } from '@shared/titleBar';
import { DBEvent } from '@utils/types'; import { DBEvent, Widget } from '@utils/types';
export function LocalFollowsWidget() { export function LocalFollowsWidget({ params }: { params: Widget }) {
const { db } = useStorage(); const { db } = useStorage();
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } = const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery({ useInfiniteQuery({
@ -116,7 +116,7 @@ export function LocalFollowsWidget() {
return ( return (
<div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl"> <div className="relative shrink-0 grow-0 basis-[400px] bg-white/10 backdrop-blur-xl">
<TitleBar title="👋 Network" /> <TitleBar id={params.id} title="Follows" />
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20"> <div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
{status === 'loading' ? ( {status === 'loading' ? (
<div className="px-3 py-1.5"> <div className="px-3 py-1.5">

View File

@ -1,27 +1,27 @@
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import { create } from 'zustand'; import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
interface ActivitiesState { interface ActivitiesState {
activities: null | Array<NDKEvent>; activities: Array<NDKEvent>;
totalNewActivities: number;
setActivities: (events: NDKEvent[]) => void; setActivities: (events: NDKEvent[]) => void;
addActivity: (event: NDKEvent) => void; addActivity: (event: NDKEvent) => void;
clearTotalNewActivities: () => void;
} }
export const useActivities = create<ActivitiesState>()( export const useActivities = create<ActivitiesState>((set) => ({
persist( activities: null,
(set) => ({ totalNewActivities: 0,
activities: null, setActivities: (events: NDKEvent[]) => {
setActivities: (events: NDKEvent[]) => { set(() => ({ activities: events }));
set(() => ({ activities: events })); },
}, addActivity: (event: NDKEvent) => {
addActivity: (event: NDKEvent) => { set((state) => ({
set((state) => ({ activities: [event, ...state.activities] })); activities: state.activities ? [event, ...state.activities] : [event],
}, totalNewActivities: state.totalNewActivities++,
}), }));
{ },
name: 'activities', clearTotalNewActivities: () => {
storage: createJSONStorage(() => localStorage), set(() => ({ totalNewActivities: 0 }));
} },
) }));
);

View File

@ -12,7 +12,7 @@ export function useEvent(id: string, embed?: string) {
const { status, data } = useQuery( const { status, data } = useQuery(
['event', id], ['event', id],
async () => { async () => {
// return embed event (nostr.band api) // return embed event (nostr.band api) or repost
if (embed) { if (embed) {
const event: NDKEvent = JSON.parse(embed); const event: NDKEvent = JSON.parse(embed);
return event; return event;

View File

@ -3,17 +3,20 @@ import { EventPointer } from 'nostr-tools/lib/nip19';
import { RichContent } from '@utils/types'; import { RichContent } from '@utils/types';
function isURL(str: string) { function isURL(string: string) {
const pattern = new RegExp( try {
'^(https?:\\/\\/)?' + // protocol const url = new URL(string);
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name if (url.protocol.length > 0) {
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address if (url.protocol === 'https:' || url.protocol === 'http:') {
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path return true;
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string } else {
'(\\#[-a-z\\d_]*)?$', // fragment locator return false;
'i' }
); }
return !!pattern.test(str); return true;
} catch (e) {
return false;
}
} }
export function parser(eventContent: string) { export function parser(eventContent: string) {