diff --git a/.changeset/poor-pumas-rush.md b/.changeset/poor-pumas-rush.md new file mode 100644 index 000000000..7c511324c --- /dev/null +++ b/.changeset/poor-pumas-rush.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Clean up embedded note component diff --git a/src/components/embed-event/event-types/embedded-note.tsx b/src/components/embed-event/event-types/embedded-note.tsx index bdb1e36d0..03e0c3a2a 100644 --- a/src/components/embed-event/event-types/embedded-note.tsx +++ b/src/components/embed-event/event-types/embedded-note.tsx @@ -1,6 +1,7 @@ -import { Button, Card, CardBody, CardHeader, CardProps, Spacer, useDisclosure } from "@chakra-ui/react"; +import { MouseEventHandler, useCallback } from "react"; +import { Card, CardProps, Flex, LinkBox, LinkOverlay, Spacer } from "@chakra-ui/react"; +import { Link as RouterLink } from "react-router-dom"; -import { NoteContents } from "../../note/note-contents"; import { NostrEvent } from "../../../types/nostr-event"; import { UserAvatarLink } from "../../user-avatar-link"; import { UserLink } from "../../user-link"; @@ -10,33 +11,46 @@ import appSettings from "../../../services/settings/app-settings"; import EventVerificationIcon from "../../event-verification-icon"; import { TrustProvider } from "../../../providers/trust"; import { NoteLink } from "../../note-link"; -import { ArrowDownSIcon, ArrowUpSIcon } from "../../icons"; import Timestamp from "../../timestamp"; -import OpenInDrawerButton from "../../open-in-drawer-button"; 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: rgba(0, 0, 0, 0.15); + } +`; export default function EmbeddedNote({ event, ...props }: Omit & { event: NostrEvent }) { const { showSignatureVerification } = useSubject(appSettings); - const expand = useDisclosure(); + const navigate = useNavigateInDrawer(); + const to = `/n/${getSharableEventAddress(event)}`; + + const handleClick = useCallback( + (e) => { + e.preventDefault(); + navigate(to); + }, + [navigate, to], + ); return ( - - + + - - + {showSignatureVerification && } - - {expand.isOpen && } + + ); diff --git a/src/components/note/inline-note-content.tsx b/src/components/note/inline-note-content.tsx new file mode 100644 index 000000000..55f964a6c --- /dev/null +++ b/src/components/note/inline-note-content.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { Box, BoxProps } from "@chakra-ui/react"; +import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; +import { EmbedableContent, embedUrls, truncateEmbedableContent } from "../../helpers/embeds"; +import { embedNostrLinks, embedNostrMentions, embedNostrHashtags, embedEmoji, renderGenericUrl } from "../embed-types"; +import { LightboxProvider } from "../lightbox-provider"; + +function buildContents(event: NostrEvent | DraftNostrEvent) { + let content: EmbedableContent = [event.content.trim().replace(/\n+/g, "\n")]; + + // common + content = embedUrls(content, [renderGenericUrl]); + + // nostr + content = embedNostrLinks(content); + content = embedNostrMentions(content, event); + content = embedNostrHashtags(content, event); + content = embedEmoji(content, event); + + return content; +} + +export type NoteContentsProps = { + event: NostrEvent | DraftNostrEvent; + maxLength?: number; +}; + +export const InlineNoteContent = React.memo( + ({ event, maxLength, ...props }: NoteContentsProps & Omit) => { + let content = buildContents(event); + let truncated = maxLength !== undefined ? truncateEmbedableContent(content, maxLength) : content; + + return ( + + + {truncated} + {truncated !== content ? "..." : null} + + + ); + }, +); diff --git a/src/components/note/note-contents.tsx b/src/components/note/note-contents.tsx index 63d3faa57..1dfbaadcb 100644 --- a/src/components/note/note-contents.tsx +++ b/src/components/note/note-contents.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Box, BoxProps } from "@chakra-ui/react"; import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; -import { EmbedableContent, embedUrls } from "../../helpers/embeds"; +import { EmbedableContent, embedUrls, truncateEmbedableContent } from "../../helpers/embeds"; import { embedLightningInvoice, embedNostrLinks, @@ -60,11 +60,16 @@ function buildContents(event: NostrEvent | DraftNostrEvent, simpleLinks = false) export type NoteContentsProps = { event: NostrEvent | DraftNostrEvent; noOpenGraphLinks?: boolean; + maxLength?: number; }; export const NoteContents = React.memo( - ({ event, noOpenGraphLinks, ...props }: NoteContentsProps & Omit) => { - const content = buildContents(event, noOpenGraphLinks); + ({ event, noOpenGraphLinks, maxLength, ...props }: NoteContentsProps & Omit) => { + let content = buildContents(event, noOpenGraphLinks); + + if (maxLength !== undefined) { + content = truncateEmbedableContent(content, maxLength); + } return ( diff --git a/src/helpers/embeds.ts b/src/helpers/embeds.ts index 64c9dc689..43060bba8 100644 --- a/src/helpers/embeds.ts +++ b/src/helpers/embeds.ts @@ -92,3 +92,34 @@ export function embedUrls(content: EmbedableContent, handlers: LinkEmbedHandler[ }, }); } + +export function truncateEmbedableContent(content: EmbedableContent, maxLength = 256) { + let length = 0; + for (let i = 0; i < content.length; i++) { + const chunk = content[i]; + length += typeof chunk === "string" ? chunk.length : 8; + + if (length > maxLength) { + if (typeof chunk === "string") { + const newContent = i > 0 ? content.slice(0, i) : []; + const chunkLength = chunk.length - (length - maxLength); + + // find the nearest newline + const newLines = chunk.matchAll(/\n/g); + for (const match of newLines) { + console.log(match.index, chunkLength, chunk.length); + + if (match.index && match.index > chunkLength) { + newContent.push(chunk.slice(0, match.index)); + return newContent; + } + } + + // just cut the string + newContent.push(chunk.slice(0, maxLength - length)); + return newContent; + } else return content.slice(0, i); + } + } + return content; +}