mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-08 20:08:02 +02:00
Show list embeds in notes
This commit is contained in:
parent
3d5d23407d
commit
6b4fd8ab42
5
.changeset/shy-trees-boil.md
Normal file
5
.changeset/shy-trees-boil.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Show list embeds in notes
|
51
src/components/embed-event/event-types/embedded-list.tsx
Normal file
51
src/components/embed-event/event-types/embedded-list.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { AvatarGroup, Card, CardBody, CardHeader, CardProps, Flex, Heading, Link, Text } from "@chakra-ui/react";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
|
import { getEventsFromList, getListName, getPubkeysFromList, isSpecialListKind } from "../../../helpers/nostr/lists";
|
||||||
|
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
||||||
|
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||||
|
import { UserAvatarLink } from "../../user-avatar-link";
|
||||||
|
import { UserLink } from "../../user-link";
|
||||||
|
import { NoteLink } from "../../note-link";
|
||||||
|
import ListFeedButton from "../../../views/lists/components/list-feed-button";
|
||||||
|
|
||||||
|
export default function EmbeddedList({ list: list, ...props }: Omit<CardProps, "children"> & { list: NostrEvent }) {
|
||||||
|
const people = getPubkeysFromList(list);
|
||||||
|
const notes = getEventsFromList(list);
|
||||||
|
const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card {...props}>
|
||||||
|
<CardHeader display="flex" alignItems="center" p="2" pb="0" gap="2">
|
||||||
|
<Heading size="md">
|
||||||
|
<Link as={RouterLink} to={`/lists/${link}`}>
|
||||||
|
{getListName(list)}
|
||||||
|
</Link>
|
||||||
|
</Heading>
|
||||||
|
<ListFeedButton list={list} ml="auto" size="sm" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody p="2">
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text>Created by:</Text>
|
||||||
|
<UserAvatarLink pubkey={list.pubkey} size="xs" />
|
||||||
|
<UserLink pubkey={list.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
|
||||||
|
</Flex>
|
||||||
|
{people.length > 0 && (
|
||||||
|
<AvatarGroup overflow="hidden" mb="2" max={16} size="sm">
|
||||||
|
{people.map(({ pubkey, relay }) => (
|
||||||
|
<UserAvatarLink key={pubkey} pubkey={pubkey} relay={relay} />
|
||||||
|
))}
|
||||||
|
</AvatarGroup>
|
||||||
|
)}
|
||||||
|
{notes.length > 0 && (
|
||||||
|
<Flex gap="2" overflow="hidden">
|
||||||
|
{notes.map(({ id, relay }) => (
|
||||||
|
<NoteLink key={id} noteId={id} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@ -15,6 +15,8 @@ import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs";
|
|||||||
import EmbeddedEmojiPack from "./event-types/embedded-emoji-pack";
|
import EmbeddedEmojiPack from "./event-types/embedded-emoji-pack";
|
||||||
import EmbeddedGoal, { EmbeddedGoalOptions } from "./event-types/embedded-goal";
|
import EmbeddedGoal, { EmbeddedGoalOptions } from "./event-types/embedded-goal";
|
||||||
import EmbeddedUnknown from "./event-types/embedded-unknown";
|
import EmbeddedUnknown from "./event-types/embedded-unknown";
|
||||||
|
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../helpers/nostr/lists";
|
||||||
|
import EmbeddedList from "./event-types/embedded-list";
|
||||||
|
|
||||||
export type EmbedProps = {
|
export type EmbedProps = {
|
||||||
goalProps?: EmbeddedGoalOptions;
|
goalProps?: EmbeddedGoalOptions;
|
||||||
@ -30,6 +32,9 @@ export function EmbedEvent({ event, goalProps }: { event: NostrEvent } & EmbedPr
|
|||||||
return <EmbeddedGoal goal={event} {...goalProps} />;
|
return <EmbeddedGoal goal={event} {...goalProps} />;
|
||||||
case EMOJI_PACK_KIND:
|
case EMOJI_PACK_KIND:
|
||||||
return <EmbeddedEmojiPack pack={event} />;
|
return <EmbeddedEmojiPack pack={event} />;
|
||||||
|
case PEOPLE_LIST_KIND:
|
||||||
|
case NOTE_LIST_KIND:
|
||||||
|
return <EmbeddedList list={event} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <EmbeddedUnknown event={event} />;
|
return <EmbeddedUnknown event={event} />;
|
||||||
|
@ -20,6 +20,7 @@ import clientRelaysService from "../../../services/client-relays";
|
|||||||
import QuoteNote from "../quote-note";
|
import QuoteNote from "../quote-note";
|
||||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||||
import { useSigningContext } from "../../../providers/signing-provider";
|
import { useSigningContext } from "../../../providers/signing-provider";
|
||||||
|
import { EmbedEvent } from "../../embed-event";
|
||||||
|
|
||||||
export function RepostButton({ event }: { event: NostrEvent }) {
|
export function RepostButton({ event }: { event: NostrEvent }) {
|
||||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||||
@ -51,7 +52,7 @@ export function RepostButton({ event }: { event: NostrEvent }) {
|
|||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
/>
|
/>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Modal isOpen={isOpen} onClose={onClose}>
|
<Modal isOpen={isOpen} onClose={onClose} size="2xl">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader px="4" py="2">
|
<ModalHeader px="4" py="2">
|
||||||
@ -59,7 +60,7 @@ export function RepostButton({ event }: { event: NostrEvent }) {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody px="4" py="0">
|
<ModalBody px="4" py="0">
|
||||||
<QuoteNote noteId={event.id} />
|
<EmbedEvent event={event} />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter px="4" py="4">
|
<ModalFooter px="4" py="4">
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
AccordionPanel,
|
AccordionPanel,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Code,
|
|
||||||
Flex,
|
Flex,
|
||||||
Input,
|
Input,
|
||||||
Link,
|
Link,
|
||||||
@ -21,12 +20,11 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
useToast,
|
useToast,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Event, Kind, nip19 } from "nostr-tools";
|
import { Event, Kind } from "nostr-tools";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { useCurrentAccount } from "../hooks/use-current-account";
|
import { useCurrentAccount } from "../hooks/use-current-account";
|
||||||
import signingService from "../services/signing";
|
import signingService from "../services/signing";
|
||||||
import QuoteNote from "../components/note/quote-note";
|
|
||||||
import createDefer, { Deferred } from "../classes/deferred";
|
import createDefer, { Deferred } from "../classes/deferred";
|
||||||
import useEventRelays from "../hooks/use-event-relays";
|
import useEventRelays from "../hooks/use-event-relays";
|
||||||
import { useWriteRelayUrls } from "../hooks/use-client-relays";
|
import { useWriteRelayUrls } from "../hooks/use-client-relays";
|
||||||
@ -36,6 +34,7 @@ import { getEventCoordinate, getEventUID, isReplaceable } from "../helpers/nostr
|
|||||||
import NostrPublishAction from "../classes/nostr-publish-action";
|
import NostrPublishAction from "../classes/nostr-publish-action";
|
||||||
import { Tag } from "../types/nostr-event";
|
import { Tag } from "../types/nostr-event";
|
||||||
import deleteEventService from "../services/delete-events";
|
import deleteEventService from "../services/delete-events";
|
||||||
|
import { EmbedEvent } from "../components/embed-event";
|
||||||
|
|
||||||
type DeleteEventContextType = {
|
type DeleteEventContextType = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -51,13 +50,6 @@ export function useDeleteEventContext() {
|
|||||||
return useContext(DeleteEventContext);
|
return useContext(DeleteEventContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventPreview({ event }: { event: Event }) {
|
|
||||||
if (event.kind === Kind.Text) {
|
|
||||||
return <QuoteNote noteId={event.id} />;
|
|
||||||
}
|
|
||||||
return <Code>{nip19.noteEncode(event.id)}</Code>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
@ -124,11 +116,11 @@ export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
|||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader px="4" py="2">
|
<ModalHeader px="4" py="2">
|
||||||
Delete Note?
|
Delete Event?
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody px="4" py="0">
|
<ModalBody px="4" py="0">
|
||||||
<EventPreview event={event} />
|
<EmbedEvent event={event} />
|
||||||
<Input
|
<Input
|
||||||
name="reason"
|
name="reason"
|
||||||
value={reason}
|
value={reason}
|
||||||
|
23
src/views/lists/components/list-feed-button.tsx
Normal file
23
src/views/lists/components/list-feed-button.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
import { Kind } from "nostr-tools";
|
||||||
|
|
||||||
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
|
import { getEventCoordinate } from "../../../helpers/nostr/events";
|
||||||
|
import { PEOPLE_LIST_KIND } from "../../../helpers/nostr/lists";
|
||||||
|
|
||||||
|
export default function ListFeedButton({ list, ...props }: { list: NostrEvent } & Omit<ButtonProps, "children">) {
|
||||||
|
const shouldShowFeedButton = list.kind === PEOPLE_LIST_KIND || list.kind === Kind.Contacts;
|
||||||
|
|
||||||
|
if (!shouldShowFeedButton) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
as={RouterLink}
|
||||||
|
to={{ pathname: "/", search: new URLSearchParams({ people: getEventCoordinate(list) }).toString() }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
View Feed
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
@ -1,14 +1,13 @@
|
|||||||
import { Link as RouterList, useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { Kind, nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
|
||||||
|
|
||||||
import { UserLink } from "../../components/user-link";
|
import { UserLink } from "../../components/user-link";
|
||||||
import { Button, Divider, Flex, Heading, SimpleGrid, Spacer } from "@chakra-ui/react";
|
import { Button, Divider, Flex, Heading, SimpleGrid, Spacer } from "@chakra-ui/react";
|
||||||
import { ArrowLeftSIcon } from "../../components/icons";
|
import { ArrowLeftSIcon } from "../../components/icons";
|
||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||||
import { useDeleteEventContext } from "../../providers/delete-event-provider";
|
import { useDeleteEventContext } from "../../providers/delete-event-provider";
|
||||||
import { getEventCoordinate, parseCoordinate } from "../../helpers/nostr/events";
|
import { parseCoordinate } from "../../helpers/nostr/events";
|
||||||
import { PEOPLE_LIST_KIND, getEventsFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
import { getEventsFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||||
import { EventRelays } from "../../components/note/note-relays";
|
import { EventRelays } from "../../components/note/note-relays";
|
||||||
import UserCard from "./components/user-card";
|
import UserCard from "./components/user-card";
|
||||||
@ -16,6 +15,7 @@ import NoteCard from "./components/note-card";
|
|||||||
import { TrustProvider } from "../../providers/trust";
|
import { TrustProvider } from "../../providers/trust";
|
||||||
import ListMenu from "./components/list-menu";
|
import ListMenu from "./components/list-menu";
|
||||||
import ListFavoriteButton from "./components/list-favorite-button";
|
import ListFavoriteButton from "./components/list-favorite-button";
|
||||||
|
import ListFeedButton from "./components/list-feed-button";
|
||||||
|
|
||||||
function useListCoordinate() {
|
function useListCoordinate() {
|
||||||
const { addr } = useParams() as { addr: string };
|
const { addr } = useParams() as { addr: string };
|
||||||
@ -47,7 +47,6 @@ export default function ListDetailsView() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isAuthor = account?.pubkey === event.pubkey;
|
const isAuthor = account?.pubkey === event.pubkey;
|
||||||
const shouldShowFeedButton = event.kind === PEOPLE_LIST_KIND || event.kind === Kind.Contacts;
|
|
||||||
const people = getPubkeysFromList(event);
|
const people = getPubkeysFromList(event);
|
||||||
const notes = getEventsFromList(event);
|
const notes = getEventsFromList(event);
|
||||||
|
|
||||||
@ -67,14 +66,7 @@ export default function ListDetailsView() {
|
|||||||
|
|
||||||
<EventRelays event={event} />
|
<EventRelays event={event} />
|
||||||
|
|
||||||
{shouldShowFeedButton && (
|
<ListFeedButton list={event} />
|
||||||
<Button
|
|
||||||
as={RouterLink}
|
|
||||||
to={{ pathname: "/", search: new URLSearchParams({ people: getEventCoordinate(event) }).toString() }}
|
|
||||||
>
|
|
||||||
View Feed
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{isAuthor && (
|
{isAuthor && (
|
||||||
<Button colorScheme="red" onClick={() => deleteEvent(event).then(() => navigate("/lists"))}>
|
<Button colorScheme="red" onClick={() => deleteEvent(event).then(() => navigate("/lists"))}>
|
||||||
Delete
|
Delete
|
||||||
|
Loading…
x
Reference in New Issue
Block a user