snort/src/Element/Thread.tsx

110 lines
3.5 KiB
TypeScript
Raw Normal View History

2023-01-25 18:31:44 +00:00
import "./Thread.css";
2023-01-06 13:01:54 +00:00
import { useMemo } from "react";
2023-01-06 14:05:50 +00:00
import { Link } from "react-router-dom";
2023-01-26 07:25:05 +00:00
2023-01-20 11:11:50 +00:00
import { TaggedRawEvent, u256 } from "Nostr";
import { default as NEvent } from "Nostr/Event";
import EventKind from "Nostr/EventKind";
import { eventLink } from "Util";
2023-01-26 07:25:05 +00:00
import BackButton from "Element/BackButton";
2023-01-20 11:11:50 +00:00
import Note from "Element/Note";
import NoteGhost from "Element/NoteGhost";
2022-12-20 12:08:41 +00:00
2023-01-16 18:14:08 +00:00
export interface ThreadProps {
this?: u256,
notes?: TaggedRawEvent[]
}
export default function Thread(props: ThreadProps) {
2022-12-28 22:09:39 +00:00
const thisEvent = props.this;
2023-01-17 13:03:15 +00:00
const notes = props.notes ?? [];
const parsedNotes = notes.map(a => new NEvent(a));
2022-12-20 12:08:41 +00:00
// root note has no thread info
2023-01-17 13:03:15 +00:00
const root = useMemo(() => parsedNotes.find(a => a.Thread === null), [notes]);
2023-01-06 13:01:54 +00:00
const chains = useMemo(() => {
2023-01-16 18:14:08 +00:00
let chains = new Map<u256, NEvent[]>();
2023-01-17 13:03:15 +00:00
parsedNotes?.filter(a => a.Kind === EventKind.TextNote).sort((a, b) => b.CreatedAt - a.CreatedAt).forEach((v) => {
2023-01-06 19:29:12 +00:00
let replyTo = v.Thread?.ReplyTo?.Event ?? v.Thread?.Root?.Event;
2023-01-06 13:01:54 +00:00
if (replyTo) {
if (!chains.has(replyTo)) {
chains.set(replyTo, [v]);
} else {
2023-01-16 18:14:08 +00:00
chains.get(replyTo)!.push(v);
2023-01-06 13:01:54 +00:00
}
2023-01-06 14:05:50 +00:00
} else if (v.Tags.length > 0) {
2023-01-06 13:01:54 +00:00
console.log("Not replying to anything: ", v);
}
});
return chains;
}, [notes]);
2022-12-20 12:08:41 +00:00
2023-01-06 14:05:50 +00:00
const brokenChains = useMemo(() => {
2023-01-17 13:03:15 +00:00
return Array.from(chains?.keys()).filter(a => !parsedNotes?.some(b => b.Id === a));
2023-01-06 14:05:50 +00:00
}, [chains]);
2023-01-06 19:29:12 +00:00
const mentionsRoot = useMemo(() => {
2023-01-17 13:03:15 +00:00
return parsedNotes?.filter(a => a.Kind === EventKind.TextNote && a.Thread)
2023-01-06 19:29:12 +00:00
}, [chains]);
2023-01-06 13:01:54 +00:00
function renderRoot() {
if (root) {
2023-01-17 13:03:15 +00:00
return <Note
data-ev={root}
related={notes}
isThread />
2023-01-06 13:01:54 +00:00
} else {
2023-01-06 14:05:50 +00:00
return <NoteGhost>
2023-01-16 18:14:08 +00:00
Loading thread root.. ({notes?.length} notes loaded)
2023-01-06 14:05:50 +00:00
</NoteGhost>
2023-01-06 13:01:54 +00:00
}
}
2023-01-16 18:14:08 +00:00
function renderChain(from: u256) {
2023-01-06 13:01:54 +00:00
if (from && chains) {
let replies = chains.get(from);
if (replies) {
return (
<div className="indented">
{replies.map(a => {
return (
<>
2023-01-16 18:14:08 +00:00
<Note data-ev={a}
key={a.Id}
2023-01-17 13:03:15 +00:00
related={notes}
2023-01-16 18:14:08 +00:00
highlight={thisEvent === a.Id} />
2023-01-06 13:01:54 +00:00
{renderChain(a.Id)}
</>
)
})}
</div>
)
}
}
}
2022-12-20 12:08:41 +00:00
return (
2023-01-30 19:58:49 +00:00
<>
<BackButton />
<div className="thread-container">
{renderRoot()}
{root ? renderChain(root.Id) : null}
{root ? null : <>
<h3>Other Replies</h3>
{brokenChains.map(a => {
return (
<>
<NoteGhost key={a}>
Missing event <Link to={eventLink(a)}>{a.substring(0, 8)}</Link>
</NoteGhost>
{renderChain(a)}
</>
)
})}
</>}
</div>
</>
2022-12-20 12:08:41 +00:00
);
2023-01-26 07:25:05 +00:00
}