From 44bde791b42db71d3e736df0833e18f44f3b62ae Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 27 Nov 2023 23:02:12 +0000 Subject: [PATCH] feat: simple comments --- src/const.ts | 1 + src/element/comments.tsx | 59 +++++++++++++++++++++++++++++++++++ src/element/mention.tsx | 11 +++++++ src/element/profile-image.tsx | 24 +++++++++----- src/element/text.tsx | 31 ++++++++++++++++++ src/element/torrent-list.tsx | 9 ++---- src/page/torrent.tsx | 6 +++- 7 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 src/element/comments.tsx create mode 100644 src/element/mention.tsx create mode 100644 src/element/text.tsx diff --git a/src/const.ts b/src/const.ts index 660de1c..3380915 100644 --- a/src/const.ts +++ b/src/const.ts @@ -215,6 +215,7 @@ export const Categories = [ ] as Array; export const TorrentKind = 2003 as EventKind; +export const TorrentCommentKind = 2004 as EventKind; export function FormatBytes(b: number, f?: number) { f ??= 2; diff --git a/src/element/comments.tsx b/src/element/comments.tsx new file mode 100644 index 0000000..f75706b --- /dev/null +++ b/src/element/comments.tsx @@ -0,0 +1,59 @@ +import { useState } from "react"; +import { NostrLink, NoteCollection, RequestBuilder } from "@snort/system"; +import { useRequestBuilder } from "@snort/system-react"; +import { unwrap } from "@snort/shared"; + +import { ProfileImage } from "./profile-image"; +import { Button } from "./button"; +import { useLogin } from "../login"; +import { Text } from "./text"; +import { TorrentCommentKind } from "../const"; + +export function Comments({ link }: { link: NostrLink }) { + const rb = new RequestBuilder(`replies:${link.encode()}`); + rb.withFilter().kinds([TorrentCommentKind]).replyToLink([link]); + const comments = useRequestBuilder(NoteCollection, rb); + + return ( +
+ + {comments.data + ?.sort((a, b) => (a.created_at > b.created_at ? -1 : 1)) + .map((a) => ( +
+ + {new Date(a.created_at * 1000).toLocaleString()} + + +
+ ))} +
+ ); +} + +function WriteComment({ link }: { link: NostrLink }) { + const login = useLogin(); + const [msg, setMsg] = useState(""); + if (!login) return; + + async function sendComment() { + const ev = await login?.builder.generic((eb) => { + return eb + .kind(TorrentCommentKind) + .content(msg) + .tag([...unwrap(link.toEventTag()), "root"]); + }); + console.debug(ev); + if (ev) { + await login?.system.BroadcastEvent(ev); + } + } + + return ( +
+

Write a Comment

+ + +
+ ); +} diff --git a/src/element/mention.tsx b/src/element/mention.tsx new file mode 100644 index 0000000..3802725 --- /dev/null +++ b/src/element/mention.tsx @@ -0,0 +1,11 @@ +import { hexToBech32 } from "@snort/shared"; +import { NostrLink } from "@snort/system"; +import { useUserProfile } from "@snort/system-react"; +import { Link } from "react-router-dom"; + +export function Mention({ link }: { link: NostrLink }) { + const profile = useUserProfile(link.id); + const npub = hexToBech32("npub", link.id); + + return {profile?.name ?? npub.slice(0, 12)}; +} diff --git a/src/element/profile-image.tsx b/src/element/profile-image.tsx index 3285b5b..e6f38ab 100644 --- a/src/element/profile-image.tsx +++ b/src/element/profile-image.tsx @@ -6,9 +6,10 @@ import { Link } from "react-router-dom"; type ProfileImageProps = HTMLProps & { pubkey?: string; size?: number; + withName?: boolean; }; -export function ProfileImage({ pubkey, size, ...props }: ProfileImageProps) { +export function ProfileImage({ pubkey, size, withName, children, ...props }: ProfileImageProps) { const profile = useUserProfile(pubkey); const v = { backgroundImage: `url(${profile?.picture})`, @@ -18,12 +19,19 @@ export function ProfileImage({ pubkey, size, ...props }: ProfileImageProps) { v.height = `${size}px`; } return ( - -
- +
+ +
+ {withName === true && <>{profile?.name}} + + {children} +
); } diff --git a/src/element/text.tsx b/src/element/text.tsx new file mode 100644 index 0000000..5590979 --- /dev/null +++ b/src/element/text.tsx @@ -0,0 +1,31 @@ +import { ParsedFragment, transformText, tryParseNostrLink } from "@snort/system"; +import { useMemo } from "react"; +import { Mention } from "./mention"; +import { Link } from "react-router-dom"; + +export function Text({ content, tags }: { content: string; tags: Array> }) { + const frags = useMemo(() => transformText(content, tags), [content, tags]); + + function renderFrag(f: ParsedFragment) { + switch (f.type) { + case "mention": + case "link": { + const link = tryParseNostrLink(f.content); + if (link) { + return ; + } else { + return ( + + {f.content} + + ); + } + } + default: { + return {f.content}; + } + } + } + + return
{frags.map(renderFrag)}
; +} diff --git a/src/element/torrent-list.tsx b/src/element/torrent-list.tsx index 7cfe5ec..7f4b68a 100644 --- a/src/element/torrent-list.tsx +++ b/src/element/torrent-list.tsx @@ -1,10 +1,9 @@ import "./torrent-list.css"; -import { hexToBech32 } from "@snort/shared"; -import { NostrLink, TaggedNostrEvent } from "@snort/system"; -import { useUserProfile } from "@snort/system-react"; +import { NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system"; import { FormatBytes } from "../const"; import { Link } from "react-router-dom"; import { MagnetLink } from "./magnet"; +import { Mention } from "./mention"; export function TorrentList({ items }: { items: Array }) { return ( @@ -29,13 +28,11 @@ export function TorrentList({ items }: { items: Array }) { } function TorrentTableEntry({ item }: { item: TaggedNostrEvent }) { - const profile = useUserProfile(item.pubkey); const name = item.tags.find((a) => a[0] === "title")?.at(1); const size = item.tags .filter((a) => a[0] === "file") .map((a) => Number(a[2])) .reduce((acc, v) => (acc += v), 0); - const npub = hexToBech32("npub", item.pubkey); return ( @@ -69,7 +66,7 @@ function TorrentTableEntry({ item }: { item: TaggedNostrEvent }) { {FormatBytes(size)} - {profile?.name ?? npub.slice(0, 12)} + ); diff --git a/src/page/torrent.tsx b/src/page/torrent.tsx index 204ddd7..7a02324 100644 --- a/src/page/torrent.tsx +++ b/src/page/torrent.tsx @@ -1,5 +1,5 @@ import { unwrap } from "@snort/shared"; -import { NoteCollection, RequestBuilder, TaggedNostrEvent, parseNostrLink } from "@snort/system"; +import { NostrLink, NoteCollection, RequestBuilder, TaggedNostrEvent, parseNostrLink } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; import { useLocation, useNavigate, useParams } from "react-router-dom"; import { FormatBytes, TorrentKind } from "../const"; @@ -7,6 +7,7 @@ import { ProfileImage } from "../element/profile-image"; import { MagnetLink } from "../element/magnet"; import { useLogin } from "../login"; import { Button } from "../element/button"; +import { Comments } from "../element/comments"; export function TorrentPage() { const location = useLocation(); @@ -28,6 +29,7 @@ export function TorrentPage() { export function TorrentDetail({ item }: { item: TaggedNostrEvent }) { const login = useLogin(); const navigate = useNavigate(); + const link = NostrLink.fromEvent(item); const name = item.tags.find((a) => a[0] === "title")?.at(1); const size = item.tags .filter((a) => a[0] === "file") @@ -83,6 +85,8 @@ export function TorrentDetail({ item }: { item: TaggedNostrEvent }) { Delete )} +

Comments

+ ); }