import "./text.css"; import { useMemo, type ReactNode, type FunctionComponent } from "react"; import { type NostrLink, parseNostrLink, validateNostrLink, } from "@snort/system"; import { Event } from "element/Event"; import { Mention } from "element/mention"; import { Emoji } from "element/emoji"; import { HyperText } from "element/hypertext"; import { splitByUrl } from "utils"; import type { Tags } from "types"; export type Fragment = string | ReactNode; const NostrPrefixRegex = /^nostr:/; const EmojiRegex = /:([\w-]+):/g; function extractLinks(fragments: Fragment[]) { return fragments .map((f) => { if (typeof f === "string") { return splitByUrl(f).map((a) => { const validateLink = () => { const normalizedStr = a.toLowerCase(); if ( normalizedStr.startsWith("web+nostr:") || normalizedStr.startsWith("nostr:") ) { return validateNostrLink(normalizedStr); } return ( normalizedStr.startsWith("http:") || normalizedStr.startsWith("https:") ); }; if (validateLink()) { return {a}; } return a; }); } return f; }) .flat(); } function extractEmoji(fragments: Fragment[], tags: string[][]) { return fragments .map((f) => { if (typeof f === "string") { return f.split(EmojiRegex).map((i) => { const t = tags.find((a) => a[0] === "emoji" && a[1] === i); if (t) { return ; } else { return i; } }); } return f; }) .flat(); } function extractNprofiles(fragments: Fragment[]) { return fragments .map((f) => { if (typeof f === "string") { return f.split(/(nostr:nprofile1[a-z0-9]+)/g).map((i) => { if (i.startsWith("nostr:nprofile1")) { try { const link = parseNostrLink(i.replace(NostrPrefixRegex, "")); return ; } catch (error) { return i; } } else { return i; } }); } return f; }) .flat(); } function extractNpubs(fragments: Fragment[]) { return fragments .map((f) => { if (typeof f === "string") { return f.split(/(nostr:npub1[a-z0-9]+)/g).map((i) => { if (i.startsWith("nostr:npub1")) { try { const link = parseNostrLink(i.replace(NostrPrefixRegex, "")); return ; } catch (error) { return i; } } else { return i; } }); } return f; }) .flat(); } function extractNevents(fragments: Fragment[], Event: NostrComponent) { return fragments .map((f) => { if (typeof f === "string") { return f.split(/(nostr:nevent1[a-z0-9]+)/g).map((i) => { if (i.startsWith("nostr:nevent1")) { try { const link = parseNostrLink(i.replace(NostrPrefixRegex, "")); return ; } catch (error) { return i; } } else { return i; } }); } return f; }) .flat(); } function extractNaddrs(fragments: Fragment[], Address: NostrComponent) { return fragments .map((f) => { if (typeof f === "string") { return f.split(/(nostr:naddr1[a-z0-9]+)/g).map((i) => { if (i.startsWith("nostr:naddr1")) { try { const link = parseNostrLink(i.replace(NostrPrefixRegex, "")); return
; } catch (error) { console.error(error); return i; } } else { return i; } }); } return f; }) .flat(); } function extractNoteIds(fragments: Fragment[], Event: NostrComponent) { return fragments .map((f) => { if (typeof f === "string") { return f.split(/(nostr:note1[a-z0-9]+)/g).map((i) => { if (i.startsWith("nostr:note1")) { try { const link = parseNostrLink(i.replace(NostrPrefixRegex, "")); return ; } catch (error) { return i; } } else { return i; } }); } return f; }) .flat(); } export type NostrComponent = FunctionComponent<{ link: NostrLink }>; export interface NostrComponents { Event: NostrComponent; } const components: NostrComponents = { Event, }; export function transformText( ps: Fragment[], tags: Array, customComponents = components ) { let fragments = extractEmoji(ps, tags); fragments = extractNprofiles(fragments); fragments = extractNevents(fragments, customComponents.Event); fragments = extractNaddrs(fragments, customComponents.Event); fragments = extractNoteIds(fragments, customComponents.Event); fragments = extractNpubs(fragments); fragments = extractLinks(fragments); return fragments; } interface TextProps { content: string; tags: Tags; customComponents?: NostrComponents; } export function Text({ content, tags, customComponents }: TextProps) { // todo: RTL langugage support const element = useMemo(() => { return ( {transformText([content], tags, customComponents)} ); }, [content, tags]); return <>{element}; }