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