Clean up embedded note component

This commit is contained in:
hzrd149 2023-09-12 08:54:01 -05:00
parent 36b1114d31
commit dfce72d57c
5 changed files with 113 additions and 16 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Clean up embedded note component

View File

@ -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<CardProps, "children"> & { event: NostrEvent }) {
const { showSignatureVerification } = useSubject(appSettings);
const expand = useDisclosure();
const navigate = useNavigateInDrawer();
const to = `/n/${getSharableEventAddress(event)}`;
const handleClick = useCallback<MouseEventHandler>(
(e) => {
e.preventDefault();
navigate(to);
},
[navigate, to],
);
return (
<TrustProvider event={event}>
<Card {...props}>
<CardHeader padding="2" display="flex" gap="2" alignItems="center" flexWrap="wrap">
<Card as={LinkBox} {...props}>
<Flex p="2" gap="2" alignItems="center">
<UserAvatarLink pubkey={event.pubkey} size="xs" />
<UserLink pubkey={event.pubkey} fontWeight="bold" isTruncated fontSize="lg" />
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
<Button size="sm" onClick={expand.onToggle} leftIcon={expand.isOpen ? <ArrowUpSIcon /> : <ArrowDownSIcon />}>
Expand
</Button>
<OpenInDrawerButton to={`/n/${getSharableEventAddress(event)}`} size="sm" />
<HoverLinkOverlay as={RouterLink} to={to} onClick={handleClick} />
<Spacer />
{showSignatureVerification && <EventVerificationIcon event={event} />}
<NoteLink noteId={event.id} color="current" whiteSpace="nowrap">
<Timestamp timestamp={event.created_at} />
</NoteLink>
</CardHeader>
<CardBody p="0">{expand.isOpen && <NoteContents px="2" event={event} />}</CardBody>
</Flex>
<InlineNoteContent px="2" event={event} maxLength={96} />
</Card>
</TrustProvider>
);

View File

@ -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<BoxProps, "children">) => {
let content = buildContents(event);
let truncated = maxLength !== undefined ? truncateEmbedableContent(content, maxLength) : content;
return (
<LightboxProvider>
<Box whiteSpace="pre-wrap" {...props}>
{truncated}
{truncated !== content ? "..." : null}
</Box>
</LightboxProvider>
);
},
);

View File

@ -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<BoxProps, "children">) => {
const content = buildContents(event, noOpenGraphLinks);
({ event, noOpenGraphLinks, maxLength, ...props }: NoteContentsProps & Omit<BoxProps, "children">) => {
let content = buildContents(event, noOpenGraphLinks);
if (maxLength !== undefined) {
content = truncateEmbedableContent(content, maxLength);
}
return (
<LightboxProvider>

View File

@ -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;
}