import { createContext, useContext } from "react"; import { post } from "../api"; import { asQuote, compose } from "../compose"; import { useT } from "../i18n"; import { navigate } from "../router"; import { loadSessions } from "../store"; import { toast } from "../dialogs"; import { Icon } from "./Icon"; /** What the message menus of one chat need to know. */ export const ChatCtx = createContext<{ sessionId: string; canFork: boolean; canReply: boolean }>({ sessionId: "", canFork: false, canReply: false }); /** * Actions of one message: copy, quote into the reply, fork the chat from here and * (for own messages) edit and resend. Hover on desktop, always faintly visible on touch. */ export function MessageMenu({ messageId, text, forkable, onRewind }: { messageId: string; text: string; forkable: boolean; onRewind?: () => void }) { const t = useT(); const { sessionId, canFork, canReply } = useContext(ChatCtx); const close = (e: React.MouseEvent) => { e.preventDefault(); (e.currentTarget.closest("details") as HTMLDetailsElement).open = false; }; if (!text.trim() && !onRewind) return null; return (
{text.trim() && ( { close(e); const done = () => toast(t("msg.copied"), "success"); if (navigator.clipboard?.writeText) void navigator.clipboard.writeText(text).then(done, () => toast(t("msg.copyFailed"))); else toast(t("msg.copyFailed")); }} > {t("msg.copy")} )} {text.trim() && canReply && ( (close(e), compose({ sessionId, text: asQuote(text) }))}> {t("msg.quote")} )} {onRewind && ( (close(e), onRewind())}> {t("chat.rewind")} )} {forkable && canFork && ( { close(e); try { const fork = await post<{ id: string }>(`/sessions/${sessionId}/fork`, { message_id: messageId }); await loadSessions(); toast(t("chat.forked")); navigate(`/s/${fork.id}`); } catch (err: any) { toast(err.message); } }} > {t("msg.forkHere")} )}
); }