mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
make notes clickable
This commit is contained in:
parent
07d5e7719e
commit
d2f307642a
5
.changeset/nice-hornets-own.md
Normal file
5
.changeset/nice-hornets-own.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Make notes clickable
|
@ -15,13 +15,7 @@ import Timestamp from "../../timestamp";
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
import { InlineNoteContent } from "../../note/inline-note-content";
|
||||
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
const HoverLinkOverlay = styled(LinkOverlay)`
|
||||
&:hover:before {
|
||||
background-color: var(--chakra-colors-card-hover-overlay);
|
||||
}
|
||||
`;
|
||||
import HoverLinkOverlay from "../../hover-link-overlay";
|
||||
|
||||
export default function EmbeddedNote({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
|
||||
const { showSignatureVerification } = useSubject(appSettings);
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Image, ImageProps } from "@chakra-ui/react";
|
||||
import { Image, ImageProps, Link, LinkProps } from "@chakra-ui/react";
|
||||
|
||||
import appSettings from "../../services/settings/app-settings";
|
||||
import { useTrusted } from "../../providers/trust";
|
||||
@ -61,23 +61,69 @@ export const TrustImage = forwardRef<HTMLImageElement, TrustImageProps>((props,
|
||||
else return <Image {...props} onClick={handleClick} style={{ ...style, ...props.style }} ref={ref} />;
|
||||
});
|
||||
|
||||
export type EmbeddedImageProps = TrustImageProps & {
|
||||
export type EmbeddedImageProps = Omit<LinkProps, "children" | "href" | "onClick"> & {
|
||||
src?: string;
|
||||
event?: NostrEvent;
|
||||
imageProps?: TrustImageProps;
|
||||
};
|
||||
|
||||
export const EmbeddedImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(({ src, event, ...props }, ref) => {
|
||||
const thumbnail = appSettings.value.imageProxy
|
||||
? new URL(`/256,fit/${src}`, appSettings.value.imageProxy).toString()
|
||||
: src;
|
||||
function useImageThumbnail(src?: string) {
|
||||
return appSettings.value.imageProxy ? new URL(`/256,fit/${src}`, appSettings.value.imageProxy).toString() : src;
|
||||
}
|
||||
|
||||
ref = ref || useRef<HTMLImageElement | null>(null);
|
||||
const { show } = useRegisterSlide(
|
||||
ref as MutableRefObject<HTMLImageElement | null>,
|
||||
src ? { type: "image", src, event } : undefined,
|
||||
);
|
||||
export const EmbeddedImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(
|
||||
({ src, event, imageProps, ...props }, ref) => {
|
||||
const thumbnail = useImageThumbnail(src);
|
||||
|
||||
return <TrustImage {...props} src={thumbnail} cursor="pointer" ref={ref} onClick={show} />;
|
||||
});
|
||||
ref = ref || useRef<HTMLImageElement | null>(null);
|
||||
const { show } = useRegisterSlide(
|
||||
ref as MutableRefObject<HTMLImageElement | null>,
|
||||
src ? { type: "image", src, event } : undefined,
|
||||
);
|
||||
const handleClick = useCallback<MouseEventHandler<HTMLElement>>(
|
||||
(e) => {
|
||||
!e.isPropagationStopped() && show();
|
||||
e.preventDefault();
|
||||
},
|
||||
[show],
|
||||
);
|
||||
|
||||
// NOTE: the parent <div> has display=block and and <a> has inline-block
|
||||
// this is so that the <a> element can act like a block without being full width
|
||||
return (
|
||||
<div>
|
||||
<Link href={src} isExternal onClick={handleClick} display="inline-block" {...props}>
|
||||
<TrustImage {...imageProps} src={thumbnail} cursor="pointer" ref={ref} onClick={handleClick} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const GalleryImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(
|
||||
({ src, event, imageProps, ...props }, ref) => {
|
||||
const thumbnail = useImageThumbnail(src);
|
||||
|
||||
ref = ref || useRef<HTMLImageElement | null>(null);
|
||||
const { show } = useRegisterSlide(
|
||||
ref as MutableRefObject<HTMLImageElement | null>,
|
||||
src ? { type: "image", src, event } : undefined,
|
||||
);
|
||||
const handleClick = useCallback<MouseEventHandler<HTMLElement>>(
|
||||
(e) => {
|
||||
!e.isPropagationStopped() && show();
|
||||
e.preventDefault();
|
||||
},
|
||||
[show],
|
||||
);
|
||||
|
||||
return (
|
||||
<Link href={src} isExternal onClick={handleClick} {...props}>
|
||||
<TrustImage src={thumbnail} cursor="pointer" ref={ref} onClick={handleClick} {...imageProps} />
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export function ImageGallery({ images, event }: { images: string[]; event?: NostrEvent }) {
|
||||
const photos = useMemo(() => {
|
||||
@ -93,7 +139,9 @@ export function ImageGallery({ images, event }: { images: string[]; event?: Nost
|
||||
<PhotoGallery
|
||||
layout="rows"
|
||||
photos={photos}
|
||||
renderPhoto={({ photo, imageProps, wrapperStyle }) => <EmbeddedImage {...imageProps} />}
|
||||
renderPhoto={({ photo, imageProps, wrapperStyle }) => (
|
||||
<GalleryImage src={imageProps.src} style={imageProps.style} />
|
||||
)}
|
||||
targetRowHeight={(containerWidth) => containerWidth / rowMultiplier}
|
||||
/>
|
||||
);
|
||||
@ -165,5 +213,5 @@ export function embedImageGallery(content: EmbedableContent, event?: NostrEvent)
|
||||
export function renderImageUrl(match: URL) {
|
||||
if (!isImageURL(match)) return null;
|
||||
|
||||
return <EmbeddedImage src={match.toString()} maxH={["initial", "35vh"]} />;
|
||||
return <EmbeddedImage src={match.toString()} imageProps={{ maxH: ["initial", "35vh"] }} />;
|
||||
}
|
||||
|
10
src/components/hover-link-overlay.tsx
Normal file
10
src/components/hover-link-overlay.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { LinkOverlay } from "@chakra-ui/react";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
const HoverLinkOverlay = styled(LinkOverlay)`
|
||||
&:hover:before {
|
||||
background-color: var(--chakra-colors-card-hover-overlay);
|
||||
}
|
||||
`;
|
||||
|
||||
export default HoverLinkOverlay;
|
@ -10,6 +10,7 @@ import {
|
||||
Flex,
|
||||
IconButton,
|
||||
Link,
|
||||
LinkBox,
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
@ -44,14 +45,16 @@ import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
import { COMMUNITY_DEFINITION_KIND, getCommunityName } from "../../helpers/nostr/communities";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { useBreakpointValue } from "../../providers/breakpoint-provider";
|
||||
import HoverLinkOverlay from "../hover-link-overlay";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
export type NoteProps = Omit<CardProps, "children"> & {
|
||||
event: NostrEvent;
|
||||
variant?: CardProps["variant"];
|
||||
showReplyButton?: boolean;
|
||||
hideDrawerButton?: boolean;
|
||||
hideThreadLink?: boolean;
|
||||
registerIntersectionEntity?: boolean;
|
||||
clickable?: boolean;
|
||||
};
|
||||
export const Note = React.memo(
|
||||
({
|
||||
@ -59,8 +62,8 @@ export const Note = React.memo(
|
||||
variant = "outline",
|
||||
showReplyButton,
|
||||
hideDrawerButton,
|
||||
hideThreadLink,
|
||||
registerIntersectionEntity = true,
|
||||
clickable = true,
|
||||
...props
|
||||
}: NoteProps) => {
|
||||
const account = useCurrentAccount();
|
||||
@ -87,29 +90,26 @@ export const Note = React.memo(
|
||||
<TrustProvider event={event}>
|
||||
<ExpandProvider>
|
||||
<Card
|
||||
as={LinkBox}
|
||||
variant={variant}
|
||||
ref={registerIntersectionEntity ? ref : undefined}
|
||||
data-event-id={event.id}
|
||||
{...props}
|
||||
>
|
||||
{clickable && <HoverLinkOverlay as={RouterLink} to={`/n/${nip19.noteEncode(event.id)}`} />}
|
||||
<CardHeader p="2">
|
||||
<Flex flex="1" gap="2" alignItems="center">
|
||||
<UserAvatarLink pubkey={event.pubkey} size={["xs", "sm"]} />
|
||||
<UserLink pubkey={event.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
|
||||
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
|
||||
<Flex grow={1} />
|
||||
{!hideThreadLink && (
|
||||
<NoteLink noteId={event.id} whiteSpace="nowrap" color="current">
|
||||
thread
|
||||
</NoteLink>
|
||||
)}
|
||||
{showSignatureVerification && <EventVerificationIcon event={event} />}
|
||||
{!hideDrawerButton && (
|
||||
<OpenInDrawerButton to={`/n/${getSharableEventAddress(event)}`} size="sm" variant="ghost" />
|
||||
)}
|
||||
<NoteLink noteId={event.id} whiteSpace="nowrap" color="current">
|
||||
<Link as={RouterLink} whiteSpace="nowrap" color="current" to={`/n/${nip19.noteEncode(event.id)}`}>
|
||||
<Timestamp timestamp={event.created_at} />
|
||||
</NoteLink>
|
||||
</Link>
|
||||
</Flex>
|
||||
{community && (
|
||||
<Text fontStyle="italic">
|
||||
|
@ -7,7 +7,7 @@ import useSubject from "../../../hooks/use-subject";
|
||||
import { getMatchLink } from "../../../helpers/regexp";
|
||||
import { LightboxProvider } from "../../lightbox-provider";
|
||||
import { isImageURL } from "../../../helpers/url";
|
||||
import { EmbeddedImage, EmbeddedImageProps } from "../../embed-types";
|
||||
import { EmbeddedImage, EmbeddedImageProps, GalleryImage } from "../../embed-types";
|
||||
import { TrustProvider } from "../../../providers/trust";
|
||||
import PhotoGallery, { PhotoWithoutSize } from "../../photo-gallery";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||
@ -15,11 +15,11 @@ import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { getEventUID } from "../../../helpers/nostr/events";
|
||||
import { useBreakpointValue } from "../../../providers/breakpoint-provider";
|
||||
|
||||
function GalleryImage({ event, ...props }: EmbeddedImageProps & { event: NostrEvent }) {
|
||||
function CustomGalleryImage({ event, ...props }: EmbeddedImageProps & { event: NostrEvent }) {
|
||||
const ref = useRef<HTMLImageElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
||||
|
||||
return <EmbeddedImage {...props} event={event} ref={ref} />;
|
||||
return <GalleryImage {...props} event={event} ref={ref} />;
|
||||
}
|
||||
|
||||
type PhotoWithEvent = PhotoWithoutSize & { event: NostrEvent };
|
||||
@ -30,7 +30,9 @@ function ImageGallery({ images }: { images: PhotoWithEvent[] }) {
|
||||
<PhotoGallery<Photo & { event: NostrEvent }>
|
||||
layout="masonry"
|
||||
photos={images}
|
||||
renderPhoto={({ photo, imageProps }) => <GalleryImage event={photo.event} {...imageProps} />}
|
||||
renderPhoto={({ photo, imageProps }) => (
|
||||
<CustomGalleryImage src={imageProps.src} event={photo.event} style={imageProps.style} />
|
||||
)}
|
||||
columns={rowMultiplier}
|
||||
/>
|
||||
);
|
||||
|
@ -33,8 +33,8 @@ export default function buildTheme(
|
||||
semanticTokens: {
|
||||
colors: {
|
||||
"card-hover-overlay": {
|
||||
_light: "blackAlpha.100",
|
||||
_dark: "whiteAlpha.100",
|
||||
_light: "blackAlpha.50",
|
||||
_dark: "whiteAlpha.50",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -47,8 +47,8 @@ export const ThreadPost = ({ post, initShowReplies, focusId }: ThreadItemProps)
|
||||
<Note
|
||||
event={post.event}
|
||||
borderColor={focusId === post.event.id ? "blue.500" : undefined}
|
||||
clickable={focusId !== post.event.id}
|
||||
hideDrawerButton
|
||||
hideThreadLink
|
||||
/>
|
||||
</TrustProvider>
|
||||
)}
|
||||
|
@ -53,13 +53,13 @@ export default function NoteView() {
|
||||
pageContent = (
|
||||
<>
|
||||
{parentPosts.map((parent) => (
|
||||
<Note key={parent.event.id + "-rely"} event={parent.event} hideDrawerButton hideThreadLink />
|
||||
<Note key={parent.event.id + "-rely"} event={parent.event} hideDrawerButton />
|
||||
))}
|
||||
<ThreadPost key={post.event.id} post={post} initShowReplies focusId={focusId} />
|
||||
</>
|
||||
);
|
||||
} else if (events[focusId]) {
|
||||
pageContent = <Note event={events[focusId]} variant="filled" hideDrawerButton hideThreadLink />;
|
||||
pageContent = <Note event={events[focusId]} variant="filled" hideDrawerButton />;
|
||||
}
|
||||
|
||||
return <VerticalPageLayout>{pageContent}</VerticalPageLayout>;
|
||||
|
Loading…
x
Reference in New Issue
Block a user