From 8ef20c27b33ba0097fe763e22441a978203ab650 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 27 Mar 2023 23:58:29 +0100 Subject: [PATCH] feat: per event zap targets --- packages/app/src/Element/NoteCreator.tsx | 45 ++++++++++++++++++++++-- packages/app/src/Element/NoteFooter.tsx | 18 +++++++--- packages/app/src/Feed/EventPublisher.ts | 14 ++++++-- packages/app/src/lang.json | 12 +++++++ packages/app/src/translations/en.json | 6 +++- packages/nostr/src/legacy/Tag.ts | 5 +++ 6 files changed, 90 insertions(+), 10 deletions(-) diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index cada250..825a7f9 100644 --- a/packages/app/src/Element/NoteCreator.tsx +++ b/packages/app/src/Element/NoteCreator.tsx @@ -1,6 +1,6 @@ import "./NoteCreator.css"; import { useState } from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { RawEvent, TaggedRawEvent } from "@snort/nostr"; import Icon from "Icons/Icon"; @@ -11,6 +11,7 @@ import Modal from "Element/Modal"; import ProfileImage from "Element/ProfileImage"; import useFileUpload from "Upload"; import Note from "Element/Note"; +import { LNURL } from "LNURL"; import messages from "./messages"; @@ -40,16 +41,34 @@ export interface NoteCreatorProps { export function NoteCreator(props: NoteCreatorProps) { const { show, setShow, replyTo, onSend, autoFocus } = props; + const { formatMessage } = useIntl(); const publisher = useEventPublisher(); const [note, setNote] = useState(""); const [error, setError] = useState(""); const [active, setActive] = useState(false); const [preview, setPreview] = useState(); + const [showAdvanced, setShowAdvanced] = useState(false); + const [zapForward, setZapForward] = useState(""); const uploader = useFileUpload(); async function sendNote() { if (note) { - const ev = replyTo ? await publisher.reply(replyTo, note) : await publisher.note(note); + let extraTags: Array> | undefined; + if (zapForward) { + try { + const svc = new LNURL(zapForward); + await svc.load(); + } catch { + setError( + formatMessage({ + defaultMessage: "Invalid LNURL", + }) + ); + return; + } + extraTags = [["zap", zapForward]]; + } + const ev = replyTo ? await publisher.reply(replyTo, note, extraTags) : await publisher.note(note, extraTags); console.debug("Sending note: ", ev); publisher.broadcast(ev); setNote(""); @@ -152,6 +171,9 @@ export function NoteCreator(props: NoteCreatorProps) { + @@ -159,6 +181,25 @@ export function NoteCreator(props: NoteCreatorProps) { {replyTo ? : } + {showAdvanced && ( + <> +

+ +

+

+ +

+ setZapForward(e.target.value)} + /> + + )} )} diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index dafba60..f11e7cd 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -153,10 +153,18 @@ export default function NoteFooter(props: NoteFooterProps) { } } + function getLNURL() { + return ev.Tags.find(a => a.Key === "zap")?.LNURL || author?.lud16 || author?.lud06; + } + + function getTargetName() { + return ev.Tags.find(a => a.Key === "zap")?.LNURL || author?.display_name || author?.name; + } + async function fastZap(e?: React.MouseEvent) { if (zapping || e?.isPropagationStopped()) return; - const lnurl = author?.lud16 || author?.lud06; + const lnurl = getLNURL(); if (wallet?.isReady() && lnurl) { setZapping(true); try { @@ -203,7 +211,7 @@ export default function NoteFooter(props: NoteFooterProps) { useEffect(() => { if (prefs.autoZap && !ZapCache.has(ev.id) && !isMine && !zapping) { - const lnurl = author?.lud16 || author?.lud06; + const lnurl = getLNURL(); if (wallet?.isReady() && lnurl) { setZapping(true); queueMicrotask(async () => { @@ -222,7 +230,7 @@ export default function NoteFooter(props: NoteFooterProps) { }, [prefs.autoZap, author, zapping]); function tipButton() { - const service = author?.lud16 || author?.lud06; + const service = getLNURL(); if (service) { return ( <> @@ -418,11 +426,11 @@ export default function NoteFooter(props: NoteFooterProps) { zaps={zaps} /> setTip(false)} show={tip} author={author?.pubkey} - target={author?.display_name || author?.name} + target={getTargetName()} note={ev.id} /> diff --git a/packages/app/src/Feed/EventPublisher.ts b/packages/app/src/Feed/EventPublisher.ts index e4769b6..f83a9d1 100644 --- a/packages/app/src/Feed/EventPublisher.ts +++ b/packages/app/src/Feed/EventPublisher.ts @@ -176,10 +176,15 @@ export default function useEventPublisher() { return await signEvent(ev); } }, - note: async (msg: string) => { + note: async (msg: string, extraTags?: Array>) => { if (pubKey) { const ev = EventExt.forPubKey(pubKey, EventKind.TextNote); processContent(ev, msg); + if (extraTags) { + for (const et of extraTags) { + ev.tags.push(et); + } + } return await signEvent(ev); } }, @@ -200,7 +205,7 @@ export default function useEventPublisher() { /** * Reply to a note */ - reply: async (replyTo: TaggedRawEvent, msg: string) => { + reply: async (replyTo: TaggedRawEvent, msg: string, extraTags?: Array>) => { if (pubKey) { const ev = EventExt.forPubKey(pubKey, EventKind.TextNote); @@ -230,6 +235,11 @@ export default function useEventPublisher() { } } processContent(ev, msg); + if (extraTags) { + for (const et of extraTags) { + ev.tags.push(et); + } + } return await signEvent(ev); } }, diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index a36641f..806dd22 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -78,6 +78,9 @@ "2k0Cv+": { "defaultMessage": "Dislikes ({n})" }, + "3Rx6Qo": { + "defaultMessage": "Advanced" + }, "3cc4Ct": { "defaultMessage": "Light" }, @@ -278,6 +281,9 @@ "FDguSC": { "defaultMessage": "{n} Zaps" }, + "FP+D3H": { + "defaultMessage": "LNURL to forward zaps to" + }, "FS3b54": { "defaultMessage": "Done!" }, @@ -437,6 +443,9 @@ "OLEm6z": { "defaultMessage": "Unknown login error" }, + "P04gQm": { + "defaultMessage": "All zaps sent to this note will be received by the following LNURL" + }, "P61BTu": { "defaultMessage": "Copy Event JSON" }, @@ -472,6 +481,9 @@ "defaultMessage": "Art by {name}", "description": "Artwork attribution label" }, + "R1fEdZ": { + "defaultMessage": "Forward Zaps" + }, "R2OqnW": { "defaultMessage": "Delete Account" }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 592d640..8bb7e76 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -25,6 +25,7 @@ "2LbrkB": "Enter password", "2a2YiP": "{n} Bookmarks", "2k0Cv+": "Dislikes ({n})", + "3Rx6Qo": "Advanced", "3cc4Ct": "Light", "3gOsZq": "Translators", "3t3kok": "{n,plural,=1{{n} new note} other{{n} new notes}}", @@ -90,6 +91,7 @@ "Eqjl5K": "Only Snort and our integration partner identifier gives you a colorful domain name, but you are welcome to use other services too.", "F+B3x1": "We have also partnered with nostrplebs.com to give you more options", "FDguSC": "{n} Zaps", + "FP+D3H": "LNURL to forward zaps to", "FS3b54": "Done!", "FfYsOb": "An error has occured!", "FmXUJg": "follows you", @@ -142,6 +144,7 @@ "OEW7yJ": "Zaps", "OKhRC6": "Share", "OLEm6z": "Unknown login error", + "P04gQm": "All zaps sent to this note will be received by the following LNURL", "P61BTu": "Copy Event JSON", "P7FD0F": "System (Default)", "P7nJT9": "Total today (UTC): {amount} sats", @@ -153,6 +156,7 @@ "QTdJfH": "Create an Account", "QawghE": "You can change your username at any point.", "QxCuTo": "Art by {name}", + "R1fEdZ": "Forward Zaps", "R2OqnW": "Delete Account", "RDZVQL": "Check", "RahCRH": "Expired", @@ -321,4 +325,4 @@ "zjJZBd": "You're ready!", "zonsdq": "Failed to load LNURL service", "zvCDao": "Automatically show latest notes" -} +} \ No newline at end of file diff --git a/packages/nostr/src/legacy/Tag.ts b/packages/nostr/src/legacy/Tag.ts index d2ca31b..9b19783 100644 --- a/packages/nostr/src/legacy/Tag.ts +++ b/packages/nostr/src/legacy/Tag.ts @@ -12,6 +12,7 @@ export default class Tag { DTag?: string; Index: number; Invalid: boolean; + LNURL?: string; constructor(tag: string[], index: number) { this.Original = tag; @@ -50,6 +51,10 @@ export default class Tag { this.PubKey = tag[1]; break; } + case "zap": { + this.LNURL = tag[1]; + break; + } } }