snort/src/element/Note.js

182 lines
6.7 KiB
JavaScript
Raw Normal View History

2022-12-18 14:51:47 +00:00
import "./Note.css";
2023-01-02 13:40:42 +00:00
import { useCallback, useState } from "react";
2022-12-18 14:51:47 +00:00
import { useSelector } from "react-redux";
import moment from "moment";
2023-01-07 20:54:12 +00:00
import { useNavigate } from "react-router-dom";
import { faHeart, faReply, faThumbsDown, faTrash, faBolt } from "@fortawesome/free-solid-svg-icons";
2022-12-29 15:36:40 +00:00
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2022-12-28 22:09:39 +00:00
import Event from "../nostr/Event";
2022-12-27 23:46:13 +00:00
import ProfileImage from "./ProfileImage";
2022-12-29 22:23:41 +00:00
import useEventPublisher from "../feed/EventPublisher";
import { NoteCreator } from "./NoteCreator";
2023-01-06 11:05:27 +00:00
import { extractLinks, extractMentions, extractInvoices } from "../Text";
2023-01-06 14:36:13 +00:00
import { eventLink } from "../Util";
2023-01-07 20:54:12 +00:00
import LNURLTip from "./LNURLTip";
2022-12-18 14:51:47 +00:00
export default function Note(props) {
const navigate = useNavigate();
const data = props.data;
2023-01-02 11:15:13 +00:00
const opt = props.options;
2022-12-28 22:09:39 +00:00
const dataEvent = props["data-ev"];
2022-12-20 23:14:13 +00:00
const reactions = props.reactions;
const deletion = props.deletion;
2023-01-06 22:10:18 +00:00
const emojiReactions = reactions?.filter(({ Content }) => Content && Content !== "+" && Content !== "-" && Content !== "❤️")
2023-01-07 23:01:32 +00:00
.reduce((acc, { Content }) => {
const amount = acc[Content] || 0
return { ...acc, [Content]: amount + 1 }
}, {})
2023-01-06 22:10:18 +00:00
const likes = reactions?.filter(({ Content }) => Content === "+" || Content === "❤️").length ?? 0
const dislikes = reactions?.filter(({ Content }) => Content === "-").length ?? 0
2022-12-28 22:09:39 +00:00
const publisher = useEventPublisher();
2023-01-07 20:54:12 +00:00
const [reply, setReply] = useState(false);
const [tip, setTip] = useState(false);
2022-12-20 12:08:41 +00:00
const users = useSelector(s => s.users?.users);
const login = useSelector(s => s.login.publicKey);
2022-12-28 22:09:39 +00:00
const ev = dataEvent ?? Event.FromObject(data);
const isMine = ev.PubKey === login;
2023-01-06 22:10:18 +00:00
const liked = reactions?.find(({ PubKey, Content }) => Content === "+" && PubKey === login)
const disliked = reactions?.find(({ PubKey, Content }) => Content === "-" && PubKey === login)
2023-01-07 20:54:12 +00:00
const author = users[ev.PubKey];
2022-12-18 14:51:47 +00:00
2023-01-02 11:15:13 +00:00
const options = {
showHeader: true,
showTime: true,
showFooter: true,
...opt
};
2023-01-06 22:10:18 +00:00
function hasReacted(emoji) {
2023-01-07 23:01:32 +00:00
return reactions?.find(({ PubKey, Content }) => Content === emoji && PubKey === login)
2023-01-06 22:10:18 +00:00
}
2023-01-02 13:40:42 +00:00
const transformBody = useCallback(() => {
let body = ev?.Content ?? "";
let fragments = extractLinks([body]);
2023-01-06 11:05:27 +00:00
fragments = extractMentions(fragments, ev.Tags, users);
fragments = extractInvoices(fragments);
if (deletion?.length > 0) {
return (
<>
<b className="error">Deleted</b>
</>
);
}
return fragments;
}, [data, dataEvent, reactions, deletion]);
2023-01-02 13:40:42 +00:00
2022-12-18 14:51:47 +00:00
function goToEvent(e, id) {
2022-12-20 12:08:41 +00:00
if (!window.location.pathname.startsWith("/e/")) {
e.stopPropagation();
2023-01-06 14:36:13 +00:00
navigate(eventLink(id));
2022-12-20 12:08:41 +00:00
}
2022-12-18 14:51:47 +00:00
}
function replyTag() {
2023-01-06 19:29:12 +00:00
if (ev.Thread === null) {
2022-12-18 14:51:47 +00:00
return null;
}
2023-01-06 19:29:12 +00:00
let replyId = ev.Thread?.ReplyTo?.Event;
let mentions = ev.Thread?.PubKeys?.map(a => [a, users[a]])?.map(a => a[1]?.name ?? a[0].substring(0, 8));
2022-12-18 14:51:47 +00:00
return (
<div className="reply" onClick={(e) => goToEvent(e, replyId)}>
2022-12-20 12:08:41 +00:00
{mentions?.join(", ") ?? replyId?.substring(0, 8)}
2022-12-18 14:51:47 +00:00
</div>
)
}
2023-01-06 22:10:18 +00:00
async function react(emoji) {
let evLike = await publisher.like(ev, emoji);
publisher.broadcast(evLike);
}
2022-12-28 22:09:39 +00:00
async function like() {
let evLike = await publisher.like(ev);
publisher.broadcast(evLike);
}
async function dislike() {
let evLike = await publisher.dislike(ev);
publisher.broadcast(evLike);
}
async function deleteEvent() {
if (window.confirm(`Are you sure you want to delete ${ev.Id.substring(0, 8)}?`)) {
let evDelete = await publisher.delete(ev.Id);
publisher.broadcast(evDelete);
}
}
2023-01-07 20:54:12 +00:00
function tipButton() {
let service = author?.lud16 || author?.lud06;
if (service) {
return (
<>
<span className="pill" onClick={(e) => setTip(true)}>
<FontAwesomeIcon icon={faBolt} />
</span>
</>
)
}
return null;
}
2022-12-18 22:23:52 +00:00
if (!ev.IsContent()) {
return (
<>
<pre>{ev.Id}</pre>
<pre>Kind: {ev.Kind}</pre>
<pre>Content: {ev.Content}</pre>
</>
);
2022-12-18 14:51:47 +00:00
}
return (
<div className="note">
2023-01-02 11:15:13 +00:00
{options.showHeader ?
<div className="header flex">
<ProfileImage pubkey={ev.PubKey} subHeader={replyTag()} />
{options.showTime ?
<div className="info">
{moment(ev.CreatedAt * 1000).fromNow()}
</div> : null}
</div> : null}
2022-12-18 14:51:47 +00:00
<div className="body" onClick={(e) => goToEvent(e, ev.Id)}>
2022-12-20 12:08:41 +00:00
{transformBody()}
2022-12-18 14:51:47 +00:00
</div>
2023-01-02 11:15:13 +00:00
{options.showFooter ?
<div className="footer">
{isMine ? <span className="pill">
2023-01-07 20:54:12 +00:00
<FontAwesomeIcon icon={faTrash} onClick={(e) => deleteEvent()} />
</span> : null}
2023-01-07 20:54:12 +00:00
{tipButton()}
<span className="pill" onClick={(e) => setReply(s => !s)}>
2023-01-02 11:15:13 +00:00
<FontAwesomeIcon icon={faReply} />
</span>
2023-01-06 22:10:18 +00:00
{Object.keys(emojiReactions).map((emoji) => {
2023-01-07 23:01:32 +00:00
return (
<span className="pill" onClick={() => react(emoji)}>
<span style={{ filter: hasReacted(emoji) ? 'none' : 'grayscale(1)' }}>
{emoji}
</span>
&nbsp;
{emojiReactions[emoji]}
2023-01-06 22:10:18 +00:00
</span>
2023-01-07 23:01:32 +00:00
)
2023-01-06 22:10:18 +00:00
})}
2023-01-02 11:15:13 +00:00
<span className="pill" onClick={() => like()}>
<FontAwesomeIcon color={liked ? "red" : "currentColor"} icon={faHeart} /> &nbsp;
{likes}
</span>
<span className="pill" onClick={() => dislike()}>
<FontAwesomeIcon color={disliked ? "orange" : "currentColor"} icon={faThumbsDown} /> &nbsp;
{dislikes}
2023-01-02 11:15:13 +00:00
</span>
</div> : null}
2023-01-07 20:54:12 +00:00
<NoteCreator replyTo={ev} onSend={(e) => setReply(false)} show={reply} />
<LNURLTip svc={author?.lud16 || author?.lud06} onClose={(e) => setTip(false)} show={tip} />
2022-12-18 14:51:47 +00:00
</div>
)
}