show notes in relay view

This commit is contained in:
hzrd149 2023-08-12 15:22:24 -05:00
parent e0529916bb
commit 03d84eb1cd
12 changed files with 229 additions and 175 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Show notes in relay view

View File

@ -1,5 +1,5 @@
import { useCallback } from "react";
import { Flex, SimpleGrid } from "@chakra-ui/react";
import { Flex, FlexProps, SimpleGrid } from "@chakra-ui/react";
import IntersectionObserverProvider from "../../providers/intersection-observer";
import GenericNoteTimeline from "./generic-note-timeline";
import { ImageGalleryProvider } from "../image-gallery";
@ -27,7 +27,11 @@ export function useTimelinePageEventFilter() {
export type TimelineViewType = "timeline" | "images" | "health";
export default function TimelinePage({ timeline, header }: { timeline: TimelineLoader; header?: React.ReactNode }) {
export default function TimelinePage({
timeline,
header,
...props
}: { timeline: TimelineLoader; header?: React.ReactNode } & Omit<FlexProps, "children" | "direction" | "gap">) {
const callback = useTimelineCurserIntersectionCallback(timeline);
const [params, setParams] = useSearchParams();
@ -55,7 +59,7 @@ export default function TimelinePage({ timeline, header }: { timeline: TimelineL
};
return (
<IntersectionObserverProvider<string> callback={callback}>
<Flex direction="column" gap="2" pt="4" pb="8">
<Flex direction="column" gap="2" {...props}>
{header}
{renderTimeline()}
<TimelineActionAndStatus timeline={timeline} />

View File

@ -100,7 +100,7 @@ function HashTagPage() {
</Flex>
);
return <TimelinePage timeline={timeline} header={header} />;
return <TimelinePage timeline={timeline} header={header} pt="4" pb="8" />;
}
export default function HashTagView() {

View File

@ -52,7 +52,7 @@ function FollowingTabBody() {
</Flex>
);
return <TimelinePage timeline={timeline} header={header} />;
return <TimelinePage timeline={timeline} header={header} pt="4" pb="8" />;
}
export default function FollowingTab() {

View File

@ -42,7 +42,7 @@ function GlobalPage() {
</Flex>
);
return <TimelinePage timeline={timeline} header={header} />;
return <TimelinePage timeline={timeline} header={header} pt="4" pb="8" />;
}
export default function GlobalTab() {

View File

@ -178,16 +178,7 @@ export default function RelayCard({ url, ...props }: { url: string } & Omit<Card
<RelayJoinAction url={url} size="sm" />
<RelayShareButton relay={url} ml="auto" size="sm" />
<RelayDebugButton url={url} size="sm" />
<Button
as="a"
href={`https://nostr.watch/relay/${new URL(url).host}`}
target="_blank"
rightIcon={<ExternalLinkIcon />}
size="sm"
>
More
</Button>
<RelayDebugButton url={url} size="sm" title="Show raw NIP-11 metadata" />
</CardFooter>
</Card>
</>

View File

@ -1,145 +0,0 @@
import { useParams } from "react-router-dom";
import {
Button,
Flex,
Heading,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Textarea,
useDisclosure,
} from "@chakra-ui/react";
import { safeRelayUrl } from "../../helpers/url";
import { useRelayInfo } from "../../hooks/use-relay-info";
import { RelayDebugButton, RelayJoinAction, RelayMetadata } from "./components/relay-card";
import useSubject from "../../hooks/use-subject";
import { useReadRelayUrls, useWriteRelayUrls } from "../../hooks/use-client-relays";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import RelayReviewNote from "./components/relay-review-note";
import SupportedNIPs from "./components/supported-nips";
import { useForm } from "react-hook-form";
import StarRating from "../../components/star-rating";
import { DraftNostrEvent } from "../../types/nostr-event";
import { RELAY_REVIEW_LABEL, RELAY_REVIEW_LABEL_NAMESPACE, REVIEW_KIND } from "../../helpers/nostr/reviews";
import dayjs from "dayjs";
import { useSigningContext } from "../../providers/signing-provider";
import { nostrPostAction } from "../../classes/nostr-post-action";
function RelayReviews({ relay }: { relay: string }) {
const readRelays = useReadRelayUrls();
const timeline = useTimelineLoader(`${relay}-reviews`, readRelays, {
kinds: [1985],
"#r": [relay],
"#l": [RELAY_REVIEW_LABEL],
});
const events = useSubject(timeline.timeline);
return (
<Flex direction="column" gap="2">
{events.map((event) => (
<RelayReviewNote key={event.id} event={event} hideUrl />
))}
</Flex>
);
}
function RelayReviewForm({ onClose, relay }: { onClose: () => void; relay: string }) {
const { requestSignature } = useSigningContext();
const writeRelays = useWriteRelayUrls();
const { register, getValues, watch, handleSubmit, setValue } = useForm({
defaultValues: {
quality: 0.6,
content: "",
},
});
watch("quality");
const onSubmit = handleSubmit(async (values) => {
const draft: DraftNostrEvent = {
kind: REVIEW_KIND,
content: values.content,
tags: [
["l", RELAY_REVIEW_LABEL, new URL(relay).host, JSON.stringify({ quality: values.quality })],
["L", RELAY_REVIEW_LABEL_NAMESPACE],
["r", relay],
],
created_at: dayjs().unix(),
};
const signed = await requestSignature(draft);
if (!signed) return;
nostrPostAction(writeRelays, signed);
onClose();
});
return (
<Flex as="form" direction="column" onSubmit={onSubmit} gap="2" mb="2">
<Flex gap="2">
<Heading size="md">Write review</Heading>
<StarRating quality={getValues().quality} fontSize="1.5rem" onChange={(q) => setValue("quality", q)} />
</Flex>
<Textarea {...register("content")} rows={5} placeholder="A short description of your experience with the relay" />
<Flex gap="2" ml="auto">
<Button onClick={onClose}>Cancel</Button>
<Button type="submit" colorScheme="brand">
Submit
</Button>
</Flex>
</Flex>
);
}
function RelayPage({ relay }: { relay: string }) {
const { info } = useRelayInfo(relay);
const showReviewForm = useDisclosure();
return (
<Flex direction="column" alignItems="stretch" gap="2" p="2">
<Flex gap="2" alignItems="center">
<Heading isTruncated size={{ base: "md", sm: "lg" }}>
{relay}
</Heading>
<RelayDebugButton url={relay} ml="auto" />
<RelayJoinAction url={relay} />
</Flex>
<RelayMetadata url={relay} />
{info?.supported_nips && <SupportedNIPs nips={info?.supported_nips} />}
<Tabs display="flex" flexDirection="column" flexGrow="1" isLazy colorScheme="brand">
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
<Tab>Reviews</Tab>
<Tab isDisabled>Notes</Tab>
</TabList>
<TabPanels>
<TabPanel py="2" px="0">
{showReviewForm.isOpen ? (
<RelayReviewForm onClose={showReviewForm.onClose} relay={relay} />
) : (
<Button colorScheme="brand" ml="aut" mb="2" onClick={showReviewForm.onOpen}>
Write review
</Button>
)}
<RelayReviews relay={relay} />
</TabPanel>
<TabPanel py="2" px="0"></TabPanel>
</TabPanels>
</Tabs>
</Flex>
);
}
export default function RelayView() {
const { relay } = useParams<string>();
if (!relay) return <>No relay url</>;
const safeUrl = safeRelayUrl(relay);
if (!safeUrl) return <>Bad relay url</>;
return <RelayPage relay={safeUrl} />;
}

View File

@ -0,0 +1,84 @@
import { useParams } from "react-router-dom";
import {
Button,
ButtonGroup,
Flex,
Heading,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
useDisclosure,
} from "@chakra-ui/react";
import { safeRelayUrl } from "../../../helpers/url";
import { useRelayInfo } from "../../../hooks/use-relay-info";
import { RelayDebugButton, RelayJoinAction, RelayMetadata } from "../components/relay-card";
import SupportedNIPs from "../components/supported-nips";
import { ExternalLinkIcon } from "../../../components/icons";
import RelayReviewForm from "./relay-review-form";
import RelayReviews from "./relay-reviews";
import RelayNotes from "./relay-notes";
function RelayPage({ relay }: { relay: string }) {
const { info } = useRelayInfo(relay);
const showReviewForm = useDisclosure();
return (
<Flex direction="column" alignItems="stretch" gap="2" p="2">
<Flex gap="2" alignItems="center" wrap="wrap" justifyContent="space-between">
<Heading isTruncated size={{ base: "md", sm: "lg" }}>
{relay}
</Heading>
<ButtonGroup size={["sm", "md"]}>
<RelayDebugButton url={relay} ml="auto" />
<Button
as="a"
href={`https://nostr.watch/relay/${new URL(relay).host}`}
target="_blank"
rightIcon={<ExternalLinkIcon />}
>
More info
</Button>
<RelayJoinAction url={relay} />
</ButtonGroup>
</Flex>
<RelayMetadata url={relay} />
{info?.supported_nips && <SupportedNIPs nips={info?.supported_nips} />}
<Tabs display="flex" flexDirection="column" flexGrow="1" isLazy colorScheme="brand">
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
<Tab>Reviews</Tab>
<Tab>Notes</Tab>
</TabList>
<TabPanels>
<TabPanel py="2" px="0">
{showReviewForm.isOpen ? (
<RelayReviewForm onClose={showReviewForm.onClose} relay={relay} />
) : (
<Button colorScheme="brand" ml="aut" mb="2" onClick={showReviewForm.onOpen}>
Write review
</Button>
)}
<RelayReviews relay={relay} />
</TabPanel>
<TabPanel py="2" px="0">
<RelayNotes relay={relay} />
</TabPanel>
</TabPanels>
</Tabs>
</Flex>
);
}
export default function RelayView() {
const { relay } = useParams<string>();
if (!relay) return <>No relay url</>;
const safeUrl = safeRelayUrl(relay);
if (!safeUrl) return <>Bad relay url</>;
return <RelayPage relay={safeUrl} />;
}

View File

@ -0,0 +1,36 @@
import { useCallback } from "react";
import { Flex, Switch, useDisclosure } from "@chakra-ui/react";
import { isReply } from "../../../helpers/nostr/event";
import { useAppTitle } from "../../../hooks/use-app-title";
import useTimelineLoader from "../../../hooks/use-timeline-loader";
import { NostrEvent } from "../../../types/nostr-event";
import TimelinePage, { useTimelinePageEventFilter } from "../../../components/timeline-page";
import TimelineViewTypeButtons from "../../../components/timeline-page/timeline-view-type";
export default function RelayNotes({ relay }: { relay: string }) {
useAppTitle(`${relay} - Notes`);
const showReplies = useDisclosure();
const timelineEventFilter = useTimelinePageEventFilter();
const eventFilter = useCallback(
(event: NostrEvent) => {
if (!showReplies.isOpen && isReply(event)) return false;
return timelineEventFilter(event);
},
[showReplies.isOpen, timelineEventFilter]
);
const timeline = useTimelineLoader(`${relay}-notes`, [relay], { kinds: [1] }, { eventFilter });
const header = (
<Flex gap="2" pr="2" justifyContent="space-between" alignItems="center">
<Switch isChecked={showReplies.isOpen} onChange={showReplies.onToggle} size="sm">
Show Replies
</Switch>
<TimelineViewTypeButtons />
</Flex>
);
return <TimelinePage timeline={timeline} header={header} />;
}

View File

@ -0,0 +1,57 @@
import { Button, Flex, Heading, Textarea } from "@chakra-ui/react";
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
import { useForm } from "react-hook-form";
import StarRating from "../../../components/star-rating";
import { DraftNostrEvent } from "../../../types/nostr-event";
import { RELAY_REVIEW_LABEL, RELAY_REVIEW_LABEL_NAMESPACE, REVIEW_KIND } from "../../../helpers/nostr/reviews";
import dayjs from "dayjs";
import { useSigningContext } from "../../../providers/signing-provider";
import { nostrPostAction } from "../../../classes/nostr-post-action";
export default function RelayReviewForm({ onClose, relay }: { onClose: () => void; relay: string }) {
const { requestSignature } = useSigningContext();
const writeRelays = useWriteRelayUrls();
const { register, getValues, watch, handleSubmit, setValue } = useForm({
defaultValues: {
quality: 0.6,
content: "",
},
});
watch("quality");
const onSubmit = handleSubmit(async (values) => {
const draft: DraftNostrEvent = {
kind: REVIEW_KIND,
content: values.content,
tags: [
["l", RELAY_REVIEW_LABEL, new URL(relay).host, JSON.stringify({ quality: values.quality })],
["L", RELAY_REVIEW_LABEL_NAMESPACE],
["r", relay],
],
created_at: dayjs().unix(),
};
const signed = await requestSignature(draft);
if (!signed) return;
nostrPostAction(writeRelays, signed);
onClose();
});
return (
<Flex as="form" direction="column" onSubmit={onSubmit} gap="2" mb="2">
<Flex gap="2">
<Heading size="md">Write review</Heading>
<StarRating quality={getValues().quality} fontSize="1.5rem" onChange={(q) => setValue("quality", q)} />
</Flex>
<Textarea {...register("content")} rows={5} placeholder="A short description of your experience with the relay" />
<Flex gap="2" ml="auto">
<Button onClick={onClose}>Cancel</Button>
<Button type="submit" colorScheme="brand">
Submit
</Button>
</Flex>
</Flex>
);
}

View File

@ -0,0 +1,28 @@
import { Flex } from "@chakra-ui/react";
import { RELAY_REVIEW_LABEL } from "../../../helpers/nostr/reviews";
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
import useSubject from "../../../hooks/use-subject";
import useTimelineLoader from "../../../hooks/use-timeline-loader";
import RelayReviewNote from "../components/relay-review-note";
import { useAppTitle } from "../../../hooks/use-app-title";
export default function RelayReviews({ relay }: { relay: string }) {
useAppTitle(`${relay} - Reviews`);
const readRelays = useReadRelayUrls();
const timeline = useTimelineLoader(`${relay}-reviews`, readRelays, {
kinds: [1985],
"#r": [relay],
"#l": [RELAY_REVIEW_LABEL],
});
const events = useSubject(timeline.timeline);
return (
<Flex direction="column" gap="2">
{events.map((event) => (
<RelayReviewNote key={event.id} event={event} hideUrl />
))}
</Flex>
);
}

View File

@ -38,24 +38,18 @@ export default function UserNotesTab() {
);
const header = (
<Flex gap="2" px="2">
<FormControl display="flex" alignItems="center" w="auto">
<Switch id="replies" mr="2" isChecked={showReplies} onChange={toggleReplies} />
<FormLabel htmlFor="replies" mb="0">
Replies
</FormLabel>
</FormControl>
<FormControl display="flex" alignItems="center" w="auto">
<Switch id="reposts" mr="2" isChecked={!hideReposts} onChange={toggleReposts} />
<FormLabel htmlFor="reposts" mb="0">
Reposts
</FormLabel>
</FormControl>
<Flex gap="2" px="2" alignItems="center">
<Switch id="replies" mr="2" isChecked={showReplies} onChange={toggleReplies} size="sm">
Replies
</Switch>
<Switch id="reposts" mr="2" isChecked={!hideReposts} onChange={toggleReposts} size="sm">
Reposts
</Switch>
<Spacer />
<RelayIconStack relays={readRelays} direction="row-reverse" maxRelays={4} />
<TimelineViewType />
</Flex>
);
return <TimelinePage header={header} timeline={timeline} />;
return <TimelinePage header={header} timeline={timeline} pt="2" pb="8" />;
}