Convert remaining pages
This commit is contained in:
@ -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)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
@ -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(() => {
|
@ -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} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -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) {
|
@ -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 (
|
@ -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;
|
@ -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
|
Reference in New Issue
Block a user