From bbc7e443dfc857be761fffe66346b4c7e4da9a01 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 5 Apr 2023 16:10:14 +0100 Subject: [PATCH 1/7] feat: render polls --- packages/app/src/Element/Note.css | 32 ++++++++ packages/app/src/Element/Note.tsx | 11 ++- packages/app/src/Element/Poll.tsx | 103 ++++++++++++++++++++++++ packages/app/src/Element/Timeline.tsx | 1 + packages/app/src/Element/Zap.tsx | 3 + packages/app/src/Feed/EventPublisher.ts | 3 +- packages/app/src/Feed/ThreadFeed.ts | 2 +- packages/app/src/Feed/TimelineFeed.ts | 2 +- packages/nostr/src/legacy/EventKind.ts | 1 + 9 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 packages/app/src/Element/Poll.tsx diff --git a/packages/app/src/Element/Note.css b/packages/app/src/Element/Note.css index 597fb135..2be8075e 100644 --- a/packages/app/src/Element/Note.css +++ b/packages/app/src/Element/Note.css @@ -38,6 +38,7 @@ display: flex; align-items: center; } + .note > .header > .info .saved svg { margin-right: 8px; } @@ -117,6 +118,7 @@ border-top-left-radius: 16px; border-top-right-radius: 16px; } + .note > .footer .ctx-menu li:last-of-type { padding-bottom: 12px; border-bottom-left-radius: 16px; @@ -147,6 +149,36 @@ margin-left: 56px; } +.note .poll-body { + padding: 5px; + user-select: none; +} + +.note .poll-body > div { + border: 1px solid var(--font-secondary-color); + border-radius: 5px; + margin-bottom: 3px; + position: relative; + overflow: hidden; +} + +.note .poll-body > div > div { + padding: 5px 10px; + z-index: 2; +} + +.note .poll-body > div:hover { + cursor: pointer; + border: 1px solid var(--highlight); +} + +.note .poll-body > div > .progress { + background-color: var(--gray); + height: stretch; + position: absolute; + z-index: 1; +} + .reaction-pill { display: flex; min-width: 1rem; diff --git a/packages/app/src/Element/Note.tsx b/packages/app/src/Element/Note.tsx index 5585d296..91276548 100644 --- a/packages/app/src/Element/Note.tsx +++ b/packages/app/src/Element/Note.tsx @@ -28,6 +28,7 @@ import useModeration from "Hooks/useModeration"; import { setPinned, setBookmarked } from "State/Login"; import type { RootState } from "State/Store"; import { UserCache } from "Cache/UserCache"; +import Poll from "Element/Poll"; import messages from "./messages"; import { EventExt } from "System/EventExt"; @@ -270,7 +271,8 @@ export default function Note(props: NoteProps) { ); } - if (ev.kind !== EventKind.TextNote) { + const canRenderAsTextNote = [EventKind.TextNote, EventKind.Polls]; + if (!canRenderAsTextNote.includes(ev.kind)) { return ( <>

@@ -300,6 +302,12 @@ export default function Note(props: NoteProps) { } } + function pollOptions() { + if (ev.kind !== EventKind.Polls) return; + + return ; + } + function content() { if (!inView) return undefined; return ( @@ -332,6 +340,7 @@ export default function Note(props: NoteProps) {
goToEvent(e, ev, true)}> {transformBody()} {translation()} + {pollOptions()} {options.showReactionsLink && (
setShowReactions(true)}> diff --git a/packages/app/src/Element/Poll.tsx b/packages/app/src/Element/Poll.tsx new file mode 100644 index 00000000..330cfc70 --- /dev/null +++ b/packages/app/src/Element/Poll.tsx @@ -0,0 +1,103 @@ +import { TaggedRawEvent } from "@snort/nostr"; +import { useState } from "react"; +import { useSelector } from "react-redux"; +import { useIntl } from "react-intl"; + +import { ParsedZap } from "Element/Zap"; +import Text from "Element/Text"; +import useEventPublisher from "Feed/EventPublisher"; +import { RootState } from "State/Store"; +import { useWallet } from "Wallet"; +import { useUserProfile } from "Hooks/useUserProfile"; +import { LNURL } from "LNURL"; +import { unwrap } from "Util"; +import { formatShort } from "Number"; +import Spinner from "Icons/Spinner"; +import SendSats from "Element/SendSats"; + +interface PollProps { + ev: TaggedRawEvent; + zaps: Array; +} + +export default function Poll(props: PollProps) { + const { formatMessage } = useIntl(); + const publisher = useEventPublisher(); + const { wallet } = useWallet(); + const prefs = useSelector((s: RootState) => s.login.preferences); + const pollerProfile = useUserProfile(props.ev.pubkey); + const [error, setError] = useState(""); + const [invoice, setInvoice] = useState(""); + const [voting, setVoting] = useState(); + + const options = props.ev.tags.filter(a => a[0] === "poll_option").sort((a, b) => Number(a[1]) - Number(b[1])); + async function zapVote(opt: number) { + const amount = prefs.defaultZapAmount; + try { + setVoting(opt); + const zap = await publisher.zap(amount, props.ev.pubkey, props.ev.id, undefined, [ + ["poll_option", opt.toString()], + ]); + + const lnurl = props.ev.tags.find(a => a[0] === "zap")?.[1] || pollerProfile?.lud16 || pollerProfile?.lud06; + if (!lnurl) return; + + const svc = new LNURL(lnurl); + await svc.load(); + + if (!svc.canZap) { + throw new Error("Cant vote because LNURL service does not support zaps"); + } + + const invoice = await svc.getInvoice(amount, undefined, zap); + if (wallet?.isReady()) { + await wallet?.payInvoice(unwrap(invoice.pr)); + } else { + setInvoice(unwrap(invoice.pr)); + } + } catch (e) { + if (e instanceof Error) { + setError(e.message); + } + setError( + formatMessage({ + defaultMessage: "Failed to send vote", + }) + ); + } finally { + setVoting(undefined); + } + } + + const allTotal = props.zaps.filter(a => a.pollOption !== undefined).reduce((acc, v) => (acc += v.amount), 0); + return ( + <> +
+ {options.map(a => { + const opt = Number(a[1]); + const desc = a[2]; + const zapsOnOption = props.zaps.filter(b => b.pollOption === opt); + const total = zapsOnOption.reduce((acc, v) => (acc += v.amount), 0); + const weight = total / allTotal; + const percent = `${Math.floor(weight * 100)}%`; + return ( +
zapVote(opt)}> +
+ {opt === voting ? : } +
+
+ {percent} +   + ({formatShort(total)}) +
+
+
+ ); + })} + {error && {error}} +
+ + setInvoice("")} invoice={invoice} /> + + ); +} diff --git a/packages/app/src/Element/Timeline.tsx b/packages/app/src/Element/Timeline.tsx index a74c0c5d..54893e0a 100644 --- a/packages/app/src/Element/Timeline.tsx +++ b/packages/app/src/Element/Timeline.tsx @@ -81,6 +81,7 @@ const Timeline = (props: TimelineProps) => { case EventKind.SetMetadata: { return } pubkey={e.pubkey} className="card" />; } + case EventKind.Polls: case EventKind.TextNote: { const eRef = e.tags.find(tagFilterOfTextRepost(e))?.at(1); if (eRef) { diff --git a/packages/app/src/Element/Zap.tsx b/packages/app/src/Element/Zap.tsx index 2f5ac6cc..89a9a344 100644 --- a/packages/app/src/Element/Zap.tsx +++ b/packages/app/src/Element/Zap.tsx @@ -38,6 +38,7 @@ export function parseZap(zapReceipt: TaggedRawEvent, refNote?: TaggedRawEvent): const isForwardedZap = refNote?.tags.some(a => a[0] === "zap") ?? false; const anonZap = findTag(zapRequest, "anon"); const metaHash = sha256(innerZapJson); + const pollOpt = zapRequest.tags.find(a => a[0] === "poll_option")?.[1]; const ret: ParsedZap = { id: zapReceipt.id, zapService: zapReceipt.pubkey, @@ -49,6 +50,7 @@ export function parseZap(zapReceipt: TaggedRawEvent, refNote?: TaggedRawEvent): anonZap: anonZap !== undefined, content: zapRequest.content, errors: [], + pollOption: pollOpt ? Number(pollOpt) : undefined, }; if (invoice?.descriptionHash !== metaHash) { ret.valid = false; @@ -96,6 +98,7 @@ export interface ParsedZap { zapService: HexKey; anonZap: boolean; errors: Array; + pollOption?: number; } const Zap = ({ zap, showZapped = true }: { zap: ParsedZap; showZapped?: boolean }) => { diff --git a/packages/app/src/Feed/EventPublisher.ts b/packages/app/src/Feed/EventPublisher.ts index f83a9d1e..cde70f23 100644 --- a/packages/app/src/Feed/EventPublisher.ts +++ b/packages/app/src/Feed/EventPublisher.ts @@ -188,7 +188,7 @@ export default function useEventPublisher() { return await signEvent(ev); } }, - zap: async (amount: number, author: HexKey, note?: HexKey, msg?: string) => { + zap: async (amount: number, author: HexKey, note?: HexKey, msg?: string, extraTags?: Array>) => { if (pubKey) { const ev = EventExt.forPubKey(pubKey, EventKind.ZapRequest); if (note) { @@ -198,6 +198,7 @@ export default function useEventPublisher() { const relayTag = ["relays", ...Object.keys(relays).map(a => a.trim())]; ev.tags.push(relayTag); ev.tags.push(["amount", amount.toString()]); + ev.tags.push(...(extraTags ?? [])); processContent(ev, msg || ""); return await signEvent(ev); } diff --git a/packages/app/src/Feed/ThreadFeed.ts b/packages/app/src/Feed/ThreadFeed.ts index 66b47488..66596257 100644 --- a/packages/app/src/Feed/ThreadFeed.ts +++ b/packages/app/src/Feed/ThreadFeed.ts @@ -35,7 +35,7 @@ export default function useThreadFeed(link: NostrLink) { useEffect(() => { if (store.data) { - const mainNotes = store.data?.filter(a => a.kind === EventKind.TextNote) ?? []; + const mainNotes = store.data?.filter(a => a.kind === EventKind.TextNote || a.kind === EventKind.Polls) ?? []; const eTags = mainNotes.map(a => a.tags.filter(b => b[0] === "e").map(b => b[1])).flat(); const eTagsMissing = eTags.filter(a => !mainNotes.some(b => b.id === a)); diff --git a/packages/app/src/Feed/TimelineFeed.ts b/packages/app/src/Feed/TimelineFeed.ts index a8b198f2..dc80ddeb 100644 --- a/packages/app/src/Feed/TimelineFeed.ts +++ b/packages/app/src/Feed/TimelineFeed.ts @@ -39,7 +39,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel } const b = new RequestBuilder(`timeline:${subject.type}:${subject.discriminator}`); - const f = b.withFilter().kinds([EventKind.TextNote, EventKind.Repost]); + const f = b.withFilter().kinds([EventKind.TextNote, EventKind.Repost, EventKind.Polls]); if (options.relay) { b.withOptions({ diff --git a/packages/nostr/src/legacy/EventKind.ts b/packages/nostr/src/legacy/EventKind.ts index 236b1c36..516ffb4f 100644 --- a/packages/nostr/src/legacy/EventKind.ts +++ b/packages/nostr/src/legacy/EventKind.ts @@ -9,6 +9,7 @@ enum EventKind { Repost = 6, // NIP-18 Reaction = 7, // NIP-25 BadgeAward = 8, // NIP-58 + Polls = 6969, // NIP-69 Relays = 10002, // NIP-65 Ephemeral = 20_000, Auth = 22242, // NIP-42 From b9b9989647f3d0f5a184ec997b7d84a8ba23e96d Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 5 Apr 2023 18:07:42 +0100 Subject: [PATCH 2/7] bug: zap amount --- packages/app/src/Element/Poll.tsx | 10 ++++------ packages/app/src/Feed/EventPublisher.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/app/src/Element/Poll.tsx b/packages/app/src/Element/Poll.tsx index 330cfc70..9e1995bc 100644 --- a/packages/app/src/Element/Poll.tsx +++ b/packages/app/src/Element/Poll.tsx @@ -1,7 +1,7 @@ import { TaggedRawEvent } from "@snort/nostr"; import { useState } from "react"; import { useSelector } from "react-redux"; -import { useIntl } from "react-intl"; +import { FormattedNumber, useIntl } from "react-intl"; import { ParsedZap } from "Element/Zap"; import Text from "Element/Text"; @@ -35,7 +35,7 @@ export default function Poll(props: PollProps) { const amount = prefs.defaultZapAmount; try { setVoting(opt); - const zap = await publisher.zap(amount, props.ev.pubkey, props.ev.id, undefined, [ + const zap = await publisher.zap(amount * 1000, props.ev.pubkey, props.ev.id, undefined, [ ["poll_option", opt.toString()], ]); @@ -79,18 +79,16 @@ export default function Poll(props: PollProps) { const zapsOnOption = props.zaps.filter(b => b.pollOption === opt); const total = zapsOnOption.reduce((acc, v) => (acc += v.amount), 0); const weight = total / allTotal; - const percent = `${Math.floor(weight * 100)}%`; return (
zapVote(opt)}>
{opt === voting ? : }
- {percent} -   + %   ({formatShort(total)})
-
+
); })} diff --git a/packages/app/src/Feed/EventPublisher.ts b/packages/app/src/Feed/EventPublisher.ts index cde70f23..3063cf78 100644 --- a/packages/app/src/Feed/EventPublisher.ts +++ b/packages/app/src/Feed/EventPublisher.ts @@ -188,6 +188,15 @@ export default function useEventPublisher() { return await signEvent(ev); } }, + /** + * Create a zap request event for a given target event/profile + * @param amount Millisats amout! + * @param author Author pubkey to tag in the zap + * @param note Note Id to tag in the zap + * @param msg Custom message to be included in the zap + * @param extraTags Any extra tags to include on the zap request event + * @returns + */ zap: async (amount: number, author: HexKey, note?: HexKey, msg?: string, extraTags?: Array>) => { if (pubKey) { const ev = EventExt.forPubKey(pubKey, EventKind.ZapRequest); From 82851800bfe8d3bcfec01480556b3c62ade4a2ef Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 10 Apr 2023 13:53:53 +0100 Subject: [PATCH 3/7] review changes --- packages/app/src/Element/Poll.tsx | 29 +++++++++++++++++++------ packages/app/src/Feed/EventPublisher.ts | 5 ++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/app/src/Element/Poll.tsx b/packages/app/src/Element/Poll.tsx index 9e1995bc..0aa74ebf 100644 --- a/packages/app/src/Element/Poll.tsx +++ b/packages/app/src/Element/Poll.tsx @@ -32,6 +32,8 @@ export default function Poll(props: PollProps) { const options = props.ev.tags.filter(a => a[0] === "poll_option").sort((a, b) => Number(a[1]) - Number(b[1])); async function zapVote(opt: number) { + if (voting) return; + const amount = prefs.defaultZapAmount; try { setVoting(opt); @@ -39,6 +41,14 @@ export default function Poll(props: PollProps) { ["poll_option", opt.toString()], ]); + if (!zap) { + throw new Error( + formatMessage({ + defaultMessage: "Can't create vote, maybe you're not logged in?", + }) + ); + } + const lnurl = props.ev.tags.find(a => a[0] === "zap")?.[1] || pollerProfile?.lud16 || pollerProfile?.lud06; if (!lnurl) return; @@ -46,7 +56,11 @@ export default function Poll(props: PollProps) { await svc.load(); if (!svc.canZap) { - throw new Error("Cant vote because LNURL service does not support zaps"); + throw new Error( + formatMessage({ + defaultMessage: "Can't vote because LNURL service does not support zaps", + }) + ); } const invoice = await svc.getInvoice(amount, undefined, zap); @@ -58,12 +72,13 @@ export default function Poll(props: PollProps) { } catch (e) { if (e instanceof Error) { setError(e.message); + } else { + setError( + formatMessage({ + defaultMessage: "Failed to send vote", + }) + ); } - setError( - formatMessage({ - defaultMessage: "Failed to send vote", - }) - ); } finally { setVoting(undefined); } @@ -78,7 +93,7 @@ export default function Poll(props: PollProps) { const desc = a[2]; const zapsOnOption = props.zaps.filter(b => b.pollOption === opt); const total = zapsOnOption.reduce((acc, v) => (acc += v.amount), 0); - const weight = total / allTotal; + const weight = allTotal === 0 ? 0 : total / allTotal; return (
zapVote(opt)}>
diff --git a/packages/app/src/Feed/EventPublisher.ts b/packages/app/src/Feed/EventPublisher.ts index 3063cf78..f31c6382 100644 --- a/packages/app/src/Feed/EventPublisher.ts +++ b/packages/app/src/Feed/EventPublisher.ts @@ -33,6 +33,10 @@ export default function useEventPublisher() { const hasNip07 = "nostr" in window; async function signEvent(ev: RawEvent): Promise { + if (!pubKey) { + throw new Error("Cant sign events when logged out"); + } + if (hasNip07 && !privKey) { ev.id = await EventExt.createId(ev); const tmpEv = (await barrierNip07(() => window.nostr.signEvent(ev))) as RawEvent; @@ -92,7 +96,6 @@ export default function useEventPublisher() { }, broadcast: (ev: RawEvent | undefined) => { if (ev) { - console.debug("Sending event: ", ev); System.BroadcastEvent(ev); } }, From 657f684c2c7ef1d863897b8dfce1d2449c238912 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 10 Apr 2023 14:00:26 +0100 Subject: [PATCH 4/7] Vote amount message --- packages/app/src/Element/Poll.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/app/src/Element/Poll.tsx b/packages/app/src/Element/Poll.tsx index 0aa74ebf..e6d522e9 100644 --- a/packages/app/src/Element/Poll.tsx +++ b/packages/app/src/Element/Poll.tsx @@ -1,7 +1,7 @@ import { TaggedRawEvent } from "@snort/nostr"; import { useState } from "react"; import { useSelector } from "react-redux"; -import { FormattedNumber, useIntl } from "react-intl"; +import { FormattedMessage, FormattedNumber, useIntl } from "react-intl"; import { ParsedZap } from "Element/Zap"; import Text from "Element/Text"; @@ -87,6 +87,14 @@ export default function Poll(props: PollProps) { const allTotal = props.zaps.filter(a => a.pollOption !== undefined).reduce((acc, v) => (acc += v.amount), 0); return ( <> + + +
{options.map(a => { const opt = Number(a[1]); From bf31816051498f94f1597de5816bed848b8b4a9f Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 10 Apr 2023 15:55:25 +0100 Subject: [PATCH 5/7] poll creation --- packages/app/public/icons.svg | 9 +++ packages/app/src/Element/Modal.css | 12 +--- packages/app/src/Element/NoteCreator.css | 53 +++++----------- packages/app/src/Element/NoteCreator.tsx | 80 +++++++++++++++++++++--- packages/app/src/Feed/EventPublisher.ts | 9 +-- packages/app/src/State/NoteCreator.ts | 15 +++-- 6 files changed, 112 insertions(+), 66 deletions(-) diff --git a/packages/app/public/icons.svg b/packages/app/public/icons.svg index aa7fafc1..ea768eed 100644 --- a/packages/app/public/icons.svg +++ b/packages/app/public/icons.svg @@ -376,5 +376,14 @@ stroke-linejoin="round" /> + + + \ No newline at end of file diff --git a/packages/app/src/Element/Modal.css b/packages/app/src/Element/Modal.css index 4ce97359..c26c5d9f 100644 --- a/packages/app/src/Element/Modal.css +++ b/packages/app/src/Element/Modal.css @@ -7,7 +7,6 @@ background-color: var(--modal-bg-color); display: flex; justify-content: center; - align-items: center; z-index: 42; overflow-y: auto; } @@ -17,12 +16,7 @@ padding: 10px; border-radius: 10px; width: 500px; - min-height: 10vh; -} - -@media (max-width: 720px) { - .modal-body { - width: 100vw; - margin: 0 10px; - } + border: 1px solid var(--font-tertiary-color); + margin-top: auto; + margin-bottom: auto; } diff --git a/packages/app/src/Element/NoteCreator.css b/packages/app/src/Element/NoteCreator.css index d2909f27..b19f6d48 100644 --- a/packages/app/src/Element/NoteCreator.css +++ b/packages/app/src/Element/NoteCreator.css @@ -17,7 +17,7 @@ resize: none; background-color: var(--note-bg); border-radius: 10px 10px 0 0; - min-height: 120px; + min-height: 100px; max-width: stretch; min-width: stretch; max-height: 210px; @@ -41,6 +41,9 @@ } } +.note-creator.poll textarea { + min-height: 120px; +} .note-creator-actions { width: 100%; display: flex; @@ -50,19 +53,22 @@ margin-bottom: 5px; } -.note-creator .attachment { - cursor: pointer; - position: absolute; - right: 16px; - bottom: 12px; +.note-creator .insert { + display: flex; + justify-content: flex-end; + width: stretch; +} + +.note-creator .insert > button { width: 48px; height: 36px; background: var(--gray-dark); color: white; - border-radius: 100px; + border-radius: 17px; + margin-right: 5px; display: flex; - align-items: center; justify-content: center; + align-items: center; } .note-creator .attachment:hover { @@ -87,19 +93,11 @@ position: absolute; left: 16px; bottom: 12px; - font-color: var(--error); + color: var(--error); margin-right: 12px; font-size: 16px; } -.note-creator .btn { - border-radius: 20px; - font-weight: bold; - background-color: var(--bg-color); - color: var(--font-color); - font-size: var(--font-size); -} - .note-create-button { width: 48px; height: 48px; @@ -114,31 +112,10 @@ justify-content: center; } -@media (min-width: 520px) { - .note-create-button { - right: 10vw; - } -} - -@media (min-width: 1020px) { - .note-create-button { - right: calc(50% - 360px); - } -} - .note-creator-modal .modal-body { background: var(--modal-bg-color); } -@media (max-width: 720px) { - .note-creator-modal { - align-items: flex-start; - } - .note-creator-modal .modal-body { - margin-top: 20vh; - } -} - .note-preview { word-break: break-all; } diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index 69c22104..a338c5a2 100644 --- a/packages/app/src/Element/NoteCreator.tsx +++ b/packages/app/src/Element/NoteCreator.tsx @@ -1,7 +1,7 @@ import "./NoteCreator.css"; import { FormattedMessage, useIntl } from "react-intl"; import { useDispatch, useSelector } from "react-redux"; -import { TaggedRawEvent } from "@snort/nostr"; +import { EventKind, TaggedRawEvent } from "@snort/nostr"; import Icon from "Icons/Icon"; import useEventPublisher from "Feed/EventPublisher"; @@ -21,6 +21,7 @@ import { setZapForward, setSensitive, reset, + setPollOptions, } from "State/NoteCreator"; import type { RootState } from "State/Store"; import { LNURL } from "LNURL"; @@ -56,6 +57,7 @@ export function NoteCreator() { const showAdvanced = useSelector((s: RootState) => s.noteCreator.showAdvanced); const zapForward = useSelector((s: RootState) => s.noteCreator.zapForward); const sensitive = useSelector((s: RootState) => s.noteCreator.sensitive); + const pollOptions = useSelector((s: RootState) => s.noteCreator.pollOptions); const dispatch = useDispatch(); async function sendNote() { @@ -81,8 +83,14 @@ export function NoteCreator() { extraTags ??= []; extraTags.push(["content-warning", sensitive]); } - const ev = replyTo ? await publisher.reply(replyTo, note, extraTags) : await publisher.note(note, extraTags); - console.debug("Sending note: ", ev); + const kind = pollOptions ? EventKind.Polls : EventKind.TextNote; + if (pollOptions) { + extraTags ??= []; + extraTags.push(...pollOptions.map((a, i) => ["poll_option", i.toString(), a])); + } + const ev = replyTo + ? await publisher.reply(replyTo, note, extraTags, kind) + : await publisher.note(note, extraTags, kind); publisher.broadcast(ev); dispatch(reset()); } @@ -127,7 +135,7 @@ export function NoteCreator() { async function loadPreview() { if (preview) { - dispatch(setPreview(null)); + dispatch(setPreview(undefined)); } else { const tmpNote = await publisher.note(note); if (tmpNote) { @@ -151,6 +159,52 @@ export function NoteCreator() { } } + function renderPollOptions() { + if (pollOptions) { + return ( + <> +

+ +

+ {pollOptions?.map((a, i) => ( +
+
+ +
+
+ changePollOption(i, e.target.value)} /> + {i > 1 && ( + + )} +
+
+ ))} + + + ); + } + } + + function changePollOption(i: number, v: string) { + if (pollOptions) { + const copy = [...pollOptions]; + copy[i] = v; + dispatch(setPollOptions(copy)); + } + } + + function removePollOption(i: number) { + if (pollOptions) { + const copy = [...pollOptions]; + copy.splice(i, 1); + dispatch(setPollOptions(copy)); + } + } + return ( <> {show && ( @@ -158,8 +212,8 @@ export function NoteCreator() { {replyTo && } {preview && getPreviewNote()} {!preview && ( -
-
+
+