diff --git a/package.json b/package.json
index 8a36b8de..727a7912 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,6 @@
"qr-code-styling": "^1.6.0-rc.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-modal": "^3.16.1",
"react-redux": "^8.0.5",
"react-router-dom": "^6.5.0",
"react-scripts": "5.0.1",
diff --git a/public/index.html b/public/index.html
index 554bed67..c5af8453 100644
--- a/public/index.html
+++ b/public/index.html
@@ -8,7 +8,7 @@
+ content="default-src 'self'; child-src 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src wss://* 'self'; img-src * data:; font-src https://fonts.gstatic.com;" />
diff --git a/src/element/Modal.css b/src/element/Modal.css
new file mode 100644
index 00000000..b49b1e1a
--- /dev/null
+++ b/src/element/Modal.css
@@ -0,0 +1,11 @@
+.modal {
+ width: 100vw;
+ height: 100vh;
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: rgba(0,0,0, 0.8);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/src/element/Modal.js b/src/element/Modal.js
new file mode 100644
index 00000000..04df6a89
--- /dev/null
+++ b/src/element/Modal.js
@@ -0,0 +1,18 @@
+import "./Modal.css";
+import { useEffect } from "react"
+
+export default function Modal(props) {
+ const onClose = props.onClose || (() => {});
+
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ document.body.classList.add("scroll-lock");
+ return () => document.body.classList.remove("scroll-lock");
+ }, []);
+
+ return (
+
onClose(e)}>
+ {props.children}
+
+ )
+}
\ No newline at end of file
diff --git a/src/element/Note.js b/src/element/Note.js
index 64df3541..74107930 100644
--- a/src/element/Note.js
+++ b/src/element/Note.js
@@ -1,5 +1,5 @@
import "./Note.css";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { useSelector } from "react-redux";
import moment from "moment";
import { Link, useNavigate } from "react-router-dom";
@@ -11,9 +11,9 @@ import ProfileImage from "./ProfileImage";
import useEventPublisher from "../feed/EventPublisher";
import { NoteCreator } from "./NoteCreator";
-const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/;
-const FileExtensionRegex = /\.([\w]+)$/;
-const MentionRegex = /(#\[\d+\])/g;
+const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/i;
+const FileExtensionRegex = /\.([\w]+)$/i;
+const MentionRegex = /(#\[\d+\])/gi;
export default function Note(props) {
const navigate = useNavigate();
@@ -21,22 +21,10 @@ export default function Note(props) {
const dataEvent = props["data-ev"];
const reactions = props.reactions;
const publisher = useEventPublisher();
- const [sig, setSig] = useState(false);
const [showReply, setShowReply] = useState(false);
const users = useSelector(s => s.users?.users);
const ev = dataEvent ?? Event.FromObject(data);
- useEffect(() => {
- if (sig === false) {
- verifyEvent();
- }
- }, []);
-
- async function verifyEvent() {
- let res = await ev.Verify();
- setSig(res);
- }
-
function goToEvent(e, id) {
if (!window.location.pathname.startsWith("/e/")) {
e.stopPropagation();
@@ -66,7 +54,7 @@ export default function Note(props) {
return urlBody.map(a => {
if (a.startsWith("http")) {
let url = new URL(a);
- let ext = url.pathname.match(FileExtensionRegex);
+ let ext = url.pathname.toLowerCase().match(FileExtensionRegex);
if (ext) {
switch (ext[1]) {
case "gif":
diff --git a/src/feed/EventPublisher.js b/src/feed/EventPublisher.js
index eb99602f..224a07b3 100644
--- a/src/feed/EventPublisher.js
+++ b/src/feed/EventPublisher.js
@@ -1,12 +1,10 @@
-import { useContext } from "react";
import { useSelector } from "react-redux";
-import { NostrContext } from "..";
+import { System } 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);
const nip07 = useSelector(s => s.login.nip07);
@@ -33,7 +31,7 @@ export default function useEventPublisher() {
return {
broadcast: (ev) => {
console.debug("Sending event: ", ev);
- system.BroadcastEvent(ev);
+ System.BroadcastEvent(ev);
},
metadata: async (obj) => {
let ev = Event.ForPubKey(pubKey);
diff --git a/src/feed/LoginFeed.js b/src/feed/LoginFeed.js
index 62f96d31..0496730a 100644
--- a/src/feed/LoginFeed.js
+++ b/src/feed/LoginFeed.js
@@ -1,37 +1,53 @@
import { useContext, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { NostrContext } from "..";
+import { System } from "..";
import Event from "../nostr/Event";
import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions";
-import { setFollows, setRelays } from "../state/Login";
+import { addNotifications, setFollows, setRelays } from "../state/Login";
/**
* Managed loading data for the current logged in user
*/
export default function useLoginFeed() {
const dispatch = useDispatch();
- const system = useContext(NostrContext);
const pubKey = useSelector(s => s.login.publicKey);
useEffect(() => {
- if (system && pubKey) {
+ if (pubKey) {
let sub = new Subscriptions();
+ sub.Id = "login";
sub.Authors.add(pubKey);
sub.Kinds.add(EventKind.ContactList);
+
+ let notifications = new Subscriptions();
+ notifications.Kinds.add(EventKind.TextNote);
+ notifications.Kinds.add(EventKind.Reaction);
+ notifications.PTags.add(pubKey);
+ sub.AddSubscription(notifications);
+
sub.OnEvent = (e) => {
let ev = Event.FromObject(e);
- if (ev.Content !== "") {
- let relays = JSON.parse(ev.Content);
- dispatch(setRelays(relays));
+ switch (ev.Kind) {
+ case EventKind.ContactList: {
+ if (ev.Content !== "") {
+ let relays = JSON.parse(ev.Content);
+ dispatch(setRelays(relays));
+ }
+ let pTags = ev.Tags.filter(a => a.Key === "p").map(a => a.PubKey);
+ dispatch(setFollows(pTags));
+ break;
+ }
+ default: {
+ dispatch(addNotifications(ev.ToObject()));
+ break;
+ }
}
- let pTags = ev.Tags.filter(a => a.Key === "p").map(a => a.PubKey);
- dispatch(setFollows(pTags));
}
- system.AddSubscription(sub);
- return () => system.RemoveSubscription(sub.Id);
+ System.AddSubscription(sub);
+ return () => System.RemoveSubscription(sub.Id);
}
- }, [system, pubKey]);
+ }, [pubKey]);
return {};
}
\ No newline at end of file
diff --git a/src/feed/ProfileFeed.js b/src/feed/ProfileFeed.js
index 60bf77cd..237b85b0 100644
--- a/src/feed/ProfileFeed.js
+++ b/src/feed/ProfileFeed.js
@@ -1,19 +1,17 @@
-import { useContext, useEffect } from "react";
+import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { NostrContext } from "..";
import { addPubKey } from "../state/Users";
export default function useProfile(pubKey) {
const dispatch = useDispatch();
- const system = useContext(NostrContext);
const user = useSelector(s => s.users.users[pubKey]);
const pubKeys = useSelector(s => s.users.pubKeys);
useEffect(() => {
- if (system && pubKey !== "" && !pubKeys.includes(pubKey)) {
+ if (pubKey !== "" && !pubKeys.includes(pubKey)) {
dispatch(addPubKey(pubKey));
}
- }, [system]);
+ }, [pubKey]);
return user;
}
\ No newline at end of file
diff --git a/src/feed/Subscription.js b/src/feed/Subscription.js
new file mode 100644
index 00000000..46e9bb0a
--- /dev/null
+++ b/src/feed/Subscription.js
@@ -0,0 +1,51 @@
+import { useEffect, useMemo, useState } from "react";
+import { System } from "..";
+import { Subscriptions } from "../nostr/Subscriptions";
+
+/**
+ *
+ * @param {Subscriptions} sub
+ * @param {any} opt
+ * @returns
+ */
+export default function useSubscription(sub, opt) {
+ const [notes, setNotes] = useState([]);
+
+ const options = {
+ leaveOpen: false,
+ ...opt
+ };
+
+ useEffect(() => {
+ if (sub) {
+ sub.OnEvent = (e) => {
+ setNotes(n => {
+ if (Array.isArray(n) && !n.some(a => a.id === e.id)) {
+ return [
+ ...n,
+ e
+ ]
+ } else {
+ return n;
+ }
+ });
+ };
+
+ if (!options.leaveOpen) {
+ sub.OnEnd = (c) => {
+ c.RemoveSubscription(sub.Id);
+ if (sub.IsFinished()) {
+ System.RemoveSubscription(sub.Id);
+ }
+ };
+ }
+
+ System.AddSubscription(sub);
+ return () => {
+ System.RemoveSubscription(sub.Id);
+ };
+ }
+ }, [sub]);
+
+ return { notes, sub };
+}
\ No newline at end of file
diff --git a/src/feed/ThreadFeed.js b/src/feed/ThreadFeed.js
index afbd3bb3..2de35d8e 100644
--- a/src/feed/ThreadFeed.js
+++ b/src/feed/ThreadFeed.js
@@ -1,79 +1,56 @@
-import { useContext, useEffect, useState } from "react";
-import { useDispatch, useSelector } from "react-redux";
-import { NostrContext } from "..";
-import Event from "../nostr/Event";
+import { useMemo } from "react";
+import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions";
-import { addNote, reset } from "../state/Thread";
-import { addPubKey } from "../state/Users";
+import useSubscription from "./Subscription";
export default function useThreadFeed(id) {
- const dispatch = useDispatch();
- const system = useContext(NostrContext);
- const notes = useSelector(s => s.thread.notes);
- const [thisLoaded, setThisLoaded] = useState(false);
+ const sub = useMemo(() => {
+ const thisSub = new Subscriptions();
+ thisSub.Id = "thread";
+ thisSub.Ids.add(id);
- // track profiles
- useEffect(() => {
- let keys = [];
- for (let n of notes) {
- if (n.pubkey) {
- keys.push(n.pubkey);
+ // get replies to this event
+ const subRelated = new Subscriptions();
+ subRelated.Kinds.add(EventKind.Reaction);
+ subRelated.Kinds.add(EventKind.TextNote);
+ subRelated.ETags = thisSub.Ids;
+ thisSub.AddSubscription(subRelated);
+
+ return thisSub;
+ }, [id]);
+
+ const main = useSubscription(sub, { leaveOpen: true });
+
+ const relatedThisSub = useMemo(() => {
+ let thisNote = main.notes.find(a => a.id === id);
+
+ if (thisNote) {
+ let otherSubs = new Subscriptions();
+ otherSubs.Id = "thread-related";
+ for (let e of thisNote.tags.filter(a => a[0] === "e")) {
+ otherSubs.Ids.add(e[1]);
}
- for (let t of n.tags) {
- if (t[0] === "p" && t[1]) {
- keys.push(t[1]);
- }
+ // no #e skip related
+ if (otherSubs.Ids.size === 0) {
+ return null;
}
+
+ let relatedSubs = new Subscriptions();
+ relatedSubs.Kinds.add(EventKind.Reaction);
+ relatedSubs.Kinds.add(EventKind.TextNote);
+ relatedSubs.ETags = otherSubs.Ids;
+
+ otherSubs.AddSubscription(relatedSubs);
+ return otherSubs;
}
+ }, [main.notes]);
- dispatch(addPubKey(keys));
- }, [notes]);
+ const others = useSubscription(relatedThisSub, { leaveOpen: true });
- useEffect(() => {
- if (system) {
- let sub = new Subscriptions();
- let thisNote = notes.find(a => a.id === id);
- if (thisNote && !thisLoaded) {
- console.debug(notes);
- setThisLoaded(true);
-
- 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 {
- // this event is a root note, no other notes need to be loaded
- return;
- }
- } else if (notes.length === 0) {
- sub.Ids.add(id);
- } else {
- return;
- }
-
- // get replies to this event
- let subRelated = new Subscriptions();
- subRelated.ETags = sub.Ids;
- sub.AddSubscription(subRelated);
-
- sub.OnEvent = (e) => {
- dispatch(addNote(e));
- };
- system.AddSubscription(sub);
- }
- }, [system, notes]);
-
- useEffect(() => {
- dispatch(reset());
- }, []);
-
- return { notes };
+ return {
+ notes: [
+ ...main.notes,
+ ...others.notes
+ ]
+ };
}
\ No newline at end of file
diff --git a/src/feed/TimelineFeed.js b/src/feed/TimelineFeed.js
index a8945242..efc890a1 100644
--- a/src/feed/TimelineFeed.js
+++ b/src/feed/TimelineFeed.js
@@ -1,38 +1,21 @@
-import { useContext, useEffect, useState } from "react";
-import { NostrContext } from "..";
+import { useCallback, useMemo } from "react";
import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions";
+import useSubscription from "./Subscription";
export default function useTimelineFeed(pubKeys) {
- const system = useContext(NostrContext);
- const [notes, setNotes] = useState([]);
+ const sub = useMemo(() => {
+ let sub = new Subscriptions();
+ sub.Id = "timeline";
+ sub.Authors = new Set(pubKeys);
+ sub.Kinds.add(EventKind.TextNote);
+ sub.Limit = 10;
- useEffect(() => {
- if (system && pubKeys.length > 0) {
- const sub = new Subscriptions();
- sub.Authors = new Set(pubKeys);
- sub.Kinds.add(EventKind.TextNote);
- sub.Limit = 10;
+ return sub;
+ }, [pubKeys]);
- sub.OnEvent = (e) => {
- setNotes(n => {
- if (Array.isArray(n) && !n.some(a => a.id === e.id)) {
- return [
- ...n,
- e
- ]
- } else {
- return n;
- }
- });
- };
-
- system.AddSubscription(sub);
- return () => {
- system.RemoveSubscription(sub.Id);
- };
- }
- }, [system, pubKeys]);
+ const { notes } = useSubscription(sub, { leaveOpen: true });
+
return { notes };
}
\ No newline at end of file
diff --git a/src/feed/UsersFeed.js b/src/feed/UsersFeed.js
index 1e40e500..5d8bedcf 100644
--- a/src/feed/UsersFeed.js
+++ b/src/feed/UsersFeed.js
@@ -1,6 +1,6 @@
-import { useContext, useEffect, useState } from "react";
+import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { NostrContext } from "..";
+import { System } from "..";
import Event from "../nostr/Event";
import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions";
@@ -8,7 +8,6 @@ import { setUserData } from "../state/Users";
export default function useUsersCache() {
const dispatch = useDispatch();
- const system = useContext(NostrContext);
const pKeys = useSelector(s => s.users.pubKeys);
const users = useSelector(s => s.users.users);
const [loading, setLoading] = useState(false);
@@ -35,15 +34,16 @@ export default function useUsersCache() {
if (needProfiles.length === 0) {
return;
}
- console.debug("Need profiles: ", needProfiles);
+
let sub = new Subscriptions();
+ sub.Id = "profiles";
sub.Authors = new Set(needProfiles);
sub.Kinds.add(EventKind.SetMetadata);
sub.OnEvent = (ev) => {
dispatch(setUserData(mapEventToProfile(ev)));
};
- let events = await system.RequestSubscription(sub);
+ let events = await System.RequestSubscription(sub);
let profiles = events
.filter(a => a.kind === EventKind.SetMetadata)
.map(mapEventToProfile);
@@ -61,14 +61,14 @@ export default function useUsersCache() {
}
useEffect(() => {
- if (system && pKeys.length > 0 && !loading) {
+ if (pKeys.length > 0 && !loading) {
setLoading(true);
getUsers()
.catch(console.error)
.then(() => setLoading(false));
}
- }, [system, pKeys, loading]);
+ }, [pKeys, loading]);
return { users };
}
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index 1f4c9098..ce1f65b1 100644
--- a/src/index.css
+++ b/src/index.css
@@ -145,8 +145,9 @@ div.form-group > div:first-child {
margin-top: 5vh;
}
-.ReactModal__Body--open {
+body.scroll-lock {
overflow: hidden;
+ height: 100vh;
}
.mr10 {
diff --git a/src/index.js b/src/index.js
index 2931746c..51335a19 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,3 @@
-
import './index.css';
import React from 'react';
@@ -17,26 +16,25 @@ import LoginPage from './pages/Login';
import ProfilePage from './pages/ProfilePage';
import RootPage from './pages/Root';
import Store from "./state/Store";
+import NotificationsPage from './pages/Notifications';
-const System = new NostrSystem();
-export const NostrContext = React.createContext();
+export const System = new NostrSystem();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
-
-
-
-
- } />
- } />
- } />
- } />
-
-
-
-
-
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
);
diff --git a/src/nostr/Connection.js b/src/nostr/Connection.js
index 8288fe23..621cae25 100644
--- a/src/nostr/Connection.js
+++ b/src/nostr/Connection.js
@@ -1,5 +1,6 @@
import { Subscriptions } from "./Subscriptions";
import Event from "./Event";
+import * as secp from "@noble/secp256k1";
const DefaultConnectTimeout = 1000;
@@ -16,15 +17,11 @@ export default class Connection {
}
Connect() {
- try {
- this.Socket = new WebSocket(this.Address);
- this.Socket.onopen = (e) => this.OnOpen(e);
- this.Socket.onmessage = (e) => this.OnMessage(e);
- this.Socket.onerror = (e) => this.OnError(e);
- this.Socket.onclose = (e) => this.OnClose(e);
- } catch (e) {
- console.warn(`[${this.Address}] Connect failed!`);
- }
+ this.Socket = new WebSocket(this.Address);
+ this.Socket.onopen = (e) => this.OnOpen(e);
+ this.Socket.onmessage = (e) => this.OnMessage(e);
+ this.Socket.onerror = (e) => this.OnError(e);
+ this.Socket.onclose = (e) => this.OnClose(e);
}
OnOpen(e) {
@@ -82,6 +79,9 @@ export default class Connection {
* @param {Event} e
*/
SendEvent(e) {
+ if (!this.Write) {
+ return;
+ }
let req = ["EVENT", e.ToObject()];
this._SendJson(req);
}
@@ -91,11 +91,20 @@ export default class Connection {
* @param {Subscriptions | Array} sub Subscriptions object
*/
AddSubscription(sub) {
+ if (!this.Read) {
+ return;
+ }
+
let subObj = sub.ToObject();
if (Object.keys(subObj).length === 0) {
debugger;
throw "CANNOT SEND EMPTY SUB - FIX ME";
}
+
+ if (this.Subscriptions[sub.Id]) {
+ return;
+ }
+
let req = ["REQ", sub.Id, subObj];
if (sub.OrSubs.length > 0) {
req = [
@@ -133,7 +142,13 @@ export default class Connection {
_OnEvent(subId, ev) {
if (this.Subscriptions[subId]) {
- this.Subscriptions[subId].OnEvent(ev);
+ this._VerifySig(ev)
+ .then((e) => {
+ if (this.Subscriptions[subId]) {
+ this.Subscriptions[subId].OnEvent(e);
+ }
+ })
+ .catch(console.error);
} else {
console.warn(`No subscription for event! ${subId}`);
}
@@ -148,4 +163,23 @@ export default class Connection {
console.warn(`No subscription for end! ${subId}`);
}
}
+
+ async _VerifySig(ev) {
+ let payload = [
+ 0,
+ ev.pubkey,
+ ev.created_at,
+ ev.kind,
+ ev.tags,
+ ev.content
+ ];
+
+ let payloadData = new TextEncoder().encode(JSON.stringify(payload));
+ let data = await secp.utils.sha256(payloadData);
+ let hash = secp.utils.bytesToHex(data);
+ if (!await secp.schnorr.verify(ev.sig, hash, ev.pubkey)) {
+ throw "Sig verify failed";
+ }
+ return ev;
+ }
}
\ No newline at end of file
diff --git a/src/pages/EventPage.js b/src/pages/EventPage.js
index bfccaa06..187b3e88 100644
--- a/src/pages/EventPage.js
+++ b/src/pages/EventPage.js
@@ -5,7 +5,7 @@ import useThreadFeed from "../feed/ThreadFeed";
export default function EventPage() {
const params = useParams();
const id = params.id;
-
+
const { notes } = useThreadFeed(id);
return ;
}
\ No newline at end of file
diff --git a/src/pages/Layout.js b/src/pages/Layout.js
index 8e791e26..60a3d734 100644
--- a/src/pages/Layout.js
+++ b/src/pages/Layout.js
@@ -1,10 +1,10 @@
import "./Layout.css";
-import { useContext, useEffect } from "react"
+import { useEffect } from "react"
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { faBell } from "@fortawesome/free-solid-svg-icons";
-import { NostrContext } from ".."
+import { System } from ".."
import ProfileImage from "../element/ProfileImage";
import { init } from "../state/Login";
import useLoginFeed from "../feed/LoginFeed";
@@ -13,20 +13,20 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Layout(props) {
const dispatch = useDispatch();
- const system = useContext(NostrContext);
const navigate = useNavigate();
const key = useSelector(s => s.login.publicKey);
const relays = useSelector(s => s.login.relays);
+ const notifications = useSelector(s => s.login.notifications);
useUsersCache();
useLoginFeed();
useEffect(() => {
- if (system && relays) {
+ if (relays) {
for (let [k, v] of Object.entries(relays)) {
- system.ConnectToRelay(k, v);
+ System.ConnectToRelay(k, v);
}
}
- }, [relays, system]);
+ }, [relays]);
useEffect(() => {
dispatch(init());
@@ -35,8 +35,9 @@ export default function Layout(props) {
function accountHeader() {
return (
<>
-
+
navigate("/notifications")}>
+ {notifications?.length ?? 0}
>
diff --git a/src/pages/Notifications.js b/src/pages/Notifications.js
new file mode 100644
index 00000000..d27c1ed8
--- /dev/null
+++ b/src/pages/Notifications.js
@@ -0,0 +1,6 @@
+export default function NotificationsPage() {
+ return (
+ <>
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js
index af4f5ce1..72efae2b 100644
--- a/src/pages/ProfilePage.js
+++ b/src/pages/ProfilePage.js
@@ -13,7 +13,7 @@ import useEventPublisher from "../feed/EventPublisher";
import useTimelineFeed from "../feed/TimelineFeed";
import Note from "../element/Note";
import QRCodeStyling from "qr-code-styling";
-import ReactModal from "react-modal";
+import Modal from "../element/Modal";
export default function ProfilePage() {
const dispatch = useDispatch();
@@ -65,7 +65,7 @@ export default function ProfilePage() {
useEffect(() => {
if (qrRef.current && showLnQr) {
let qr = new QRCodeStyling({
- data: "",
+ data: {lud16},
type: "canvas"
});
qrRef.current.innerHTML = "";
@@ -143,9 +143,10 @@ export default function ProfilePage() {
⚡️ {lud16}
: null}
{showLnQr === true ?
- setShowLnQr(false)} overlayClassName="modal" className="modal-content" preventScroll={true}>
- QR
- : null}
+ setShowLnQr(false)}>
+ {lud16}
+
+ : null}
>
)
}
diff --git a/src/state/Login.js b/src/state/Login.js
index 125927d1..7d902597 100644
--- a/src/state/Login.js
+++ b/src/state/Login.js
@@ -31,6 +31,11 @@ const LoginSlice = createSlice({
* Login keys are managed by extension
*/
nip07: false,
+
+ /**
+ * Notifications for this login session
+ */
+ notifications: []
},
reducers: {
init: (state) => {
@@ -40,7 +45,6 @@ const LoginSlice = createSlice({
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(state.privateKey, true));
}
state.relays = {
- "wss://beta.nostr.v0l.io": { read: true, write: true },
"wss://nostr.v0l.io": { read: true, write: true },
"wss://relay.damus.io": { read: true, write: true },
"wss://nostr-pub.wellorder.net": { read: true, write: true }
@@ -48,7 +52,7 @@ const LoginSlice = createSlice({
// check nip07 pub key
let nip07PubKey = window.localStorage.getItem(Nip07PublicKeyItem);
- if(nip07PubKey && !state.privateKey) {
+ if (nip07PubKey && !state.privateKey) {
state.publicKey = nip07PubKey;
state.nip07 = true;
}
@@ -72,6 +76,21 @@ const LoginSlice = createSlice({
setFollows: (state, action) => {
state.follows = action.payload;
},
+ addNotifications: (state, action) => {
+ let n = action.payload;
+ if (!Array.isArray(n)) {
+ n = [n];
+ }
+
+ for (let x in n) {
+ if (!state.notifications.some(a => a.id === x.id)) {
+ state.notifications.push(x);
+ }
+ }
+ state.notifications = [
+ ...state.notifications
+ ];
+ },
logout: (state) => {
state.privateKey = null;
window.localStorage.removeItem(PrivateKeyItem);
@@ -79,5 +98,5 @@ const LoginSlice = createSlice({
}
});
-export const { init, setPrivateKey, setPublicKey, setNip07PubKey, setRelays, setFollows, logout } = LoginSlice.actions;
+export const { init, setPrivateKey, setPublicKey, setNip07PubKey, setRelays, setFollows, addNotifications, logout } = LoginSlice.actions;
export const reducer = LoginSlice.reducer;
\ No newline at end of file
diff --git a/src/state/Store.js b/src/state/Store.js
index 64bab662..ca83e460 100644
--- a/src/state/Store.js
+++ b/src/state/Store.js
@@ -1,13 +1,11 @@
import { configureStore } from "@reduxjs/toolkit";
import { reducer as UsersReducer } from "./Users";
import { reducer as LoginReducer } from "./Login";
-import { reducer as ThreadReducer } from "./Thread";
const Store = configureStore({
reducer: {
users: UsersReducer,
- login: LoginReducer,
- thread: ThreadReducer
+ login: LoginReducer
}
});
diff --git a/src/state/Thread.js b/src/state/Thread.js
deleted file mode 100644
index 2f6504b9..00000000
--- a/src/state/Thread.js
+++ /dev/null
@@ -1,26 +0,0 @@
-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
diff --git a/yarn.lock b/yarn.lock
index 50c5a571..0ca22cec 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4124,11 +4124,6 @@ execa@^5.0.0:
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
-exenv@^1.2.0:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
- integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==
-
exit@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -5843,7 +5838,7 @@ lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -7035,7 +7030,7 @@ prompts@^2.0.1, prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7200,21 +7195,6 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
-react-lifecycles-compat@^3.0.0:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
- integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
-
-react-modal@^3.16.1:
- version "3.16.1"
- resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.16.1.tgz#34018528fc206561b1a5467fc3beeaddafb39b2b"
- integrity sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==
- dependencies:
- exenv "^1.2.0"
- prop-types "^15.7.2"
- react-lifecycles-compat "^3.0.0"
- warning "^4.0.3"
-
react-redux@^8.0.5:
version "8.0.5"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
@@ -8511,13 +8491,6 @@ walker@^1.0.7:
dependencies:
makeerror "1.0.12"
-warning@^4.0.3:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
- integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
- dependencies:
- loose-envify "^1.0.0"
-
watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"