| author | |
| committer | |
| log | 65b005f1e4bfccb6a404b3b1e5bfa363d534ac2a |
| tree | 98f4a16a8df3dc1eaffa53eb8c4ba80a3cad906f |
| parent | 752acbf6549e6dd422ac8d8ed11c0fa02577965b |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>4 files changed, 37 insertions(+), 3 deletions(-)
apps/web/src/components/Sidebar.tsx+23-1| ... | ... | @@ -29,7 +29,7 @@ import { |
| 29 | 29 | scopeThreadRef, |
| 30 | 30 | scopedThreadKey, |
| 31 | 31 | } from "@t3tools/client-runtime/environment"; |
| 32 | import type { ScopedThreadRef } from "@t3tools/contracts"; | |
| 32 | import type { ScopedThreadRef, ThreadId } from "@t3tools/contracts"; | |
| 33 | 33 | import type { TimestampFormat } from "@t3tools/contracts/settings"; |
| 34 | 34 | import { |
| 35 | 35 | AlarmClockIcon, |
| ... | ... | @@ -1647,6 +1647,24 @@ export default function Sidebar() { |
| 1647 | 1647 | ); |
| 1648 | 1648 | }, |
| 1649 | 1649 | }); |
| 1650 | const { copyToClipboard: copyThreadIdToClipboard } = useCopyToClipboard<{ threadId: ThreadId }>({ | |
| 1651 | onCopy: ({ threadId }) => { | |
| 1652 | toastManager.add({ | |
| 1653 | type: "success", | |
| 1654 | title: "Thread ID copied", | |
| 1655 | description: threadId, | |
| 1656 | }); | |
| 1657 | }, | |
| 1658 | onError: (error) => { | |
| 1659 | toastManager.add( | |
| 1660 | stackedThreadToast({ | |
| 1661 | type: "error", | |
| 1662 | title: "Failed to copy thread ID", | |
| 1663 | description: error instanceof Error ? error.message : "An error occurred.", | |
| 1664 | }), | |
| 1665 | ); | |
| 1666 | }, | |
| 1667 | }); | |
| 1650 | 1668 | const [projectScopeMenuOpen, setProjectScopeMenuOpen] = useState(false); |
| 1651 | 1669 | const newThreadContext = useHandleNewThread(); |
| 1652 | 1670 | const openAddProjectCommandPalette = useCallback( |
| ... | ... | @@ -3037,6 +3055,9 @@ export default function Sidebar() { |
| 3037 | 3055 | copyBranchToClipboard(thread.branch, { branch: thread.branch }); |
| 3038 | 3056 | } |
| 3039 | 3057 | return; |
| 3058 | case "copy-thread-id": | |
| 3059 | copyThreadIdToClipboard(thread.id, { threadId: thread.id }); | |
| 3060 | return; | |
| 3040 | 3061 | case "delete": { |
| 3041 | 3062 | if (confirmThreadDelete) { |
| 3042 | 3063 | const confirmed = await settlePromise(() => |
| ... | ... | @@ -3079,6 +3100,7 @@ export default function Sidebar() { |
| 3079 | 3100 | confirmThreadDelete, |
| 3080 | 3101 | copyBranchToClipboard, |
| 3081 | 3102 | copyPathToClipboard, |
| 3103 | copyThreadIdToClipboard, | |
| 3082 | 3104 | deleteThread, |
| 3083 | 3105 | handleMultiSelectContextMenu, |
| 3084 | 3106 | markThreadUnread, |
apps/web/src/components/threadActionMenu.logic.test.ts+1-1| ... | ... | @@ -26,7 +26,7 @@ describe("buildThreadActionMenuItems", () => { |
| 26 | 26 | ...baseState, |
| 27 | 27 | supports: { settlement: false, snooze: false, pinning: false, titleRegeneration: false }, |
| 28 | 28 | }), |
| 29 | ).toEqual(["rename", "mark-unread", "copy-path", "delete"]); | |
| 29 | ).toEqual(["rename", "mark-unread", "copy-path", "copy-thread-id", "delete"]); | |
| 30 | 30 | }); |
| 31 | 31 | |
| 32 | 32 | it("includes branch items only for threads with a branch", () => { |
apps/web/src/components/threadActionMenu.logic.ts+2| ... | ... | @@ -20,6 +20,7 @@ export type ThreadActionMenuId = |
| 20 | 20 | | "mark-unread" |
| 21 | 21 | | "copy-path" |
| 22 | 22 | | "copy-branch" |
| 23 | | "copy-thread-id" | |
| 23 | 24 | | "delete"; |
| 24 | 25 | |
| 25 | 26 | export interface ThreadActionMenuState { |
| ... | ... | @@ -100,6 +101,7 @@ export function buildThreadActionMenuItems( |
| 100 | 101 | { id: "mark-unread", label: "Mark unread" }, |
| 101 | 102 | { id: "copy-path", label: "Copy path", icon: "copy" }, |
| 102 | 103 | ...(state.branch ? [{ id: "copy-branch" as const, label: "Copy branch", icon: "copy" }] : []), |
| 104 | { id: "copy-thread-id", label: "Copy thread ID", icon: "copy" }, | |
| 103 | 105 | { id: "delete", label: "Delete", destructive: true, icon: "trash" }, |
| 104 | 106 | ]; |
| 105 | 107 | } |
apps/web/src/hooks/useThreadActionMenu.ts+11-1| ... | ... | @@ -11,7 +11,7 @@ import { |
| 11 | 11 | effectiveSnoozed, |
| 12 | 12 | type ChangeRequestStateLike, |
| 13 | 13 | } from "@t3tools/client-runtime/state/thread-settled"; |
| 14 | import type { ScopedThreadRef } from "@t3tools/contracts"; | |
| 14 | import type { ScopedThreadRef, ThreadId } from "@t3tools/contracts"; | |
| 15 | 15 | import { useCallback } from "react"; |
| 16 | 16 | |
| 17 | 17 | import { resolveSnoozePresets, snoozeWakeDescription } from "../components/Sidebar.snooze"; |
| ... | ... | @@ -95,6 +95,12 @@ export function useThreadActionMenu(input: { |
| 95 | 95 | }, |
| 96 | 96 | onError: (error) => failureToast("Failed to copy branch", error), |
| 97 | 97 | }); |
| 98 | const { copyToClipboard: copyThreadIdToClipboard } = useCopyToClipboard<{ threadId: ThreadId }>({ | |
| 99 | onCopy: ({ threadId }) => { | |
| 100 | toastManager.add({ type: "success", title: "Thread ID copied", description: threadId }); | |
| 101 | }, | |
| 102 | onError: (error) => failureToast("Failed to copy thread ID", error), | |
| 103 | }); | |
| 98 | 104 | |
| 99 | 105 | const openMenu = useCallback( |
| 100 | 106 | (position: { x: number; y: number }) => { |
| ... | ... | @@ -242,6 +248,9 @@ export function useThreadActionMenu(input: { |
| 242 | 248 | copyBranchToClipboard(thread.branch, { branch: thread.branch }); |
| 243 | 249 | } |
| 244 | 250 | return; |
| 251 | case "copy-thread-id": | |
| 252 | copyThreadIdToClipboard(thread.id, { threadId: thread.id }); | |
| 253 | return; | |
| 245 | 254 | case "delete": { |
| 246 | 255 | if (confirmThreadDelete) { |
| 247 | 256 | const confirmed = await settlePromise(() => |
| ... | ... | @@ -279,6 +288,7 @@ export function useThreadActionMenu(input: { |
| 279 | 288 | confirmThreadDelete, |
| 280 | 289 | copyBranchToClipboard, |
| 281 | 290 | copyPathToClipboard, |
| 291 | copyThreadIdToClipboard, | |
| 282 | 292 | deleteThread, |
| 283 | 293 | handleNewThread, |
| 284 | 294 | markThreadUnread, |