polish
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
ArticleNote,
|
||||
@@ -17,18 +18,21 @@ import { useEvent } from '@utils/hooks/useEvent';
|
||||
export function ChildNote({ id, root }: { id: string; root?: string }) {
|
||||
const { status, data } = useEvent(id);
|
||||
|
||||
const renderKind = (event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return <TextNote content={event.content} />;
|
||||
case NDKKind.Article:
|
||||
return <ArticleNote event={event} />;
|
||||
case 1063:
|
||||
return <FileNote event={event} />;
|
||||
default:
|
||||
return <UnknownNote event={event} />;
|
||||
}
|
||||
};
|
||||
const renderKind = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return <TextNote content={event.content} />;
|
||||
case NDKKind.Article:
|
||||
return <ArticleNote event={event} />;
|
||||
case 1063:
|
||||
return <FileNote event={event} />;
|
||||
default:
|
||||
return <UnknownNote event={event} />;
|
||||
}
|
||||
},
|
||||
[id]
|
||||
);
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
|
||||
@@ -4,11 +4,10 @@ import { download } from '@tauri-apps/plugin-upload';
|
||||
import {
|
||||
MediaControlBar,
|
||||
MediaController,
|
||||
MediaFullscreenButton,
|
||||
MediaMuteButton,
|
||||
MediaPlayButton,
|
||||
MediaTimeDisplay,
|
||||
MediaTimeRange,
|
||||
MediaVolumeRange,
|
||||
} from 'media-chrome/dist/react';
|
||||
import { memo } from 'react';
|
||||
|
||||
@@ -56,9 +55,8 @@ export function FileNote(props: { event?: NDKEvent }) {
|
||||
<MediaControlBar>
|
||||
<MediaPlayButton></MediaPlayButton>
|
||||
<MediaTimeRange></MediaTimeRange>
|
||||
<MediaTimeDisplay showDuration></MediaTimeDisplay>
|
||||
<MediaMuteButton></MediaMuteButton>
|
||||
<MediaVolumeRange></MediaVolumeRange>
|
||||
<MediaFullscreenButton></MediaFullscreenButton>
|
||||
</MediaControlBar>
|
||||
</MediaController>
|
||||
);
|
||||
|
||||
@@ -36,18 +36,21 @@ export function Repost({ event }: { event: NDKEvent }) {
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const renderKind = useCallback((repostEvent: NDKEvent) => {
|
||||
switch (repostEvent.kind) {
|
||||
case NDKKind.Text:
|
||||
return <TextNote content={repostEvent.content} />;
|
||||
case NDKKind.Article:
|
||||
return <ArticleNote event={repostEvent} />;
|
||||
case 1063:
|
||||
return <FileNote event={repostEvent} />;
|
||||
default:
|
||||
return <UnknownNote event={repostEvent} />;
|
||||
}
|
||||
}, []);
|
||||
const renderKind = useCallback(
|
||||
(repostEvent: NDKEvent) => {
|
||||
switch (repostEvent.kind) {
|
||||
case NDKKind.Text:
|
||||
return <TextNote content={repostEvent.content} />;
|
||||
case NDKKind.Article:
|
||||
return <ArticleNote event={repostEvent} />;
|
||||
case 1063:
|
||||
return <FileNote event={repostEvent} />;
|
||||
default:
|
||||
return <UnknownNote event={repostEvent} />;
|
||||
}
|
||||
},
|
||||
[event.id]
|
||||
);
|
||||
|
||||
if (embedEvent) {
|
||||
return (
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import { ImagePreview, LinkPreview, VideoPreview } from '@shared/notes';
|
||||
|
||||
import { useRichContent } from '@utils/hooks/useRichContent';
|
||||
|
||||
export function TextNote(props: { content?: string; truncate?: boolean }) {
|
||||
const { parsedContent, images, videos, linkPreview } = useRichContent(props.content);
|
||||
const { parsedContent } = useRichContent(props.content);
|
||||
|
||||
if (props.truncate) {
|
||||
return (
|
||||
<div className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert">
|
||||
<div className="break-p select-text whitespace-pre-line leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
{props.content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-img:mb-0 prose-img:mt-0">
|
||||
{parsedContent}
|
||||
</div>
|
||||
{images.length ? <ImagePreview urls={images} /> : null}
|
||||
{videos.length ? <VideoPreview urls={videos} /> : null}
|
||||
{linkPreview ? <LinkPreview url={linkPreview} /> : null}
|
||||
<div className="break-p select-text whitespace-pre-line leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
{parsedContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import Zoom from 'react-medium-image-zoom';
|
||||
|
||||
import { DownloadIcon } from '@shared/icons';
|
||||
|
||||
export function ImagePreview({ urls }: { urls: string[] }) {
|
||||
export function ImagePreview({ url }: { url: string }) {
|
||||
const downloadImage = async (url: string) => {
|
||||
const downloadDirPath = await downloadDir();
|
||||
const filename = url.substring(url.lastIndexOf('/') + 1);
|
||||
@@ -17,29 +17,25 @@ export function ImagePreview({ urls }: { urls: string[] }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<Zoom key={url} zoomMargin={50}>
|
||||
<div className="group relative">
|
||||
<img
|
||||
src={url}
|
||||
alt={url}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: 'auto' }}
|
||||
onError={fallback}
|
||||
className="h-auto w-full rounded-lg border border-neutral-300 object-cover dark:border-neutral-700"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => downloadImage(url)}
|
||||
className="absolute right-2 top-2 hidden h-10 w-10 items-center justify-center rounded-xl bg-black/50 backdrop-blur-xl group-hover:inline-flex hover:bg-blue-500"
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
</Zoom>
|
||||
))}
|
||||
</div>
|
||||
<Zoom key={url} zoomMargin={50}>
|
||||
<div className="group relative mt-2">
|
||||
<img
|
||||
src={url}
|
||||
alt={url}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: 'auto' }}
|
||||
onError={fallback}
|
||||
className="h-auto w-full rounded-lg border border-neutral-300/50 object-cover dark:border-neutral-700/50"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => downloadImage(url)}
|
||||
className="absolute right-2 top-2 hidden h-10 w-10 items-center justify-center rounded-lg bg-black/50 backdrop-blur-xl group-hover:inline-flex hover:bg-blue-500"
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
</Zoom>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function LinkPreview({ url }: { url: string }) {
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
<div className="flex w-full flex-col bg-neutral-200 dark:bg-neutral-800">
|
||||
<div className="mt-2 flex w-full flex-col rounded-lg border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<div className="h-44 w-full animate-pulse bg-neutral-400 dark:bg-neutral-600" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="h-3 w-2/3 animate-pulse rounded bg-neutral-400 dark:bg-neutral-600" />
|
||||
@@ -30,7 +30,7 @@ export function LinkPreview({ url }: { url: string }) {
|
||||
to={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex w-full flex-col rounded-lg border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
|
||||
className="mt-2 flex w-full flex-col rounded-lg border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
|
||||
>
|
||||
{isImage(data.image) ? (
|
||||
<img
|
||||
@@ -42,19 +42,19 @@ export function LinkPreview({ url }: { url: string }) {
|
||||
<div className="flex flex-col items-start px-3 py-3">
|
||||
<div className="flex flex-col items-start gap-1 text-left">
|
||||
{data.title && (
|
||||
<h5 className="line-clamp-1 text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
<div className="line-clamp-1 text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{data.title}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
{data.description ? (
|
||||
<p className="mb-2.5 line-clamp-3 break-all text-sm text-neutral-700 dark:text-neutral-400">
|
||||
<div className="mb-2 line-clamp-3 break-all text-sm text-neutral-700 dark:text-neutral-400">
|
||||
{data.description}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="break-all text-sm text-neutral-600 dark:text-neutral-400">
|
||||
<div className="break-all text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import {
|
||||
MediaControlBar,
|
||||
MediaController,
|
||||
MediaFullscreenButton,
|
||||
MediaLoadingIndicator,
|
||||
MediaMuteButton,
|
||||
MediaPlayButton,
|
||||
MediaTimeDisplay,
|
||||
MediaTimeRange,
|
||||
MediaVolumeRange,
|
||||
} from 'media-chrome/dist/react';
|
||||
|
||||
export function VideoPreview({ urls }: { urls: string[] }) {
|
||||
export function VideoPreview({ url }: { url: string }) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<MediaController key={url} className="aspect-video overflow-hidden rounded-lg">
|
||||
<video slot="media" src={url} preload="metadata" muted />
|
||||
<MediaControlBar>
|
||||
<MediaPlayButton></MediaPlayButton>
|
||||
<MediaTimeRange></MediaTimeRange>
|
||||
<MediaTimeDisplay showDuration></MediaTimeDisplay>
|
||||
<MediaMuteButton></MediaMuteButton>
|
||||
<MediaVolumeRange></MediaVolumeRange>
|
||||
</MediaControlBar>
|
||||
</MediaController>
|
||||
))}
|
||||
</div>
|
||||
<MediaController
|
||||
key={url}
|
||||
className="mt-2 aspect-video w-full overflow-hidden rounded-lg"
|
||||
>
|
||||
<video slot="media" src={url} preload="metadata" muted />
|
||||
<MediaLoadingIndicator slot="centered-chrome"></MediaLoadingIndicator>
|
||||
<MediaControlBar>
|
||||
<MediaPlayButton></MediaPlayButton>
|
||||
<MediaTimeRange></MediaTimeRange>
|
||||
<MediaMuteButton></MediaMuteButton>
|
||||
<MediaFullscreenButton></MediaFullscreenButton>
|
||||
</MediaControlBar>
|
||||
</MediaController>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@ export function NoteWrapper({
|
||||
return event.tags[0][1];
|
||||
}
|
||||
return event.tags.find((el) => el[3] === 'root')?.[1];
|
||||
}, [event]);
|
||||
}, [event.id]);
|
||||
|
||||
const reply = useMemo(() => event.tags.find((el) => el[3] === 'reply')?.[1], []);
|
||||
const reply = useMemo(
|
||||
() => event.tags.find((el) => el[3] === 'reply')?.[1],
|
||||
[event.id]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-min w-full px-3 pb-3">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 { memo, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { RepostIcon, WorldIcon } from '@shared/icons';
|
||||
@@ -39,9 +39,11 @@ export const User = memo(function User({
|
||||
}) {
|
||||
const { status, user } = useProfile(pubkey, embedProfile);
|
||||
|
||||
const createdAt = formatCreatedAt(time, variant === 'chat');
|
||||
const svgURI =
|
||||
'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(pubkey, 90, 50));
|
||||
const createdAt = useMemo(() => formatCreatedAt(time, variant === 'chat'), [pubkey]);
|
||||
const svgURI = useMemo(
|
||||
() => 'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(pubkey, 90, 50)),
|
||||
[pubkey]
|
||||
);
|
||||
|
||||
if (variant === 'mention') {
|
||||
if (status === 'pending') {
|
||||
|
||||
77
src/shared/widgets/liveUpdater.tsx
Normal file
77
src/shared/widgets/liveUpdater.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { NDKEvent, NDKFilter, NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
|
||||
import { QueryStatus, useQueryClient } from '@tanstack/react-query';
|
||||
import { forwardRef, useEffect, useState } from 'react';
|
||||
import { VListHandle } from 'virtua';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ChevronUpIcon } from '@shared/icons';
|
||||
|
||||
export const LiveUpdater = forwardRef(function LiveUpdater({
|
||||
status,
|
||||
ref,
|
||||
}: {
|
||||
status: QueryStatus;
|
||||
ref: VListHandle;
|
||||
}) {
|
||||
const { db } = useStorage();
|
||||
const { ndk } = useNDK();
|
||||
|
||||
const [events, setEvents] = useState<NDKEvent[]>([]);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const update = async () => {
|
||||
await queryClient.setQueryData(
|
||||
['newsfeed'],
|
||||
(prev: { pageParams: number; pages: Array<NDKEvent[]> }) => ({
|
||||
...prev,
|
||||
pages: [[...events], ...prev.pages],
|
||||
})
|
||||
);
|
||||
|
||||
// reset
|
||||
setEvents([]);
|
||||
|
||||
// scroll to top
|
||||
ref.scrollToIndex(0, { smooth: true });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let sub: NDKSubscription = undefined;
|
||||
|
||||
if (status === 'success' && db.account && db.account.circles.length > 0) {
|
||||
queryClient.fetchQuery({ queryKey: ['notification'] });
|
||||
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: db.account.circles,
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
};
|
||||
|
||||
sub = ndk.subscribe(filter, { closeOnEose: false, groupable: false });
|
||||
sub.addListener('event', (event: NDKEvent) =>
|
||||
setEvents((prev) => [...prev, event])
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (sub) sub.stop();
|
||||
};
|
||||
}, [status]);
|
||||
|
||||
if (!events.length) return null;
|
||||
|
||||
return (
|
||||
<div className="absolute left-0 top-11 z-50 flex h-11 w-full items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={update}
|
||||
className="inline-flex h-9 w-max items-center justify-center gap-1 rounded-full bg-blue-500 px-2.5 text-sm font-semibold text-white hover:bg-blue-600"
|
||||
>
|
||||
<ChevronUpIcon className="h-4 w-4" />
|
||||
{events.length} posts
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -1,15 +1,13 @@
|
||||
import { NDKEvent, NDKFilter, NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { VList } from 'virtua';
|
||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { VList, VListHandle } from 'virtua';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import {
|
||||
MemoizedArticleNote,
|
||||
MemoizedFileNote,
|
||||
MemoizedRepost,
|
||||
MemoizedTextNote,
|
||||
NoteSkeleton,
|
||||
@@ -19,9 +17,9 @@ import {
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
export function NewsfeedWidget() {
|
||||
const queryClient = useQueryClient();
|
||||
import { LiveUpdater } from './liveUpdater';
|
||||
|
||||
export function NewsfeedWidget() {
|
||||
const { db } = useStorage();
|
||||
const { relayUrls, ndk, fetcher } = useNDK();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
@@ -41,10 +39,10 @@ export function NewsfeedWidget() {
|
||||
const events = await fetcher.fetchLatestEvents(
|
||||
relayUrls,
|
||||
{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article],
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: db.account.circles,
|
||||
},
|
||||
50,
|
||||
20,
|
||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
||||
);
|
||||
|
||||
@@ -70,79 +68,42 @@ export function NewsfeedWidget() {
|
||||
if (!lastEvent) return;
|
||||
return lastEvent.created_at - 1;
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const ref = useRef<VListHandle>();
|
||||
const allEvents = useMemo(
|
||||
() => (data ? data.pages.flatMap((page) => page) : []),
|
||||
[data]
|
||||
);
|
||||
|
||||
const renderItem = useCallback((event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return (
|
||||
<NoteWrapper key={event.id} event={event}>
|
||||
<MemoizedTextNote />
|
||||
</NoteWrapper>
|
||||
);
|
||||
case NDKKind.Repost:
|
||||
return <MemoizedRepost key={event.id} event={event} />;
|
||||
case 1063:
|
||||
return (
|
||||
<NoteWrapper key={event.id} event={event}>
|
||||
<MemoizedFileNote />
|
||||
</NoteWrapper>
|
||||
);
|
||||
case NDKKind.Article:
|
||||
return (
|
||||
<NoteWrapper key={event.id} event={event}>
|
||||
<MemoizedArticleNote />
|
||||
</NoteWrapper>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<NoteWrapper key={event.id} event={event}>
|
||||
<UnknownNote />
|
||||
</NoteWrapper>
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let sub: NDKSubscription = undefined;
|
||||
|
||||
if (status === 'success' && db.account && db.account.circles.length > 0) {
|
||||
queryClient.fetchQuery({ queryKey: ['notification'] });
|
||||
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: db.account.circles,
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
};
|
||||
|
||||
sub = ndk.subscribe(filter, { closeOnEose: false, groupable: false });
|
||||
sub.addListener('event', async (event: NDKEvent) => {
|
||||
await queryClient.setQueryData(
|
||||
['newsfeed'],
|
||||
(prev: { pageParams: number; pages: Array<NDKEvent[]> }) => ({
|
||||
...prev,
|
||||
pages: [[event], ...prev.pages],
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (sub) sub.stop();
|
||||
};
|
||||
}, [status]);
|
||||
|
||||
console.log('RERENDER');
|
||||
const renderItem = useCallback(
|
||||
(event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return (
|
||||
<NoteWrapper key={event.id} event={event}>
|
||||
<MemoizedTextNote />
|
||||
</NoteWrapper>
|
||||
);
|
||||
case NDKKind.Repost:
|
||||
return <MemoizedRepost key={event.id} event={event} />;
|
||||
default:
|
||||
return (
|
||||
<NoteWrapper key={event.id} event={event}>
|
||||
<UnknownNote />
|
||||
</NoteWrapper>
|
||||
);
|
||||
}
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id="9999" isLive />
|
||||
<VList className="flex-1">
|
||||
<LiveUpdater status={status} ref={ref} />
|
||||
<VList className="flex-1" ref={ref} overscan={2}>
|
||||
{status === 'pending' ? (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
||||
|
||||
@@ -38,7 +38,7 @@ export function NotificationWidget() {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
||||
'#p': [db.account.pubkey],
|
||||
},
|
||||
50,
|
||||
20,
|
||||
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ export function NotificationWidget() {
|
||||
return (
|
||||
<WidgetWrapper>
|
||||
<TitleBar id="9998" title="Notification" isLive />
|
||||
<VList className="flex-1">
|
||||
<VList className="flex-1" overscan={2}>
|
||||
{status === 'pending' ? (
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="rounded-xl bg-neutral-100 px-3 py-3 dark:bg-neutral-900">
|
||||
|
||||
@@ -21,7 +21,7 @@ export function WidgetWrapper({
|
||||
minWidth={420}
|
||||
maxWidth={600}
|
||||
className={twMerge(
|
||||
'flex flex-col border-r-2 border-neutral-50 hover:border-neutral-100 dark:border-neutral-950 dark:hover:border-neutral-900',
|
||||
'relative flex flex-col border-r-2 border-neutral-50 hover:border-neutral-100 dark:border-neutral-950 dark:hover:border-neutral-900',
|
||||
className
|
||||
)}
|
||||
enable={{ right: true }}
|
||||
|
||||
Reference in New Issue
Block a user