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];