Skip to content

Commit

Permalink
make notes clickable
Browse files Browse the repository at this point in the history
  • Loading branch information
hzrd149 committed Oct 13, 2023
1 parent 07d5e77 commit d2f3076
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 41 deletions.
5 changes: 5 additions & 0 deletions .changeset/nice-hornets-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostrudel": minor
---

Make notes clickable
8 changes: 1 addition & 7 deletions src/components/embed-event/event-types/embedded-note.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
80 changes: 64 additions & 16 deletions src/components/embed-types/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;

ref = ref || useRef<HTMLImageElement | null>(null);
const { show } = useRegisterSlide(
ref as MutableRefObject<HTMLImageElement | null>,
src ? { type: "image", src, event } : undefined,
);
function useImageThumbnail(src?: string) {
return appSettings.value.imageProxy ? new URL(`/256,fit/${src}`, appSettings.value.imageProxy).toString() : src;
}

return <TrustImage {...props} src={thumbnail} cursor="pointer" ref={ref} onClick={show} />;
});
export const EmbeddedImage = 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],
);

// 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(() => {
Expand All @@ -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}
/>
);
Expand Down Expand Up @@ -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 changes: 10 additions & 0 deletions src/components/hover-link-overlay.tsx
Original file line number Diff line number Diff line change
@@ -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;
18 changes: 9 additions & 9 deletions src/components/note/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Flex,
IconButton,
Link,
LinkBox,
Text,
useDisclosure,
} from "@chakra-ui/react";
Expand Down Expand Up @@ -44,23 +45,25 @@ 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(
({
event,
variant = "outline",
showReplyButton,
hideDrawerButton,
hideThreadLink,
registerIntersectionEntity = true,
clickable = true,
...props
}: NoteProps) => {
const account = useCurrentAccount();
Expand All @@ -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">
Expand Down
10 changes: 6 additions & 4 deletions src/components/timeline-page/media-timeline/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ 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";
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 };
Expand All @@ -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}
/>
);
Expand Down
4 changes: 2 additions & 2 deletions src/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/views/note/components/thread-post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/views/note/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down

0 comments on commit d2f3076

Please sign in to comment.