import "./NoteCreator.css";
import { FormattedMessage, useIntl } from "react-intl";
import { useDispatch, useSelector } from "react-redux";
import { encodeTLV, EventKind, NostrPrefix, TaggedRawEvent } from "@snort/nostr";
import Icon from "Icons/Icon";
import useEventPublisher from "Feed/EventPublisher";
import { openFile } from "Util";
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 {
setShow,
setNote,
setError,
setActive,
setPreview,
setShowAdvanced,
setSelectedCustomRelays,
setZapForward,
setSensitive,
reset,
setPollOptions,
setOtherEvents,
} from "State/NoteCreator";
import type { RootState } from "State/Store";
import { LNURL } from "LNURL";
import messages from "./messages";
import { ClipboardEventHandler, useState } from "react";
import Spinner from "Icons/Spinner";
import { EventBuilder } from "System";
import { Menu, MenuItem } from "@szhsin/react-menu";
import { LoginStore } from "Login";
import { getCurrentSubscription } from "Subscription";
import useLogin from "Hooks/useLogin";
interface NotePreviewProps {
note: TaggedRawEvent;
}
function NotePreview({ note }: NotePreviewProps) {
return (
{note.content.slice(0, 136)}
{note.content.length > 140 && "..."}
);
}
export function NoteCreator() {
const { formatMessage } = useIntl();
const publisher = useEventPublisher();
const uploader = useFileUpload();
const {
note,
zapForward,
sensitive,
pollOptions,
replyTo,
otherEvents,
preview,
active,
show,
showAdvanced,
selectedCustomRelays,
error,
} = useSelector((s: RootState) => s.noteCreator);
const [uploadInProgress, setUploadInProgress] = useState(false);
const dispatch = useDispatch();
const sub = getCurrentSubscription(LoginStore.allSubscriptions());
const login = useLogin();
const relays = login.relays;
async function sendNote() {
if (note && publisher) {
let extraTags: Array> | undefined;
if (zapForward) {
try {
const svc = new LNURL(zapForward);
await svc.load();
extraTags = [svc.getZapTag()];
} catch {
dispatch(
setError(
formatMessage({
defaultMessage: "Invalid LNURL",
})
)
);
return;
}
}
if (sensitive) {
extraTags ??= [];
extraTags.push(["content-warning", sensitive]);
}
const kind = pollOptions ? EventKind.Polls : EventKind.TextNote;
if (pollOptions) {
extraTags ??= [];
extraTags.push(...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 = replyTo ? await publisher.reply(replyTo, note, hk) : await publisher.note(note, hk);
if (selectedCustomRelays) publisher.broadcastAll(ev, selectedCustomRelays);
else publisher.broadcast(ev);
dispatch(reset());
for (const oe of otherEvents) {
if (selectedCustomRelays) publisher.broadcastAll(oe, selectedCustomRelays);
else publisher.broadcast(oe);
}
dispatch(reset());
}
}
async function attachFile() {
try {
const file = await openFile();
if (file) {
uploadFile(file);
}
} catch (error: unknown) {
if (error instanceof Error) {
dispatch(setError(error?.message));
}
}
}
async function uploadFile(file: File | Blob) {
setUploadInProgress(true);
try {
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)}`;
dispatch(setNote(`${note ? `${note}\n` : ""}${link}`));
dispatch(setOtherEvents([...otherEvents, rx.header]));
} else if (rx.url) {
dispatch(setNote(`${note ? `${note}\n` : ""}${rx.url}`));
} else if (rx?.error) {
dispatch(setError(rx.error));
}
}
} catch (error: unknown) {
if (error instanceof Error) {
dispatch(setError(error?.message));
}
} finally {
setUploadInProgress(false);
}
}
function onChange(ev: React.ChangeEvent) {
const { value } = ev.target;
dispatch(setNote(value));
if (value) {
dispatch(setActive(true));
} else {
dispatch(setActive(false));
}
}
function cancel() {
dispatch(reset());
}
function onSubmit(ev: React.MouseEvent) {
ev.stopPropagation();
sendNote().catch(console.warn);
}
async function loadPreview() {
if (preview) {
dispatch(setPreview(undefined));
} else if (publisher) {
const tmpNote = await publisher.note(note);
if (tmpNote) {
dispatch(setPreview(tmpNote));
}
}
}
function getPreviewNote() {
if (preview) {
return (
);
}
}
function renderPollOptions() {
if (pollOptions) {
return (
<>
{pollOptions?.map((a, i) => (
))}
>
);
}
}
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));
}
}
function renderRelayCustomisation() {
return (
{Object.keys(relays.item || {})
.filter(el => relays.item[el].write)
.map((r, i, a) => (
))}
);
}
function listAccounts() {
return LoginStore.getSessions().map(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);
}
}
};
return (
<>
{show && (
dispatch(setShow(false))}>
{replyTo && }
{preview && getPreviewNote()}
{!preview && (
)}
{uploadInProgress && }
{showAdvanced && (
{renderRelayCustomisation()}
dispatch(setZapForward(e.target.value))}
/>
dispatch(setSensitive(e.target.value))}
maxLength={50}
minLength={1}
placeholder={formatMessage({
defaultMessage: "Reason",
})}
/>
)}
)}
>
);
}