diff --git a/src/app/auth/import.tsx b/src/app/auth/import.tsx index cbf25686..45b31d63 100644 --- a/src/app/auth/import.tsx +++ b/src/app/auth/import.tsx @@ -85,6 +85,9 @@ export function ImportAccountScreen() { try { const privkey = nip19.decode(nsec).data as string; await db.secureSave(pubkey, privkey); + + ndk.signer = new NDKPrivateKeySigner(privkey); + setSavedPrivkey(true); } catch (e) { return toast(`nsec invalid: ${e}`); diff --git a/src/app/chats/index.tsx b/src/app/chats/index.tsx index 723df20f..ea5ed2f7 100644 --- a/src/app/chats/index.tsx +++ b/src/app/chats/index.tsx @@ -36,7 +36,7 @@ export function ChatsScreen() {

All chats @@ -52,6 +52,12 @@ export function ChatsScreen() {

+ ) : data.length < 1 ? ( +
+
+
No message
+
+
) : ( data.map((item) => renderItem(item)) )} diff --git a/src/app/notes/article.tsx b/src/app/notes/article.tsx index 1abcf764..7b636f5e 100644 --- a/src/app/notes/article.tsx +++ b/src/app/notes/article.tsx @@ -1,22 +1,12 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { nip19 } from 'nostr-tools'; import { AddressPointer, EventPointer } from 'nostr-tools/lib/types/nip19'; import { useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { useStorage } from '@libs/storage/provider'; - import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons'; -import { - ArticleDetailNote, - NoteActions, - NoteReplyForm, - NoteStats, - UnknownNote, -} from '@shared/notes'; +import { ArticleDetailNote, NoteActions, NoteReplyForm } from '@shared/notes'; import { ReplyList } from '@shared/notes/replies/list'; -import { NoteSkeleton } from '@shared/notes/skeleton'; import { User } from '@shared/user'; import { useEvent } from '@utils/hooks/useEvent'; @@ -26,7 +16,6 @@ export function ArticleNoteScreen() { const replyRef = useRef(null); const { id } = useParams(); - const { db } = useStorage(); const naddr = id.startsWith('naddr') ? (nip19.decode(id).data as AddressPointer) : null; const { status, data } = useEvent(id, naddr); @@ -48,83 +37,65 @@ export function ArticleNoteScreen() { replyRef.current.scrollIntoView(); }; - const renderKind = (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Article: - return ; - default: - return ; - } - }; - return ( -
-
-
-
-
- -
- - -
-
+
+
+
+ +
+ +
-
- {status === 'loading' ? ( -
-
- -
-
- ) : ( - <> -
-
- -
{renderKind(data)}
-
- -
-
- -
-
- - -
- - )} -
-
+
+
+ {status === 'loading' ? ( +
Loading...
+ ) : ( +
+
+ +
+ +
+
+ +
+
+
+ )} +
+
+ +
+ +
+
+
+
); } diff --git a/src/app/notes/text.tsx b/src/app/notes/text.tsx index ca6fa72c..24ea353d 100644 --- a/src/app/notes/text.tsx +++ b/src/app/notes/text.tsx @@ -5,15 +5,12 @@ import { EventPointer } from 'nostr-tools/lib/types/nip19'; import { useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { useStorage } from '@libs/storage/provider'; - import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons'; import { ArticleNote, FileNote, NoteActions, NoteReplyForm, - NoteStats, TextNote, UnknownNote, } from '@shared/notes'; @@ -23,16 +20,12 @@ import { User } from '@shared/user'; import { useEvent } from '@utils/hooks/useEvent'; export function TextNoteScreen() { + const { id } = useParams(); + const { status, data } = useEvent(id); + const navigate = useNavigate(); const replyRef = useRef(null); - const { id } = useParams(); - - console.log(id); - - const { db } = useStorage(); - const { status, data } = useEvent(id); - const [isCopy, setIsCopy] = useState(false); const share = async () => { @@ -64,67 +57,62 @@ export function TextNoteScreen() { }; return ( -
-
-
-
-
- -
- - -
-
+
+
+
+ +
+ +
-
- {status === 'loading' ? ( -
-
- Loading... -
-
- ) : ( -
-
- -
{renderKind(data)}
-
- -
-
- -
- )} -
- - -
-
-
+
+
+ {status === 'loading' ? ( +
Loading...
+ ) : ( +
+
+ +
{renderKind(data)}
+
+ +
+
+
+ )} +
+
+ +
+ +
+
+
+
); } diff --git a/src/app/space/components/widgetList.tsx b/src/app/space/components/widgetList.tsx index c04a930b..4229f931 100644 --- a/src/app/space/components/widgetList.tsx +++ b/src/app/space/components/widgetList.tsx @@ -4,6 +4,7 @@ import { useStorage } from '@libs/storage/provider'; import { ArticleIcon, + BellIcon, FileIcon, FollowsIcon, GroupFeedsIcon, @@ -50,16 +51,18 @@ export function WidgetList({ params }: { params: Widget }) { ); case WidgetKinds.tmp.xhashtag: return ( - + ); case WidgetKinds.nostrBand.trendingAccounts: case WidgetKinds.nostrBand.trendingNotes: return ( - + ); + case WidgetKinds.local.notification: + return ; case WidgetKinds.other.learnNostr: return ( - + ); default: return null; @@ -75,7 +78,7 @@ export function WidgetList({ params }: { params: Widget }) {

{row.title}

-
+
{row.data.map((item, index) => ( +
+ + +
); diff --git a/src/shared/notes/replies/item.tsx b/src/shared/notes/replies/item.tsx index 07ed5e2b..587a8367 100644 --- a/src/shared/notes/replies/item.tsx +++ b/src/shared/notes/replies/item.tsx @@ -17,7 +17,7 @@ export function Reply({ event, root }: { event: NDKEventWithReplies; root?: stri
-
+
{data?.length === 0 ? ( -
+

👋

diff --git a/src/shared/notes/replies/replyMediaUploader.tsx b/src/shared/notes/replies/replyMediaUploader.tsx new file mode 100644 index 00000000..ff956a30 --- /dev/null +++ b/src/shared/notes/replies/replyMediaUploader.tsx @@ -0,0 +1,79 @@ +import { message, open } from '@tauri-apps/plugin-dialog'; +import { readBinaryFile } from '@tauri-apps/plugin-fs'; +import { useState } from 'react'; + +import { MediaIcon } from '@shared/icons'; + +export function ReplyMediaUploader({ setValue }) { + const [loading, setLoading] = useState(false); + + const uploadToNostrBuild = async () => { + try { + // start loading + setLoading(true); + + const selected = await open({ + multiple: false, + filters: [ + { + name: 'Media', + extensions: [ + 'png', + 'jpeg', + 'jpg', + 'gif', + 'mp4', + 'mp3', + 'webm', + 'mkv', + 'avi', + 'mov', + ], + }, + ], + }); + + if (!selected) { + setLoading(false); + return; + } + + const file = await readBinaryFile(selected.path); + const blob = new Blob([file]); + + const data = new FormData(); + data.append('fileToUpload', blob); + data.append('submit', 'Upload Image'); + + const res = await fetch('https://nostr.build/api/v2/upload/files', { + method: 'POST', + body: data, + }); + + if (res.ok) { + const json = await res.json(); + const content = json.data[0]; + + setValue((prev) => prev + ' ' + content.url); + + // stop loading + setLoading(false); + } + } catch (e) { + // stop loading + setLoading(false); + await message(`Upload failed, error: ${e}`, { title: 'Lume', type: 'error' }); + } + }; + + return ( + + ); +} diff --git a/src/shared/notification/notifyNote.tsx b/src/shared/notification/notifyNote.tsx index 3f968ca6..9ec26ea5 100644 --- a/src/shared/notification/notifyNote.tsx +++ b/src/shared/notification/notifyNote.tsx @@ -1,33 +1,38 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +import { useStorage } from '@libs/storage/provider'; + import { ArticleNote, FileNote, - NoteActions, NoteSkeleton, TextNote, UnknownNote, } from '@shared/notes'; import { User } from '@shared/user'; +import { WidgetKinds, useWidgets } from '@stores/widgets'; + import { formatCreatedAt } from '@utils/createdAt'; import { useEvent } from '@utils/hooks/useEvent'; -export function NotifyNote({ - id, - user, - content, - kind, - time, -}: { - id: string; - user: string; - content: string; - kind: NDKKind | number; - time: number; -}) { - const createdAt = formatCreatedAt(time, false); - const { status, data } = useEvent(id); +export function NotifyNote({ event }: { event: NDKEvent }) { + const createdAt = formatCreatedAt(event.created_at, false); + const rootEventId = event.tags.find((el) => el[0] === 'e')?.[1]; + + const { db } = useStorage(); + const { status, data } = useEvent(rootEventId); + + 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(); + } + }; const renderKind = (event: NDKEvent) => { switch (event.kind) { @@ -47,7 +52,7 @@ export function NotifyNote({ case NDKKind.Text: return 'replied'; case NDKKind.Reaction: - return `reacted ${content}`; + return `reacted ${event.content}`; case NDKKind.Repost: return 'reposted'; case NDKKind.Zap: @@ -68,28 +73,29 @@ export function NotifyNote({ } return ( -

-
-
- -

- {renderText(kind)} -

-
- - {createdAt} - -
-
-
- -
-
-
- {renderKind(data)} - +
+
+
+
+
+ +

+ {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 da963d08..38ed83ea 100644 --- a/src/shared/user.tsx +++ b/src/shared/user.tsx @@ -115,13 +115,13 @@ export const User = memo(function User({ loading="lazy" decoding="async" style={{ contentVisibility: 'auto' }} - className="h-8 w-8 rounded-lg" + className="h-8 w-8 rounded-md" /> {pubkey} @@ -416,10 +416,10 @@ export const User = memo(function User({
-
+
{pubkey} @@ -467,13 +467,13 @@ export const User = memo(function User({
View profile Message diff --git a/src/shared/widgets/global/articles.tsx b/src/shared/widgets/global/articles.tsx index 84987c2a..7d202cd6 100644 --- a/src/shared/widgets/global/articles.tsx +++ b/src/shared/widgets/global/articles.tsx @@ -67,9 +67,9 @@ export function GlobalArticlesWidget({ params }: { params: Widget }) {
) : ( - + {data.map((item) => renderItem(item))} -
+
)}
diff --git a/src/shared/widgets/global/files.tsx b/src/shared/widgets/global/files.tsx index 88ab1815..05011ea2 100644 --- a/src/shared/widgets/global/files.tsx +++ b/src/shared/widgets/global/files.tsx @@ -69,9 +69,9 @@ export function GlobalFilesWidget({ params }: { params: Widget }) {
) : ( - + {data.map((item) => renderItem(item))} -
+
)}
diff --git a/src/shared/widgets/global/hashtag.tsx b/src/shared/widgets/global/hashtag.tsx index 3831f587..13c31ce5 100644 --- a/src/shared/widgets/global/hashtag.tsx +++ b/src/shared/widgets/global/hashtag.tsx @@ -99,7 +99,7 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
) : ( - + {data.map((item) => renderItem(item))}
diff --git a/src/shared/widgets/local/articles.tsx b/src/shared/widgets/local/articles.tsx index 8d2a44c2..a4892c53 100644 --- a/src/shared/widgets/local/articles.tsx +++ b/src/shared/widgets/local/articles.tsx @@ -69,7 +69,7 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
) : ( - + {dbEvents.map((item) => renderItem(item))}
{dbEvents.length > 0 ? ( @@ -97,7 +97,7 @@ export function LocalArticlesWidget({ params }: { params: Widget }) { ) : null}
-
+
)}
diff --git a/src/shared/widgets/local/feeds.tsx b/src/shared/widgets/local/feeds.tsx index 2f25be62..c4cd52cd 100644 --- a/src/shared/widgets/local/feeds.tsx +++ b/src/shared/widgets/local/feeds.tsx @@ -105,7 +105,7 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
) : ( - + {dbEvents.map((item) => renderItem(item))}
{dbEvents.length > 0 ? ( @@ -133,7 +133,7 @@ export function LocalFeedsWidget({ params }: { params: Widget }) { ) : null}
-
+
)}
diff --git a/src/shared/widgets/local/files.tsx b/src/shared/widgets/local/files.tsx index b1bb0a55..7beec3d2 100644 --- a/src/shared/widgets/local/files.tsx +++ b/src/shared/widgets/local/files.tsx @@ -69,7 +69,7 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
) : ( - + {dbEvents.map((item) => renderItem(item))}
{dbEvents.length > 0 ? ( @@ -97,7 +97,7 @@ export function LocalFilesWidget({ params }: { params: Widget }) { ) : null}
-
+
)}
diff --git a/src/shared/widgets/local/follows.tsx b/src/shared/widgets/local/follows.tsx index 939993da..9d9b6326 100644 --- a/src/shared/widgets/local/follows.tsx +++ b/src/shared/widgets/local/follows.tsx @@ -104,7 +104,7 @@ export function LocalFollowsWidget({ params }: { params: Widget }) {
) : ( - + {dbEvents.map((item) => renderItem(item))}
{dbEvents.length > 0 ? ( @@ -132,7 +132,7 @@ export function LocalFollowsWidget({ params }: { params: Widget }) { ) : null}
-
+
)}
diff --git a/src/shared/widgets/local/network.tsx b/src/shared/widgets/local/network.tsx index d5d13457..3c34eb1a 100644 --- a/src/shared/widgets/local/network.tsx +++ b/src/shared/widgets/local/network.tsx @@ -145,7 +145,7 @@ export function LocalNetworkWidget() { ) : dbEvents.length === 0 ? ( ) : ( - + {!isFetched ? : null} {dbEvents.map((item) => renderItem(item))}
diff --git a/src/shared/widgets/local/notification.tsx b/src/shared/widgets/local/notification.tsx index 842cab39..b0134c56 100644 --- a/src/shared/widgets/local/notification.tsx +++ b/src/shared/widgets/local/notification.tsx @@ -25,18 +25,8 @@ export function LocalNotificationWidget({ params }: { params: Widget }) { const renderEvent = useCallback( (event: NDKEvent) => { - const rootEventId = event.tags.find((el) => el[0] === 'e')?.[1]; - if (!rootEventId) return null; if (event.pubkey === db.account.pubkey) return null; - return ( - - ); + return ; }, [activities] ); @@ -53,7 +43,7 @@ export function LocalNotificationWidget({ params }: { params: Widget }) { return ( -
+
{!activities ? (
@@ -71,8 +61,9 @@ export function LocalNotificationWidget({ params }: { params: Widget }) {

) : ( - + {activities.map((event) => renderEvent(event))} +
)}
diff --git a/src/shared/widgets/local/thread.tsx b/src/shared/widgets/local/thread.tsx index d7e302c3..63683f1c 100644 --- a/src/shared/widgets/local/thread.tsx +++ b/src/shared/widgets/local/thread.tsx @@ -1,5 +1,6 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; import { useCallback } from 'react'; +import { WVList } from 'virtua'; import { LoaderIcon } from '@shared/icons'; import { @@ -41,7 +42,7 @@ export function LocalThreadWidget({ params }: { params: Widget }) { return ( -
+ {status === 'loading' ? (
@@ -58,7 +59,7 @@ export function LocalThreadWidget({ params }: { params: Widget }) {
-
+ ); } diff --git a/src/shared/widgets/local/user.tsx b/src/shared/widgets/local/user.tsx index 39a50ca7..975a1382 100644 --- a/src/shared/widgets/local/user.tsx +++ b/src/shared/widgets/local/user.tsx @@ -81,7 +81,7 @@ export function LocalUserWidget({ params }: { params: Widget }) { return ( -
+
@@ -107,11 +107,11 @@ export function LocalUserWidget({ params }: { params: Widget }) {
) : ( - {data.map((item) => renderItem(item))} + data.map((item) => renderItem(item)) )}
-
+ ); } diff --git a/src/shared/widgets/nostrBand/trendingAccounts.tsx b/src/shared/widgets/nostrBand/trendingAccounts.tsx index ba03f357..2c38c311 100644 --- a/src/shared/widgets/nostrBand/trendingAccounts.tsx +++ b/src/shared/widgets/nostrBand/trendingAccounts.tsx @@ -57,7 +57,7 @@ export function TrendingAccountsWidget({ params }: { params: Widget }) {
) : ( - + {data.map((item: Profile) => ( ))} diff --git a/src/shared/widgets/nostrBand/trendingNotes.tsx b/src/shared/widgets/nostrBand/trendingNotes.tsx index 442d839a..c5f2adf3 100644 --- a/src/shared/widgets/nostrBand/trendingNotes.tsx +++ b/src/shared/widgets/nostrBand/trendingNotes.tsx @@ -58,7 +58,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
) : ( - + {data.map((item) => ( diff --git a/src/shared/widgets/wrapper.tsx b/src/shared/widgets/wrapper.tsx index d6f87655..63496081 100644 --- a/src/shared/widgets/wrapper.tsx +++ b/src/shared/widgets/wrapper.tsx @@ -21,7 +21,7 @@ export function WidgetWrapper({ minWidth={420} maxWidth={600} className={twMerge( - 'flex flex-col border-r border-neutral-100 dark:border-neutral-900', + '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 }} diff --git a/src/stores/activities.ts b/src/stores/activities.ts index 1087dd82..0265a9e5 100644 --- a/src/stores/activities.ts +++ b/src/stores/activities.ts @@ -3,12 +3,16 @@ import { create } from 'zustand'; interface ActivitiesState { activities: Array; + newMessages: number; setActivities: (events: NDKEvent[]) => void; addActivity: (event: NDKEvent) => void; + addNewMessage: () => void; + clearNewMessage: () => void; } export const useActivities = create((set) => ({ activities: null, + newMessages: 0, setActivities: (events: NDKEvent[]) => { set(() => ({ activities: events, @@ -19,4 +23,10 @@ export const useActivities = create((set) => ({ activities: state.activities ? [event, ...state.activities] : [event], })); }, + addNewMessage: () => { + set((state) => ({ newMessages: state.newMessages + 1 })); + }, + clearNewMessage: () => { + set(() => ({ newMessages: 0 })); + }, })); diff --git a/src/utils/hooks/useNostr.ts b/src/utils/hooks/useNostr.ts index 706ba5a6..7a1da582 100644 --- a/src/utils/hooks/useNostr.ts +++ b/src/utils/hooks/useNostr.ts @@ -30,17 +30,20 @@ export function useNostr() { ) => { if (!ndk) throw new Error('NDK instance not found'); - const subEvent = ndk.subscribe(filter, { - closeOnEose: false, - groupable: groupable ?? true, - }); + const key = JSON.stringify(filter); + if (!subManager.get(key)) { + const subEvent = ndk.subscribe(filter, { + closeOnEose: false, + groupable: groupable ?? true, + }); - subEvent.addListener('event', (event: NDKEvent) => { - callback(event); - }); + subEvent.addListener('event', (event: NDKEvent) => { + callback(event); + }); - subManager.set(JSON.stringify(filter), subEvent); - console.log('current active sub: ', subManager.size); + subManager.set(JSON.stringify(filter), subEvent); + console.log('sub: ', key); + } }; const addContact = async (pubkey: string) => { @@ -68,7 +71,7 @@ export function useNostr() { const events = await ndk.fetchEvents({ kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap], '#p': [db.account.pubkey], - limit: limit ?? 100, + limit: limit ?? 50, }); return [...events]; @@ -179,31 +182,8 @@ export function useNostr() { const getAllEventsSinceLastLogin = async (customSince?: number) => { try { const dbEventsEmpty = await db.isEventsEmpty(); - const circleSetting = await db.getSettingValue('circles'); - - const user = ndk.getUser({ hexpubkey: db.account.pubkey }); - const follows = await user.follows(); - const relayList = await user.relayList(); - - const followsAsArr = []; - follows.forEach((user) => { - followsAsArr.push(user.pubkey); - }); - - // update user's follows - await db.updateAccount('follows', JSON.stringify(followsAsArr)); - if (circleSetting !== '1') - await db.updateAccount('circles', JSON.stringify(followsAsArr)); - - // update user's relay list - if (relayList) { - for (const relay of relayList.relays) { - await db.createRelay(relay); - } - } let since: number; - if (!customSince) { if (dbEventsEmpty || db.account.last_login_at === 0) { since = db.account.circles.length > 500 ? nHoursAgo(12) : nHoursAgo(24);