mirror of
https://git.v0l.io/Kieran/dtan.git
synced 2025-01-18 04:41:32 +00:00
feat: simple comments
This commit is contained in:
parent
7bff5e384f
commit
44bde791b4
@ -215,6 +215,7 @@ export const Categories = [
|
||||
] as Array<Category>;
|
||||
|
||||
export const TorrentKind = 2003 as EventKind;
|
||||
export const TorrentCommentKind = 2004 as EventKind;
|
||||
|
||||
export function FormatBytes(b: number, f?: number) {
|
||||
f ??= 2;
|
||||
|
59
src/element/comments.tsx
Normal file
59
src/element/comments.tsx
Normal file
@ -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 (
|
||||
<div className="flex flex-col gap-2">
|
||||
<WriteComment link={link} />
|
||||
{comments.data
|
||||
?.sort((a, b) => (a.created_at > b.created_at ? -1 : 1))
|
||||
.map((a) => (
|
||||
<div className="flex flex-col gap-2 rounded p-2 bg-slate-900">
|
||||
<ProfileImage pubkey={a.pubkey} withName={true}>
|
||||
<span className="text-slate-400 text-sm">{new Date(a.created_at * 1000).toLocaleString()}</span>
|
||||
</ProfileImage>
|
||||
<Text content={a.content} tags={a.tags} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="rounded p-2 bg-slate-900">
|
||||
<h3>Write a Comment</h3>
|
||||
<textarea className="w-full" value={msg} onChange={(e) => setMsg(e.target.value)}></textarea>
|
||||
<Button onClick={sendComment}>Send</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
11
src/element/mention.tsx
Normal file
11
src/element/mention.tsx
Normal file
@ -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 <Link to={`/p/${link.encode()}`}>{profile?.name ?? npub.slice(0, 12)}</Link>;
|
||||
}
|
@ -6,9 +6,10 @@ import { Link } from "react-router-dom";
|
||||
type ProfileImageProps = HTMLProps<HTMLDivElement> & {
|
||||
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 (
|
||||
<Link to={pubkey ? `/p/${new NostrLink(NostrPrefix.Profile, pubkey).encode()}` : ""}>
|
||||
<div
|
||||
{...props}
|
||||
className="rounded-full aspect-square w-12 bg-slate-800 border border-slate-200 bg-cover bg-center"
|
||||
style={v}
|
||||
></div>
|
||||
</Link>
|
||||
<div className="flex items-center justify-between">
|
||||
<Link
|
||||
to={pubkey ? `/p/${new NostrLink(NostrPrefix.Profile, pubkey).encode()}` : ""}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<div
|
||||
{...props}
|
||||
className="rounded-full aspect-square w-12 bg-slate-800 border border-slate-200 bg-cover bg-center"
|
||||
style={v}
|
||||
></div>
|
||||
{withName === true && <>{profile?.name}</>}
|
||||
</Link>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
31
src/element/text.tsx
Normal file
31
src/element/text.tsx
Normal file
@ -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<Array<string>> }) {
|
||||
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 <Mention link={link} />;
|
||||
} else {
|
||||
return (
|
||||
<Link to={f.content} target="_blank">
|
||||
{f.content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return <span>{f.content}</span>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="text">{frags.map(renderFrag)}</div>;
|
||||
}
|
@ -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<TaggedNostrEvent> }) {
|
||||
return (
|
||||
@ -29,13 +28,11 @@ export function TorrentList({ items }: { items: Array<TaggedNostrEvent> }) {
|
||||
}
|
||||
|
||||
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 (
|
||||
<tr className="hover:bg-slate-800">
|
||||
<td>
|
||||
@ -69,7 +66,7 @@ function TorrentTableEntry({ item }: { item: TaggedNostrEvent }) {
|
||||
</td>
|
||||
<td>{FormatBytes(size)}</td>
|
||||
<td>
|
||||
<Link to={`/p/${npub}`}>{profile?.name ?? npub.slice(0, 12)}</Link>
|
||||
<Mention link={new NostrLink(NostrPrefix.PublicKey, item.pubkey)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -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
|
||||
</Button>
|
||||
)}
|
||||
<h3>Comments</h3>
|
||||
<Comments link={link} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user