import "./Note.css";
import { useCallback, useState } from "react";
import { useSelector } from "react-redux";
import moment from "moment";
import { Link, useNavigate } from "react-router-dom";
import { faHeart, faReply, faInfo, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Event from "../nostr/Event";
import ProfileImage from "./ProfileImage";
import useEventPublisher from "../feed/EventPublisher";
import { NoteCreator } from "./NoteCreator";
import Invoice from "./Invoice";
import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex } from "../Const";
export default function Note(props) {
const navigate = useNavigate();
const data = props.data;
const opt = props.options;
const dataEvent = props["data-ev"];
const reactions = props.reactions;
const deletion = props.deletion;
const publisher = useEventPublisher();
const [showReply, setShowReply] = useState(false);
const users = useSelector(s => s.users?.users);
const login = useSelector(s => s.login.publicKey);
const ev = dataEvent ?? Event.FromObject(data);
const isMine = ev.PubKey === login;
const options = {
showHeader: true,
showTime: true,
showFooter: true,
...opt
};
const transformBody = useCallback(() => {
let body = ev?.Content ?? "";
let fragments = extractLinks([body]);
fragments = extractMentions(fragments);
fragments = extractInvoices(fragments);
if (deletion?.length > 0) {
return (
<>
Deleted
>
);
}
return fragments;
}, [data, dataEvent, reactions, deletion]);
function goToEvent(e, id) {
if (!window.location.pathname.startsWith("/e/")) {
e.stopPropagation();
navigate(`/e/${id}`);
}
}
function replyTag() {
let thread = ev.GetThread();
if (thread === null) {
return null;
}
let replyId = thread?.ReplyTo?.Event;
let mentions = thread?.PubKeys?.map(a => [a, users[a]])?.map(a => a[1]?.name ?? a[0].substring(0, 8));
return (
goToEvent(e, replyId)}>
➡️ {mentions?.join(", ") ?? replyId?.substring(0, 8)}
)
}
function extractInvoices(fragments) {
return fragments.map(f => {
if (typeof f === "string") {
return f.split(InvoiceRegex).map(i => {
if (i.toLowerCase().startsWith("lnbc")) {
return
} else {
return i;
}
});
}
return f;
}).flat();
}
function extractMentions(fragments) {
return fragments.map(f => {
if (typeof f === "string") {
return f.split(MentionRegex).map((match) => {
let matchTag = match.match(/#\[(\d+)\]/);
if (matchTag && matchTag.length === 2) {
let idx = parseInt(matchTag[1]);
let ref = ev.Tags.find(a => a.Index === idx);
if (ref) {
switch (ref.Key) {
case "p": {
let pUser = users[ref.PubKey]?.name ?? ref.PubKey.substring(0, 8);
return @{pUser};
}
case "e": {
let eText = ref.Event.substring(0, 8);
return #{eText};
}
}
}
return {matchTag[0]}?;
} else {
return match;
}
});
}
return f;
}).flat();
}
function extractLinks(fragments) {
return fragments.map(f => {
if (typeof f === "string") {
return f.split(UrlRegex).map(a => {
if (a.startsWith("http")) {
try {
let url = new URL(a);
let ext = url.pathname.toLowerCase().match(FileExtensionRegex);
if (ext) {
switch (ext[1]) {
case "gif":
case "jpg":
case "jpeg":
case "png":
case "bmp":
case "webp": {
return ;
}
case "mp4":
case "mkv":
case "avi":
case "m4v": {
return
}
}
} else {
return {url.toString()}
}
} catch (e) {
console.warn(`Not a valid url: ${a}`);
}
}
return a;
});
}
return f;
}).flat();
}
async function like() {
let evLike = await publisher.like(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);
}
}
if (!ev.IsContent()) {
return (
<>
{ev.Id}
Kind: {ev.Kind}
Content: {ev.Content}
>
);
}
return (
{options.showHeader ?
{options.showTime ?
{moment(ev.CreatedAt * 1000).fromNow()}
: null}
: null}
goToEvent(e, ev.Id)}>
{transformBody()}
{options.showFooter ?
{isMine ?
deleteEvent()} />
: null}
setShowReply(!showReply)}>
like()}>
{(reactions?.length ?? 0)}
console.debug(ev)}>
: null}
{showReply ?
setShowReply(false)} /> : null}
)
}