1
0
forked from Kieran/snort

Convert remaining pages

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

View File

@ -1,29 +1,32 @@
import { useMemo } from "react";
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 { eventLink } from "../Util";
import Note from "./Note";
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;
/** @type {Array<Event>} */
const notes = props.notes?.map(a => new Event(a));
const notes = props.notes?.map(a => new NEvent(a));
// 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(() => {
let chains = new Map();
notes.filter(a => a.Kind === EventKind.TextNote).sort((a, b) => b.CreatedAt - a.CreatedAt).forEach((v) => {
let chains = new Map<u256, NEvent[]>();
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;
if (replyTo) {
if (!chains.has(replyTo)) {
chains.set(replyTo, [v]);
} else {
chains.get(replyTo).push(v);
chains.get(replyTo)!.push(v);
}
} else if (v.Tags.length > 0) {
console.log("Not replying to anything: ", v);
@ -34,15 +37,15 @@ export default function Thread(props) {
}, [notes]);
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]);
const mentionsRoot = useMemo(() => {
return notes.filter(a => a.Kind === EventKind.TextNote && a.Thread)
return notes?.filter(a => a.Kind === EventKind.TextNote && a.Thread)
}, [chains]);
function reactions(id, kind = EventKind.Reaction) {
return notes?.filter(a => a.Kind === kind && a.Tags.find(a => a.Key === "e" && a.Event === id));
function reactions(id: u256, kind = EventKind.Reaction) {
return (notes?.filter(a => a.Kind === kind && a.Tags.find(a => a.Key === "e" && a.Event === id)) || []).map(a => a.Original!);
}
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 />
} else {
return <NoteGhost>
Loading thread root.. ({notes.length} notes loaded)
Loading thread root.. ({notes?.length} notes loaded)
</NoteGhost>
}
}
function renderChain(from) {
function renderChain(from: u256) {
if (from && chains) {
let replies = chains.get(from);
if (replies) {
@ -64,7 +67,11 @@ export default function Thread(props) {
{replies.map(a => {
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)}
</>
)

View File

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

View File

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

View File

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

View File

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

View File

@ -3,14 +3,16 @@ import { useDispatch, useSelector } from "react-redux"
import Note from "../element/Note";
import NoteReaction from "../element/NoteReaction";
import useSubscription from "../feed/Subscription";
import { TaggedRawEvent } from "../nostr";
import Event from "../nostr/Event";
import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions";
import { markNotificationsRead } from "../state/Login";
import { RootState } from "../state/Store";
export default function NotificationsPage() {
const dispatch = useDispatch();
const notifications = useSelector(s => s.login.notifications);
const notifications = useSelector<RootState, TaggedRawEvent[]>(s => s.login.notifications);
useEffect(() => {
dispatch(markNotificationsRead());
@ -21,7 +23,7 @@ export default function NotificationsPage() {
.map(a => {
let ev = new Event(a);
return ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event;
})
}).filter(a => a !== undefined).map(a => a!);
}, [notifications]);
const subEvents = useMemo(() => {
@ -50,7 +52,7 @@ export default function NotificationsPage() {
{sorted?.map(a => {
if (a.kind === EventKind.TextNote) {
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) {
let ev = new Event(a);
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 Timeline from "../element/Timeline";
import { useState } from "react";
import useScroll from "../useScroll";
import { RootState } from "../state/Store";
import { HexKey } from "../nostr";
const RootTab = {
Follows: 0,
@ -12,9 +13,8 @@ const RootTab = {
};
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 [eop] = useScroll();
function followHints() {
if (follows?.length === 0 && pubKey && tab !== RootTab.Global) {
@ -27,7 +27,7 @@ export default function RootPage() {
return (
<>
{pubKey ? <>
<NoteCreator show={true}/>
<NoteCreator show={true} autoFocus={false} />
<div className="tabs root-tabs">
<div className={`root-tab f-1 ${tab === RootTab.Follows ? "active" : ""}`} onClick={() => setTab(RootTab.Follows)}>
Follows