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";
|
2022-12-20 12:08:41 +00:00
|
|
|
import Event from "../nostr/Event";
|
2022-12-20 23:14:13 +00:00
|
|
|
import EventKind from "../nostr/EventKind";
|
2023-01-06 14:36:13 +00:00
|
|
|
import { eventLink } from "../Util";
|
2022-12-20 12:08:41 +00:00
|
|
|
import Note from "./Note";
|
2022-12-28 17:05:20 +00:00
|
|
|
import NoteGhost from "./NoteGhost";
|
2022-12-20 12:08:41 +00:00
|
|
|
|
|
|
|
export default function Thread(props) {
|
2022-12-28 22:09:39 +00:00
|
|
|
const thisEvent = props.this;
|
|
|
|
|
2022-12-20 12:08:41 +00:00
|
|
|
/** @type {Array<Event>} */
|
|
|
|
const notes = props.notes?.map(a => Event.FromObject(a));
|
|
|
|
|
|
|
|
// root note has no thread info
|
2023-01-06 19:29:12 +00:00
|
|
|
const root = useMemo(() => notes.find(a => a.Thread === null), [notes]);
|
2023-01-06 13:01:54 +00:00
|
|
|
|
|
|
|
const chains = useMemo(() => {
|
|
|
|
let chains = new Map();
|
|
|
|
notes.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 {
|
|
|
|
chains.get(replyTo).push(v);
|
|
|
|
}
|
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(() => {
|
|
|
|
return Array.from(chains?.keys()).filter(a => !notes.some(b => b.Id === a));
|
|
|
|
}, [chains]);
|
|
|
|
|
2023-01-06 19:29:12 +00:00
|
|
|
const mentionsRoot = useMemo(() => {
|
|
|
|
return notes.filter(a => a.Kind === EventKind.TextNote && a.Thread)
|
|
|
|
}, [chains]);
|
|
|
|
|
2023-01-04 13:23:05 +00:00
|
|
|
function reactions(id, kind = EventKind.Reaction) {
|
|
|
|
return notes?.filter(a => a.Kind === kind && a.Tags.find(a => a.Key === "e" && a.Event === id));
|
2022-12-20 23:14:13 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 13:01:54 +00:00
|
|
|
function renderRoot() {
|
|
|
|
if (root) {
|
2023-01-10 09:18:46 +00:00
|
|
|
return <Note data-ev={root} reactions={reactions(root.Id)} deletion={reactions(root.Id, EventKind.Deletion)} isThread />
|
2023-01-06 13:01:54 +00:00
|
|
|
} else {
|
2023-01-06 14:05:50 +00:00
|
|
|
return <NoteGhost>
|
|
|
|
Loading thread root.. ({notes.length} notes loaded)
|
|
|
|
</NoteGhost>
|
2023-01-06 13:01:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 14:05:50 +00:00
|
|
|
function renderChain(from) {
|
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 (
|
|
|
|
<>
|
|
|
|
<Note data-ev={a} key={a.Id} reactions={reactions(a.Id)} deletion={reactions(a.Id, EventKind.Deletion)} hightlight={thisEvent === a.Id} />
|
|
|
|
{renderChain(a.Id)}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-20 12:08:41 +00:00
|
|
|
return (
|
|
|
|
<>
|
2023-01-06 13:01:54 +00:00
|
|
|
{renderRoot()}
|
|
|
|
{root ? renderChain(root.Id) : null}
|
2023-01-06 14:05:50 +00:00
|
|
|
{root ? null : <>
|
|
|
|
<h3>Other Replies</h3>
|
|
|
|
{brokenChains.map(a => {
|
|
|
|
return (
|
|
|
|
<>
|
2023-01-06 14:14:04 +00:00
|
|
|
<NoteGhost key={a}>
|
2023-01-06 14:36:13 +00:00
|
|
|
Missing event <Link to={eventLink(a)}>{a.substring(0, 8)}</Link>
|
2023-01-06 14:05:50 +00:00
|
|
|
</NoteGhost>
|
|
|
|
{renderChain(a)}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</>}
|
2022-12-20 12:08:41 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|