UNPKG

@nostr-dev-kit/ndk-mobile

Version:

NDK Mobile

309 lines (298 loc) 7.42 kB
"use strict"; /** * A React Native component that renders Nostr event content with rich formatting support. * Handles rendering of mentions, hashtags, URLs, emojis, and images within event content. * * Features: * - Renders nostr: mentions with optional custom mention component * - Formats hashtags with optional press handling * - Renders URLs and images with press handling * - Supports custom emoji rendering from event tags * - Special handling for reaction events (❤️, 👎) * * @package @nostr-dev-kit/ndk-mobile */ import { NDKKind, NDKUser } from "@nostr-dev-kit/ndk-hooks"; import { Image } from "expo-image"; // biome-ignore lint/style/useImportType: <explanation> import React from "react"; import { useEffect, useState } from "react"; import { Pressable, StyleSheet, Text } from "react-native"; import { useNDK, useProfileValue } from "@nostr-dev-kit/ndk-hooks"; import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; const styles = StyleSheet.create({ mention: { fontWeight: "bold", color: "#0066CC" }, hashtag: { fontWeight: "bold", color: "#0066CC" }, url: { fontWeight: "bold", color: "#0066CC", textDecorationLine: "underline" }, image: { width: "100%", height: "100%", resizeMode: "cover", borderRadius: 12 } }); /** * Props for the EventContent component */ /** * Renders an emoji from an event's emoji tags */ function RenderEmoji({ shortcode, event, fontSize }) { if (!event) return /*#__PURE__*/_jsxs(Text, { style: { fontSize }, children: [":", shortcode, ":"] }); const emojiTag = event.tags.find(tag => tag[0] === "emoji" && tag[1] === shortcode); if (!emojiTag || !emojiTag[2]) return /*#__PURE__*/_jsxs(Text, { style: { fontSize }, children: [":", shortcode, ":"] }); const emojiSize = fontSize || 14; return /*#__PURE__*/_jsx(Image, { source: { uri: emojiTag[2] }, style: { width: emojiSize, height: emojiSize, resizeMode: "contain" } }); } /** * Renders a hashtag with optional press handling */ function RenderHashtag({ hashtag, onHashtagPress, fontSize, style }) { const combinedStyle = [styles.hashtag, { fontSize }, style]; if (onHashtagPress) { return /*#__PURE__*/_jsxs(Text, { onPress: () => onHashtagPress(`#${hashtag}`), style: combinedStyle, children: ["#", hashtag] }); } return /*#__PURE__*/_jsxs(Text, { style: combinedStyle, children: ["#", hashtag] }); } /** * Renders a Nostr mention (npub or nprofile) with optional custom component */ function RenderMention({ user, onUserPress, MentionComponent, fontSize, style }) { const userProfile = useProfileValue(user.pubkey); const combinedStyle = [styles.mention, { fontSize }, style]; return /*#__PURE__*/_jsxs(Text, { style: combinedStyle, onPress: () => onUserPress?.(user.pubkey), children: ["@", MentionComponent ? /*#__PURE__*/_jsx(MentionComponent, { pubkey: user.pubkey }) : userProfile?.name ?? user.pubkey.substring(0, 8)] }); } function RenderEvent({ entity, onUserPress }) { const { ndk } = useNDK(); const [event, setEvent] = useState(null); useEffect(() => { if (!entity) return; ndk.fetchEvent(entity).then(event => setEvent(event)); }, [entity]); if (!event) return /*#__PURE__*/_jsx(Text, { children: entity }); return /*#__PURE__*/_jsx(EventContent, { event: event, onUserPress: onUserPress }); } /** * Renders a part of the content with appropriate formatting based on content type * Handles: * - Emoji shortcodes (:shortcode:) * - Image URLs * - Regular URLs * - Nostr mentions (nostr:npub1...) * - Hashtags (#hashtag) * - Plain text */ function RenderPart({ part, onUserPress, onHashtagPress, onUrlPress, MentionComponent, event, style, ...props }) { const { ndk } = useNDK(); const fontSize = style?.fontSize; // Check for emoji shortcode const emojiMatch = part.match(/^:([a-zA-Z0-9_+-]+):$/); if (emojiMatch) { return /*#__PURE__*/_jsx(RenderEmoji, { shortcode: emojiMatch[1], event: event, fontSize: fontSize }); } if (part.startsWith("https://") && part.match(/\.(jpg|jpeg|png|gif)/)) { return /*#__PURE__*/_jsx(Pressable, { onPress: () => onUrlPress?.(part), children: /*#__PURE__*/_jsx(Image, { source: { uri: part }, style: [styles.image, style] }) }); } if (part.startsWith("https://") || part.startsWith("http://")) { return /*#__PURE__*/_jsx(Text, { style: [styles.url, style], onPress: () => onUrlPress?.(part), children: part }); } const mentionMatch = part.match(/nostr:([a-zA-Z0-9]+)/)?.[1]; if (mentionMatch) { const entity = ndk.getEntity(mentionMatch); if (entity instanceof NDKUser) { if (MentionComponent) { return /*#__PURE__*/_jsx(MentionComponent, { pubkey: entity.pubkey }); } return /*#__PURE__*/_jsx(RenderMention, { user: entity, onUserPress: onUserPress, fontSize: fontSize, style: style }); } if (entity) { return /*#__PURE__*/_jsx(RenderEvent, { entity: mentionMatch, onUserPress: onUserPress }); } } const hashtagMatch = part.match(/^#([\p{L}\p{N}_\-]+)/u); if (hashtagMatch) { return /*#__PURE__*/_jsx(RenderHashtag, { hashtag: hashtagMatch[1], onHashtagPress: onHashtagPress, fontSize: fontSize, style: style }); } return /*#__PURE__*/_jsx(Text, { style: { ...style, fontSize }, ...props, children: part }); } /** * Main component for rendering Nostr event content with rich formatting * * @example * ```tsx * // Basic usage * <EventContent event={ndkEvent} /> * * // With custom handlers * <EventContent * event={ndkEvent} * onUserPress={(pubkey) => console.log('User pressed:', pubkey)} * onHashtagPress={(hashtag) => console.log('Hashtag pressed:', hashtag)} * onUrlPress={(url) => console.log('URL pressed:', url)} * /> * * // With custom mention component * <EventContent * event={ndkEvent} * MentionComponent={({ pubkey }) => <UserName pubkey={pubkey} />} * /> * ``` */ const EventContent = ({ event, numberOfLines, content, style, onUserPress, onHashtagPress, onUrlPress, MentionComponent, ...props }) => { if (!event && !content) return null; // Handle reaction events if (event?.kind === NDKKind.Reaction) { return /*#__PURE__*/_jsx(Text, { style: style, children: event.content || "❤️" }); } const contentToRender = content || event?.content || ""; const parts = contentToRender.split(/(\s+|(?=https?:\/\/)|(?<=\s)#[\p{L}\p{N}_\-]+|(?<=\s)nostr:[a-zA-Z0-9]+|:[a-zA-Z0-9_+-]+:)/u); return /*#__PURE__*/_jsx(Text, { numberOfLines: numberOfLines, style: style, ...props, children: parts.map((part, index) => /*#__PURE__*/_jsx(RenderPart, { part: part, onUserPress: onUserPress, onHashtagPress: onHashtagPress, onUrlPress: onUrlPress, MentionComponent: MentionComponent, event: event, style: style }, index)) }); }; export default EventContent; //# sourceMappingURL=content.js.map