snort/packages/app/src/Element/Event/NoteContextMenu.tsx

205 lines
6.5 KiB
TypeScript
Raw Normal View History

2023-10-13 15:34:31 +00:00
import { useState } from "react";
2023-07-12 18:27:42 +00:00
import { FormattedMessage, useIntl } from "react-intl";
2023-09-19 08:30:01 +00:00
import { HexKey, Lists, NostrLink, TaggedNostrEvent } from "@snort/system";
2023-07-12 18:27:42 +00:00
import { Menu, MenuItem } from "@szhsin/react-menu";
import { TranslateHost } from "Const";
import Icon from "Icons/Icon";
import { setPinned, setBookmarked } from "Login";
import messages from "Element/messages";
import useLogin from "Hooks/useLogin";
import useModeration from "Hooks/useModeration";
2023-09-21 20:02:59 +00:00
import useEventPublisher from "Hooks/useEventPublisher";
2023-09-28 09:26:10 +00:00
import { ReBroadcaster } from "../ReBroadcaster";
2023-07-12 18:27:42 +00:00
export interface NoteTranslation {
text: string;
fromLanguage: string;
confidence: number;
}
interface NosteContextMenuProps {
ev: TaggedNostrEvent;
2023-07-12 18:27:42 +00:00
setShowReactions(b: boolean): void;
react(content: string): Promise<void>;
onTranslated?: (t: NoteTranslation) => void;
}
export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
const { formatMessage } = useIntl();
const login = useLogin();
const { mute, block } = useModeration();
2023-10-13 15:34:31 +00:00
const { publisher, system } = useEventPublisher();
2023-09-21 20:02:59 +00:00
const [showBroadcast, setShowBroadcast] = useState(false);
2023-07-12 18:27:42 +00:00
const lang = window.navigator.language;
const langNames = new Intl.DisplayNames([...window.navigator.languages], {
type: "language",
});
2023-09-23 21:21:37 +00:00
const isMine = ev.pubkey === login.publicKey;
2023-07-12 18:27:42 +00:00
async function deleteEvent() {
if (window.confirm(formatMessage(messages.ConfirmDeletion, { id: ev.id.substring(0, 8) })) && publisher) {
const evDelete = await publisher.delete(ev.id);
2023-10-13 15:34:31 +00:00
system.BroadcastEvent(evDelete);
2023-07-12 18:27:42 +00:00
}
}
async function share() {
2023-10-17 19:02:41 +00:00
const link = NostrLink.fromEvent(ev, CONFIG.eventLinkPrefix).encode();
const url = `${window.location.protocol}//${window.location.host}/${link}`;
2023-07-12 18:27:42 +00:00
if ("share" in window.navigator) {
await window.navigator.share({
title: "Snort",
url: url,
});
} else {
await navigator.clipboard.writeText(url);
}
}
async function translate() {
const res = await fetch(`${TranslateHost}/translate`, {
method: "POST",
body: JSON.stringify({
q: ev.content,
source: "auto",
target: lang.split("-")[0],
}),
headers: { "Content-Type": "application/json" },
});
if (res.ok) {
const result = await res.json();
if (typeof props.onTranslated === "function" && result) {
props.onTranslated({
text: result.translatedText,
fromLanguage: langNames.of(result.detectedLanguage.language),
confidence: result.detectedLanguage.confidence,
} as NoteTranslation);
}
}
}
async function copyId() {
2023-09-19 08:30:01 +00:00
const link = NostrLink.fromEvent(ev).encode();
2023-07-12 18:27:42 +00:00
await navigator.clipboard.writeText(link);
}
async function pin(id: HexKey) {
if (publisher) {
2023-09-23 21:21:37 +00:00
const es = [...login.pinned.item, id];
2023-07-12 18:27:42 +00:00
const ev = await publisher.noteList(es, Lists.Pinned);
2023-10-13 15:34:31 +00:00
system.BroadcastEvent(ev);
2023-07-12 18:27:42 +00:00
setPinned(login, es, ev.created_at * 1000);
}
}
async function bookmark(id: HexKey) {
if (publisher) {
2023-09-23 21:21:37 +00:00
const es = [...login.bookmarked.item, id];
2023-07-12 18:27:42 +00:00
const ev = await publisher.noteList(es, Lists.Bookmarked);
2023-10-13 15:34:31 +00:00
system.BroadcastEvent(ev);
2023-07-12 18:27:42 +00:00
setBookmarked(login, es, ev.created_at * 1000);
}
}
async function copyEvent() {
await navigator.clipboard.writeText(JSON.stringify(ev, undefined, " "));
}
const handleReBroadcastButtonClick = () => {
2023-09-21 20:02:59 +00:00
setShowBroadcast(true);
2023-07-12 18:27:42 +00:00
};
function menuItems() {
return (
<>
<div className="close-menu-container">
{/* This menu item serves as a "close menu" button;
it allows the user to click anywhere nearby the menu to close it. */}
<MenuItem>
<div className="close-menu" />
</MenuItem>
</div>
<MenuItem onClick={() => props.setShowReactions(true)}>
<Icon name="heart" />
<FormattedMessage {...messages.Reactions} />
</MenuItem>
<MenuItem onClick={() => share()}>
<Icon name="share" />
<FormattedMessage {...messages.Share} />
</MenuItem>
2023-09-23 21:21:37 +00:00
{!login.pinned.item.includes(ev.id) && !login.readonly && (
2023-07-12 18:27:42 +00:00
<MenuItem onClick={() => pin(ev.id)}>
<Icon name="pin" />
<FormattedMessage {...messages.Pin} />
</MenuItem>
)}
2023-09-23 21:21:37 +00:00
{!login.bookmarked.item.includes(ev.id) && !login.readonly && (
2023-07-12 18:27:42 +00:00
<MenuItem onClick={() => bookmark(ev.id)}>
<Icon name="bookmark" />
<FormattedMessage {...messages.Bookmark} />
</MenuItem>
)}
<MenuItem onClick={() => copyId()}>
<Icon name="copy" />
<FormattedMessage {...messages.CopyID} />
</MenuItem>
2023-09-23 21:21:37 +00:00
{!login.readonly && (
<MenuItem onClick={() => mute(ev.pubkey)}>
<Icon name="mute" />
<FormattedMessage {...messages.Mute} />
</MenuItem>
)}
{login.preferences.enableReactions && !login.readonly && (
2023-07-12 18:27:42 +00:00
<MenuItem onClick={() => props.react("-")}>
<Icon name="dislike" />
<FormattedMessage {...messages.DislikeAction} />
</MenuItem>
)}
2023-09-23 21:21:37 +00:00
<MenuItem onClick={handleReBroadcastButtonClick}>
<Icon name="relay" />
<FormattedMessage defaultMessage="Broadcast Event" />
</MenuItem>
{ev.pubkey !== login.publicKey && !login.readonly && (
2023-07-12 18:27:42 +00:00
<MenuItem onClick={() => block(ev.pubkey)}>
<Icon name="block" />
<FormattedMessage {...messages.Block} />
</MenuItem>
)}
<MenuItem onClick={() => translate()}>
<Icon name="translate" />
<FormattedMessage {...messages.TranslateTo} values={{ lang: langNames.of(lang.split("-")[0]) }} />
</MenuItem>
2023-09-23 21:21:37 +00:00
{login.preferences.showDebugMenus && (
2023-07-12 18:27:42 +00:00
<MenuItem onClick={() => copyEvent()}>
<Icon name="json" />
<FormattedMessage {...messages.CopyJSON} />
</MenuItem>
)}
2023-09-23 21:21:37 +00:00
{isMine && !login.readonly && (
2023-07-12 18:27:42 +00:00
<MenuItem onClick={() => deleteEvent()}>
<Icon name="trash" className="red" />
<FormattedMessage {...messages.Delete} />
</MenuItem>
)}
</>
);
}
return (
<>
<Menu
menuButton={
<div className="reaction-pill">
<Icon name="dots" size={15} />
</div>
}
menuClassName="ctx-menu">
{menuItems()}
</Menu>
2023-09-21 20:02:59 +00:00
{showBroadcast && <ReBroadcaster ev={ev} onClose={() => setShowBroadcast(false)} />}
2023-07-12 18:27:42 +00:00
</>
);
}