diff --git a/src/app/error.tsx b/src/app/error.tsx index 8b667ec8..42722f38 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -39,10 +39,10 @@ export function ErrorScreen() { }, []); return ( -
+
-

+

Sorry, an unexpected error has occurred.

@@ -64,19 +64,19 @@ export function ErrorScreen() { href="https://github.com/luminous-devs/lume/issues/new" target="_blank" rel="noreferrer" - className="inline-flex h-11 w-full items-center justify-center rounded-lg text-sm font-medium text-white backdrop-blur-xl hover:bg-white/10" + className="inline-flex h-11 w-full items-center justify-center rounded-lg text-sm font-medium" > Click here to report the issue on GitHub diff --git a/src/app/explore/components/userLatestPosts.tsx b/src/app/explore/components/userLatestPosts.tsx index 62b553c2..05a0d9fb 100644 --- a/src/app/explore/components/userLatestPosts.tsx +++ b/src/app/explore/components/userLatestPosts.tsx @@ -3,14 +3,7 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback } from 'react'; import { LoaderIcon } from '@shared/icons'; -import { - ArticleNote, - FileNote, - NoteWrapper, - Repost, - TextNote, - UnknownNote, -} from '@shared/notes'; +import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes'; import { useNostr } from '@utils/hooks/useNostr'; @@ -28,31 +21,11 @@ export function UserLatestPosts({ pubkey }: { pubkey: string }) { (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ( - - - - ); + return ; case NDKKind.Repost: - return ; - case 1063: - return ( - - - - ); - case NDKKind.Article: - return ( - - - - ); + return ; default: - return ( - - - - ); + return ; } }, [data] diff --git a/src/app/notes/article.tsx b/src/app/notes/article.tsx index 1d79ab3c..dada6bb9 100644 --- a/src/app/notes/article.tsx +++ b/src/app/notes/article.tsx @@ -5,7 +5,7 @@ import { useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons'; -import { ArticleDetailNote, NoteActions, NoteReplyForm } from '@shared/notes'; +import { NoteActions, NoteReplyForm } from '@shared/notes'; import { ReplyList } from '@shared/notes/replies/list'; import { User } from '@shared/user'; @@ -78,9 +78,7 @@ export function ArticleNoteScreen() {
-
- -
+
{data.content}
diff --git a/src/app/relays/components/relayEventList.tsx b/src/app/relays/components/relayEventList.tsx index 399e1666..12c764eb 100644 --- a/src/app/relays/components/relayEventList.tsx +++ b/src/app/relays/components/relayEventList.tsx @@ -6,14 +6,7 @@ import { VList } from 'virtua'; import { useNDK } from '@libs/ndk/provider'; import { LoaderIcon } from '@shared/icons'; -import { - ArticleNote, - FileNote, - NoteWrapper, - Repost, - TextNote, - UnknownNote, -} from '@shared/notes'; +import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes'; export function RelayEventList({ relayUrl }: { relayUrl: string }) { const { fetcher } = useNDK(); @@ -24,9 +17,9 @@ export function RelayEventList({ relayUrl }: { relayUrl: string }) { const events = await fetcher.fetchLatestEvents( [url], { - kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article], + kinds: [NDKKind.Text, NDKKind.Repost], }, - 100 + 20 ); return events as unknown as NDKEvent[]; }, @@ -37,31 +30,11 @@ export function RelayEventList({ relayUrl }: { relayUrl: string }) { (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ( - - - - ); + return ; case NDKKind.Repost: - return ; - case 1063: - return ( - - - - ); - case NDKKind.Article: - return ( - - - - ); + return ; default: - return ( - - - - ); + return ; } }, [data] diff --git a/src/app/users/index.tsx b/src/app/users/index.tsx index bcad621b..f34c57cf 100644 --- a/src/app/users/index.tsx +++ b/src/app/users/index.tsx @@ -7,14 +7,7 @@ import { UserProfile } from '@app/users/components/profile'; import { useNDK } from '@libs/ndk/provider'; -import { - ArticleNote, - FileNote, - NoteWrapper, - Repost, - TextNote, - UnknownNote, -} from '@shared/notes'; +import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes'; export function UserScreen() { const { pubkey } = useParams(); @@ -23,9 +16,9 @@ export function UserScreen() { queryKey: ['user-feed', pubkey], queryFn: async () => { const events = await ndk.fetchEvents({ - kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Article], + kinds: [NDKKind.Text, NDKKind.Repost], authors: [pubkey], - limit: 50, + limit: 20, }); const sorted = [...events].sort((a, b) => b.created_at - a.created_at); return sorted; @@ -38,31 +31,11 @@ export function UserScreen() { (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ( - - - - ); + return ; case NDKKind.Repost: - return ; - case 1063: - return ( - - - - ); - case NDKKind.Article: - return ( - - - - ); + return ; default: - return ( - - - - ); + return ; } }, [data] diff --git a/src/libs/ndk/instance.ts b/src/libs/ndk/instance.ts index 5a0028b4..dfc0737e 100644 --- a/src/libs/ndk/instance.ts +++ b/src/libs/ndk/instance.ts @@ -77,10 +77,10 @@ export const NDKInstance = () => { const outboxSetting = await db.getSettingValue('outbox'); const explicitRelayUrls = await getExplicitRelays(); - const dexieAdapter = new NDKCacheAdapterTauri(db); + const tauriAdapter = new NDKCacheAdapterTauri(db); const instance = new NDK({ explicitRelayUrls, - cacheAdapter: dexieAdapter, + cacheAdapter: tauriAdapter, outboxRelayUrls: ['wss://purplepag.es'], enableOutboxModel: outboxSetting === '1', }); diff --git a/src/main.jsx b/src/main.jsx index 39f2d8f1..6cd4518b 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,6 +1,4 @@ -import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'; -import { QueryClient } from '@tanstack/react-query'; -import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { createRoot } from 'react-dom/client'; import { Toaster } from 'sonner'; @@ -17,31 +15,16 @@ const queryClient = new QueryClient({ }, }); -const persister = createSyncStoragePersister({ - storage: window.localStorage, -}); - const container = document.getElementById('root'); const root = createRoot(container); root.render( - { - if (query.queryKey === 'widgets') return false; - return true; - }, - }, - }} - > + - + ); diff --git a/src/shared/notes/actions.tsx b/src/shared/notes/actions.tsx index 2c399257..17a1f148 100644 --- a/src/shared/notes/actions.tsx +++ b/src/shared/notes/actions.tsx @@ -25,15 +25,9 @@ export function NoteActions({ return ( -
-
- - - - -
+
{extraButtons && ( -
+
@@ -59,6 +54,12 @@ export function NoteActions({
)} +
+ + + + +
); diff --git a/src/shared/notes/article.tsx b/src/shared/notes/article.tsx new file mode 100644 index 00000000..56d16760 --- /dev/null +++ b/src/shared/notes/article.tsx @@ -0,0 +1,72 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { memo } from 'react'; +import { Link } from 'react-router-dom'; + +import { User } from '@shared/user'; + +import { NoteActions } from './actions'; + +export function ArticleNote({ event }: { event: NDKEvent }) { + const getMetadata = () => { + const title = event.tags.find((tag) => tag[0] === 'title')?.[1]; + const image = event.tags.find((tag) => tag[0] === 'image')?.[1]; + const summary = event.tags.find((tag) => tag[0] === 'summary')?.[1]; + + let publishedAt: Date | string | number = event.tags.find( + (tag) => tag[0] === 'published_at' + )?.[1]; + if (publishedAt) { + publishedAt = new Date(parseInt(publishedAt) * 1000).toLocaleDateString('en-US'); + } else { + publishedAt = new Date(event.created_at * 1000).toLocaleDateString('en-US'); + } + + return { + title, + image, + publishedAt, + summary, + }; + }; + + const metadata = getMetadata(); + + return ( +
+
+ +
+ + {metadata.image && ( + {metadata.title} + )} +
+
+ {metadata.title} +
+ {metadata.summary ? ( +

+ {metadata.summary} +

+ ) : null} + + {metadata.publishedAt.toString()} + +
+ +
+ +
+
+ ); +} + +export const MemoizedArticleNote = memo(ArticleNote); diff --git a/src/shared/notes/child.tsx b/src/shared/notes/child.tsx index 0937a08a..5cf63e8a 100644 --- a/src/shared/notes/child.tsx +++ b/src/shared/notes/child.tsx @@ -1,90 +1,29 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; -import { nip19 } from 'nostr-tools'; -import { useCallback } from 'react'; - -import { - ArticleNote, - FileNote, - LinkPreview, - NoteActions, - NoteSkeleton, - TextNote, - UnknownNote, -} from '@shared/notes'; +import { NoteSkeleton } from '@shared/notes'; import { User } from '@shared/user'; import { useEvent } from '@utils/hooks/useEvent'; -export function ChildNote({ id, root }: { id: string; root?: string }) { +export function ChildNote({ id, isRoot }: { id: string; isRoot?: boolean }) { const { status, data } = useEvent(id); - const renderKind = useCallback( - (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Article: - return ; - case 1063: - return ; - default: - return ; - } - }, - [id] - ); - if (status === 'pending') { - return ( - <> -
-
- -
- - ); - } - - if (status === 'error') { - const noteLink = `https://njump.me/${nip19.noteEncode(id)}`; - return ( - <> -
-
-
-
-
- Lume (System) -
-
-
-
-
-
- Lume cannot find this post with your current relay set, but you can view - it via njump.me -
- -
-
-
- - ); + return ; } return ( - <> -
-
- -
-
-
- {renderKind(data)} - -
+
+
+
+
+ {data.content}
- + +
); } diff --git a/src/shared/notes/file.tsx b/src/shared/notes/file.tsx new file mode 100644 index 00000000..1792b906 --- /dev/null +++ b/src/shared/notes/file.tsx @@ -0,0 +1,93 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { downloadDir } from '@tauri-apps/api/path'; +import { download } from '@tauri-apps/plugin-upload'; +import { + MediaControlBar, + MediaController, + MediaFullscreenButton, + MediaMuteButton, + MediaPlayButton, + MediaTimeRange, +} from 'media-chrome/dist/react'; +import { memo } from 'react'; +import { Link } from 'react-router-dom'; + +import { DownloadIcon } from '@shared/icons'; +import { NoteActions } from '@shared/notes'; +import { User } from '@shared/user'; + +import { fileType } from '@utils/nip94'; + +export function FileNote({ event }: { event: NDKEvent }) { + const downloadImage = async (url: string) => { + const downloadDirPath = await downloadDir(); + const filename = url.substring(url.lastIndexOf('/') + 1); + return await download(url, downloadDirPath + `/${filename}`); + }; + + const renderFileType = () => { + const url = event.tags.find((el) => el[0] === 'url')[1]; + const type = fileType(url); + + switch (type) { + case 'image': + return ( +
+ {url} + +
+ ); + case 'video': + return ( + + + ); + default: + return ( + + {url} + + ); + } + }; + + return ( +
+
+ +
{renderFileType()}
+ +
+
+ ); +} + +export const MemoizedFileNote = memo(FileNote); diff --git a/src/shared/notes/index.ts b/src/shared/notes/index.ts index a9018ac7..83e74953 100644 --- a/src/shared/notes/index.ts +++ b/src/shared/notes/index.ts @@ -1,9 +1,18 @@ +export * from './text'; +export * from './repost'; +export * from './file'; +export * from './article'; +export * from './child'; +export * from './notify'; +export * from './unknown'; +export * from './skeleton'; +export * from './stats'; +export * from './actions'; export * from './actions/reaction'; export * from './actions/reply'; export * from './actions/repost'; export * from './actions/zap'; -export * from './mentions/note'; -export * from './mentions/user'; +export * from './actions/more'; export * from './preview/image'; export * from './preview/link'; export * from './preview/video'; @@ -11,20 +20,11 @@ export * from './replies/form'; export * from './replies/item'; export * from './replies/list'; export * from './replies/sub'; -export * from './kinds/text'; -export * from './kinds/file'; -export * from './kinds/article'; -export * from './kinds/articleDetail'; -export * from './kinds/unknown'; -export * from './metadata'; -export * from './kinds/repost'; -export * from './child'; -export * from './skeleton'; -export * from './actions'; -export * from './mentions/hashtag'; -export * from './mentions/boost'; -export * from './mentions/invoice'; -export * from './stats'; -export * from './wrapper'; -export * from './actions/more'; export * from './replies/replyMediaUploader'; +export * from './mentions/note'; +export * from './mentions/user'; +export * from './mentions/hashtag'; +export * from './mentions/invoice'; +export * from './kinds/text'; +export * from './kinds/article'; +export * from './kinds/file'; diff --git a/src/shared/notes/kinds/article.tsx b/src/shared/notes/kinds/article.tsx index 4f62ea5e..6417d40c 100644 --- a/src/shared/notes/kinds/article.tsx +++ b/src/shared/notes/kinds/article.tsx @@ -1,21 +1,18 @@ -import { NDKEvent } from '@nostr-dev-kit/ndk'; -import { memo, useMemo } from 'react'; +import { NDKTag } from '@nostr-dev-kit/ndk'; +import { memo } from 'react'; import { Link } from 'react-router-dom'; -export function ArticleNote(props: { event?: NDKEvent }) { - const metadata = useMemo(() => { - const title = props.event.tags.find((tag) => tag[0] === 'title')?.[1]; - const image = props.event.tags.find((tag) => tag[0] === 'image')?.[1]; - const summary = props.event.tags.find((tag) => tag[0] === 'summary')?.[1]; +export function ArticleKind({ id, tags }: { id: string; tags: NDKTag[] }) { + const getMetadata = () => { + const title = tags.find((tag) => tag[0] === 'title')?.[1]; + const image = tags.find((tag) => tag[0] === 'image')?.[1]; + const summary = tags.find((tag) => tag[0] === 'summary')?.[1]; - let publishedAt: Date | string | number = props.event.tags.find( + let publishedAt: Date | string | number = tags.find( (tag) => tag[0] === 'published_at' )?.[1]; - if (publishedAt) { - publishedAt = new Date(parseInt(publishedAt) * 1000).toLocaleDateString('en-US'); - } else { - publishedAt = new Date(props.event.created_at * 1000).toLocaleDateString('en-US'); - } + + publishedAt = new Date(parseInt(publishedAt) * 1000).toLocaleDateString('en-US'); return { title, @@ -23,23 +20,25 @@ export function ArticleNote(props: { event?: NDKEvent }) { publishedAt, summary, }; - }, [props.event.id]); + }; + + const metadata = getMetadata(); return ( {metadata.image && ( {metadata.title} )}
-
+
{metadata.title}
{metadata.summary ? ( @@ -55,4 +54,4 @@ export function ArticleNote(props: { event?: NDKEvent }) { ); } -export const MemoizedArticleNote = memo(ArticleNote); +export const MemoizedArticleKind = memo(ArticleKind); diff --git a/src/shared/notes/kinds/articleDetail.tsx b/src/shared/notes/kinds/articleDetail.tsx deleted file mode 100644 index 4fc5f6b6..00000000 --- a/src/shared/notes/kinds/articleDetail.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { NDKEvent } from '@nostr-dev-kit/ndk'; -import Markdown from 'markdown-to-jsx'; - -import { Boost, Hashtag, Invoice, MentionUser } from '@shared/notes'; - -export function ArticleDetailNote({ event }: { event: NDKEvent }) { - return ( - str, - forceBlock: true, - enforceAtxHeadings: true, - }} - className="break-p prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-500" - > - {event.content} - - ); -} diff --git a/src/shared/notes/kinds/file.tsx b/src/shared/notes/kinds/file.tsx index 38fb833b..326b6383 100644 --- a/src/shared/notes/kinds/file.tsx +++ b/src/shared/notes/kinds/file.tsx @@ -1,4 +1,4 @@ -import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { NDKTag } from '@nostr-dev-kit/ndk'; import { downloadDir } from '@tauri-apps/api/path'; import { download } from '@tauri-apps/plugin-upload'; import { @@ -10,14 +10,14 @@ import { MediaTimeRange, } from 'media-chrome/dist/react'; import { memo } from 'react'; +import { Link } from 'react-router-dom'; import { DownloadIcon } from '@shared/icons'; -import { LinkPreview } from '@shared/notes'; import { fileType } from '@utils/nip94'; -export function FileNote(props: { event?: NDKEvent }) { - const url = props.event.tags.find((el) => el[0] === 'url')[1]; +export function FileKind({ tags }: { tags: NDKTag[] }) { + const url = tags.find((el) => el[0] === 'url')[1]; const type = fileType(url); const downloadImage = async (url: string) => { @@ -28,11 +28,14 @@ export function FileNote(props: { event?: NDKEvent }) { if (type === 'image') { return ( -
+
{url} - · -

- - {compactNumber.format(data.zap)} - {' '} - zaps -

-
-
- - ) : ( -
- )} -
- ); -} diff --git a/src/shared/notes/notify.tsx b/src/shared/notes/notify.tsx new file mode 100644 index 00000000..add7dfcd --- /dev/null +++ b/src/shared/notes/notify.tsx @@ -0,0 +1,108 @@ +import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +import { memo } from 'react'; + +import { ShareIcon } from '@shared/icons'; +import { MemoizedArticleKind, MemoizedFileKind, NoteSkeleton } from '@shared/notes'; +import { User } from '@shared/user'; + +import { WidgetKinds } from '@stores/constants'; + +import { formatCreatedAt } from '@utils/createdAt'; +import { useEvent } from '@utils/hooks/useEvent'; +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 { addWidget } = useWidget(); + + const renderKind = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ( +
+ {event.content} +
+ ); + case NDKKind.Article: + return ; + case 1063: + return ; + default: + return ( +
+ {event.content} +
+ ); + } + }; + + 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') { + return ( +
+
+ +
+
+ ); + } + + return ( +
+
+
+
+
+ {event.kind === 7 ? (event.content === '+' ? '👍' : event.content) : '⚡️'} +
+
+
+ +

+ {renderText(event.kind)} +

+
+
{createdAt}
+
+
+
+
+
{renderKind(data)}
+ +
+
+
+ ); +} + +export const MemoizedNotifyNote = memo(NotifyNote); diff --git a/src/shared/notes/preview/link.tsx b/src/shared/notes/preview/link.tsx index 3097550d..2c84ddab 100644 --- a/src/shared/notes/preview/link.tsx +++ b/src/shared/notes/preview/link.tsx @@ -25,12 +25,25 @@ export function LinkPreview({ url }: { url: string }) { ); } + if (!data.title && !data.image) { + return ( + + {url} + + ); + } + return ( {isImage(data.image) ? (
- {data.title && ( -
+ {data.title ? ( +
{data.title}
- )} + ) : null} {data.description ? (
{data.description} diff --git a/src/shared/notes/repost.tsx b/src/shared/notes/repost.tsx new file mode 100644 index 00000000..ab1c10b4 --- /dev/null +++ b/src/shared/notes/repost.tsx @@ -0,0 +1,78 @@ +import { NDKEvent, NDKKind, NostrEvent } from '@nostr-dev-kit/ndk'; +import { useQuery } from '@tanstack/react-query'; +import { memo } from 'react'; + +import { useNDK } from '@libs/ndk/provider'; + +import { + MemoizedArticleKind, + MemoizedFileKind, + MemoizedTextKind, + NoteActions, + NoteSkeleton, +} from '@shared/notes'; +import { User } from '@shared/user'; + +export function Repost({ event }: { event: NDKEvent }) { + const { ndk } = useNDK(); + const { status, data } = useQuery({ + queryKey: ['repost', event.id], + queryFn: async () => { + try { + if (event.content.length > 50) { + const embed = JSON.parse(event.content) as NostrEvent; + const embedEvent = new NDKEvent(ndk, embed); + return embedEvent; + } + + const id = event.tags.find((el) => el[0] === 'e')[1]; + if (!id) throw new Error('Failed to get repost event id'); + + const ndkEvent = await ndk.fetchEvent(id); + if (!ndkEvent) return Promise.reject(new Error('Failed to get repost event')); + + return ndkEvent; + } catch { + throw new Error('Failed to get repost event'); + } + }, + refetchOnWindowFocus: false, + }); + + const renderContentByKind = () => { + if (!data) return null; + switch (data.kind) { + case NDKKind.Text: + return ; + case 1063: + return ; + case NDKKind.Article: + return ; + default: + return null; + } + }; + + if (status === 'pending') { + return ( +
+ +
+ ); + } + + return ( +
+
+ +
+ + {renderContentByKind()} + +
+
+
+ ); +} + +export const MemoizedRepost = memo(Repost); diff --git a/src/shared/notes/text.tsx b/src/shared/notes/text.tsx new file mode 100644 index 00000000..3b6b1e1a --- /dev/null +++ b/src/shared/notes/text.tsx @@ -0,0 +1,39 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { memo } from 'react'; + +import { ChildNote, NoteActions } from '@shared/notes'; +import { User } from '@shared/user'; + +import { useNostr } from '@utils/hooks/useNostr'; +import { useRichContent } from '@utils/hooks/useRichContent'; + +export function TextNote({ event }: { event: NDKEvent }) { + const { parsedContent } = useRichContent(event.content); + const { getEventThread } = useNostr(); + + const thread = getEventThread(event); + + return ( +
+
+ + {thread ? ( +
+
+ {thread.rootEventId ? : null} + {thread.replyEventId ? : null} +
+
+ ) : null} +
+
+ {parsedContent} +
+
+ +
+
+ ); +} + +export const MemoizedTextNote = memo(TextNote); diff --git a/src/shared/notes/unknown.tsx b/src/shared/notes/unknown.tsx new file mode 100644 index 00000000..7e79a6e9 --- /dev/null +++ b/src/shared/notes/unknown.tsx @@ -0,0 +1,28 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; + +import { NoteActions } from '@shared/notes'; +import { User } from '@shared/user'; + +export function UnknownNote({ event }: { event: NDKEvent }) { + return ( +
+
+ +
+ + Event kind: {event.kind} + +

+ Unsupported kind +

+
+
+
+ {event.content.toString()} +
+
+ +
+
+ ); +} diff --git a/src/shared/notes/wrapper.tsx b/src/shared/notes/wrapper.tsx deleted file mode 100644 index 51551c9c..00000000 --- a/src/shared/notes/wrapper.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { NDKEvent } from '@nostr-dev-kit/ndk'; -import { ReactElement, cloneElement, useMemo } from 'react'; - -import { ChildNote, NoteActions } from '@shared/notes'; -import { User } from '@shared/user'; - -export function NoteWrapper({ - event, - children, -}: { - event: NDKEvent; - children: ReactElement; -}) { - const root = useMemo(() => { - if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) { - return event.tags[0][1]; - } - return event.tags.find((el) => el[3] === 'root')?.[1]; - }, [event.id]); - - const reply = useMemo( - () => event.tags.find((el) => el[3] === 'reply')?.[1], - [event.id] - ); - - return ( -
-
-
{root && }
-
{reply && }
-
- -
-
-
- {cloneElement( - children, - event.kind === 1 ? { content: event.content } : { event: event } - )} - -
-
-
-
-
- ); -} diff --git a/src/shared/notification/notifyNote.tsx b/src/shared/notification/notifyNote.tsx deleted file mode 100644 index f6ebcd6c..00000000 --- a/src/shared/notification/notifyNote.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; - -import { - ArticleNote, - FileNote, - NoteSkeleton, - TextNote, - UnknownNote, -} from '@shared/notes'; -import { User } from '@shared/user'; - -import { WidgetKinds } from '@stores/constants'; - -import { formatCreatedAt } from '@utils/createdAt'; -import { useEvent } from '@utils/hooks/useEvent'; -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 { addWidget } = useWidget(); - - const openThread = (event, thread: string) => { - const selection = window.getSelection(); - if (selection.toString().length === 0) { - addWidget.mutate({ - kind: WidgetKinds.local.thread, - title: 'Thread', - content: thread, - }); - } else { - event.stopPropagation(); - } - }; - - const renderKind = (event: NDKEvent) => { - if (!event) return null; - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Article: - return ; - case 1063: - return ; - default: - return ; - } - }; - - const renderText = (kind: number) => { - switch (kind) { - case NDKKind.Text: - return 'replied'; - case NDKKind.Reaction: - return `reacted ${event.content}`; - case NDKKind.Repost: - return 'reposted'; - case NDKKind.Zap: - return 'zapped'; - default: - return 'Unknown'; - } - }; - - if (status === 'pending') { - return ( -
-
- -
-
- ); - } - - return ( -
-
-
-
-
- -

- {renderText(event.kind)} -

-
- {createdAt} -
- {event.kind === 1 ? : null} -
-
openThread(e, data?.id)} - onKeyDown={(e) => openThread(e, data?.id)} - role="button" - tabIndex={0} - className="cursor-default rounded-lg border border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800" - > - -
{renderKind(data)}
-
-
-
- ); -} diff --git a/src/shared/user.tsx b/src/shared/user.tsx index 30aa39e1..4d219395 100644 --- a/src/shared/user.tsx +++ b/src/shared/user.tsx @@ -18,6 +18,7 @@ export const User = memo(function User({ time, variant = 'default', embedProfile, + subtext, }: { pubkey: string; eventId?: string; @@ -34,8 +35,10 @@ export const User = memo(function User({ | 'miniavatar' | 'avatar' | 'stacked' - | 'ministacked'; + | 'ministacked' + | 'childnote'; embedProfile?: string; + subtext?: string; }) { const { status, user } = useProfile(pubkey, embedProfile); @@ -63,7 +66,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-6 w-6 rounded-md" /> @@ -106,7 +108,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-8 w-8 rounded-md" /> @@ -117,7 +118,7 @@ export const User = memo(function User({ /> -
+
{user?.name || user?.display_name || user?.displayName || @@ -149,7 +150,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-14 w-14 rounded-lg" /> @@ -207,7 +207,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-11 w-11 rounded-lg" /> @@ -244,7 +243,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-12 w-12 rounded-lg" /> @@ -261,18 +259,17 @@ export const User = memo(function User({ if (variant === 'miniavatar') { if (status === 'pending') { return ( -
+
); } return ( - + @@ -286,6 +283,44 @@ export const User = memo(function User({ ); } + if (variant === 'childnote') { + if (status === 'pending') { + return ( +
+ ); + } + + return ( + <> + + + + {pubkey} + + +
+ {user?.display_name || + user?.name || + user?.displayName || + displayNpub(pubkey, 16)}{' '} + + {subtext}: + +
+ + ); + } + if (variant === 'stacked') { if (status === 'pending') { return ( @@ -300,7 +335,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="inline-block h-8 w-8 rounded-full ring-1 ring-neutral-200 dark:ring-neutral-800" /> @@ -328,7 +362,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="inline-block h-6 w-6 rounded-full ring-1 ring-white dark:ring-black" /> @@ -358,8 +391,8 @@ export const User = memo(function User({ } return ( -
-
+
+
@@ -369,7 +402,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-6 w-6 rounded" /> @@ -415,7 +447,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-10 w-10 rounded-lg" /> @@ -453,22 +484,21 @@ export const User = memo(function User({ return ( -
+
- + {pubkey} @@ -498,7 +528,6 @@ export const User = memo(function User({ alt={pubkey} loading="lazy" decoding="async" - style={{ contentVisibility: 'auto' }} className="h-10 w-10 rounded-lg" /> diff --git a/src/shared/widgets/global/articles.tsx b/src/shared/widgets/global/articles.tsx index 35fb77ee..6250d822 100644 --- a/src/shared/widgets/global/articles.tsx +++ b/src/shared/widgets/global/articles.tsx @@ -6,7 +6,7 @@ import { VList } from 'virtua'; import { useNDK } from '@libs/ndk/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; -import { ArticleNote, NoteWrapper } from '@shared/notes'; +import { MemoizedArticleNote } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; @@ -79,11 +79,7 @@ export function GlobalArticlesWidget({ params }: { params: Widget }) {
) : ( - allEvents.map((item) => ( - - - - )) + allEvents.map((item) => ) )}
{hasNextPage ? ( diff --git a/src/shared/widgets/global/files.tsx b/src/shared/widgets/global/files.tsx index eefc7a6f..2a2b5342 100644 --- a/src/shared/widgets/global/files.tsx +++ b/src/shared/widgets/global/files.tsx @@ -6,7 +6,7 @@ import { VList } from 'virtua'; import { useNDK } from '@libs/ndk/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; -import { FileNote, NoteWrapper } from '@shared/notes'; +import { MemoizedFileNote } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; @@ -79,11 +79,7 @@ export function GlobalFilesWidget({ params }: { params: Widget }) {
) : ( - allEvents.map((item) => ( - - - - )) + allEvents.map((item) => ) )}
{hasNextPage ? ( diff --git a/src/shared/widgets/global/hashtag.tsx b/src/shared/widgets/global/hashtag.tsx index f1b88abe..3dcae2d0 100644 --- a/src/shared/widgets/global/hashtag.tsx +++ b/src/shared/widgets/global/hashtag.tsx @@ -6,14 +6,7 @@ import { VList } from 'virtua'; import { useNDK } from '@libs/ndk/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; -import { - ArticleNote, - FileNote, - NoteWrapper, - Repost, - TextNote, - UnknownNote, -} from '@shared/notes'; +import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; @@ -37,7 +30,7 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) { const events = await fetcher.fetchLatestEvents( relayUrls, { - kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article], + kinds: [NDKKind.Text, NDKKind.Repost], '#t': [params.content], }, FETCH_LIMIT, @@ -65,36 +58,19 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) { ); // render event match event kind - const renderItem = useCallback((event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ( - - - - ); - case NDKKind.Repost: - return ; - case 1063: - return ( - - - - ); - case NDKKind.Article: - return ( - - - - ); - default: - return ( - - - - ); - } - }, []); + const renderItem = useCallback( + (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }, + [data] + ); return ( diff --git a/src/shared/widgets/index.ts b/src/shared/widgets/index.ts index f23db79a..c566c928 100644 --- a/src/shared/widgets/index.ts +++ b/src/shared/widgets/index.ts @@ -13,3 +13,4 @@ export * from './tmp/feeds'; export * from './tmp/hashtag'; export * from './newsfeed'; export * from './notification'; +export * from './liveUpdater'; diff --git a/src/shared/widgets/liveUpdater.tsx b/src/shared/widgets/liveUpdater.tsx index 7e42c8bc..dcf0f054 100644 --- a/src/shared/widgets/liveUpdater.tsx +++ b/src/shared/widgets/liveUpdater.tsx @@ -1,20 +1,13 @@ 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 { useEffect, useState } from 'react'; 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; -}) { +export function LiveUpdater({ status }: { status: QueryStatus }) { const { db } = useStorage(); const { ndk } = useNDK(); @@ -32,9 +25,6 @@ export const LiveUpdater = forwardRef(function LiveUpdater({ // reset setEvents([]); - - // scroll to top - ref.scrollToIndex(0, { smooth: true }); }; useEffect(() => { @@ -74,4 +64,4 @@ export const LiveUpdater = forwardRef(function LiveUpdater({
); -}); +} diff --git a/src/shared/widgets/local/articles.tsx b/src/shared/widgets/local/articles.tsx index 6bc44dc6..1a9a9470 100644 --- a/src/shared/widgets/local/articles.tsx +++ b/src/shared/widgets/local/articles.tsx @@ -7,7 +7,7 @@ import { useNDK } from '@libs/ndk/provider'; import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; -import { ArticleNote, NoteWrapper } from '@shared/notes'; +import { MemoizedArticleNote } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; @@ -82,11 +82,7 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
) : ( - allEvents.map((item) => ( - - - - )) + allEvents.map((item) => ) )}
{hasNextPage ? ( diff --git a/src/shared/widgets/local/feeds.tsx b/src/shared/widgets/local/feeds.tsx index 29979aa9..d129d99c 100644 --- a/src/shared/widgets/local/feeds.tsx +++ b/src/shared/widgets/local/feeds.tsx @@ -7,12 +7,9 @@ import { useNDK } from '@libs/ndk/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; import { - MemoizedArticleNote, - MemoizedFileNote, MemoizedRepost, MemoizedTextNote, NoteSkeleton, - NoteWrapper, UnknownNote, } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; @@ -38,7 +35,7 @@ export function LocalFeedsWidget({ params }: { params: Widget }) { const events = await fetcher.fetchLatestEvents( relayUrls, { - kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article], + kinds: [NDKKind.Text, NDKKind.Repost], authors: JSON.parse(params.content), }, FETCH_LIMIT, @@ -65,36 +62,19 @@ export function LocalFeedsWidget({ params }: { params: Widget }) { [data] ); - const renderItem = useCallback((event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ( - - - - ); - case NDKKind.Repost: - return ; - case 1063: - return ( - - - - ); - case NDKKind.Article: - return ( - - - - ); - default: - return ( - - - - ); - } - }, []); + const renderItem = useCallback( + (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Repost: + return ; + default: + return ; + } + }, + [data] + ); return ( diff --git a/src/shared/widgets/local/files.tsx b/src/shared/widgets/local/files.tsx index e36dc59a..c9d4a7d0 100644 --- a/src/shared/widgets/local/files.tsx +++ b/src/shared/widgets/local/files.tsx @@ -7,7 +7,7 @@ import { useNDK } from '@libs/ndk/provider'; import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; -import { FileNote, NoteWrapper } from '@shared/notes'; +import { MemoizedFileNote } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; @@ -82,11 +82,7 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
) : ( - allEvents.map((item) => ( - - - - )) + allEvents.map((item) => ) )}
{hasNextPage ? ( diff --git a/src/shared/widgets/local/user.tsx b/src/shared/widgets/local/user.tsx index 4f6311f7..c65beaeb 100644 --- a/src/shared/widgets/local/user.tsx +++ b/src/shared/widgets/local/user.tsx @@ -6,12 +6,9 @@ import { WVList } from 'virtua'; import { useNDK } from '@libs/ndk/provider'; import { - ArticleNote, - FileNote, + MemoizedRepost, + MemoizedTextNote, NoteSkeleton, - NoteWrapper, - Repost, - TextNote, UnknownNote, } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; @@ -30,8 +27,7 @@ export function LocalUserWidget({ params }: { params: Widget }) { const dedupQueue = new Set(); const events = await ndk.fetchEvents({ - // @ts-expect-error, NDK not support file metadata yet - kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article], + kinds: [NDKKind.Text, NDKKind.Repost], authors: [params.content], since: nHoursAgo(24), }); @@ -62,31 +58,11 @@ export function LocalUserWidget({ params }: { params: Widget }) { (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ( - - - - ); + return ; case NDKKind.Repost: - return ; - case 1063: - return ( - - - - ); - case NDKKind.Article: - return ( - - - - ); + return ; default: - return ( - - - - ); + return ; } }, [data] diff --git a/src/shared/widgets/newsfeed.tsx b/src/shared/widgets/newsfeed.tsx index b8bb6ed1..f6d3f50c 100644 --- a/src/shared/widgets/newsfeed.tsx +++ b/src/shared/widgets/newsfeed.tsx @@ -11,13 +11,11 @@ import { MemoizedRepost, MemoizedTextNote, NoteSkeleton, - NoteWrapper, UnknownNote, } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; - -import { LiveUpdater } from './liveUpdater'; +import { LiveUpdater } from '@shared/widgets'; export function NewsfeedWidget() { const { db } = useStorage(); @@ -81,19 +79,11 @@ export function NewsfeedWidget() { (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ( - - - - ); + return ; case NDKKind.Repost: return ; default: - return ( - - - - ); + return ; } }, [data] @@ -102,7 +92,7 @@ export function NewsfeedWidget() { return ( - + {status === 'pending' ? (
diff --git a/src/shared/widgets/nostrBand/trendingNotes.tsx b/src/shared/widgets/nostrBand/trendingNotes.tsx index 46beb1ad..39541c00 100644 --- a/src/shared/widgets/nostrBand/trendingNotes.tsx +++ b/src/shared/widgets/nostrBand/trendingNotes.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'; import { VList } from 'virtua'; import { LoaderIcon } from '@shared/icons'; -import { NoteWrapper, TextNote } from '@shared/notes'; +import { MemoizedTextNote } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; @@ -58,9 +58,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) { ) : ( {data.map((item) => ( - - - + ))}
diff --git a/src/shared/widgets/notification.tsx b/src/shared/widgets/notification.tsx index 493ba319..ac343e62 100644 --- a/src/shared/widgets/notification.tsx +++ b/src/shared/widgets/notification.tsx @@ -7,8 +7,7 @@ import { useNDK } from '@libs/ndk/provider'; import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; -import { NoteSkeleton } from '@shared/notes'; -import { NotifyNote } from '@shared/notification/notifyNote'; +import { MemoizedNotifyNote, NoteSkeleton } from '@shared/notes'; import { TitleBar } from '@shared/titleBar'; import { WidgetWrapper } from '@shared/widgets'; @@ -67,7 +66,7 @@ export function NotificationWidget() { const renderEvent = useCallback((event: NDKEvent) => { if (event.pubkey === db.account.pubkey) return null; - return ; + return ; }, []); useEffect(() => { diff --git a/src/utils/hooks/useNostr.ts b/src/utils/hooks/useNostr.ts index 7aee73f6..6bda592b 100644 --- a/src/utils/hooks/useNostr.ts +++ b/src/utils/hooks/useNostr.ts @@ -50,6 +50,26 @@ export function useNostr() { } }; + const getEventThread = (event: NDKEvent) => { + let rootEventId: string; + let replyEventId: string; + + if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) { + rootEventId = event.tags[0][1]; + } + + rootEventId = event.tags.find((el) => el[3] === 'root')?.[1] || null; + // eslint-disable-next-line prefer-const + replyEventId = event.tags.find((el) => el[3] === 'reply')?.[1] || null; + + if (!rootEventId && !replyEventId) return null; + + return { + rootEventId, + replyEventId, + }; + }; + const getAllActivities = async (limit?: number) => { try { const events = await ndk.fetchEvents({ @@ -163,36 +183,6 @@ export function useNostr() { return dedup; }; - const getAllEventsSinceLastLogin = async (customSince?: number) => { - try { - const dbEventsEmpty = await db.isEventsEmpty(); - - let since: number; - if (!customSince) { - if (dbEventsEmpty || db.account.last_login_at === 0) { - since = db.account.circles.length > 500 ? nHoursAgo(12) : nHoursAgo(24); - } else { - since = db.account.last_login_at; - } - } else { - since = customSince; - } - - const events = (await fetcher.fetchAllEvents( - relayUrls, - { - kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article], - authors: db.account.circles, - }, - { since: since } - )) as unknown as NDKEvent[]; - - return events; - } catch (e) { - console.error('prefetch events failed, error: ', e); - } - }; - const getContactsByPubkey = async (pubkey: string) => { const user = ndk.getUser({ pubkey: pubkey }); const follows = [...(await user.follows())].map((user) => user.hexpubkey); @@ -279,8 +269,8 @@ export function useNostr() { return { sub, + getEventThread, getAllNIP04Chats, - getAllEventsSinceLastLogin, getContactsByPubkey, getEventsByPubkey, getAllRelaysByUsers,