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