Threads progress
This commit is contained in:
parent
e6ef1a5bc9
commit
e617d6d528
@ -39,16 +39,22 @@ export default function Note(props) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let replyId = thread.ReplyTo.Event;
|
let replyId = thread?.ReplyTo?.Event;
|
||||||
return (
|
return (
|
||||||
<div className="reply" onClick={(e) => goToEvent(e, replyId)}>
|
<div className="reply" onClick={(e) => goToEvent(e, replyId)}>
|
||||||
➡️ {replyId.substring(0, 8)}
|
➡️ {replyId?.substring(0, 8)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!ev.IsContent()) {
|
if (!ev.IsContent()) {
|
||||||
return <pre>Event: {ev.Id}</pre>;
|
return (
|
||||||
|
<>
|
||||||
|
<pre>{ev.Id}</pre>
|
||||||
|
<pre>Kind: {ev.Kind}</pre>
|
||||||
|
<pre>Content: {ev.Content}</pre>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -31,7 +31,7 @@ export default class Connection {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "EOSE": {
|
case "EOSE": {
|
||||||
// ignored for now
|
this._OnEnd(msg[1]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -90,7 +90,15 @@ export default class Connection {
|
|||||||
if (this.Subscriptions[subId]) {
|
if (this.Subscriptions[subId]) {
|
||||||
this.Subscriptions[subId].OnEvent(ev);
|
this.Subscriptions[subId].OnEvent(ev);
|
||||||
} else {
|
} 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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -75,7 +75,7 @@ export default class Event {
|
|||||||
this.PubKey,
|
this.PubKey,
|
||||||
this.CreatedAt,
|
this.CreatedAt,
|
||||||
this.Kind,
|
this.Kind,
|
||||||
this.Tags.map(a => a.ToObject()),
|
this.Tags.map(a => a.ToObject()).filter(a => a !== null),
|
||||||
this.Content
|
this.Content
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ export default class Event {
|
|||||||
pubkey: this.PubKey,
|
pubkey: this.PubKey,
|
||||||
created_at: this.CreatedAt,
|
created_at: this.CreatedAt,
|
||||||
kind: this.Kind,
|
kind: this.Kind,
|
||||||
tags: this.Tags.map(a => a.ToObject()),
|
tags: this.Tags.map(a => a.ToObject()).filter(a => a !== null),
|
||||||
content: this.Content,
|
content: this.Content,
|
||||||
sig: this.Signature
|
sig: this.Signature
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import Connection from "./Connection";
|
||||||
|
|
||||||
export class Subscriptions {
|
export class Subscriptions {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -52,6 +53,12 @@ export class Subscriptions {
|
|||||||
*/
|
*/
|
||||||
this.OnEvent = (e) => { console.warn(`No event handler was set on subscription: ${this.Id}`) };
|
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
|
* Collection of OR sub scriptions linked to this
|
||||||
*/
|
*/
|
||||||
|
@ -5,6 +5,7 @@ export default class Tag {
|
|||||||
this.PubKey = null;
|
this.PubKey = null;
|
||||||
this.Relay = null;
|
this.Relay = null;
|
||||||
this.Marker = null;
|
this.Marker = null;
|
||||||
|
this.Other = null;
|
||||||
|
|
||||||
switch (this.Key) {
|
switch (this.Key) {
|
||||||
case "e": {
|
case "e": {
|
||||||
@ -19,17 +20,24 @@ export default class Tag {
|
|||||||
this.PubKey = tag[1];
|
this.PubKey = tag[1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default: {
|
||||||
|
this.Other = tag;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToObject() {
|
ToObject() {
|
||||||
switch(this.Key) {
|
switch (this.Key) {
|
||||||
case "e": {
|
case "e": {
|
||||||
return ["e", this.Event, this.Relay, this.Marker].filter(a => a !== null);
|
return ["e", this.Event, this.Relay, this.Marker].filter(a => a !== null);
|
||||||
}
|
}
|
||||||
case "p": {
|
case "p": {
|
||||||
return ["p", this.PubKey];
|
return ["p", this.PubKey];
|
||||||
}
|
}
|
||||||
|
default: {
|
||||||
|
return this.Other;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,12 @@ export default function EventPage() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const id = params.id;
|
const id = params.id;
|
||||||
|
|
||||||
const { note, notes } = useThreadFeed(id);
|
const { notes } = useThreadFeed(id);
|
||||||
|
|
||||||
if(note) {
|
if(notes) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{notes?.map(n => <Note key={n.id} data={n}/>)}
|
{notes?.map(n => <Note key={n.id} data={n}/>)}
|
||||||
<Note data={note}/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import useProfileFeed from "./feed/ProfileFeed";
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const id = params.id;
|
const id = params.id;
|
||||||
|
useProfileFeed(id);
|
||||||
|
|
||||||
const user = useSelector(s => s.users.users[id]);
|
const user = useSelector(s => s.users.users[id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile">
|
<div className="profile">
|
||||||
<img src={} />
|
<img src={user?.picture} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -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));
|
||||||
|
}, []);
|
||||||
}
|
}
|
@ -1,62 +1,70 @@
|
|||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { NostrContext } from "../..";
|
import { NostrContext } from "../..";
|
||||||
|
import Event from "../../nostr/Event";
|
||||||
import { Subscriptions } from "../../nostr/Subscriptions";
|
import { Subscriptions } from "../../nostr/Subscriptions";
|
||||||
|
import { addNote, reset } from "../../state/Thread";
|
||||||
import { addPubKey } from "../../state/Users";
|
import { addPubKey } from "../../state/Users";
|
||||||
|
|
||||||
export default function useThreadFeed(id) {
|
export default function useThreadFeed(id) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const system = useContext(NostrContext);
|
const system = useContext(NostrContext);
|
||||||
const [note, setNote] = useState(null);
|
const notes = useSelector(s => s.thread.notes);
|
||||||
const [notes, setNotes] = useState([]);
|
|
||||||
const [relatedEvents, setRelatedEvents] = useState([]);
|
|
||||||
|
|
||||||
|
// track profiles
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (note) {
|
for (let n of notes) {
|
||||||
let eFetch = [];
|
if (n.pubkey) {
|
||||||
dispatch(addPubKey(note.pubkey));
|
dispatch(addPubKey(n.pubkey));
|
||||||
for (let t of note.tags) {
|
}
|
||||||
if (t[0] === "p") {
|
for(let t of n.tags) {
|
||||||
|
if(t[0] === "p" && t[1]) {
|
||||||
dispatch(addPubKey(t[1]));
|
dispatch(addPubKey(t[1]));
|
||||||
} else if (t[0] === "e") {
|
|
||||||
eFetch.push(t[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(eFetch.length > 0) {
|
|
||||||
setRelatedEvents(eFetch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [note]);
|
}, [notes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (system) {
|
if (system) {
|
||||||
let sub = new Subscriptions();
|
let sub = new Subscriptions();
|
||||||
sub.Ids.add(id);
|
if (notes.length === 1) {
|
||||||
|
let thisNote = Event.FromObject(notes[0]);
|
||||||
sub.OnEvent = (e) => {
|
let thread = thisNote.GetThread();
|
||||||
if(e.id === id && !note) {
|
if (thread !== null) {
|
||||||
setNote(e);
|
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);
|
system.AddSubscription(sub);
|
||||||
return () => system.RemoveSubscription(sub.Id);
|
|
||||||
}
|
}
|
||||||
}, [system]);
|
}, [system, notes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(system && relatedEvents.length > 0) {
|
dispatch(reset());
|
||||||
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 };
|
|
||||||
|
|
||||||
|
return { notes };
|
||||||
}
|
}
|
@ -11,8 +11,6 @@ export default function useUsersStore() {
|
|||||||
const system = useContext(NostrContext);
|
const system = useContext(NostrContext);
|
||||||
const pKeys = useSelector(s => s.users.pubKeys);
|
const pKeys = useSelector(s => s.users.pubKeys);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pKeys.length > 0) {
|
if (pKeys.length > 0) {
|
||||||
const sub = new Subscriptions();
|
const sub = new Subscriptions();
|
||||||
@ -34,5 +32,4 @@ export default function useUsersStore() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [pKeys]);
|
}, [pKeys]);
|
||||||
|
|
||||||
}
|
}
|
@ -8,7 +8,8 @@ const DefaultRelays = JSON.stringify([
|
|||||||
"wss://nostr.zebedee.cloud",
|
"wss://nostr.zebedee.cloud",
|
||||||
"wss://relay.damus.io",
|
"wss://relay.damus.io",
|
||||||
"wss://nostr.rocks",
|
"wss://nostr.rocks",
|
||||||
"wss://nostr.rocks"
|
"wss://nostr.rocks",
|
||||||
|
"wss://nostr.fmt.wiz.biz"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const LoginSlice = createSlice({
|
const LoginSlice = createSlice({
|
||||||
|
@ -2,12 +2,14 @@ import { configureStore } from "@reduxjs/toolkit";
|
|||||||
import { reducer as TimelineReducer } from "./Timeline";
|
import { reducer as TimelineReducer } from "./Timeline";
|
||||||
import { reducer as UsersReducer } from "./Users";
|
import { reducer as UsersReducer } from "./Users";
|
||||||
import { reducer as LoginReducer } from "./Login";
|
import { reducer as LoginReducer } from "./Login";
|
||||||
|
import { reducer as ThreadReducer } from "./Thread";
|
||||||
|
|
||||||
const Store = configureStore({
|
const Store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
timeline: TimelineReducer,
|
timeline: TimelineReducer,
|
||||||
users: UsersReducer,
|
users: UsersReducer,
|
||||||
login: LoginReducer
|
login: LoginReducer,
|
||||||
|
thread: ThreadReducer
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
26
src/state/Thread.js
Normal file
26
src/state/Thread.js
Normal file
@ -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;
|
Loading…
Reference in New Issue
Block a user