From e617d6d52829529cfcc6c2b8fd5806e91e450da0 Mon Sep 17 00:00:00 2001 From: Kieran Date: Sun, 18 Dec 2022 22:23:52 +0000 Subject: [PATCH] Threads progress --- src/element/Note.js | 14 ++++-- src/nostr/Connection.js | 12 +++++- src/nostr/Event.js | 4 +- src/nostr/Subscriptions.js | 7 +++ src/nostr/Tag.js | 12 +++++- src/pages/EventPage.js | 5 +-- src/pages/ProfilePage.js | 6 ++- src/pages/feed/ProfileFeed.js | 12 +++++- src/pages/feed/ThreadFeed.js | 80 +++++++++++++++++++---------------- src/pages/feed/UsersFeed.js | 3 -- src/state/Login.js | 3 +- src/state/Store.js | 4 +- src/state/Thread.js | 26 ++++++++++++ 13 files changed, 131 insertions(+), 57 deletions(-) create mode 100644 src/state/Thread.js diff --git a/src/element/Note.js b/src/element/Note.js index 25d5b032..4ce18d3e 100644 --- a/src/element/Note.js +++ b/src/element/Note.js @@ -39,16 +39,22 @@ export default function Note(props) { return null; } - let replyId = thread.ReplyTo.Event; + let replyId = thread?.ReplyTo?.Event; return (
goToEvent(e, replyId)}> - ➡️ {replyId.substring(0, 8)} + ➡️ {replyId?.substring(0, 8)}
) } - if(!ev.IsContent()) { - return
Event: {ev.Id}
; + if (!ev.IsContent()) { + return ( + <> +
{ev.Id}
+
Kind: {ev.Kind}
+
Content: {ev.Content}
+ + ); } return ( diff --git a/src/nostr/Connection.js b/src/nostr/Connection.js index 1a7b360a..ab1056af 100644 --- a/src/nostr/Connection.js +++ b/src/nostr/Connection.js @@ -31,7 +31,7 @@ export default class Connection { break; } case "EOSE": { - // ignored for now + this._OnEnd(msg[1]); break; } default: { @@ -90,7 +90,15 @@ export default class Connection { if (this.Subscriptions[subId]) { this.Subscriptions[subId].OnEvent(ev); } else { - console.warn("No subscription for event!"); + console.warn(`No subscription for event! ${subId}`); + } + } + + _OnEnd(subId) { + if (this.Subscriptions[subId]) { + this.Subscriptions[subId].OnEnd(this); + } else { + console.warn(`No subscription for end! ${subId}`); } } } \ No newline at end of file diff --git a/src/nostr/Event.js b/src/nostr/Event.js index 3442692c..69030dba 100644 --- a/src/nostr/Event.js +++ b/src/nostr/Event.js @@ -75,7 +75,7 @@ export default class Event { this.PubKey, this.CreatedAt, this.Kind, - this.Tags.map(a => a.ToObject()), + this.Tags.map(a => a.ToObject()).filter(a => a !== null), this.Content ]; @@ -130,7 +130,7 @@ export default class Event { pubkey: this.PubKey, created_at: this.CreatedAt, kind: this.Kind, - tags: this.Tags.map(a => a.ToObject()), + tags: this.Tags.map(a => a.ToObject()).filter(a => a !== null), content: this.Content, sig: this.Signature }; diff --git a/src/nostr/Subscriptions.js b/src/nostr/Subscriptions.js index ee83a6cc..6d36f047 100644 --- a/src/nostr/Subscriptions.js +++ b/src/nostr/Subscriptions.js @@ -1,4 +1,5 @@ import { v4 as uuid } from "uuid"; +import Connection from "./Connection"; export class Subscriptions { constructor() { @@ -52,6 +53,12 @@ export class Subscriptions { */ this.OnEvent = (e) => { console.warn(`No event handler was set on subscription: ${this.Id}`) }; + /** + * End of data event + * @param {Connection} c + */ + this.OnEnd = (c) => {}; + /** * Collection of OR sub scriptions linked to this */ diff --git a/src/nostr/Tag.js b/src/nostr/Tag.js index 9a026f79..85cba679 100644 --- a/src/nostr/Tag.js +++ b/src/nostr/Tag.js @@ -5,6 +5,7 @@ export default class Tag { this.PubKey = null; this.Relay = null; this.Marker = null; + this.Other = null; switch (this.Key) { case "e": { @@ -19,17 +20,24 @@ export default class Tag { this.PubKey = tag[1]; break; } + default: { + this.Other = tag; + break; + } } } ToObject() { - switch(this.Key) { + switch (this.Key) { case "e": { return ["e", this.Event, this.Relay, this.Marker].filter(a => a !== null); - } + } case "p": { return ["p", this.PubKey]; } + default: { + return this.Other; + } } return null; } diff --git a/src/pages/EventPage.js b/src/pages/EventPage.js index 6769f3b2..da200210 100644 --- a/src/pages/EventPage.js +++ b/src/pages/EventPage.js @@ -7,13 +7,12 @@ export default function EventPage() { const params = useParams(); const id = params.id; - const { note, notes } = useThreadFeed(id); + const { notes } = useThreadFeed(id); - if(note) { + if(notes) { return ( <> {notes?.map(n => )} - ) } diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 655a1ba0..601bb873 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -1,15 +1,17 @@ import { useSelector } from "react-redux"; import { useParams } from "react-router-dom"; +import useProfileFeed from "./feed/ProfileFeed"; export default function ProfilePage() { const params = useParams(); const id = params.id; - + useProfileFeed(id); + const user = useSelector(s => s.users.users[id]); return (
- +
) } \ No newline at end of file diff --git a/src/pages/feed/ProfileFeed.js b/src/pages/feed/ProfileFeed.js index aa4b8052..acc6d925 100644 --- a/src/pages/feed/ProfileFeed.js +++ b/src/pages/feed/ProfileFeed.js @@ -1,3 +1,13 @@ -export default function useProfileFeed(id) { +import { useContext, useEffect } from "react"; +import { useDispatch } from "react-redux"; +import { NostrContext } from "../.."; +import { addPubKey } from "../../state/Users"; +export default function useProfileFeed(id) { + const dispatch = useDispatch(); + const system = useContext(NostrContext); + + useEffect(() => { + dispatch(addPubKey(id)); + }, []); } \ No newline at end of file diff --git a/src/pages/feed/ThreadFeed.js b/src/pages/feed/ThreadFeed.js index 76c73c72..ead4eda5 100644 --- a/src/pages/feed/ThreadFeed.js +++ b/src/pages/feed/ThreadFeed.js @@ -1,62 +1,70 @@ import { useContext, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { NostrContext } from "../.."; +import Event from "../../nostr/Event"; import { Subscriptions } from "../../nostr/Subscriptions"; +import { addNote, reset } from "../../state/Thread"; import { addPubKey } from "../../state/Users"; export default function useThreadFeed(id) { const dispatch = useDispatch(); const system = useContext(NostrContext); - const [note, setNote] = useState(null); - const [notes, setNotes] = useState([]); - const [relatedEvents, setRelatedEvents] = useState([]); + const notes = useSelector(s => s.thread.notes); + // track profiles useEffect(() => { - if (note) { - let eFetch = []; - dispatch(addPubKey(note.pubkey)); - for (let t of note.tags) { - if (t[0] === "p") { + for (let n of notes) { + if (n.pubkey) { + dispatch(addPubKey(n.pubkey)); + } + for(let t of n.tags) { + if(t[0] === "p" && t[1]) { dispatch(addPubKey(t[1])); - } else if (t[0] === "e") { - eFetch.push(t[1]); } } - if(eFetch.length > 0) { - setRelatedEvents(eFetch); - } } - }, [note]); + }, [notes]); useEffect(() => { if (system) { let sub = new Subscriptions(); - sub.Ids.add(id); - - sub.OnEvent = (e) => { - if(e.id === id && !note) { - setNote(e); + if (notes.length === 1) { + let thisNote = Event.FromObject(notes[0]); + let thread = thisNote.GetThread(); + if (thread !== null) { + if (thread.ReplyTo) { + sub.Ids.add(thread.ReplyTo.Event); + } + if (thread.Root) { + sub.Ids.add(thread.Root.Event); + } + for (let m of thread.Mentions) { + sub.Ids.add(m.Event); + } } + } else if (notes.length === 0) { + sub.Ids.add(id); + + // get replies to this event + let subRelated = new Subscriptions(); + subRelated.ETags.add(id); + sub.AddSubscription(subRelated); + } else { + return; + } + sub.OnEvent = (e) => { + dispatch(addNote(e)); + }; + sub.OnEnd = (c) => { + c.RemoveSubscription(sub.Id); }; system.AddSubscription(sub); - return () => system.RemoveSubscription(sub.Id); } - }, [system]); + }, [system, notes]); useEffect(() => { - if(system && relatedEvents.length > 0) { - let sub = new Subscriptions(); - sub.ETags = new Set(relatedEvents); - sub.OnEvent = (e) => { - let temp = new Set(notes); - temp.add(e); - setNotes(Array.from(temp)); - }; - system.AddSubscription(sub); - return () => system.RemoveSubscription(sub.Id); - } - }, [system, relatedEvents]) - - return { note, notes }; + dispatch(reset()); + }, []); + return { notes }; } \ No newline at end of file diff --git a/src/pages/feed/UsersFeed.js b/src/pages/feed/UsersFeed.js index 96a797e9..037a36e2 100644 --- a/src/pages/feed/UsersFeed.js +++ b/src/pages/feed/UsersFeed.js @@ -11,8 +11,6 @@ export default function useUsersStore() { const system = useContext(NostrContext); const pKeys = useSelector(s => s.users.pubKeys); - - useEffect(() => { if (pKeys.length > 0) { const sub = new Subscriptions(); @@ -34,5 +32,4 @@ export default function useUsersStore() { } } }, [pKeys]); - } \ No newline at end of file diff --git a/src/state/Login.js b/src/state/Login.js index 576c51bc..30cec609 100644 --- a/src/state/Login.js +++ b/src/state/Login.js @@ -8,7 +8,8 @@ const DefaultRelays = JSON.stringify([ "wss://nostr.zebedee.cloud", "wss://relay.damus.io", "wss://nostr.rocks", - "wss://nostr.rocks" + "wss://nostr.rocks", + "wss://nostr.fmt.wiz.biz" ]); const LoginSlice = createSlice({ diff --git a/src/state/Store.js b/src/state/Store.js index 421c0a41..9046a36e 100644 --- a/src/state/Store.js +++ b/src/state/Store.js @@ -2,12 +2,14 @@ import { configureStore } from "@reduxjs/toolkit"; import { reducer as TimelineReducer } from "./Timeline"; import { reducer as UsersReducer } from "./Users"; import { reducer as LoginReducer } from "./Login"; +import { reducer as ThreadReducer } from "./Thread"; const Store = configureStore({ reducer: { timeline: TimelineReducer, users: UsersReducer, - login: LoginReducer + login: LoginReducer, + thread: ThreadReducer } }); diff --git a/src/state/Thread.js b/src/state/Thread.js new file mode 100644 index 00000000..2f6504b9 --- /dev/null +++ b/src/state/Thread.js @@ -0,0 +1,26 @@ +import { createSlice } from '@reduxjs/toolkit' + +const ThreadSlice = createSlice({ + name: "Thread", + initialState: { + notes: [], + }, + reducers: { + setNotes: (state, action) => { + state.notes = action.payload; + }, + addNote: (state, action) => { + if (!state.notes.some(n => n.id === action.payload.id)) { + let tmp = new Set(state.notes); + tmp.add(action.payload); + state.notes = Array.from(tmp); + } + }, + reset: (state) => { + state.notes = []; + } + } +}); + +export const { setNotes, addNote, reset } = ThreadSlice.actions; +export const reducer = ThreadSlice.reducer; \ No newline at end of file