mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-09 20:29:17 +02:00
Clean up embedded note component
This commit is contained in:
parent
36b1114d31
commit
dfce72d57c
5
.changeset/poor-pumas-rush.md
Normal file
5
.changeset/poor-pumas-rush.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Clean up embedded note component
|
@ -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>
|
||||
);
|
||||
|
42
src/components/note/inline-note-content.tsx
Normal file
42
src/components/note/inline-note-content.tsx
Normal 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>
|
||||
);
|
||||
},
|
||||
);
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user