add support for nevent and nprofile in note and user view

This commit is contained in:
hzrd149 2023-04-13 15:07:41 -05:00
parent 2f06d27819
commit 80eda9145a
7 changed files with 64 additions and 71 deletions

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add support for nprofile and nevent types in paths

@ -1,7 +1,7 @@
import React, { Suspense, useEffect } from "react";
import { createBrowserRouter, Navigate, Outlet, RouterProvider, useLocation } from "react-router-dom";
import { Button, Flex, Spinner, Text, useColorMode } from "@chakra-ui/react";
import { ErrorBoundary } from "./components/error-boundary";
import { ErrorBoundary, ErrorFallback } from "./components/error-boundary";
import { Page } from "./components/page";
import { normalizeToHex } from "./helpers/nip19";
import { deleteDatabase } from "./services/db";
@ -87,12 +87,6 @@ const router = createBrowserRouter([
children: [
{
path: "/u/:pubkey",
loader: ({ params }) => {
if (!params.pubkey) throw new Error("Missing pubkey");
const hexKey = normalizeToHex(params.pubkey);
if (!hexKey) throw new Error(params.pubkey + " is not a valid pubkey");
return { pubkey: hexKey };
},
element: <UserView />,
children: [
{ path: "", element: <UserNotesTab /> },
@ -106,12 +100,6 @@ const router = createBrowserRouter([
},
{
path: "/n/:id",
loader: ({ params }) => {
if (!params.id) throw new Error("Missing pubkey");
const hex = normalizeToHex(params.id);
if (!hex) throw new Error(params.id + " is not a valid event id");
return { id: hex };
},
element: <NoteView />,
},
{ path: "settings", element: <SettingsView /> },

@ -9,7 +9,6 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Toast,
useDisclosure,
useToast,
} from "@chakra-ui/react";

@ -9,8 +9,8 @@ type Options = {
enabled?: boolean;
};
export function useThreadLoader(eventId: string, opts?: Options) {
const relays = useReadRelayUrls();
export function useThreadLoader(eventId: string, additionalRelays: string[] = [], opts?: Options) {
const relays = useReadRelayUrls(additionalRelays);
const ref = useRef<ThreadLoader | null>(null);
const loader = (ref.current = ref.current || new ThreadLoader(relays, eventId));

@ -1,41 +1,6 @@
import { Alert, AlertIcon, AlertTitle, Spinner } from "@chakra-ui/react";
import { Alert, AlertIcon, AlertTitle } from "@chakra-ui/react";
import { Navigate, useParams } from "react-router-dom";
import { Kind, nip19 } from "nostr-tools";
import { useUserMetadata } from "../../hooks/use-user-metadata";
import useSingleEvent from "../../hooks/use-single-event";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import { EventPointer, ProfilePointer } from "nostr-tools/lib/nip19";
export function NpubLinkHandler({ pubkey, relays }: { pubkey: string; relays?: string[] }) {
const readRelays = useReadRelayUrls(relays);
const metadata = useUserMetadata(pubkey, readRelays);
if (!metadata) return <Spinner />;
return <Navigate to={`/u/${pubkey}`} replace />;
}
export function NoteLinkHandler({ eventId, relays }: { eventId: string; relays?: string[] }) {
const readRelays = useReadRelayUrls(relays);
const { event, loading } = useSingleEvent(eventId, readRelays);
if (loading) return <Spinner />;
if (!event)
return (
<Alert status="error">
<AlertIcon />
<AlertTitle>Failed to find event</AlertTitle>
</Alert>
);
if (event.kind !== Kind.Text && event.kind !== 6)
return (
<Alert status="error">
<AlertIcon />
<AlertTitle>Cant handle event kind {event.kind}</AlertTitle>
</Alert>
);
return <Navigate to={`/n/${eventId}`} replace />;
}
import { nip19 } from "nostr-tools";
export default function NostrLinkView() {
const { link } = useParams() as { link?: string };
@ -51,15 +16,13 @@ export default function NostrLinkView() {
const cleanLink = link.replace(/(web\+)?nostr:/, "");
const decoded = nip19.decode(cleanLink);
if (decoded.type === "npub") return <NpubLinkHandler pubkey={decoded.data as string} />;
if (decoded.type === "nprofile") {
const data = decoded.data as ProfilePointer;
return <NpubLinkHandler pubkey={data.pubkey} relays={data.relays} />;
}
if (decoded.type === "note") return <NoteLinkHandler eventId={decoded.data as string} />;
if (decoded.type === "nevent") {
const data = decoded.data as EventPointer;
return <NoteLinkHandler eventId={data.id} relays={data.relays} />;
switch (decoded.type) {
case "npub":
case "nprofile":
return <Navigate to={`/u/${cleanLink}`} replace />;
case "note":
case "nevent":
return <Navigate to={`/n/${cleanLink}`} replace />;
}
return (

@ -1,13 +1,33 @@
import { Flex, Spinner } from "@chakra-ui/react";
import { useLoaderData } from "react-router-dom";
import { nip19 } from "nostr-tools";
import { useParams } from "react-router-dom";
import { Note } from "../../components/note";
import { isHex } from "../../helpers/nip19";
import { useThreadLoader } from "../../hooks/use-thread-loader";
import { ThreadPost } from "./thread-post";
const NoteView = () => {
const { id } = useLoaderData() as { id: string };
function useNotePointer() {
const { id } = useParams() as { id: string };
if (isHex(id)) return { id, relays: [] };
const pointer = nip19.decode(id);
const { thread, events, rootId, focusId, loading } = useThreadLoader(id, { enabled: !!id });
switch (pointer.type) {
case "note":
return { id: pointer.data as string, relays: [] };
case "nevent":
const p = pointer.data as nip19.EventPointer;
return { id: p.id, relays: p.relays ?? [] };
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}
const NoteView = () => {
const pointer = useNotePointer();
const { thread, events, rootId, focusId, loading } = useThreadLoader(pointer.id, pointer.relays, {
enabled: !!pointer.id,
});
if (loading) return <Spinner />;
let pageContent = <span>Missing Event</span>;

@ -1,9 +1,9 @@
import { Flex, Image, Spinner, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
import { Outlet, useLoaderData, useMatches, useNavigate } from "react-router-dom";
import { Flex, Spinner, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
import { Outlet, useMatches, useNavigate, useParams } from "react-router-dom";
import { useUserMetadata } from "../../hooks/use-user-metadata";
import { getUserDisplayName } from "../../helpers/user-metadata";
import { useIsMobile } from "../../hooks/use-is-mobile";
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19";
import { Bech32Prefix, isHex, normalizeToBech32 } from "../../helpers/nip19";
import { useAppTitle } from "../../hooks/use-app-title";
import Header from "./components/header";
import { Suspense } from "react";
@ -12,6 +12,8 @@ import { useReadRelayUrls } from "../../hooks/use-client-relays";
import relayScoreboardService from "../../services/relay-scoreboard";
import { RelayMode } from "../../classes/relay";
import { AdditionalRelayProvider } from "../../providers/additional-relay-context";
import { nip19 } from "nostr-tools";
import { unique } from "../../helpers/array";
const tabs = [
{ label: "Notes", path: "notes" },
@ -22,6 +24,22 @@ const tabs = [
{ label: "Reports", path: "reports" },
];
function useUserPointer() {
const { pubkey } = useParams() as { pubkey: string };
if (isHex(pubkey)) return { pubkey, relays: [] };
const pointer = nip19.decode(pubkey);
switch (pointer.type) {
case "npub":
return { pubkey: pointer.data as string, relays: [] };
case "nprofile":
const d = pointer.data as nip19.ProfilePointer;
return { pubkey: d.pubkey, relays: d.relays ?? [] };
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}
function useUserTop4Relays(pubkey: string) {
// get user relays
const userRelays = useFallbackUserRelays(pubkey)
@ -34,9 +52,9 @@ function useUserTop4Relays(pubkey: string) {
}
const UserView = () => {
const { pubkey, relays: pointerRelays } = useUserPointer();
const isMobile = useIsMobile();
const navigate = useNavigate();
const { pubkey } = useLoaderData() as { pubkey: string };
const userTopRelays = useUserTop4Relays(pubkey);
const matches = useMatches();
@ -50,7 +68,7 @@ const UserView = () => {
useAppTitle(getUserDisplayName(metadata, npub ?? pubkey));
return (
<AdditionalRelayProvider relays={userTopRelays}>
<AdditionalRelayProvider relays={unique([...userTopRelays, ...pointerRelays])}>
<Flex direction="column" alignItems="stretch" gap="2" overflow={isMobile ? "auto" : "hidden"} height="100%">
{/* {metadata?.banner && <Image src={metadata.banner} mb={-120} />} */}
<Header pubkey={pubkey} />