feat: render embed for zapstr replies
This commit is contained in:
parent
323d4e761e
commit
d8fc92fafc
@ -2,10 +2,8 @@ import { Link } from "react-router-dom";
|
||||
import { EventKind, NostrPrefix } from "@snort/nostr";
|
||||
|
||||
import Mention from "Element/Mention";
|
||||
import NostrFileHeader from "Element/NostrFileHeader";
|
||||
import { parseNostrLink } from "Util";
|
||||
import NoteQuote from "Element/NoteQuote";
|
||||
import ZapstrEmbed from "Element/ZapstrEmbed";
|
||||
|
||||
export default function NostrLink({ link, depth }: { link: string; depth?: number }) {
|
||||
const nav = parseNostrLink(link);
|
||||
@ -13,13 +11,6 @@ export default function NostrLink({ link, depth }: { link: string; depth?: numbe
|
||||
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
|
||||
return <Mention pubkey={nav.id} relays={nav.relays} />;
|
||||
} else if (nav?.type === NostrPrefix.Note || nav?.type === NostrPrefix.Event || nav?.type === NostrPrefix.Address) {
|
||||
if (nav.kind === EventKind.FileHeader) {
|
||||
return <NostrFileHeader link={nav} />;
|
||||
}
|
||||
if (nav.kind === 31337) {
|
||||
return <ZapstrEmbed link={nav} />;
|
||||
}
|
||||
|
||||
if ((depth ?? 0) > 0) {
|
||||
const evLink = nav.encode();
|
||||
return (
|
||||
|
@ -30,6 +30,7 @@ import { EventExt } from "System/EventExt";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { setBookmarked, setPinned } from "Login";
|
||||
import { NostrFileElement } from "Element/NostrFileHeader";
|
||||
import ZapstrEmbed from "Element/ZapstrEmbed";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -78,7 +79,9 @@ export default function Note(props: NoteProps) {
|
||||
if (ev.kind === EventKind.FileHeader) {
|
||||
return <NostrFileElement ev={ev} />;
|
||||
}
|
||||
|
||||
if (ev.kind === 31337) {
|
||||
return <ZapstrEmbed ev={ev} />;
|
||||
}
|
||||
const navigate = useNavigate();
|
||||
const [showReactions, setShowReactions] = useState(false);
|
||||
const deletions = useMemo(() => getReactions(related, ev.id, EventKind.Deletion), [related]);
|
||||
|
@ -142,7 +142,7 @@ export function NoteCreator() {
|
||||
if (file) {
|
||||
const rx = await uploader.upload(file, file.name);
|
||||
if (rx.header) {
|
||||
const link = `nostr:${encodeTLV(rx.header.id, NostrPrefix.Event, undefined, rx.header.kind)}`;
|
||||
const link = `nostr:${encodeTLV(NostrPrefix.Event, rx.header.id, undefined, rx.header.kind)}`;
|
||||
dispatch(setNote(`${note ? `${note}\n` : ""}${link}`));
|
||||
dispatch(setOtherEvents([...otherEvents, rx.header]));
|
||||
} else if (rx.url) {
|
||||
|
@ -264,7 +264,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
}
|
||||
|
||||
async function share() {
|
||||
const link = encodeTLV(ev.id, NostrPrefix.Event, ev.relays);
|
||||
const link = encodeTLV(NostrPrefix.Event, ev.id, ev.relays);
|
||||
const url = `${window.location.protocol}//${window.location.host}/e/${link}`;
|
||||
if ("share" in window.navigator) {
|
||||
await window.navigator.share({
|
||||
@ -300,7 +300,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
}
|
||||
|
||||
async function copyId() {
|
||||
const link = encodeTLV(ev.id, NostrPrefix.Event, ev.relays);
|
||||
const link = encodeTLV(NostrPrefix.Event, ev.id, ev.relays);
|
||||
await navigator.clipboard.writeText(link);
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ export default function WriteDm({ chatPubKey }: { chatPubKey: string }) {
|
||||
if (file) {
|
||||
const rx = await uploader.upload(file, file.name);
|
||||
if (rx.header) {
|
||||
const link = `nostr:${encodeTLV(rx.header.id, NostrPrefix.Event, undefined, rx.header.kind)}`;
|
||||
const link = `nostr:${encodeTLV(NostrPrefix.Event, rx.header.id, undefined, rx.header.kind)}`;
|
||||
setMsg(`${msg ? `${msg}\n` : ""}${link}`);
|
||||
setOtherEvents([...otherEvents, rx.header]);
|
||||
} else if (rx.url) {
|
||||
|
@ -1,25 +1,27 @@
|
||||
import "./ZapstrEmbed.css";
|
||||
import { Link } from "react-router-dom";
|
||||
import { encodeTLV, NostrPrefix, RawEvent } from "@snort/nostr";
|
||||
|
||||
import useEventFeed from "Feed/EventFeed";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import { NostrLink } from "Util";
|
||||
import { ProxyImg } from "Element/ProxyImg";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export default function ZapstrEmbed({ link }: { link: NostrLink }) {
|
||||
const ev = useEventFeed(link);
|
||||
export default function ZapstrEmbed({ ev }: { ev: RawEvent }) {
|
||||
const media = ev.tags.find(a => a[0] === "media");
|
||||
const cover = ev.tags.find(a => a[0] === "cover");
|
||||
const subject = ev.tags.find(a => a[0] === "subject");
|
||||
const refPersons = ev.tags.filter(a => a[0] === "p");
|
||||
|
||||
if (!ev.data) return <Spinner />;
|
||||
|
||||
const media = ev.data.tags.find(a => a[0] === "media");
|
||||
const cover = ev.data.tags.find(a => a[0] === "cover");
|
||||
const subject = ev.data.tags.find(a => a[0] === "subject");
|
||||
const refPersons = ev.data.tags.filter(a => a[0] === "p");
|
||||
const link = encodeTLV(
|
||||
NostrPrefix.Address,
|
||||
ev.tags.find(a => a[0] === "d")?.[1] ?? "",
|
||||
undefined,
|
||||
ev.kind,
|
||||
ev.pubkey
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="flex zapstr mb10">
|
||||
<div className="flex zapstr mb10 card">
|
||||
<ProxyImg src={cover?.[1] ?? ""} size={100} />
|
||||
<div className="flex f-col">
|
||||
<div>
|
||||
@ -33,7 +35,7 @@ export default function ZapstrEmbed({ link }: { link: NostrLink }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Link to={`https://zapstr.live/?track=${link.encode()}`} target="_blank">
|
||||
<Link to={`https://zapstr.live/?track=${link}`} target="_blank">
|
||||
<button>
|
||||
<FormattedMessage defaultMessage="Open on Zapstr" />
|
||||
</button>
|
||||
|
@ -296,7 +296,7 @@ export default function ProfilePage() {
|
||||
function renderIcons() {
|
||||
if (!id) return;
|
||||
|
||||
const link = encodeTLV(id, NostrPrefix.Profile);
|
||||
const link = encodeTLV(NostrPrefix.Profile, id);
|
||||
return (
|
||||
<div className="icon-actions">
|
||||
<IconButton onClick={() => setShowProfileQr(true)}>
|
||||
|
@ -15,7 +15,7 @@ export default function ExportKeys() {
|
||||
<FormattedMessage defaultMessage="Public Key" />
|
||||
</h3>
|
||||
<Copy text={hexToBech32("npub", publicKey ?? "")} maxSize={48} className="mb10" />
|
||||
<Copy text={encodeTLV(publicKey ?? "", NostrPrefix.Profile)} maxSize={48} />
|
||||
<Copy text={encodeTLV(NostrPrefix.Profile, publicKey ?? "")} maxSize={48} />
|
||||
{privateKey && (
|
||||
<>
|
||||
<h3>
|
||||
|
@ -91,7 +91,7 @@ export function bech32ToText(str: string) {
|
||||
*/
|
||||
export function eventLink(hex: u256, relays?: Array<string> | string) {
|
||||
const encoded = relays
|
||||
? encodeTLV(hex, NostrPrefix.Event, Array.isArray(relays) ? relays : [relays])
|
||||
? encodeTLV(NostrPrefix.Event, hex, Array.isArray(relays) ? relays : [relays])
|
||||
: hexToBech32(NostrPrefix.Note, hex);
|
||||
return `/e/${encoded}`;
|
||||
}
|
||||
@ -101,7 +101,7 @@ export function eventLink(hex: u256, relays?: Array<string> | string) {
|
||||
*/
|
||||
export function profileLink(hex: HexKey, relays?: Array<string> | string) {
|
||||
const encoded = relays
|
||||
? encodeTLV(hex, NostrPrefix.Profile, Array.isArray(relays) ? relays : [relays])
|
||||
? encodeTLV(NostrPrefix.Event, hex, Array.isArray(relays) ? relays : [relays])
|
||||
: hexToBech32(NostrPrefix.PublicKey, hex);
|
||||
return `/p/${encoded}`;
|
||||
}
|
||||
@ -119,7 +119,7 @@ export function hexToBech32(hrp: string, hex?: string) {
|
||||
const buf = secp.utils.hexToBytes(hex);
|
||||
return bech32.encode(hrp, bech32.toWords(buf));
|
||||
} else {
|
||||
return encodeTLV(hex, hrp as NostrPrefix);
|
||||
return encodeTLV(hrp as NostrPrefix, hex);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Invalid hex", hex, e);
|
||||
|
@ -27,13 +27,9 @@ export interface TLVEntry {
|
||||
value: string | HexKey | number;
|
||||
}
|
||||
|
||||
export function encodeTLV(hex: string, prefix: NostrPrefix, relays?: string[], kind?: number) {
|
||||
if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 !== 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
export function encodeTLV(prefix: NostrPrefix, id: string, relays?: string[], kind?: number, author?: string) {
|
||||
const enc = new TextEncoder();
|
||||
const buf = secp.utils.hexToBytes(hex);
|
||||
const buf = prefix === NostrPrefix.Address ? enc.encode(id) : secp.utils.hexToBytes(id);
|
||||
|
||||
const tl0 = [0, buf.length, ...buf];
|
||||
const tl1 =
|
||||
@ -43,9 +39,11 @@ export function encodeTLV(hex: string, prefix: NostrPrefix, relays?: string[], k
|
||||
return [1, data.length, ...data];
|
||||
})
|
||||
.flat() ?? [];
|
||||
|
||||
const tl2 = author ? [2, 32, ...secp.utils.hexToBytes(author)] : [];
|
||||
const tl3 = kind ? [3, 4, ...new Uint8Array(new Uint32Array([kind]).buffer).reverse()] : []
|
||||
|
||||
return bech32.encode(prefix, bech32.toWords([...tl0, ...tl1, ...tl3]), 1_000);
|
||||
return bech32.encode(prefix, bech32.toWords([...tl0, ...tl1, ...tl2, ...tl3]), 1_000);
|
||||
}
|
||||
|
||||
export function decodeTLV(str: string) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user