diff --git a/src/element/Note.js b/src/element/Note.js index 280d8cab..16c0f642 100644 --- a/src/element/Note.js +++ b/src/element/Note.js @@ -69,9 +69,10 @@ export default function Note(props) { if (!ev.IsContent()) { return ( <> -
{ev.Id}
-
Kind: {ev.Kind}
-
Content: {ev.Content}
+

Unknown event kind: {ev.Kind}

+
+                    {JSON.stringify(ev.ToObject(), undefined, '  ')}
+                
); } diff --git a/src/element/NoteFooter.js b/src/element/NoteFooter.js index 1b2794bc..81b2b4c9 100644 --- a/src/element/NoteFooter.js +++ b/src/element/NoteFooter.js @@ -65,13 +65,13 @@ export default function NoteFooter(props) { return null; } - function reactionIcon(content) { + function reactionIcon(content, reacted) { switch (content) { case Reaction.Positive: { - return ; + return ; } case Reaction.Negative: { - return ; + return ; } } return content; @@ -90,10 +90,15 @@ export default function NoteFooter(props) { setReply(s => !s)}> - {Object.keys(groupReactions).map((emoji) => { + {Object.keys(groupReactions || {}).map((emoji) => { + let didReact = hasReacted(emoji); return ( - react(emoji)} key={emoji}> - {reactionIcon(emoji)} + { + if (!didReact) { + react(emoji); + } + }} key={emoji}> + {reactionIcon(emoji, didReact)} {groupReactions[emoji] ? <> {groupReactions[emoji]} : null} ) diff --git a/src/element/NoteReaction.js b/src/element/NoteReaction.js index 3317ecfd..0f2538f8 100644 --- a/src/element/NoteReaction.js +++ b/src/element/NoteReaction.js @@ -3,38 +3,77 @@ import moment from "moment"; import EventKind from "../nostr/EventKind"; import Note from "./Note"; import ProfileImage from "./ProfileImage"; +import Event from "../nostr/Event"; +import { eventLink } from "../Util"; +import { Link } from "react-router-dom"; +import { useMemo } from "react"; export default function NoteReaction(props) { - const data = props.data; - const root = props.root; + const ev = props["data-ev"] || Event.FromObject(props.data); - if (data.kind !== EventKind.Reaction) { + const refEvent = useMemo(() => { + if(ev) { + let eTags = ev.Tags.filter(a => a.Key === "e"); + return eTags[0].Event; + } + return null; + }, [ev]); + + if (ev.Kind !== EventKind.Reaction && ev.Kind !== EventKind.Repost) { return null; } - function mapReaction() { - switch (data.content) { + function mapReaction(c) { + switch (c) { case "+": return "❤️"; case "-": return "👎"; default: { - if (data.content.length === 0) { + if (c.length === 0) { return "❤️"; } - return data.content; + return c; } } } + function tagLine() { + switch (ev.Kind) { + case EventKind.Reaction: return Reacted with {mapReaction(ev.Content)}; + case EventKind.Repost: return Reposted + } + } + + /** + * Some clients embed the reposted note in the content + */ + function extractRoot() { + if (ev?.Kind === EventKind.Repost && ev.Content.length > 0) { + try { + let r = JSON.parse(ev.Content); + return r; + } catch (e) { + console.error("Could not load reposted content", e); + } + } + return props.root; + } + + const root = extractRoot(); + const opt = { + showHeader: ev?.Kind === EventKind.Repost, + showFooter: ev?.Kind === EventKind.Repost + }; return (
- Reacted with {mapReaction()}} /> +
- {moment(data.created_at * 1000).fromNow()} + {moment(ev.CreatedAt * 1000).fromNow()}
- {root ? : root} + {root ? : null} + {!root && refEvent ?

#{refEvent.substring(0, 8)}

: null}
); } \ No newline at end of file diff --git a/src/element/Timeline.js b/src/element/Timeline.js index 05be16d8..c0f4003c 100644 --- a/src/element/Timeline.js +++ b/src/element/Timeline.js @@ -1,6 +1,8 @@ +import { useMemo } from "react"; import useTimelineFeed from "../feed/TimelineFeed"; import EventKind from "../nostr/EventKind"; import Note from "./Note"; +import NoteReaction from "./NoteReaction"; /** * A list of notes by pubkeys @@ -12,10 +14,21 @@ export default function Timeline({ global, pubkeys }) { return feed?.others?.filter(a => a.kind === kind && a.tags.some(b => b[0] === "e" && b[1] === id)); } - return ( - <> - {feed.main?.sort((a, b) => b.created_at - a.created_at) - .map(a => )} - - ) + const mainFeed = useMemo(() => { + return feed.main?.sort((a, b) => b.created_at - a.created_at); + }, [feed]); + + function eventElement(e) { + switch (e.kind) { + case EventKind.TextNote: { + return + } + case EventKind.Reaction: + case EventKind.Repost: { + return + } + } + } + + return mainFeed.map(eventElement); } \ No newline at end of file diff --git a/src/feed/EventPublisher.js b/src/feed/EventPublisher.js index f1f08ef3..0fc85939 100644 --- a/src/feed/EventPublisher.js +++ b/src/feed/EventPublisher.js @@ -120,13 +120,18 @@ export default function useEventPublisher() { ev.Tags.push(new Tag(["e", id])); return await signEvent(ev); }, + /** + * Respot a note + * @param {Event} note + * @returns + */ repost: async (note) => { if (typeof note.Id !== "string") { throw "Must be parsed note in Event class"; } let ev = Event.ForPubKey(pubKey); ev.Kind = EventKind.Repost; - ev.Content = ""; + ev.Content = JSON.stringify(note.Original); ev.Tags.push(new Tag(["e", note.Id])); ev.Tags.push(new Tag(["p", note.PubKey])); return await signEvent(ev); diff --git a/src/feed/TimelineFeed.js b/src/feed/TimelineFeed.js index 9c96f5d0..f913cc20 100644 --- a/src/feed/TimelineFeed.js +++ b/src/feed/TimelineFeed.js @@ -18,6 +18,7 @@ export default function useTimelineFeed(pubKeys, global = false) { sub.Id = `timeline:${subTab}`; sub.Authors = new Set(global ? [] : pubKeys); sub.Kinds.add(EventKind.TextNote); + sub.Kinds.add(EventKind.Repost); sub.Limit = 20; return sub; diff --git a/src/nostr/Tag.js b/src/nostr/Tag.js index d27554c2..be151232 100644 --- a/src/nostr/Tag.js +++ b/src/nostr/Tag.js @@ -42,7 +42,9 @@ export default class Tag { ToObject() { switch (this.Key) { case "e": { - return ["e", this.Event, this.Relay, this.Marker]; + let ret = ["e", this.Event, this.Relay, this.Marker]; + let trimEnd = ret.reverse().findIndex(a => a != null); + return ret.reverse().slice(0, ret.length - trimEnd); } case "p": { return ["p", this.PubKey];