This commit is contained in:
parent
7bff5e384f
commit
44bde791b4
@ -215,6 +215,7 @@ export const Categories = [
|
|||||||
] as Array<Category>;
|
] as Array<Category>;
|
||||||
|
|
||||||
export const TorrentKind = 2003 as EventKind;
|
export const TorrentKind = 2003 as EventKind;
|
||||||
|
export const TorrentCommentKind = 2004 as EventKind;
|
||||||
|
|
||||||
export function FormatBytes(b: number, f?: number) {
|
export function FormatBytes(b: number, f?: number) {
|
||||||
f ??= 2;
|
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> & {
|
type ProfileImageProps = HTMLProps<HTMLDivElement> & {
|
||||||
pubkey?: string;
|
pubkey?: string;
|
||||||
size?: number;
|
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 profile = useUserProfile(pubkey);
|
||||||
const v = {
|
const v = {
|
||||||
backgroundImage: `url(${profile?.picture})`,
|
backgroundImage: `url(${profile?.picture})`,
|
||||||
@ -18,12 +19,19 @@ export function ProfileImage({ pubkey, size, ...props }: ProfileImageProps) {
|
|||||||
v.height = `${size}px`;
|
v.height = `${size}px`;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Link to={pubkey ? `/p/${new NostrLink(NostrPrefix.Profile, pubkey).encode()}` : ""}>
|
<div className="flex items-center justify-between">
|
||||||
|
<Link
|
||||||
|
to={pubkey ? `/p/${new NostrLink(NostrPrefix.Profile, pubkey).encode()}` : ""}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
className="rounded-full aspect-square w-12 bg-slate-800 border border-slate-200 bg-cover bg-center"
|
className="rounded-full aspect-square w-12 bg-slate-800 border border-slate-200 bg-cover bg-center"
|
||||||
style={v}
|
style={v}
|
||||||
></div>
|
></div>
|
||||||
|
{withName === true && <>{profile?.name}</>}
|
||||||
</Link>
|
</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 "./torrent-list.css";
|
||||||
import { hexToBech32 } from "@snort/shared";
|
import { NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system";
|
||||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
|
||||||
import { useUserProfile } from "@snort/system-react";
|
|
||||||
import { FormatBytes } from "../const";
|
import { FormatBytes } from "../const";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { MagnetLink } from "./magnet";
|
import { MagnetLink } from "./magnet";
|
||||||
|
import { Mention } from "./mention";
|
||||||
|
|
||||||
export function TorrentList({ items }: { items: Array<TaggedNostrEvent> }) {
|
export function TorrentList({ items }: { items: Array<TaggedNostrEvent> }) {
|
||||||
return (
|
return (
|
||||||
@ -29,13 +28,11 @@ export function TorrentList({ items }: { items: Array<TaggedNostrEvent> }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TorrentTableEntry({ item }: { item: TaggedNostrEvent }) {
|
function TorrentTableEntry({ item }: { item: TaggedNostrEvent }) {
|
||||||
const profile = useUserProfile(item.pubkey);
|
|
||||||
const name = item.tags.find((a) => a[0] === "title")?.at(1);
|
const name = item.tags.find((a) => a[0] === "title")?.at(1);
|
||||||
const size = item.tags
|
const size = item.tags
|
||||||
.filter((a) => a[0] === "file")
|
.filter((a) => a[0] === "file")
|
||||||
.map((a) => Number(a[2]))
|
.map((a) => Number(a[2]))
|
||||||
.reduce((acc, v) => (acc += v), 0);
|
.reduce((acc, v) => (acc += v), 0);
|
||||||
const npub = hexToBech32("npub", item.pubkey);
|
|
||||||
return (
|
return (
|
||||||
<tr className="hover:bg-slate-800">
|
<tr className="hover:bg-slate-800">
|
||||||
<td>
|
<td>
|
||||||
@ -69,7 +66,7 @@ function TorrentTableEntry({ item }: { item: TaggedNostrEvent }) {
|
|||||||
</td>
|
</td>
|
||||||
<td>{FormatBytes(size)}</td>
|
<td>{FormatBytes(size)}</td>
|
||||||
<td>
|
<td>
|
||||||
<Link to={`/p/${npub}`}>{profile?.name ?? npub.slice(0, 12)}</Link>
|
<Mention link={new NostrLink(NostrPrefix.PublicKey, item.pubkey)} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { unwrap } from "@snort/shared";
|
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 { useRequestBuilder } from "@snort/system-react";
|
||||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import { FormatBytes, TorrentKind } from "../const";
|
import { FormatBytes, TorrentKind } from "../const";
|
||||||
@ -7,6 +7,7 @@ import { ProfileImage } from "../element/profile-image";
|
|||||||
import { MagnetLink } from "../element/magnet";
|
import { MagnetLink } from "../element/magnet";
|
||||||
import { useLogin } from "../login";
|
import { useLogin } from "../login";
|
||||||
import { Button } from "../element/button";
|
import { Button } from "../element/button";
|
||||||
|
import { Comments } from "../element/comments";
|
||||||
|
|
||||||
export function TorrentPage() {
|
export function TorrentPage() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -28,6 +29,7 @@ export function TorrentPage() {
|
|||||||
export function TorrentDetail({ item }: { item: TaggedNostrEvent }) {
|
export function TorrentDetail({ item }: { item: TaggedNostrEvent }) {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const link = NostrLink.fromEvent(item);
|
||||||
const name = item.tags.find((a) => a[0] === "title")?.at(1);
|
const name = item.tags.find((a) => a[0] === "title")?.at(1);
|
||||||
const size = item.tags
|
const size = item.tags
|
||||||
.filter((a) => a[0] === "file")
|
.filter((a) => a[0] === "file")
|
||||||
@ -83,6 +85,8 @@ export function TorrentDetail({ item }: { item: TaggedNostrEvent }) {
|
|||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<h3>Comments</h3>
|
||||||
|
<Comments link={link} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user