Threading improvments
This commit is contained in:
parent
b26f3a9b95
commit
99410dd8c2
@ -1,10 +1,11 @@
|
||||
import "./Note.css";
|
||||
import Event from "../nostr/Event";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import moment from "moment";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import Event from "../nostr/Event";
|
||||
import ProfileImage from "./ProfileImage";
|
||||
import useEventPublisher from "../pages/feed/EventPublisher";
|
||||
|
||||
const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/;
|
||||
const FileExtensionRegex = /\.([\w]+)$/;
|
||||
@ -13,11 +14,12 @@ const MentionRegex = /(#\[\d+\])/g;
|
||||
export default function Note(props) {
|
||||
const navigate = useNavigate();
|
||||
const data = props.data;
|
||||
const dataEvent = props["data-ev"];
|
||||
const reactions = props.reactions;
|
||||
const publisher = useEventPublisher();
|
||||
const [sig, setSig] = useState(false);
|
||||
const users = useSelector(s => s.users?.users);
|
||||
const user = users[data?.pubkey];
|
||||
const ev = Event.FromObject(data);
|
||||
const ev = dataEvent ?? Event.FromObject(data);
|
||||
|
||||
useEffect(() => {
|
||||
if (sig === false) {
|
||||
@ -109,6 +111,11 @@ export default function Note(props) {
|
||||
});
|
||||
}
|
||||
|
||||
async function like() {
|
||||
let evLike = await publisher.like(ev);
|
||||
publisher.broadcast(evLike);
|
||||
}
|
||||
|
||||
if (!ev.IsContent()) {
|
||||
return (
|
||||
<>
|
||||
@ -131,7 +138,7 @@ export default function Note(props) {
|
||||
{transformBody()}
|
||||
</div>
|
||||
<div className="footer">
|
||||
<span className="pill">
|
||||
<span className="pill" onClick={() => like()}>
|
||||
👍 {(reactions?.length ?? 0)}
|
||||
</span>
|
||||
<span className="pill" onClick={() => console.debug(ev)}>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "./Note.css";
|
||||
import moment from "moment";
|
||||
import ProfileImage from "./ProfileImage";
|
||||
|
||||
export default function NoteGhost(props) {
|
||||
@ -7,12 +6,9 @@ export default function NoteGhost(props) {
|
||||
<div className="note">
|
||||
<div className="header">
|
||||
<ProfileImage pubKey="" />
|
||||
<div className="info">
|
||||
{moment().fromNow()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">
|
||||
Loading...
|
||||
{props.text ?? "Loading..."}
|
||||
</div>
|
||||
<div className="footer">
|
||||
</div>
|
||||
|
@ -4,6 +4,8 @@ import Note from "./Note";
|
||||
import NoteGhost from "./NoteGhost";
|
||||
|
||||
export default function Thread(props) {
|
||||
const thisEvent = props.this;
|
||||
|
||||
/** @type {Array<Event>} */
|
||||
const notes = props.notes?.map(a => Event.FromObject(a));
|
||||
|
||||
@ -11,18 +13,21 @@ export default function Thread(props) {
|
||||
const root = notes.find(a => a.GetThread() === null);
|
||||
|
||||
function reactions(id) {
|
||||
return notes?.filter(a => a.Kind === EventKind.Reaction && a.GetThread()?.Root?.Event === id);
|
||||
return notes?.filter(a => a.Kind === EventKind.Reaction && a.Tags.find(a => a.Key === "e").Event === id);
|
||||
}
|
||||
|
||||
const repliesToRoot = notes?.
|
||||
filter(a => a.GetThread()?.Root?.Event === root?.Id && a.Kind === EventKind.TextNote)
|
||||
.sort((a, b) => b.CreatedAt - a.CreatedAt);
|
||||
filter(a => a.GetThread()?.Root !== null && a.Kind === EventKind.TextNote && a.Id !== thisEvent)
|
||||
.sort((a, b) => a.CreatedAt - b.CreatedAt);
|
||||
const thisNote = notes?.find(a => a.Id === thisEvent);
|
||||
return (
|
||||
<>
|
||||
{root === undefined ?
|
||||
<NoteGhost />
|
||||
: <Note data={root?.ToObject()} reactions={reactions(root?.Id)} />}
|
||||
{repliesToRoot?.map(a => <Note key={a.Id} data={a.ToObject()} reactions={reactions(a.Id)} />)}
|
||||
<NoteGhost text={`Loading... (${notes.length} events loaded)`}/>
|
||||
: <Note data-ev={root} reactions={reactions(root?.Id)} />}
|
||||
{thisNote ? <Note data-ev={thisNote} reactions={reactions(thisNote.Id)}/> : null}
|
||||
<h4>Other Replies</h4>
|
||||
{repliesToRoot?.map(a => <Note key={a.Id} data-ev={a} reactions={reactions(a.Id)} />)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -149,30 +149,4 @@ export default class Event {
|
||||
ev.PubKey = pubKey;
|
||||
return ev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new SetMetadata event
|
||||
* @param {String} pubKey Pubkey of the creator of this event
|
||||
* @param {any} obj Metadata content
|
||||
* @returns {Event}
|
||||
*/
|
||||
static SetMetadata(pubKey, obj) {
|
||||
let ev = Event.ForPubKey(pubKey);
|
||||
ev.Kind = EventKind.SetMetadata;
|
||||
ev.Content = JSON.stringify(obj);
|
||||
return ev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TextNote event
|
||||
* @param {String} pubKey
|
||||
* @param {String} message
|
||||
* @returns
|
||||
*/
|
||||
static NewNote(pubKey, message) {
|
||||
let ev = Event.ForPubKey(pubKey);
|
||||
ev.Kind = EventKind.TextNote;
|
||||
ev.Content = message;
|
||||
return ev;
|
||||
}
|
||||
}
|
@ -7,5 +7,5 @@ export default function EventPage() {
|
||||
const id = params.id;
|
||||
|
||||
const { notes } = useThreadFeed(id);
|
||||
return <Thread notes={notes}/>;
|
||||
return <Thread notes={notes} this={id}/>;
|
||||
}
|
@ -3,19 +3,17 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { useParams } from "react-router-dom";
|
||||
import useProfile from "./feed/ProfileFeed";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import Event from "../nostr/Event";
|
||||
import { NostrContext } from "..";
|
||||
import { resetProfile } from "../state/Users";
|
||||
import Nostrich from "../nostrich.jpg";
|
||||
import useEventPublisher from "./feed/EventPublisher";
|
||||
|
||||
export default function ProfilePage() {
|
||||
const system = useContext(NostrContext);
|
||||
const dispatch = useDispatch();
|
||||
const params = useParams();
|
||||
const id = params.id;
|
||||
const user = useProfile(id);
|
||||
const publisher = useEventPublisher();
|
||||
const loginPubKey = useSelector(s => s.login.publicKey);
|
||||
const privKey = useSelector(s => s.login.privateKey);
|
||||
const isMe = loginPubKey === id;
|
||||
|
||||
let [name, setName] = useState("");
|
||||
@ -37,7 +35,7 @@ export default function ProfilePage() {
|
||||
}, [user]);
|
||||
|
||||
async function saveProfile() {
|
||||
let ev = Event.SetMetadata(id, {
|
||||
let ev = await publisher.metadata({
|
||||
name,
|
||||
about,
|
||||
picture,
|
||||
@ -45,10 +43,8 @@ export default function ProfilePage() {
|
||||
nip05,
|
||||
lud16
|
||||
});
|
||||
await ev.Sign(privKey);
|
||||
|
||||
console.debug(ev);
|
||||
system.BroadcastEvent(ev);
|
||||
publisher.broadcast(ev);
|
||||
dispatch(resetProfile(id));
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,20 @@
|
||||
import "./Root.css";
|
||||
import Timeline from "./Timeline";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useContext, useState } from "react";
|
||||
import Event from "../nostr/Event";
|
||||
import { NostrContext } from "..";
|
||||
import { useState } from "react";
|
||||
import Timeline from "./Timeline";
|
||||
import useEventPublisher from "./feed/EventPublisher";
|
||||
|
||||
export default function RootPage() {
|
||||
const system = useContext(NostrContext);
|
||||
const publisher = useEventPublisher();
|
||||
const pubKey = useSelector(s => s.login.publicKey);
|
||||
const privKey = useSelector(s => s.login.privateKey);
|
||||
|
||||
const [note, setNote] = useState("");
|
||||
|
||||
async function sendNote() {
|
||||
let ev = Event.NewNote(pubKey, note);
|
||||
await ev.Sign(privKey);
|
||||
let ev = await publisher.note(note);
|
||||
|
||||
console.debug("Sending note: ", ev);
|
||||
system.BroadcastEvent(ev);
|
||||
publisher.broadcast(ev);
|
||||
setNote("");
|
||||
}
|
||||
|
||||
|
54
src/pages/feed/EventPublisher.js
Normal file
54
src/pages/feed/EventPublisher.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { useContext } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { NostrContext } from "../..";
|
||||
import Event from "../../nostr/Event";
|
||||
import EventKind from "../../nostr/EventKind";
|
||||
import Tag from "../../nostr/Tag";
|
||||
|
||||
export default function useEventPublisher() {
|
||||
const system = useContext(NostrContext);
|
||||
const pubKey = useSelector(s => s.login.publicKey);
|
||||
const privKey = useSelector(s => s.login.privateKey);
|
||||
|
||||
return {
|
||||
broadcast: (ev) => {
|
||||
console.debug("Sending event: ", ev);
|
||||
system.BroadcastEvent(ev);
|
||||
},
|
||||
metadata: async (obj) => {
|
||||
let ev = Event.ForPubKey(pubKey);
|
||||
ev.Kind = EventKind.SetMetadata;
|
||||
ev.Content = JSON.stringify(obj);
|
||||
await ev.Sign(privKey);
|
||||
return ev;
|
||||
},
|
||||
note: async (msg) => {
|
||||
if(typeof msg !== "string") {
|
||||
throw "Must be text!";
|
||||
}
|
||||
let ev = Event.ForPubKey(pubKey);
|
||||
ev.Kind = EventKind.TextNote;
|
||||
ev.Content = msg;
|
||||
await ev.Sign(privKey);
|
||||
return ev;
|
||||
},
|
||||
like: async (evRef) => {
|
||||
let ev = Event.ForPubKey(pubKey);
|
||||
ev.Kind = EventKind.Reaction;
|
||||
ev.Content = "+";
|
||||
ev.Tags.push(new Tag(["e", evRef.Id], 0));
|
||||
ev.Tags.push(new Tag(["p", evRef.PubKey], 1));
|
||||
await ev.Sign(privKey);
|
||||
return ev;
|
||||
},
|
||||
dislike: async (evRef) => {
|
||||
let ev = Event.ForPubKey(pubKey);
|
||||
ev.Kind = EventKind.Reaction;
|
||||
ev.Content = "-";
|
||||
ev.Tags.push(new Tag(["e", evRef.Id], 0));
|
||||
ev.Tags.push(new Tag(["p", evRef.PubKey], 1));
|
||||
await ev.Sign(privKey);
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user