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