Convert remaining pages

This commit is contained in:
2023-01-16 18:14:08 +00:00
parent 8aed2c5550
commit 6bfecd9d07
7 changed files with 50 additions and 38 deletions

View File

@ -1,29 +1,32 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import Event from "../nostr/Event"; import { TaggedRawEvent, u256 } from "../nostr";
import { default as NEvent } from "../nostr/Event";
import EventKind from "../nostr/EventKind"; import EventKind from "../nostr/EventKind";
import { eventLink } from "../Util"; import { eventLink } from "../Util";
import Note from "./Note"; import Note from "./Note";
import NoteGhost from "./NoteGhost"; import NoteGhost from "./NoteGhost";
export default function Thread(props) { export interface ThreadProps {
this?: u256,
notes?: TaggedRawEvent[]
}
export default function Thread(props: ThreadProps) {
const thisEvent = props.this; const thisEvent = props.this;
const notes = props.notes?.map(a => new NEvent(a));
/** @type {Array<Event>} */
const notes = props.notes?.map(a => new Event(a));
// root note has no thread info // root note has no thread info
const root = useMemo(() => notes.find(a => a.Thread === null), [notes]); const root = useMemo(() => notes?.find(a => a.Thread === null), [notes]);
const chains = useMemo(() => { const chains = useMemo(() => {
let chains = new Map(); let chains = new Map<u256, NEvent[]>();
notes.filter(a => a.Kind === EventKind.TextNote).sort((a, b) => b.CreatedAt - a.CreatedAt).forEach((v) => { notes?.filter(a => a.Kind === EventKind.TextNote).sort((a, b) => b.CreatedAt - a.CreatedAt).forEach((v) => {
let replyTo = v.Thread?.ReplyTo?.Event ?? v.Thread?.Root?.Event; let replyTo = v.Thread?.ReplyTo?.Event ?? v.Thread?.Root?.Event;
if (replyTo) { if (replyTo) {
if (!chains.has(replyTo)) { if (!chains.has(replyTo)) {
chains.set(replyTo, [v]); chains.set(replyTo, [v]);
} else { } else {
chains.get(replyTo).push(v); chains.get(replyTo)!.push(v);
} }
} else if (v.Tags.length > 0) { } else if (v.Tags.length > 0) {
console.log("Not replying to anything: ", v); console.log("Not replying to anything: ", v);
@ -34,15 +37,15 @@ export default function Thread(props) {
}, [notes]); }, [notes]);
const brokenChains = useMemo(() => { const brokenChains = useMemo(() => {
return Array.from(chains?.keys()).filter(a => !notes.some(b => b.Id === a)); return Array.from(chains?.keys()).filter(a => !notes?.some(b => b.Id === a));
}, [chains]); }, [chains]);
const mentionsRoot = useMemo(() => { const mentionsRoot = useMemo(() => {
return notes.filter(a => a.Kind === EventKind.TextNote && a.Thread) return notes?.filter(a => a.Kind === EventKind.TextNote && a.Thread)
}, [chains]); }, [chains]);
function reactions(id, kind = EventKind.Reaction) { function reactions(id: u256, kind = EventKind.Reaction) {
return notes?.filter(a => a.Kind === kind && a.Tags.find(a => a.Key === "e" && a.Event === id)); return (notes?.filter(a => a.Kind === kind && a.Tags.find(a => a.Key === "e" && a.Event === id)) || []).map(a => a.Original!);
} }
function renderRoot() { function renderRoot() {
@ -50,12 +53,12 @@ export default function Thread(props) {
return <Note data-ev={root} reactions={reactions(root.Id)} deletion={reactions(root.Id, EventKind.Deletion)} isThread /> return <Note data-ev={root} reactions={reactions(root.Id)} deletion={reactions(root.Id, EventKind.Deletion)} isThread />
} else { } else {
return <NoteGhost> return <NoteGhost>
Loading thread root.. ({notes.length} notes loaded) Loading thread root.. ({notes?.length} notes loaded)
</NoteGhost> </NoteGhost>
} }
} }
function renderChain(from) { function renderChain(from: u256) {
if (from && chains) { if (from && chains) {
let replies = chains.get(from); let replies = chains.get(from);
if (replies) { if (replies) {
@ -64,7 +67,11 @@ export default function Thread(props) {
{replies.map(a => { {replies.map(a => {
return ( return (
<> <>
<Note data-ev={a} key={a.Id} reactions={reactions(a.Id)} deletion={reactions(a.Id, EventKind.Deletion)} hightlight={thisEvent === a.Id} /> <Note data-ev={a}
key={a.Id}
reactions={reactions(a.Id)}
deletion={reactions(a.Id, EventKind.Deletion)}
highlight={thisEvent === a.Id} />
{renderChain(a.Id)} {renderChain(a.Id)}
</> </>
) )

View File

@ -6,8 +6,7 @@ import { parseId } from "../Util";
export default function EventPage() { export default function EventPage() {
const params = useParams(); const params = useParams();
const id = parseId(params.id); const id = parseId(params.id!);
const thread = useThreadFeed(id); const thread = useThreadFeed(id);
const filtered = useMemo(() => { const filtered = useMemo(() => {

View File

@ -9,15 +9,18 @@ import { System } from "../nostr/System"
import ProfileImage from "../element/ProfileImage"; import ProfileImage from "../element/ProfileImage";
import { init } from "../state/Login"; import { init } from "../state/Login";
import useLoginFeed from "../feed/LoginFeed"; import useLoginFeed from "../feed/LoginFeed";
import { RootState } from "../state/Store";
import { HexKey, TaggedRawEvent } from "../nostr";
import { RelaySettings } from "../nostr/Connection";
export default function Layout(props) { export default function Layout() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const isInit = useSelector(s => s.login.loggedOut); const isInit = useSelector<RootState, boolean | undefined>(s => s.login.loggedOut);
const key = useSelector(s => s.login.publicKey); const key = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const relays = useSelector(s => s.login.relays); const relays = useSelector<RootState, Record<string, RelaySettings>>(s => s.login.relays);
const notifications = useSelector(s => s.login.notifications); const notifications = useSelector<RootState, TaggedRawEvent[]>(s => s.login.notifications);
const readNotifications = useSelector(s => s.login.readNotifications); const readNotifications = useSelector<RootState, number>(s => s.login.readNotifications);
useLoginFeed(); useLoginFeed();
useEffect(() => { useEffect(() => {
@ -37,7 +40,7 @@ export default function Layout(props) {
dispatch(init()); dispatch(init());
}, []); }, []);
async function goToNotifications(e) { async function goToNotifications(e: any) {
e.stopPropagation(); e.stopPropagation();
// request permissions to send notifications // request permissions to send notifications
if ("Notification" in window && Notification.permission !== "granted") { if ("Notification" in window && Notification.permission !== "granted") {
@ -64,7 +67,7 @@ export default function Layout(props) {
{unreadNotifications > 0 && (<span className="unread-count"> {unreadNotifications > 0 && (<span className="unread-count">
{unreadNotifications > 100 ? ">99" : unreadNotifications} {unreadNotifications > 100 ? ">99" : unreadNotifications}
</span>)} </span>)}
<ProfileImage pubkey={key} showUsername={false} /> <ProfileImage pubkey={key || ""} showUsername={false} />
</> </>
) )
} }
@ -84,7 +87,7 @@ export default function Layout(props) {
</div> </div>
</div> </div>
<Outlet/> <Outlet />
</div> </div>
) )
} }

View File

@ -6,11 +6,13 @@ import * as secp from '@noble/secp256k1';
import { setPrivateKey, setPublicKey } from "../state/Login"; import { setPrivateKey, setPublicKey } from "../state/Login";
import { EmailRegex } from "../Const"; import { EmailRegex } from "../Const";
import { bech32ToHex } from "../Util"; import { bech32ToHex } from "../Util";
import { RootState } from "../state/Store";
import { HexKey } from "../nostr";
export default function LoginPage() { export default function LoginPage() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const publicKey = useSelector(s => s.login.publicKey); const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const [key, setKey] = useState(""); const [key, setKey] = useState("");
const [error, setError] = useState(""); const [error, setError] = useState("");
@ -20,7 +22,7 @@ export default function LoginPage() {
} }
}, [publicKey]); }, [publicKey]);
async function getNip05PubKey(addr) { async function getNip05PubKey(addr: string) {
let [username, domain] = addr.split("@"); let [username, domain] = addr.split("@");
let rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`); let rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);
if (rsp.ok) { if (rsp.ok) {

View File

@ -1,8 +1,7 @@
import { Outlet } from "react-router-dom";
import { RecommendedFollows } from "../Const"; import { RecommendedFollows } from "../Const";
import ProfilePreview from "../element/ProfilePreview"; import ProfilePreview from "../element/ProfilePreview";
export default function NewUserPage(props) { export default function NewUserPage() {
function followSomebody() { function followSomebody() {
return ( return (

View File

@ -3,14 +3,16 @@ import { useDispatch, useSelector } from "react-redux"
import Note from "../element/Note"; import Note from "../element/Note";
import NoteReaction from "../element/NoteReaction"; import NoteReaction from "../element/NoteReaction";
import useSubscription from "../feed/Subscription"; import useSubscription from "../feed/Subscription";
import { TaggedRawEvent } from "../nostr";
import Event from "../nostr/Event"; import Event from "../nostr/Event";
import EventKind from "../nostr/EventKind"; import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions"; import { Subscriptions } from "../nostr/Subscriptions";
import { markNotificationsRead } from "../state/Login"; import { markNotificationsRead } from "../state/Login";
import { RootState } from "../state/Store";
export default function NotificationsPage() { export default function NotificationsPage() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const notifications = useSelector(s => s.login.notifications); const notifications = useSelector<RootState, TaggedRawEvent[]>(s => s.login.notifications);
useEffect(() => { useEffect(() => {
dispatch(markNotificationsRead()); dispatch(markNotificationsRead());
@ -21,7 +23,7 @@ export default function NotificationsPage() {
.map(a => { .map(a => {
let ev = new Event(a); let ev = new Event(a);
return ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event; return ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event;
}) }).filter(a => a !== undefined).map(a => a!);
}, [notifications]); }, [notifications]);
const subEvents = useMemo(() => { const subEvents = useMemo(() => {
@ -50,7 +52,7 @@ export default function NotificationsPage() {
{sorted?.map(a => { {sorted?.map(a => {
if (a.kind === EventKind.TextNote) { if (a.kind === EventKind.TextNote) {
let reactions = otherNotes?.notes?.filter(c => c.tags.find(b => b[0] === "e" && b[1] === a.id)); let reactions = otherNotes?.notes?.filter(c => c.tags.find(b => b[0] === "e" && b[1] === a.id));
return <Note data={a} key={a.id} reactions={reactions} /> return <Note data={a} key={a.id} reactions={reactions} deletion={[]}/>
} else if (a.kind === EventKind.Reaction) { } else if (a.kind === EventKind.Reaction) {
let ev = new Event(a); let ev = new Event(a);
let reactedTo = ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event; let reactedTo = ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event;

View File

@ -4,7 +4,8 @@ import { Link } from "react-router-dom";
import { NoteCreator } from "../element/NoteCreator"; import { NoteCreator } from "../element/NoteCreator";
import Timeline from "../element/Timeline"; import Timeline from "../element/Timeline";
import { useState } from "react"; import { useState } from "react";
import useScroll from "../useScroll"; import { RootState } from "../state/Store";
import { HexKey } from "../nostr";
const RootTab = { const RootTab = {
Follows: 0, Follows: 0,
@ -12,9 +13,8 @@ const RootTab = {
}; };
export default function RootPage() { export default function RootPage() {
const [loggedOut, pubKey, follows] = useSelector(s => [s.login.loggedOut, s.login.publicKey, s.login.follows]); const [loggedOut, pubKey, follows] = useSelector<RootState, [boolean | undefined, HexKey | undefined, HexKey[]]>(s => [s.login.loggedOut, s.login.publicKey, s.login.follows]);
const [tab, setTab] = useState(RootTab.Follows); const [tab, setTab] = useState(RootTab.Follows);
const [eop] = useScroll();
function followHints() { function followHints() {
if (follows?.length === 0 && pubKey && tab !== RootTab.Global) { if (follows?.length === 0 && pubKey && tab !== RootTab.Global) {
@ -27,7 +27,7 @@ export default function RootPage() {
return ( return (
<> <>
{pubKey ? <> {pubKey ? <>
<NoteCreator show={true}/> <NoteCreator show={true} autoFocus={false} />
<div className="tabs root-tabs"> <div className="tabs root-tabs">
<div className={`root-tab f-1 ${tab === RootTab.Follows ? "active" : ""}`} onClick={() => setTab(RootTab.Follows)}> <div className={`root-tab f-1 ${tab === RootTab.Follows ? "active" : ""}`} onClick={() => setTab(RootTab.Follows)}>
Follows Follows