import "./NoteCreator.css"; import { FormattedMessage, useIntl } from "react-intl"; import { EventKind, NostrPrefix, TaggedNostrEvent, EventBuilder, tryParseNostrLink, NostrLink, NostrEvent } from "@snort/system"; import Icon from "Icons/Icon"; import useEventPublisher from "Hooks/useEventPublisher"; import { openFile } from "SnortUtils"; import Textarea from "Element/Textarea"; import Modal from "Element/Modal"; import ProfileImage from "Element/ProfileImage"; import useFileUpload from "Upload"; import Note from "Element/Note"; import { ClipboardEventHandler } from "react"; import useLogin from "Hooks/useLogin"; import { System } from "index"; import AsyncButton from "Element/AsyncButton"; import { AsyncIcon } from "Element/AsyncIcon"; import { fetchNip05Pubkey } from "@snort/shared"; import { ZapTarget } from "Zapper"; import { useNoteCreator } from "State/NoteCreator"; export function NoteCreator() { const { formatMessage } = useIntl(); const publisher = useEventPublisher(); const uploader = useFileUpload(); const login = useLogin(); const note = useNoteCreator(); const relays = login.relays; async function buildNote() { try { note.update(v => v.error = ""); if (note && publisher) { let extraTags: Array> | undefined; if (note.zapSplits) { const parsedSplits = [] as Array; for (const s of note.zapSplits) { if (s.value.startsWith(NostrPrefix.PublicKey) || s.value.startsWith(NostrPrefix.Profile)) { const link = tryParseNostrLink(s.value); if (link) { parsedSplits.push({ ...s, value: link.id }); } else { throw new Error( formatMessage( { defaultMessage: "Failed to parse zap split: {input}", }, { input: s.value, }, ), ); } } else if (s.value.includes("@")) { const [name, domain] = s.value.split("@"); const pubkey = await fetchNip05Pubkey(name, domain); if (pubkey) { parsedSplits.push({ ...s, value: pubkey }); } else { throw new Error( formatMessage( { defaultMessage: "Failed to parse zap split: {input}", }, { input: s.value, }, ), ); } } else { throw new Error( formatMessage( { defaultMessage: "Invalid zap split: {input}", }, { input: s.value, }, ), ); } } extraTags = parsedSplits.map(v => ["zap", v.value, "", String(v.weight)]); } if (note.sensitive) { extraTags ??= []; extraTags.push(["content-warning", note.sensitive]); } const kind = note.pollOptions ? EventKind.Polls : EventKind.TextNote; if (note.pollOptions) { extraTags ??= []; extraTags.push(...note.pollOptions.map((a, i) => ["poll_option", i.toString(), a])); } const hk = (eb: EventBuilder) => { extraTags?.forEach(t => eb.tag(t)); eb.kind(kind); return eb; }; const ev = note.replyTo ? await publisher.reply(note.replyTo, note.note, hk) : await publisher.note(note.note, hk); return ev; } } catch (e) { note.update(v => { if (e instanceof Error) { v.error = e.message; } else { v.error = e as string; } }); } } async function sendEventToRelays(ev: NostrEvent) { if (note.selectedCustomRelays) { await Promise.all(note.selectedCustomRelays.map(r => System.WriteOnceToRelay(r, ev))); } else { System.BroadcastEvent(ev); } } async function sendNote() { const ev = await buildNote(); if (ev) { await sendEventToRelays(ev); for (const oe of note.otherEvents ?? []) { await sendEventToRelays(oe); } note.update(v => { v.reset(); v.show = false; }) } } async function attachFile() { try { const file = await openFile(); if (file) { uploadFile(file); } } catch (e) { note.update(v => { if (e instanceof Error) { v.error = e.message; } else { v.error = e as string; } }); } } async function uploadFile(file: File | Blob) { try { if (file) { const rx = await uploader.upload(file, file.name); note.update(v => { if (rx.header) { const link = `nostr:${new NostrLink(NostrPrefix.Event, rx.header.id, rx.header.kind).encode()}`; v.note = `${v.note ? `${v.note}\n` : ""}${link}`; v.otherEvents = [...(v.otherEvents ?? []), rx.header]; } else if (rx.url) { v.note = `${v.note ? `${v.note}\n` : ""}${rx.url}`; } else if (rx?.error) { v.error = rx.error; } }); } } catch (e) { note.update(v => { if (e instanceof Error) { v.error = e.message; } else { v.error = e as string; } }); } } function onChange(ev: React.ChangeEvent) { const { value } = ev.target; note.update(n => n.note = value); } function cancel() { note.update(v => { v.show = false; v.reset(); }); } async function onSubmit(ev: React.MouseEvent) { ev.stopPropagation(); await sendNote(); } async function loadPreview() { if (note.preview) { note.update(v => v.preview = undefined); } else if (publisher) { const tmpNote = await buildNote(); note.update(v => v.preview = tmpNote); } } function getPreviewNote() { if (note.preview) { return ( ); } } function renderPollOptions() { if (note.pollOptions) { return ( <>

{note.pollOptions?.map((a, i) => (
changePollOption(i, e.target.value)} /> {i > 1 && ( )}
))} ); } } function changePollOption(i: number, v: string) { if (note.pollOptions) { const copy = [...note.pollOptions]; copy[i] = v; note.update(v => v.pollOptions = copy); } } function removePollOption(i: number) { if (note.pollOptions) { const copy = [...note.pollOptions]; copy.splice(i, 1); note.update(v => v.pollOptions = copy); } } function renderRelayCustomisation() { return (
{Object.keys(relays.item || {}) .filter(el => relays.item[el].write) .map((r, i, a) => (
{r}
{ note.update(v => v.selectedCustomRelays = ( // set false if all relays selected e.target.checked && note.selectedCustomRelays && note.selectedCustomRelays.length == a.length - 1 ? undefined : // otherwise return selectedCustomRelays with target relay added / removed a.filter(el => el === r ? e.target.checked : !note.selectedCustomRelays || note.selectedCustomRelays.includes(el)) )); } } />
))}
); } /*function listAccounts() { return LoginStore.getSessions().map(a => ( { ev.stopPropagation = true; LoginStore.switchAccount(a); }}> )); }*/ const handlePaste: ClipboardEventHandler = evt => { if (evt.clipboardData) { const clipboardItems = evt.clipboardData.items; const items: DataTransferItem[] = Array.from(clipboardItems).filter(function (item: DataTransferItem) { // Filter the image items only return /^image\//.test(item.type); }); if (items.length === 0) { return; } const item = items[0]; const blob = item.getAsFile(); if (blob) { uploadFile(blob); } } }; if (!note.show) return null; return ( note.update(v => v.show = false)}> {note.replyTo && ( )} {note.preview && getPreviewNote()} {!note.preview && (