Repost with content
This commit is contained in:
parent
c01e0ca457
commit
0e0266c813
@ -69,9 +69,10 @@ export default function Note(props) {
|
||||
if (!ev.IsContent()) {
|
||||
return (
|
||||
<>
|
||||
<pre>{ev.Id}</pre>
|
||||
<pre>Kind: {ev.Kind}</pre>
|
||||
<pre>Content: {ev.Content}</pre>
|
||||
<h4>Unknown event kind: {ev.Kind}</h4>
|
||||
<pre>
|
||||
{JSON.stringify(ev.ToObject(), undefined, ' ')}
|
||||
</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -65,13 +65,13 @@ export default function NoteFooter(props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function reactionIcon(content) {
|
||||
function reactionIcon(content, reacted) {
|
||||
switch (content) {
|
||||
case Reaction.Positive: {
|
||||
return <FontAwesomeIcon color={hasReacted(content) ? "red" : "currentColor"} icon={faHeart} />;
|
||||
return <FontAwesomeIcon color={reacted ? "red" : "currentColor"} icon={faHeart} />;
|
||||
}
|
||||
case Reaction.Negative: {
|
||||
return <FontAwesomeIcon color={hasReacted(content) ? "orange" : "currentColor"} icon={faThumbsDown} />;
|
||||
return <FontAwesomeIcon color={reacted ? "orange" : "currentColor"} icon={faThumbsDown} />;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
@ -90,10 +90,15 @@ export default function NoteFooter(props) {
|
||||
<span className="pill" onClick={(e) => setReply(s => !s)}>
|
||||
<FontAwesomeIcon icon={faReply} />
|
||||
</span>
|
||||
{Object.keys(groupReactions).map((emoji) => {
|
||||
{Object.keys(groupReactions || {}).map((emoji) => {
|
||||
let didReact = hasReacted(emoji);
|
||||
return (
|
||||
<span className="pill" onClick={() => react(emoji)} key={emoji}>
|
||||
{reactionIcon(emoji)}
|
||||
<span className="pill" onClick={() => {
|
||||
if (!didReact) {
|
||||
react(emoji);
|
||||
}
|
||||
}} key={emoji}>
|
||||
{reactionIcon(emoji, didReact)}
|
||||
{groupReactions[emoji] ? <> {groupReactions[emoji]}</> : null}
|
||||
</span>
|
||||
)
|
||||
|
@ -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 <small>Reacted with {mapReaction(ev.Content)}</small>;
|
||||
case EventKind.Repost: return <small>Reposted</small>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<div className="reaction">
|
||||
<div className="header flex">
|
||||
<ProfileImage pubkey={data.pubkey} subHeader={<small>Reacted with {mapReaction()}</small>} />
|
||||
<ProfileImage pubkey={ev.RootPubKey} subHeader={tagLine()} />
|
||||
<div className="info">
|
||||
{moment(data.created_at * 1000).fromNow()}
|
||||
{moment(ev.CreatedAt * 1000).fromNow()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{root ? <Note data={root} options={{ showHeader: false, showFooter: false }} /> : root}
|
||||
{root ? <Note data={root} options={opt} /> : null}
|
||||
{!root && refEvent ? <p><Link to={eventLink(refEvent)}>#{refEvent.substring(0, 8)}</Link></p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 => <Note key={a.id} data={a} reactions={reaction(a.id)} deletion={reaction(a.id, EventKind.Deletion)} />)}
|
||||
</>
|
||||
)
|
||||
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 <Note key={e.id} data={e} reactions={reaction(e.id)} deletion={reaction(e.id, EventKind.Deletion)} />
|
||||
}
|
||||
case EventKind.Reaction:
|
||||
case EventKind.Repost: {
|
||||
return <NoteReaction data={e} key={e.id}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mainFeed.map(eventElement);
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user