mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-25 11:13:30 +02:00
fix embeded note expanding
This commit is contained in:
19
src/components/note/expanded.tsx
Normal file
19
src/components/note/expanded.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useDisclosure } from "@chakra-ui/react";
|
||||
import React, { PropsWithChildren, useContext, useMemo } from "react";
|
||||
|
||||
type ContextType = { expanded: boolean; onExpand: () => void; onCollapse: () => void; onToggle: () => void };
|
||||
|
||||
const ExpandedContext = React.createContext<ContextType | undefined>(undefined);
|
||||
|
||||
export function useExpand() {
|
||||
const ctx = useContext(ExpandedContext);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function ExpandProvider({ children }: PropsWithChildren) {
|
||||
const { isOpen: expanded, onOpen: onExpand, onClose: onCollapse, onToggle } = useDisclosure();
|
||||
|
||||
return (
|
||||
<ExpandedContext.Provider value={{ expanded, onExpand, onCollapse, onToggle }}>{children}</ExpandedContext.Provider>
|
||||
);
|
||||
}
|
@@ -32,6 +32,7 @@ import { convertTimestampToDate } from "../../helpers/date";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import NoteLikeButton from "./note-like-button";
|
||||
import NoteZapButton from "./note-zap-button";
|
||||
import { ExpandProvider } from "./expanded";
|
||||
|
||||
export type NoteProps = {
|
||||
event: NostrEvent;
|
||||
@@ -49,51 +50,53 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
|
||||
const share = () => openModal(buildShare(event));
|
||||
|
||||
return (
|
||||
<Card variant="outline">
|
||||
<CardHeader padding="2">
|
||||
<Flex flex="1" gap="2" alignItems="center" wrap="wrap">
|
||||
<UserAvatarLink pubkey={event.pubkey} size={isMobile ? "xs" : "sm"} />
|
||||
<ExpandProvider>
|
||||
<Card variant="outline">
|
||||
<CardHeader padding="2">
|
||||
<Flex flex="1" gap="2" alignItems="center" wrap="wrap">
|
||||
<UserAvatarLink pubkey={event.pubkey} size={isMobile ? "xs" : "sm"} />
|
||||
|
||||
<Heading size="sm" display="inline">
|
||||
<UserLink pubkey={event.pubkey} />
|
||||
</Heading>
|
||||
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
|
||||
{!isMobile && <Flex grow={1} />}
|
||||
<Link as={RouterLink} to={`/n/${normalizeToBech32(event.id, Bech32Prefix.Note)}`} whiteSpace="nowrap">
|
||||
{moment(convertTimestampToDate(event.created_at)).fromNow()}
|
||||
</Link>
|
||||
</Flex>
|
||||
</CardHeader>
|
||||
<CardBody px="2" py="0">
|
||||
<NoteContents event={event} trusted={following.includes(event.pubkey)} maxHeight={maxHeight} />
|
||||
</CardBody>
|
||||
<CardFooter padding="2" display="flex" gap="2">
|
||||
<IconButton
|
||||
variant="link"
|
||||
icon={<ReplyIcon />}
|
||||
title="Reply"
|
||||
aria-label="Reply"
|
||||
onClick={reply}
|
||||
size="sm"
|
||||
isDisabled={account.readonly}
|
||||
/>
|
||||
<IconButton
|
||||
variant="link"
|
||||
icon={<ShareIcon />}
|
||||
onClick={share}
|
||||
aria-label="Share Note"
|
||||
title="Share Note"
|
||||
size="sm"
|
||||
isDisabled={account.readonly}
|
||||
/>
|
||||
<ButtonGroup size="sm" variant="link">
|
||||
<NoteZapButton note={event} size="sm" />
|
||||
<NoteLikeButton note={event} size="sm" />
|
||||
</ButtonGroup>
|
||||
<Box flexGrow={1} />
|
||||
<NoteRelays event={event} size="sm" variant="link" />
|
||||
<NoteMenu event={event} size="sm" variant="link" aria-label="More Options" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Heading size="sm" display="inline">
|
||||
<UserLink pubkey={event.pubkey} />
|
||||
</Heading>
|
||||
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
|
||||
{!isMobile && <Flex grow={1} />}
|
||||
<Link as={RouterLink} to={`/n/${normalizeToBech32(event.id, Bech32Prefix.Note)}`} whiteSpace="nowrap">
|
||||
{moment(convertTimestampToDate(event.created_at)).fromNow()}
|
||||
</Link>
|
||||
</Flex>
|
||||
</CardHeader>
|
||||
<CardBody px="2" py="0">
|
||||
<NoteContents event={event} trusted={following.includes(event.pubkey)} maxHeight={maxHeight} />
|
||||
</CardBody>
|
||||
<CardFooter padding="2" display="flex" gap="2">
|
||||
<IconButton
|
||||
variant="link"
|
||||
icon={<ReplyIcon />}
|
||||
title="Reply"
|
||||
aria-label="Reply"
|
||||
onClick={reply}
|
||||
size="sm"
|
||||
isDisabled={account.readonly}
|
||||
/>
|
||||
<IconButton
|
||||
variant="link"
|
||||
icon={<ShareIcon />}
|
||||
onClick={share}
|
||||
aria-label="Share Note"
|
||||
title="Share Note"
|
||||
size="sm"
|
||||
isDisabled={account.readonly}
|
||||
/>
|
||||
<ButtonGroup size="sm" variant="link">
|
||||
<NoteZapButton note={event} size="sm" />
|
||||
<NoteLikeButton note={event} size="sm" />
|
||||
</ButtonGroup>
|
||||
<Box flexGrow={1} />
|
||||
<NoteRelays event={event} size="sm" variant="link" />
|
||||
<NoteMenu event={event} size="sm" variant="link" aria-label="More Options" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</ExpandProvider>
|
||||
);
|
||||
});
|
||||
|
@@ -1,25 +1,14 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
AspectRatio,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
IconButton,
|
||||
Image,
|
||||
ImageProps,
|
||||
Link,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { AspectRatio, Box, Button, ButtonGroup, Image, ImageProps, Link, useDisclosure } from "@chakra-ui/react";
|
||||
import { InlineInvoiceCard } from "../inline-invoice-card";
|
||||
import { TweetEmbed } from "../tweet-embed";
|
||||
import { UserLink } from "../user-link";
|
||||
import { normalizeToHex } from "../../helpers/nip19";
|
||||
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
|
||||
import { NoteLink } from "../note-link";
|
||||
import settings from "../../services/settings";
|
||||
import styled from "@emotion/styled";
|
||||
import QuoteNote from "./quote-note";
|
||||
// import { ExternalLinkIcon } from "../icons";
|
||||
import { useExpand } from "./expanded";
|
||||
|
||||
const BlurredImage = (props: ImageProps) => {
|
||||
const { isOpen, onToggle } = useDisclosure();
|
||||
@@ -299,33 +288,37 @@ export type NoteContentsProps = {
|
||||
|
||||
export const NoteContents = React.memo(({ event, trusted, maxHeight }: NoteContentsProps) => {
|
||||
const parts = embedContent(event.content, event, trusted ?? false);
|
||||
const [height, setHeight] = useState(maxHeight);
|
||||
const expand = useExpand();
|
||||
const [innerHeight, setInnerHeight] = useState(0);
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const testHeight = useCallback(() => {
|
||||
if (ref.current && maxHeight) {
|
||||
const rect = ref.current.getClientRects()[0];
|
||||
setHeight(rect.height < maxHeight ? undefined : maxHeight);
|
||||
setInnerHeight(rect.height);
|
||||
}
|
||||
}, [maxHeight, setHeight]);
|
||||
}, [maxHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
testHeight();
|
||||
}, [testHeight]);
|
||||
|
||||
const showOverlay = !!maxHeight && !expand?.expanded && innerHeight > maxHeight;
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
whiteSpace="pre-wrap"
|
||||
maxHeight={height}
|
||||
maxHeight={!expand?.expanded ? maxHeight : undefined}
|
||||
position="relative"
|
||||
overflow={maxHeight ? "hidden" : "initial"}
|
||||
overflow={maxHeight && !expand?.expanded ? "hidden" : "initial"}
|
||||
onLoad={() => testHeight()}
|
||||
>
|
||||
{parts.map((part, i) => (
|
||||
<span key={"part-" + i}>{part}</span>
|
||||
))}
|
||||
{height && <GradientOverlay onClick={() => setHeight(undefined)} />}
|
||||
<div ref={ref}>
|
||||
{parts.map((part, i) => (
|
||||
<span key={"part-" + i}>{part}</span>
|
||||
))}
|
||||
</div>
|
||||
{showOverlay && <GradientOverlay onClick={expand?.onExpand} />}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
@@ -78,6 +78,7 @@ export default function ZapModal({
|
||||
|
||||
const onSubmitZap: SubmitHandler<FormValues> = async (values) => {
|
||||
try {
|
||||
if (!tipAddress) throw new Error("No lightning address");
|
||||
if (lnurlMetadata) {
|
||||
const amountInMilisat = values.amount * 1000;
|
||||
|
||||
@@ -123,7 +124,7 @@ export default function ZapModal({
|
||||
setInvoice(payRequest);
|
||||
} else throw new Error("Failed to get invoice");
|
||||
}
|
||||
} else throw new Error("No lightning address");
|
||||
} else throw new Error("Failed to get LNURL metadata");
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
}
|
||||
@@ -157,8 +158,16 @@ export default function ZapModal({
|
||||
);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
// if there was an invoice and we a closing the modal. presume it was paid
|
||||
if (invoice && onPaid) {
|
||||
onPaid();
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} {...props}>
|
||||
<Modal onClose={handleClose} {...props}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalBody padding="4">
|
||||
|
Reference in New Issue
Block a user