fix: stream cards rendering

This commit is contained in:
2024-05-28 10:47:11 +01:00
parent 0977e61422
commit 05fab75680
6 changed files with 43 additions and 50 deletions

View File

@ -12,7 +12,7 @@ export function Badge({ ev }: { ev: NostrEvent }) {
<img className="badge-thumbnail" src={thumb || image} alt={name} /> <img className="badge-thumbnail" src={thumb || image} alt={name} />
<div className="badge-details"> <div className="badge-details">
<h4 className="badge-name">{name}</h4> <h4 className="badge-name">{name}</h4>
{description?.length > 0 && <p className="badge-description">{description}</p>} {description?.length > 0 && <div className="badge-description">{description}</div>}
</div> </div>
</div> </div>
); );

View File

@ -60,7 +60,7 @@ export function EmojiPack({ ev }: { ev: NostrEvent }) {
{emoji.map(e => { {emoji.map(e => {
const [, name, image] = e; const [, name, image] = e;
return ( return (
<div className="emoji-definition"> <div className="emoji-definition" key={name}>
<img alt={name} className="custom-emoji" src={image} /> <img alt={name} className="custom-emoji" src={image} />
<span className="emoji-name">{name}</span> <span className="emoji-name">{name}</span>
</div> </div>

View File

@ -11,6 +11,7 @@ import LiveStreamClip from "./stream/clip";
import { ExternalLink } from "./external-link"; import { ExternalLink } from "./external-link";
import { extractStreamInfo } from "@/utils"; import { extractStreamInfo } from "@/utils";
import LiveVideoPlayer from "./stream/live-video-player"; import LiveVideoPlayer from "./stream/live-video-player";
import { HTMLProps } from "react";
interface EventProps { interface EventProps {
link: NostrLink; link: NostrLink;
@ -64,9 +65,9 @@ export function NostrEvent({ ev }: { ev: TaggedNostrEvent }) {
} }
} }
export function EventEmbed({ link }: EventProps) { export function EventEmbed({ link, ...props }: EventProps & HTMLProps<HTMLDivElement>) {
const event = useEventFeed(link); const event = useEventFeed(link);
if (event) { if (event) {
return <NostrEvent ev={event} />; return <NostrEvent ev={event} {...props} />;
} }
} }

View File

@ -14,85 +14,86 @@ interface MarkdownProps {
} }
const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps, ref) => { const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps, ref) => {
function renderToken(t: Token, key: number): ReactNode { let ctr = 0;
function renderToken(t: Token): ReactNode {
try { try {
switch (t.type) { switch (t.type) {
case "paragraph": { case "paragraph": {
return <p key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</p>; return <div key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</div>;
} }
case "image": { case "image": {
return <img key={key} src={t.href} />; return <img key={ctr++} src={t.href} />;
} }
case "heading": { case "heading": {
switch (t.depth) { switch (t.depth) {
case 1: case 1:
return <h1 key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h1>; return <h1 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h1>;
case 2: case 2:
return <h2 key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h2>; return <h2 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h2>;
case 3: case 3:
return <h3 key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h3>; return <h3 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h3>;
case 4: case 4:
return <h4 key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h4>; return <h4 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h4>;
case 5: case 5:
return <h5 key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h5>; return <h5 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h5>;
case 6: case 6:
return <h6 key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h6>; return <h6 key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</h6>;
} }
throw new Error("Invalid heading"); throw new Error("Invalid heading");
} }
case "codespan": { case "codespan": {
return <code key={key}>{t.raw}</code>; return <code key={ctr++}>{t.raw}</code>;
} }
case "code": { case "code": {
return <pre key={key}>{t.raw}</pre>; return <pre key={ctr++}>{t.raw}</pre>;
} }
case "br": { case "br": {
return <br key={key} />; return <br key={ctr++} />;
} }
case "hr": { case "hr": {
return <hr key={key} />; return <hr key={ctr++} />;
} }
case "blockquote": { case "blockquote": {
return <blockquote key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</blockquote>; return <blockquote key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</blockquote>;
} }
case "link": { case "link": {
return ( return (
<HyperText link={t.href} key={key}> <HyperText link={t.href} key={ctr++}>
{t.tokens ? t.tokens.map(renderToken) : t.raw} {t.tokens ? t.tokens.map(renderToken) : t.raw}
</HyperText> </HyperText>
); );
} }
case "list": { case "list": {
if (t.ordered) { if (t.ordered) {
return <ol key={key}>{t.items.map(renderToken)}</ol>; return <ol key={ctr++}>{t.items.map(renderToken)}</ol>;
} else { } else {
return <ul key={key}>{t.items.map(renderToken)}</ul>; return <ul key={ctr++}>{t.items.map(renderToken)}</ul>;
} }
} }
case "list_item": { case "list_item": {
return <li key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</li>; return <li key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</li>;
} }
case "em": { case "em": {
return <em key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</em>; return <em key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</em>;
} }
case "del": { case "del": {
return <s key={key}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</s>; return <s key={ctr++}>{t.tokens ? t.tokens.map(renderToken) : t.raw}</s>;
} }
case "table": { case "table": {
return ( return (
<table className="table-auto border-collapse" key={key}> <table className="table-auto border-collapse" key={ctr++}>
<thead> <thead>
<tr> <tr>
{(t.header as Tokens.TableCell[]).map((v, h_key) => ( {(t.header as Tokens.TableCell[]).map(v => (
<th className="border" key={h_key}> <th className="border" key={ctr++}>
{v.tokens ? v.tokens.map(renderToken) : v.text} {v.tokens ? v.tokens.map(renderToken) : v.text}
</th> </th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{(t.rows as Tokens.TableCell[][]).map((v, r_key) => ( {(t.rows as Tokens.TableCell[][]).map(v => (
<tr key={r_key}> <tr key={ctr++}>
{v.map((d, d_key) => ( {v.map((d, d_key) => (
<td className="border px-2 py-1" key={d_key}> <td className="border px-2 py-1" key={d_key}>
{d.tokens ? d.tokens.map(renderToken) : d.text} {d.tokens ? d.tokens.map(renderToken) : d.text}
@ -111,7 +112,7 @@ const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps
if (props.plainText ?? false) { if (props.plainText ?? false) {
return t.raw; return t.raw;
} }
return <Text content={t.raw} tags={[]} key={key} />; return <Text content={t.raw} tags={[]} key={ctr++} />;
} }
} }
} catch (e) { } catch (e) {

View File

@ -37,7 +37,6 @@ export interface SendZapsProps {
onFinish: () => void; onFinish: () => void;
onTargetReady?: () => void; onTargetReady?: () => void;
button?: ReactNode; button?: ReactNode;
key?: string;
} }
export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish, onTargetReady }: SendZapsProps) { export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish, onTargetReady }: SendZapsProps) {
@ -216,7 +215,7 @@ export function SendZapsDialog(props: Omit<SendZapsProps, "onFinish">) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [ready, setReady] = useState(false); const [ready, setReady] = useState(false);
return ( return (
<Fragment key={props.key}> <Fragment>
{props.button ? ( {props.button ? (
<div onClick={() => setOpen(true)}>{props.button}</div> <div onClick={() => setOpen(true)}>{props.button}</div>
) : ( ) : (

View File

@ -1,5 +1,5 @@
import { NostrLink, NostrPrefix, ParsedFragment, transformText, tryParseNostrLink } from "@snort/system"; import { NostrLink, NostrPrefix, ParsedFragment, transformText, tryParseNostrLink } from "@snort/system";
import { Fragment, FunctionComponent, useMemo } from "react"; import { FunctionComponent, useMemo } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Emoji } from "./emoji"; import { Emoji } from "./emoji";
@ -22,10 +22,11 @@ export function Text({ content, tags, eventComponent, className }: TextProps) {
return transformText(content, tags); return transformText(content, tags);
}, [content, tags]); }, [content, tags]);
function renderFrag(f: ParsedFragment, key: number) { let ctr = 0;
function renderFrag(f: ParsedFragment) {
switch (f.type) { switch (f.type) {
case "custom_emoji": case "custom_emoji":
return <Emoji name={f.content} url={f.content} key={key} />; return <Emoji name={f.content} url={f.content} key={ctr++} />;
case "media": case "media":
case "link": { case "link": {
if (f.content.startsWith("nostr:")) { if (f.content.startsWith("nostr:")) {
@ -36,25 +37,23 @@ export function Text({ content, tags, eventComponent, className }: TextProps) {
link.type === NostrPrefix.Address || link.type === NostrPrefix.Address ||
link.type === NostrPrefix.Note link.type === NostrPrefix.Note
) { ) {
return ( return eventComponent?.({ link }) ?? <EventEmbed link={link} key={ctr++} />;
<Fragment key={key}>{eventComponent?.({ link })} </Fragment> ?? <EventEmbed link={link} key={key} />
);
} else { } else {
return <Mention pubkey={link.id} key={key} />; return <Mention pubkey={link.id} key={ctr++} />;
} }
} }
} }
return ( return (
<HyperText link={f.content} key={key}> <HyperText link={f.content} key={ctr++}>
{f.content} {f.content}
</HyperText> </HyperText>
); );
} }
case "mention": case "mention":
return <Mention pubkey={f.content} key={key} />; return <Mention pubkey={f.content} key={ctr++} />;
case "hashtag": case "hashtag":
return ( return (
<Link to={`/t/${f.content}`} key={key}> <Link to={`/t/${f.content}`} key={ctr++}>
#{f.content} #{f.content}
</Link> </Link>
); );
@ -63,14 +62,7 @@ export function Text({ content, tags, eventComponent, className }: TextProps) {
// LUD-17: https://github.com/lnurl/luds/blob/luds/17.md // LUD-17: https://github.com/lnurl/luds/blob/luds/17.md
const url = new URL(f.content); const url = new URL(f.content);
url.protocol = "https:"; url.protocol = "https:";
return ( return <SendZapsDialog pubkey={undefined} lnurl={url.toString()} button={<Link to={""}>{f.content}</Link>} />;
<SendZapsDialog
pubkey={undefined}
lnurl={url.toString()}
button={<Link to={""}>{f.content}</Link>}
key={key}
/>
);
} }
return f.content; return f.content;
} }