authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 14:41:28-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 14:42:02-08:00
logeb59853596b9ea2547c1bde87d2cd9b59a6fef95
tree9d7b6122cecc2f6d572d2ff74047d0241e02cf94
parenta2682de4214cd277089fbf4a4c848d57a967f76d

Add Copy Thread ID action to thread context menu

- Add a `Copy Thread ID` item to the thread context menu in the sidebar - Copy the selected thread ID via Clipboard API - Show success/error toasts based on copy result

1 files changed, 25 insertions(+), 0 deletions(-)

apps/web/src/components/Sidebar.tsx+25
......@@ -60,6 +60,13 @@ import { isNonEmpty as isNonEmptyString } from "effect/String";
6060
6161const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = [];
6262
63async function copyTextToClipboard(text: string): Promise<void> {
64 if (typeof navigator === "undefined" || navigator.clipboard?.writeText === undefined) {
65 throw new Error("Clipboard API unavailable.");
66 }
67 await navigator.clipboard.writeText(text);
68}
69
6370function formatRelativeTime(iso: string): string {
6471 const diff = Date.now() - new Date(iso).getTime();
6572 const minutes = Math.floor(diff / 60_000);
......@@ -581,6 +588,7 @@ export default function Sidebar() {
581588 [
582589 { id: "rename", label: "Rename thread" },
583590 { id: "mark-unread", label: "Mark unread" },
591 { id: "copy-thread-id", label: "Copy Thread ID" },
584592 { id: "delete", label: "Delete", destructive: true },
585593 ],
586594 position,
......@@ -599,6 +607,23 @@ export default function Sidebar() {
599607 markThreadUnread(threadId);
600608 return;
601609 }
610 if (clicked === "copy-thread-id") {
611 try {
612 await copyTextToClipboard(threadId);
613 toastManager.add({
614 type: "success",
615 title: "Thread ID copied",
616 description: threadId,
617 });
618 } catch (error) {
619 toastManager.add({
620 type: "error",
621 title: "Failed to copy thread ID",
622 description: error instanceof Error ? error.message : "An error occurred.",
623 });
624 }
625 return;
626 }
602627 if (clicked !== "delete") return;
603628 if (appSettings.confirmThreadDelete) {
604629 const confirmed = await api.dialogs.confirm(