| ... | ... | @@ -60,6 +60,13 @@ import { isNonEmpty as isNonEmptyString } from "effect/String"; |
| 60 | 60 | |
| 61 | 61 | const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = []; |
| 62 | 62 | |
| 63 | async 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 | |
| 63 | 70 | function formatRelativeTime(iso: string): string { |
| 64 | 71 | const diff = Date.now() - new Date(iso).getTime(); |
| 65 | 72 | const minutes = Math.floor(diff / 60_000); |
| ... | ... | @@ -581,6 +588,7 @@ export default function Sidebar() { |
| 581 | 588 | [ |
| 582 | 589 | { id: "rename", label: "Rename thread" }, |
| 583 | 590 | { id: "mark-unread", label: "Mark unread" }, |
| 591 | { id: "copy-thread-id", label: "Copy Thread ID" }, |
| 584 | 592 | { id: "delete", label: "Delete", destructive: true }, |
| 585 | 593 | ], |
| 586 | 594 | position, |
| ... | ... | @@ -599,6 +607,23 @@ export default function Sidebar() { |
| 599 | 607 | markThreadUnread(threadId); |
| 600 | 608 | return; |
| 601 | 609 | } |
| 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 | } |
| 602 | 627 | if (clicked !== "delete") return; |
| 603 | 628 | if (appSettings.confirmThreadDelete) { |
| 604 | 629 | const confirmed = await api.dialogs.confirm( |