mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-09 20:29:17 +02:00
add bookmark button
This commit is contained in:
parent
f6f465611d
commit
0af6c2cfcd
5
.changeset/few-tools-suffer.md
Normal file
5
.changeset/few-tools-suffer.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add bookmark button to notes
|
5
.changeset/silent-rivers-search.md
Normal file
5
.changeset/silent-rivers-search.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Show note lists
|
@ -337,3 +337,15 @@ export const ErrorIcon = createIcon({
|
||||
d: "M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z",
|
||||
defaultProps,
|
||||
});
|
||||
|
||||
export const BookmarkIcon = createIcon({
|
||||
displayName: "BookmarkIcon",
|
||||
d: "M5 2H19C19.5523 2 20 2.44772 20 3V22.1433C20 22.4194 19.7761 22.6434 19.5 22.6434C19.4061 22.6434 19.314 22.6168 19.2344 22.5669L12 18.0313L4.76559 22.5669C4.53163 22.7136 4.22306 22.6429 4.07637 22.4089C4.02647 22.3293 4 22.2373 4 22.1433V3C4 2.44772 4.44772 2 5 2ZM18 4H6V19.4324L12 15.6707L18 19.4324V4Z",
|
||||
defaultProps,
|
||||
});
|
||||
|
||||
export const BookmarkedIcon = createIcon({
|
||||
displayName: "BookmaredkIcon",
|
||||
d: "M4 2H20C20.5523 2 21 2.44772 21 3V22.2763C21 22.5525 20.7761 22.7764 20.5 22.7764C20.4298 22.7764 20.3604 22.7615 20.2963 22.7329L12 19.0313L3.70373 22.7329C3.45155 22.8455 3.15591 22.7322 3.04339 22.4801C3.01478 22.4159 3 22.3465 3 22.2763V3C3 2.44772 3.44772 2 4 2ZM12 13.5L14.9389 15.0451L14.3776 11.7725L16.7553 9.45492L13.4695 8.97746L12 6L10.5305 8.97746L7.24472 9.45492L9.62236 11.7725L9.06107 15.0451L12 13.5Z",
|
||||
defaultProps,
|
||||
});
|
||||
|
113
src/components/note/buttons/bookmark-button.tsx
Normal file
113
src/components/note/buttons/bookmark-button.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
IconButton,
|
||||
IconButtonProps,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuDivider,
|
||||
MenuItem,
|
||||
MenuItemOption,
|
||||
MenuList,
|
||||
MenuOptionGroup,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import { useCurrentAccount } from "../../../hooks/use-current-account";
|
||||
import { useSigningContext } from "../../../providers/signing-provider";
|
||||
import useUserLists from "../../../hooks/use-user-lists";
|
||||
import {
|
||||
NOTE_LIST_KIND,
|
||||
draftAddEvent,
|
||||
draftRemoveEvent,
|
||||
getEventsFromList,
|
||||
getListName,
|
||||
} from "../../../helpers/nostr/lists";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/events";
|
||||
import clientRelaysService from "../../../services/client-relays";
|
||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||
import { BookmarkIcon, BookmarkedIcon, PlusCircleIcon } from "../../icons";
|
||||
import NewListModal from "../../../views/lists/components/new-list-modal";
|
||||
|
||||
export default function BookmarkButton({ event, ...props }: { event: NostrEvent } & Omit<IconButtonProps, "icon">) {
|
||||
const toast = useToast();
|
||||
const newListModal = useDisclosure();
|
||||
const account = useCurrentAccount();
|
||||
const { requestSignature } = useSigningContext();
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const lists = useUserLists(account?.pubkey).filter((list) => list.kind === NOTE_LIST_KIND);
|
||||
|
||||
const inLists = lists.filter((list) => getEventsFromList(list).some((p) => p.id === event.id));
|
||||
|
||||
const handleChange = useCallback(
|
||||
async (cords: string | string[]) => {
|
||||
if (!Array.isArray(cords)) return;
|
||||
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const addToList = lists.find((list) => !inLists.includes(list) && cords.includes(getEventCoordinate(list)));
|
||||
const removeFromList = lists.find(
|
||||
(list) => inLists.includes(list) && !cords.includes(getEventCoordinate(list)),
|
||||
);
|
||||
|
||||
if (addToList) {
|
||||
const draft = draftAddEvent(addToList, event.id);
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Add to list", writeRelays, signed);
|
||||
} else if (removeFromList) {
|
||||
const draft = draftRemoveEvent(removeFromList, event.id);
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Remove from list", writeRelays, signed);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
[lists, event.id],
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu closeOnSelect={false}>
|
||||
<MenuButton as={IconButton} icon={inLists.length > 0 ? <BookmarkedIcon /> : <BookmarkIcon />} {...props} />
|
||||
<MenuList minWidth="240px">
|
||||
{lists.length > 0 && (
|
||||
<MenuOptionGroup
|
||||
type="checkbox"
|
||||
value={inLists.map((list) => getEventCoordinate(list))}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{lists.map((list) => (
|
||||
<MenuItemOption
|
||||
key={getEventCoordinate(list)}
|
||||
value={getEventCoordinate(list)}
|
||||
isDisabled={account?.readonly && isLoading}
|
||||
isTruncated
|
||||
maxW="90vw"
|
||||
>
|
||||
{getListName(list)}
|
||||
</MenuItemOption>
|
||||
))}
|
||||
</MenuOptionGroup>
|
||||
)}
|
||||
<MenuDivider />
|
||||
<MenuItem icon={<PlusCircleIcon />} onClick={newListModal.onOpen}>
|
||||
New list
|
||||
</MenuItem>
|
||||
|
||||
{newListModal.isOpen && (
|
||||
<NewListModal
|
||||
onClose={newListModal.onClose}
|
||||
isOpen
|
||||
onCreated={newListModal.onClose}
|
||||
initKind={NOTE_LIST_KIND}
|
||||
/>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
}
|
@ -32,6 +32,7 @@ import NoteContentWithWarning from "./note-content-with-warning";
|
||||
import { TrustProvider } from "../../providers/trust";
|
||||
import { NoteLink } from "../note-link";
|
||||
import { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
||||
import BookmarkButton from "./buttons/bookmark-button";
|
||||
|
||||
export type NoteProps = {
|
||||
event: NostrEvent;
|
||||
@ -86,6 +87,7 @@ export const Note = React.memo(({ event, variant = "outline" }: NoteProps) => {
|
||||
/>
|
||||
)}
|
||||
<EventRelays event={event} />
|
||||
<BookmarkButton event={event} aria-label="Bookmark note" size="sm" variant="link" />
|
||||
<NoteMenu event={event} size="sm" variant="link" aria-label="More Options" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
@ -11,9 +11,11 @@ export type NoteRelaysProps = {
|
||||
event: NostrEvent;
|
||||
};
|
||||
|
||||
export const EventRelays = memo(({ event }: NoteRelaysProps & Omit<RelayIconStackProps, "relays" | "maxRelays">) => {
|
||||
const maxRelays = useBreakpointValue({ base: 3, md: undefined });
|
||||
const eventRelays = useSubject(getEventRelays(getEventUID(event)));
|
||||
export const EventRelays = memo(
|
||||
({ event, ...props }: NoteRelaysProps & Omit<RelayIconStackProps, "relays" | "maxRelays">) => {
|
||||
const maxRelays = useBreakpointValue({ base: 3, md: undefined });
|
||||
const eventRelays = useSubject(getEventRelays(getEventUID(event)));
|
||||
|
||||
return <RelayIconStack relays={eventRelays} direction="row-reverse" maxRelays={maxRelays} />;
|
||||
});
|
||||
return <RelayIconStack relays={eventRelays} direction="row-reverse" maxRelays={maxRelays} {...props} />;
|
||||
},
|
||||
);
|
||||
|
@ -1,19 +1,23 @@
|
||||
import dayjs from "dayjs";
|
||||
import { DraftNostrEvent, NostrEvent, isDTag, isPTag } from "../../types/nostr-event";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { DraftNostrEvent, NostrEvent, isDTag, isETag, isPTag } from "../../types/nostr-event";
|
||||
|
||||
export const PEOPLE_LIST_KIND = 30000;
|
||||
export const NOTE_LIST_KIND = 30001;
|
||||
export const PIN_LIST_KIND = 10001;
|
||||
export const MUTE_LIST_KIND = 10000;
|
||||
|
||||
export function getListName(event: NostrEvent) {
|
||||
if (event.kind === 3) return "Following";
|
||||
return event.tags.find(isDTag)?.[1];
|
||||
return event.tags.find((t) => t[0] === "title")?.[1] || event.tags.find(isDTag)?.[1];
|
||||
}
|
||||
|
||||
export function getPubkeysFromList(event: NostrEvent) {
|
||||
return event.tags.filter(isPTag).map((t) => ({ pubkey: t[1], relay: t[2] }));
|
||||
}
|
||||
export function getEventsFromList(event: NostrEvent) {
|
||||
return event.tags.filter(isETag).map((t) => ({ id: t[1], relay: t[2] }));
|
||||
}
|
||||
|
||||
export function isPubkeyInList(event?: NostrEvent, pubkey?: string) {
|
||||
if (!pubkey || !event) return false;
|
||||
@ -37,25 +41,49 @@ export function createEmptyMuteList(): DraftNostrEvent {
|
||||
};
|
||||
}
|
||||
|
||||
export function draftAddPerson(event: NostrEvent | DraftNostrEvent, pubkey: string, relay?: string) {
|
||||
if (event.tags.some((t) => t[0] === "p" && t[1] === pubkey)) throw new Error("person already in list");
|
||||
export function draftAddPerson(list: NostrEvent | DraftNostrEvent, pubkey: string, relay?: string) {
|
||||
if (list.tags.some((t) => t[0] === "p" && t[1] === pubkey)) throw new Error("person already in list");
|
||||
|
||||
const draft: DraftNostrEvent = {
|
||||
created_at: dayjs().unix(),
|
||||
kind: event.kind,
|
||||
content: event.content,
|
||||
tags: [...event.tags, relay ? ["p", pubkey, relay] : ["p", pubkey]],
|
||||
kind: list.kind,
|
||||
content: list.content,
|
||||
tags: [...list.tags, relay ? ["p", pubkey, relay] : ["p", pubkey]],
|
||||
};
|
||||
|
||||
return draft;
|
||||
}
|
||||
|
||||
export function draftRemovePerson(event: NostrEvent | DraftNostrEvent, pubkey: string) {
|
||||
export function draftRemovePerson(list: NostrEvent | DraftNostrEvent, pubkey: string) {
|
||||
const draft: DraftNostrEvent = {
|
||||
created_at: dayjs().unix(),
|
||||
kind: event.kind,
|
||||
content: event.content,
|
||||
tags: event.tags.filter((t) => t[0] !== "p" || t[1] !== pubkey),
|
||||
kind: list.kind,
|
||||
content: list.content,
|
||||
tags: list.tags.filter((t) => !(t[0] === "p" && t[1] === pubkey)),
|
||||
};
|
||||
|
||||
return draft;
|
||||
}
|
||||
|
||||
export function draftAddEvent(list: NostrEvent | DraftNostrEvent, event: string, relay?: string) {
|
||||
if (list.tags.some((t) => t[0] === "e" && t[1] === event)) throw new Error("event already in list");
|
||||
|
||||
const draft: DraftNostrEvent = {
|
||||
created_at: dayjs().unix(),
|
||||
kind: list.kind,
|
||||
content: list.content,
|
||||
tags: [...list.tags, relay ? ["e", event, relay] : ["e", event]],
|
||||
};
|
||||
|
||||
return draft;
|
||||
}
|
||||
|
||||
export function draftRemoveEvent(list: NostrEvent | DraftNostrEvent, event: string) {
|
||||
const draft: DraftNostrEvent = {
|
||||
created_at: dayjs().unix(),
|
||||
kind: list.kind,
|
||||
content: list.content,
|
||||
tags: list.tags.filter((t) => !(t[0] === "e" && t[1] === event)),
|
||||
};
|
||||
|
||||
return draft;
|
||||
|
@ -1,33 +1,51 @@
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { AvatarGroup, Card, CardBody, CardHeader, Heading, Link, Spacer, Text } from "@chakra-ui/react";
|
||||
import {
|
||||
AvatarGroup,
|
||||
Box,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
Flex,
|
||||
Heading,
|
||||
Link,
|
||||
Spacer,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { Kind } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
||||
import { UserLink } from "../../../components/user-link";
|
||||
import EventVerificationIcon from "../../../components/event-verification-icon";
|
||||
import { getListName, getPubkeysFromList } from "../../../helpers/nostr/lists";
|
||||
import { getEventsFromList, getListName, getPubkeysFromList } from "../../../helpers/nostr/lists";
|
||||
import { getSharableEventNaddr } from "../../../helpers/nip19";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
||||
import { EventRelays } from "../../../components/note/note-relays";
|
||||
import { NoteLink } from "../../../components/note-link";
|
||||
|
||||
export default function ListCard({ cord, event: maybeEvent }: { cord?: string; event?: NostrEvent }) {
|
||||
const event = maybeEvent ?? (cord ? useReplaceableEvent(cord as string) : undefined);
|
||||
if (!event) return null;
|
||||
|
||||
const people = getPubkeysFromList(event);
|
||||
const notes = getEventsFromList(event);
|
||||
const link =
|
||||
event.kind === Kind.Contacts ? createCoordinate(Kind.Contacts, event.pubkey) : getSharableEventNaddr(event);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader display="flex" p="2" flex="1" gap="2" alignItems="center">
|
||||
<Heading size="md">
|
||||
<Link as={RouterLink} to={`/lists/${link}`}>
|
||||
{getListName(event)}
|
||||
</Link>
|
||||
</Heading>
|
||||
<CardHeader display="flex" p="2" flex="1" gap="2">
|
||||
<Box>
|
||||
<Heading size="md">
|
||||
<Link as={RouterLink} to={`/lists/${link}`}>
|
||||
{getListName(event)}
|
||||
</Link>
|
||||
</Heading>
|
||||
<Text>Updated: {dayjs.unix(event.created_at).fromNow()}</Text>
|
||||
</Box>
|
||||
<Spacer />
|
||||
<Text>Created by:</Text>
|
||||
<UserAvatarLink pubkey={event.pubkey} size="xs" />
|
||||
@ -38,15 +56,27 @@ export default function ListCard({ cord, event: maybeEvent }: { cord?: string; e
|
||||
{people.length > 0 && (
|
||||
<>
|
||||
<Text>{people.length} people</Text>
|
||||
<AvatarGroup overflow="hidden" mb="2">
|
||||
<AvatarGroup overflow="hidden" mb="2" max={16} size="sm">
|
||||
{people.map(({ pubkey, relay }) => (
|
||||
<UserAvatarLink key={pubkey} pubkey={pubkey} relay={relay} />
|
||||
))}
|
||||
</AvatarGroup>
|
||||
</>
|
||||
)}
|
||||
<EventRelays event={event} />
|
||||
{notes.length > 0 && (
|
||||
<>
|
||||
<Text>{notes.length} notes</Text>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{notes.map(({ id, relay }) => (
|
||||
<NoteLink key={id} noteId={id} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</CardBody>
|
||||
<CardFooter p="2" display="flex">
|
||||
<EventRelays event={event} ml="auto" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
@ -23,14 +23,17 @@ import { useSigningContext } from "../../../providers/signing-provider";
|
||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||
import clientRelaysService from "../../../services/client-relays";
|
||||
|
||||
export type NewListModalProps = { onCreated?: (list: NostrEvent) => void } & Omit<ModalProps, "children">;
|
||||
export type NewListModalProps = { onCreated?: (list: NostrEvent) => void; initKind?: number } & Omit<
|
||||
ModalProps,
|
||||
"children"
|
||||
>;
|
||||
|
||||
export default function NewListModal({ onClose, onCreated, ...props }: NewListModalProps) {
|
||||
export default function NewListModal({ onClose, onCreated, initKind, ...props }: NewListModalProps) {
|
||||
const toast = useToast();
|
||||
const { requestSignature } = useSigningContext();
|
||||
const { handleSubmit, register, formState } = useForm({
|
||||
defaultValues: {
|
||||
kind: PEOPLE_LIST_KIND,
|
||||
kind: initKind || PEOPLE_LIST_KIND,
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
|
18
src/views/lists/components/note-card.tsx
Normal file
18
src/views/lists/components/note-card.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Text } from "@chakra-ui/react";
|
||||
import { Note } from "../../../components/note";
|
||||
import { NoteLink } from "../../../components/note-link";
|
||||
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||
import useSingleEvent from "../../../hooks/use-single-event";
|
||||
|
||||
export default function NoteCard({ id, relay }: { id: string; relay?: string }) {
|
||||
const readRelays = useReadRelayUrls(relay ? [relay] : []);
|
||||
const { event } = useSingleEvent(id, readRelays);
|
||||
|
||||
return event ? (
|
||||
<Note event={event} />
|
||||
) : (
|
||||
<Text>
|
||||
Loading <NoteLink noteId={id} />
|
||||
</Text>
|
||||
);
|
||||
}
|
@ -41,7 +41,7 @@ export default function UserCard({ pubkey, relay, list, ...props }: UserCardProp
|
||||
<UserDnsIdentityIcon pubkey={pubkey} />
|
||||
</Flex>
|
||||
{account?.pubkey === list.pubkey ? (
|
||||
<Button variant="outline" colorScheme="orange" onClick={handleRemoveFromList}>
|
||||
<Button variant="outline" colorScheme="orange" onClick={handleRemoveFromList} size="sm">
|
||||
Remove
|
||||
</Button>
|
||||
) : (
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { Button, Flex, Image, Link, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
import { Button, Divider, Flex, Heading, Image, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Kind } from "nostr-tools";
|
||||
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import { ExternalLinkIcon, PlusCircleIcon } from "../../components/icons";
|
||||
import RequireCurrentAccount from "../../providers/require-current-account";
|
||||
@ -6,8 +9,8 @@ import ListCard from "./components/list-card";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import useUserLists from "../../hooks/use-user-lists";
|
||||
import NewListModal from "./components/new-list-modal";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getSharableEventNaddr } from "../../helpers/nip19";
|
||||
import { MUTE_LIST_KIND, NOTE_LIST_KIND, PEOPLE_LIST_KIND, PIN_LIST_KIND } from "../../helpers/nostr/lists";
|
||||
|
||||
function ListsPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
@ -15,6 +18,9 @@ function ListsPage() {
|
||||
const newList = useDisclosure();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const peopleLists = events.filter((event) => event.kind === PEOPLE_LIST_KIND);
|
||||
const noteLists = events.filter((event) => event.kind === NOTE_LIST_KIND);
|
||||
|
||||
return (
|
||||
<Flex direction="column" p="2" gap="2">
|
||||
<Flex gap="2">
|
||||
@ -33,11 +39,27 @@ function ListsPage() {
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<ListCard cord={`3:${account.pubkey}`} />
|
||||
<ListCard cord={`10000:${account.pubkey}`} />
|
||||
{events.map((event) => (
|
||||
<ListCard key={getEventUID(event)} event={event} />
|
||||
))}
|
||||
<Heading size="md">Special lists</Heading>
|
||||
<Divider />
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
<ListCard cord={`${Kind.Contacts}:${account.pubkey}`} />
|
||||
<ListCard cord={`${MUTE_LIST_KIND}:${account.pubkey}`} />
|
||||
<ListCard cord={`${PIN_LIST_KIND}:${account.pubkey}`} />
|
||||
</SimpleGrid>
|
||||
<Heading size="md">People lists</Heading>
|
||||
<Divider />
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{peopleLists.map((event) => (
|
||||
<ListCard key={getEventUID(event)} event={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<Heading size="md">Bookmark lists</Heading>
|
||||
<Divider />
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{noteLists.map((event) => (
|
||||
<ListCard key={getEventUID(event)} event={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
{newList.isOpen && (
|
||||
<NewListModal
|
||||
|
@ -2,16 +2,19 @@ import { Link as RouterList, useNavigate, useParams } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import { Button, Flex, Heading, IconButton, useDisclosure } from "@chakra-ui/react";
|
||||
import { Button, Divider, Flex, Heading, IconButton, SimpleGrid, useDisclosure } from "@chakra-ui/react";
|
||||
import { ArrowLeftSIcon, CodeIcon } from "../../components/icons";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import { useDeleteEventContext } from "../../providers/delete-event-provider";
|
||||
import { parseCoordinate } from "../../helpers/nostr/events";
|
||||
import { getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import { getEventsFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { EventRelays } from "../../components/note/note-relays";
|
||||
import UserCard from "./components/user-card";
|
||||
import NoteDebugModal from "../../components/debug-modals/note-debug-modal";
|
||||
import { Note } from "../../components/note";
|
||||
import NoteCard from "./components/note-card";
|
||||
import { TrustProvider } from "../../providers/trust";
|
||||
|
||||
function useListCoordinate() {
|
||||
const { addr } = useParams() as { addr: string };
|
||||
@ -45,6 +48,7 @@ export default function ListView() {
|
||||
|
||||
const isAuthor = account?.pubkey === event.pubkey;
|
||||
const people = getPubkeysFromList(event);
|
||||
const notes = getEventsFromList(event);
|
||||
|
||||
return (
|
||||
<Flex direction="column" px="2" pt="2" pb="8" overflowY="auto" overflowX="hidden" h="full" gap="2">
|
||||
@ -66,9 +70,32 @@ export default function ListView() {
|
||||
)}
|
||||
<IconButton icon={<CodeIcon />} aria-label="Show raw" onClick={debug.onOpen} />
|
||||
</Flex>
|
||||
{people.map(({ pubkey, relay }) => (
|
||||
<UserCard pubkey={pubkey} relay={relay} list={event} />
|
||||
))}
|
||||
|
||||
{people.length > 0 && (
|
||||
<>
|
||||
<Heading size="md">People</Heading>
|
||||
<Divider />
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{people.map(({ pubkey, relay }) => (
|
||||
<UserCard pubkey={pubkey} relay={relay} list={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
|
||||
{notes.length > 0 && (
|
||||
<>
|
||||
<Heading size="md">Notes</Heading>
|
||||
<Divider />
|
||||
<TrustProvider trust>
|
||||
<Flex gap="2" direction="column">
|
||||
{notes.map(({ id, relay }) => (
|
||||
<NoteCard id={id} relay={relay} />
|
||||
))}
|
||||
</Flex>
|
||||
</TrustProvider>
|
||||
</>
|
||||
)}
|
||||
|
||||
{debug.isOpen && <NoteDebugModal event={event} isOpen onClose={debug.onClose} size="4xl" />}
|
||||
</Flex>
|
||||
|
@ -79,7 +79,7 @@ export default function UserAboutTab() {
|
||||
gap="2"
|
||||
pt={metadata?.banner ? 0 : "2"}
|
||||
pb="8"
|
||||
h="full"
|
||||
minH="90vh"
|
||||
>
|
||||
<Box
|
||||
pt={!expanded.isOpen ? "20vh" : 0}
|
||||
|
@ -112,6 +112,7 @@ const UserView = () => {
|
||||
index={activeTab}
|
||||
onChange={(v) => navigate(tabs[v].path, { replace: true })}
|
||||
colorScheme="brand"
|
||||
h="full"
|
||||
>
|
||||
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
|
||||
{tabs.map(({ label }) => (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import { SimpleGrid } from "@chakra-ui/react";
|
||||
|
||||
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
@ -20,12 +20,12 @@ export default function UserListsTab() {
|
||||
const events = useSubject(timeline.timeline);
|
||||
|
||||
return (
|
||||
<Flex direction="column" p="2" gap="2">
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2" py="2" px={["2", "2", 0]}>
|
||||
<ListCard cord={`3:${pubkey}`} />
|
||||
<ListCard cord={`10000:${pubkey}`} />
|
||||
{events.map((event) => (
|
||||
<ListCard key={getEventUID(event)} event={event} />
|
||||
))}
|
||||
</Flex>
|
||||
</SimpleGrid>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user