diff --git a/apps/desktop2/src/routes/app/home.lazy.tsx b/apps/desktop2/src/routes/app/home.lazy.tsx index c25612b3..39c6a685 100644 --- a/apps/desktop2/src/routes/app/home.lazy.tsx +++ b/apps/desktop2/src/routes/app/home.lazy.tsx @@ -1,7 +1,7 @@ import { useArk } from "@lume/ark"; import { ArrowRightCircleIcon, LoaderIcon, SearchIcon } from "@lume/icons"; import { Event, Kind } from "@lume/types"; -import { EmptyFeed, TextNote } from "@lume/ui"; +import { EmptyFeed, RepostNote, TextNote } from "@lume/ui"; import { FETCH_LIMIT } from "@lume/utils"; import { useInfiniteQuery } from "@tanstack/react-query"; import { createLazyFileRoute } from "@tanstack/react-router"; @@ -18,13 +18,13 @@ function Home() { queryKey: ["timeline"], initialPageParam: 0, queryFn: async ({ pageParam }: { pageParam: number }) => { - const events = await ark.get_text_events(FETCH_LIMIT, pageParam); + const events = await ark.get_text_events(FETCH_LIMIT, pageParam, true); return events; }, getNextPageParam: (lastPage) => { const lastEvent = lastPage.at(-1); if (!lastEvent) return; - return lastEvent.created_at; + return lastEvent.created_at - 1; }, select: (data) => data?.pages.flatMap((page) => page), refetchOnWindowFocus: false, @@ -32,8 +32,8 @@ function Home() { const renderItem = (event: Event) => { switch (event.kind) { - case Kind.Text: - return ; + case Kind.Repost: + return ; default: return ; } @@ -59,7 +59,7 @@ function Home() { ) : ( - + {data.map((item) => renderItem(item))} )} diff --git a/apps/desktop2/src/routes/events/$eventId.lazy.tsx b/apps/desktop2/src/routes/events/$eventId.lazy.tsx new file mode 100644 index 00000000..6ca755b2 --- /dev/null +++ b/apps/desktop2/src/routes/events/$eventId.lazy.tsx @@ -0,0 +1,11 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; + +export const Route = createLazyFileRoute("/events/$eventId")({ + component: Event, +}); + +function Event() { + const { eventId } = Route.useParams(); + + return
{eventId}
; +} diff --git a/apps/desktop2/src/routes/users/$pubkey.lazy.tsx b/apps/desktop2/src/routes/users/$pubkey.lazy.tsx new file mode 100644 index 00000000..061cd42d --- /dev/null +++ b/apps/desktop2/src/routes/users/$pubkey.lazy.tsx @@ -0,0 +1,11 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; + +export const Route = createLazyFileRoute("/users/$pubkey")({ + component: User, +}); + +function User() { + const { pubkey } = Route.useParams(); + + return
{pubkey}
; +} diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index b95839bb..d4af7402 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -1,5 +1,6 @@ import type { CurrentAccount, Event, Keys, Metadata } from "@lume/types"; import { invoke } from "@tauri-apps/api/core"; +import { WebviewWindow } from "@tauri-apps/api/webview"; export class Ark { public account: CurrentAccount; @@ -59,21 +60,56 @@ export class Ark { public async get_event(id: string) { try { - const cmd: string = await invoke("get_event", { id }); + const eventId: string = id + .replace("nostr:", "") + .split("'")[0] + .split(".")[0]; + const cmd: string = await invoke("get_event", { id: eventId }); const event = JSON.parse(cmd) as Event; + return event; } catch (e) { return null; } } - public async get_text_events(limit: number, asOf?: number) { + public async get_text_events(limit: number, asOf?: number, dedup?: boolean) { try { let until: string = undefined; if (asOf && asOf > 0) until = asOf.toString(); - const cmd: Event[] = await invoke("get_text_events", { limit, until }); - return cmd; + const seenIds = new Set(); + const dedupQueue = new Set(); + + const nostrEvents: Event[] = await invoke("get_text_events", { + limit, + until, + }); + + if (dedup) { + for (const event of nostrEvents) { + const tags = event.tags + .filter((el) => el[0] === "e") + ?.map((item) => item[1]); + + if (tags.length) { + for (const tag of tags) { + if (seenIds.has(tag)) { + dedupQueue.add(event.id); + break; + } + + seenIds.add(tag); + } + } + } + + return nostrEvents + .filter((event) => !dedupQueue.has(event.id)) + .sort((a, b) => b.created_at - a.created_at); + } + + return nostrEvents.sort((a, b) => b.created_at - a.created_at); } catch (e) { return []; } @@ -170,9 +206,16 @@ export class Ark { }; } - public async get_profile(id: string) { + public async get_profile(pubkey: string) { try { + const id = pubkey + .replace("nostr:", "") + .split("'")[0] + .split(".")[0] + .split(",")[0] + .split("?")[0]; const cmd: Metadata = await invoke("get_profile", { id }); + return cmd; } catch { return null; @@ -202,4 +245,26 @@ export class Ark { return false; } } + + public open_thread(id: string) { + return new WebviewWindow(`event-${id}`, { + title: "Thread", + url: `/events/${id}`, + width: 600, + height: 800, + hiddenTitle: true, + titleBarStyle: "overlay", + }); + } + + public open_profile(pubkey: string) { + return new WebviewWindow(`user-${pubkey}`, { + title: "Profile", + url: `/users/${pubkey}`, + width: 600, + height: 800, + hiddenTitle: true, + titleBarStyle: "overlay", + }); + } } diff --git a/packages/ui/src/note/buttons/reply.tsx b/packages/ui/src/note/buttons/reply.tsx index 21f3260e..d1302412 100644 --- a/packages/ui/src/note/buttons/reply.tsx +++ b/packages/ui/src/note/buttons/reply.tsx @@ -2,9 +2,12 @@ import { ReplyIcon } from "@lume/icons"; import * as Tooltip from "@radix-ui/react-tooltip"; import { useTranslation } from "react-i18next"; import { useNoteContext } from "../provider"; +import { useArk } from "@lume/ark"; export function NoteReply() { + const ark = useArk(); const event = useNoteContext(); + const { t } = useTranslation(); return ( @@ -13,6 +16,7 @@ export function NoteReply() { diff --git a/packages/ui/src/note/primitives/repost.tsx b/packages/ui/src/note/primitives/repost.tsx index 821c6643..b4adc458 100644 --- a/packages/ui/src/note/primitives/repost.tsx +++ b/packages/ui/src/note/primitives/repost.tsx @@ -73,17 +73,17 @@ export function RepostNote({ return ( - +
- +
{t("note.reposted")} @@ -92,18 +92,23 @@ export function RepostNote({ -
-
+
+
- -
- -
- - - +
+
+
+ +
+ +
+ + + +
+
diff --git a/packages/ui/src/note/primitives/text.tsx b/packages/ui/src/note/primitives/text.tsx index cd4a3d97..b30e6933 100644 --- a/packages/ui/src/note/primitives/text.tsx +++ b/packages/ui/src/note/primitives/text.tsx @@ -24,8 +24,8 @@ export function TextNote({
- +
diff --git a/packages/ui/src/note/thread.tsx b/packages/ui/src/note/thread.tsx index ae2f3d46..43151c46 100644 --- a/packages/ui/src/note/thread.tsx +++ b/packages/ui/src/note/thread.tsx @@ -20,12 +20,12 @@ export function NoteThread({ className }: { className?: string }) { return (
- {thread.replyEventId ? ( - - ) : null} {thread.rootEventId ? ( ) : null} + {thread.replyEventId ? ( + + ) : null}
- ); - } + if (!user.profile) { + return ( +
+ ); + } - return ( -
- {user.profile.display_name || user.profile.name || "Anon"} -
- ); + return ( +
+ {user.profile.display_name || user.profile.name || "Anon"} +
+ ); } diff --git a/packages/ui/src/user/nip05.tsx b/packages/ui/src/user/nip05.tsx index b3ba6a9b..efe2b3bf 100644 --- a/packages/ui/src/user/nip05.tsx +++ b/packages/ui/src/user/nip05.tsx @@ -13,7 +13,6 @@ export function UserNip05({ className }: { className?: string }) { queryFn: async () => { if (!user.profile?.nip05) return false; const verify = await ark.verify_nip05(user.pubkey, user.profile?.nip05); - console.log(verify); return verify; }, enabled: !!user.profile, @@ -23,7 +22,7 @@ export function UserNip05({ className }: { className?: string }) { return (
diff --git a/packages/ui/src/user/time.tsx b/packages/ui/src/user/time.tsx index 3da95cec..5d0f026d 100644 --- a/packages/ui/src/user/time.tsx +++ b/packages/ui/src/user/time.tsx @@ -2,10 +2,13 @@ import { cn, formatCreatedAt } from "@lume/utils"; import { useMemo } from "react"; export function UserTime({ - time, - className, -}: { time: number; className?: string }) { - const createdAt = useMemo(() => formatCreatedAt(time), [time]); + time, + className, +}: { + time: number; + className?: string; +}) { + const createdAt = useMemo(() => formatCreatedAt(time), [time]); - return
{createdAt}
; + return
{createdAt}
; } diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json index 660381f7..20a52d54 100644 --- a/src-tauri/capabilities/main.json +++ b/src-tauri/capabilities/main.json @@ -1,46 +1,49 @@ { - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "desktop-capability", - "description": "Capability for the desktop", - "platforms": ["linux", "macOS", "windows"], - "windows": ["main", "settings", "event-*", "user-*", "column-*"], - "permissions": [ - "path:default", - "event:default", - "window:default", - "app:default", - "resources:default", - "menu:default", - "tray:default", - "theme:allow-set-theme", - "theme:allow-get-theme", - "notification:allow-is-permission-granted", - "notification:allow-request-permission", - "notification:default", - "os:allow-locale", - "os:allow-platform", - "updater:allow-check", - "updater:default", - "window:allow-start-dragging", - "store:allow-get", - { - "identifier": "http:default", - "allow": [ - { - "url": "http://**/" - }, - { - "url": "https://**/" - } - ] - }, - { - "identifier": "fs:allow-read-text-file", - "allow": [ - { - "path": "$RESOURCE/locales/*" - } - ] - } - ] + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "desktop-capability", + "description": "Capability for the desktop", + "platforms": ["linux", "macOS", "windows"], + "windows": ["main", "settings", "event-*", "user-*", "column-*"], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "resources:default", + "menu:default", + "tray:default", + "theme:allow-set-theme", + "theme:allow-get-theme", + "notification:allow-is-permission-granted", + "notification:allow-request-permission", + "notification:default", + "os:allow-locale", + "os:allow-platform", + "updater:allow-check", + "updater:default", + "window:allow-start-dragging", + "store:allow-get", + "clipboard-manager:allow-write", + "clipboard-manager:allow-read", + "webview:allow-create-webview-window", + { + "identifier": "http:default", + "allow": [ + { + "url": "http://**/" + }, + { + "url": "https://**/" + } + ] + }, + { + "identifier": "fs:allow-read-text-file", + "allow": [ + { + "path": "$RESOURCE/locales/*" + } + ] + } + ] } diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index 8d1b3624..21e2604b 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","store:allow-get",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}} \ No newline at end of file +{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","store:allow-get","clipboard-manager:allow-write","clipboard-manager:allow-read","webview:allow-create-webview-window",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}} \ No newline at end of file diff --git a/src-tauri/src/nostr/event.rs b/src-tauri/src/nostr/event.rs index 69e559d7..82cbf63f 100644 --- a/src-tauri/src/nostr/event.rs +++ b/src-tauri/src/nostr/event.rs @@ -6,15 +6,14 @@ use tauri::State; #[tauri::command(async)] pub async fn get_event(id: &str, nostr: State<'_, Nostr>) -> Result { let client = &nostr.client; - let event_id; - - if id.starts_with("note") { - event_id = EventId::from_bech32(id).unwrap(); - } else if id.starts_with("nevent") { - event_id = Nip19Event::from_bech32(id).unwrap().event_id; - } else { - event_id = EventId::from_hex(id).unwrap(); - } + let event_id: EventId = match Nip19::from_bech32(id) { + Ok(val) => match val { + Nip19::EventId(id) => id, + Nip19::Event(event) => event.event_id, + _ => panic!("not nip19"), + }, + Err(_) => EventId::from_hex(id).unwrap(), + }; let filter = Filter::new().id(event_id); let events = client diff --git a/src-tauri/src/nostr/metadata.rs b/src-tauri/src/nostr/metadata.rs index de6a077b..169fad75 100644 --- a/src-tauri/src/nostr/metadata.rs +++ b/src-tauri/src/nostr/metadata.rs @@ -6,15 +6,14 @@ use tauri::State; #[tauri::command(async)] pub async fn get_profile(id: &str, nostr: State<'_, Nostr>) -> Result { let client = &nostr.client; - let public_key; - - if id.starts_with("nprofile1") { - public_key = XOnlyPublicKey::from_bech32(id).unwrap(); - } else if id.starts_with("npub1") { - public_key = XOnlyPublicKey::from_bech32(id).unwrap(); - } else { - public_key = XOnlyPublicKey::from_str(id).unwrap(); - } + let public_key: XOnlyPublicKey = match Nip19::from_bech32(id) { + Ok(val) => match val { + Nip19::Pubkey(pubkey) => pubkey, + Nip19::Profile(profile) => profile.public_key, + _ => panic!("not nip19"), + }, + Err(_) => XOnlyPublicKey::from_str(id).unwrap(), + }; let filter = Filter::new() .author(public_key)