import { AUDIOS, IMAGES, NOSTR_EVENTS, NOSTR_MENTIONS, VIDEOS, cn, regionNames, } from "@lume/utils"; import { NDKKind } from "@nostr-dev-kit/ndk"; import { fetch } from "@tauri-apps/plugin-http"; import getUrls from "get-urls"; import { nanoid } from "nanoid"; import { nip19 } from "nostr-tools"; import { ReactNode, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import reactStringReplace from "react-string-replace"; import { Hashtag, ImagePreview, LinkPreview, MentionNote, MentionUser, VideoPreview, useNoteContext, useStorage, } from "../.."; import { NIP89 } from "./nip89"; export function NoteContent({ className, isTranslatable = false, }: { className?: string; isTranslatable?: boolean; }) { const storage = useStorage(); const event = useNoteContext(); const [content, setContent] = useState(event.content); const [translated, setTranslated] = useState(false); const richContent = useMemo(() => { if (event.kind !== NDKKind.Text) return content; let parsedContent: string | ReactNode[] = content.replace(/\n+/g, "\n"); let linkPreview: string = undefined; let images: string[] = []; let videos: string[] = []; let audios: string[] = []; let events: string[] = []; const text = parsedContent; const words = text.split(/( |\n)/); const urls = [...getUrls(text)]; if (storage.settings.media && !storage.settings.lowPower) { images = urls.filter((word) => IMAGES.some((el) => { const url = new URL(word); const extension = url.pathname.split(".")[1]; if (extension === el) return true; return false; }), ); videos = urls.filter((word) => VIDEOS.some((el) => { const url = new URL(word); const extension = url.pathname.split(".")[1]; if (extension === el) return true; return false; }), ); audios = urls.filter((word) => AUDIOS.some((el) => { const url = new URL(word); const extension = url.pathname.split(".")[1]; if (extension === el) return true; return false; }), ); } events = words.filter((word) => NOSTR_EVENTS.some((el) => word.startsWith(el)), ); const hashtags = words.filter((word) => word.startsWith("#")); const mentions = words.filter((word) => NOSTR_MENTIONS.some((el) => word.startsWith(el)), ); try { if (images.length) { for (const image of images) { parsedContent = reactStringReplace( parsedContent, image, (match, i) => , ); } } if (videos.length) { for (const video of videos) { parsedContent = reactStringReplace( parsedContent, video, (match, i) => , ); } } if (audios.length) { for (const audio of audios) { parsedContent = reactStringReplace( parsedContent, audio, (match, i) => , ); } } if (hashtags.length) { for (const hashtag of hashtags) { parsedContent = reactStringReplace( parsedContent, hashtag, (match, i) => { if (storage.settings.hashtag) return ; return null; }, ); } } if (events.length) { for (const event of events) { const address = event .replace("nostr:", "") .replace(/[^a-zA-Z0-9]/g, ""); const decoded = nip19.decode(address); if (decoded.type === "note") { parsedContent = reactStringReplace( parsedContent, event, (match, i) => ( ), ); } if (decoded.type === "nevent") { parsedContent = reactStringReplace( parsedContent, event, (match, i) => ( ), ); } } } if (mentions.length) { for (const mention of mentions) { const address = mention .replace("nostr:", "") .replace("@", "") .replace(/[^a-zA-Z0-9]/g, ""); const decoded = nip19.decode(address); if (decoded.type === "npub") { parsedContent = reactStringReplace( parsedContent, mention, (match, i) => ( ), ); } if (decoded.type === "nprofile" || decoded.type === "naddr") { parsedContent = reactStringReplace( parsedContent, mention, (match, i) => ( ), ); } } } parsedContent = reactStringReplace( parsedContent, /(https?:\/\/\S+)/g, (match, i) => { const url = new URL(match); if (!linkPreview) { linkPreview = match; return ; } return ( {url.toString()} ); }, ); parsedContent = reactStringReplace(parsedContent, "\n", () => { return
; }); if (typeof parsedContent[0] === "string") { parsedContent[0] = parsedContent[0].trimStart(); } return parsedContent; } catch (e) { console.warn("[parser] parse failed: ", e); return parsedContent; } }, [content]); const translate = async () => { try { const res = await fetch("https://translate.nostr.wine/translate", { method: "POST", body: JSON.stringify({ q: content, target: "vi", api_key: storage.settings.translateApiKey, }), headers: { "Content-Type": "application/json" }, }); const data = await res.json(); setContent(data.translatedText); setTranslated(true); } catch (e) { console.error(String(e)); } }; if (event.kind !== NDKKind.Text) { return ; } return (
{richContent}
{isTranslatable && storage.settings.translation ? ( translated ? ( ) : ( ) ) : null}
); }