diff --git a/src/components/session/SessionViewer/ContentPanel.tsx b/src/components/session/SessionViewer/ContentPanel.tsx index dc24baf..f226628 100644 --- a/src/components/session/SessionViewer/ContentPanel.tsx +++ b/src/components/session/SessionViewer/ContentPanel.tsx @@ -1,14 +1,12 @@ "use client"; -import { useEffect, useState, useRef } from "react"; - +import { useEffect, useRef } from "react"; import { WrapText, Code2 } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; - import { ScrollArea } from "@/components/ui/scroll-area"; import { Toggle } from "@/components/ui/toggle"; import { useMessageFormatter } from "@/hooks/session/useMessageFormatter"; -import { useToast } from "@/hooks/use-toast"; +import { useClipboard } from "@/hooks/useClipboard"; import { decodeBase64 } from "@/lib/burpParser"; import { toCurl } from "@/lib/toCurl"; import type { ContentPanelProps } from "@/types/session"; @@ -24,166 +22,50 @@ export function ContentPanel({ prettify, setPrettify, }: ContentPanelProps) { - const { toast } = useToast(); - const [isMounted, setIsMounted] = useState(false); + const { copyToClipboard, isMounted } = useClipboard(); const content = item?.[type] || { value: "", base64: false }; const { formatMessage } = useMessageFormatter(); const decodedContent = content.base64 ? decodeBase64(content.value) : content.value; const contentRef = useRef(null); - useEffect(() => { - setIsMounted(true); - }, []); - useEffect(() => { if (contentRef.current) { contentRef.current.scrollTop = 0; } }, [item]); - const copyTextToClipboard = async (text: string): Promise => { - if (!isMounted) return; - - try { - // First try the modern clipboard API - if (window?.navigator?.clipboard) { - await window.navigator.clipboard.writeText(text); - return; - } - - // Fallback to execCommand - const textArea = document.createElement("textarea"); - textArea.value = text; - - // Avoid scrolling to bottom - textArea.style.top = "0"; - textArea.style.left = "0"; - textArea.style.position = "fixed"; - textArea.style.opacity = "0"; - - document.body.appendChild(textArea); - textArea.select(); - - const successful = document.execCommand("copy"); - document.body.removeChild(textArea); - - if (!successful) { - throw new Error("Failed to copy text"); - } - } catch (err) { - console.error("Copy failed:", err); - throw err; - } - }; - const handleCopy = { raw: async () => { - try { - await copyTextToClipboard(decodedContent); - toast({ - description: "Copied raw content to clipboard", - duration: 2000, - }); - } catch (err) { - console.error("Failed to copy raw content:", err); - toast({ - description: "Failed to copy to clipboard", - variant: "destructive", - duration: 2000, - }); - } + await copyToClipboard(decodedContent, "raw content"); }, headers: async () => { - try { - const { headers } = formatMessage(decodedContent, { - wrap: false, - prettify: false, - }); - const headerLines = headers.split("\n"); - const justHeaders = headerLines.slice(1).join("\n").trim(); - await copyTextToClipboard(justHeaders); - toast({ - description: "Copied headers to clipboard", - duration: 2000, - }); - } catch (err) { - console.error("Failed to copy headers:", err); - toast({ - description: "Failed to copy to clipboard", - variant: "destructive", - duration: 2000, - }); - } - }, - cookies: async () => { - try { - const { headers } = formatMessage(decodedContent, { - wrap: false, - prettify: false, - }); - const cookieLines = headers - .split("\n") - .filter((line) => line.toLowerCase().startsWith("cookie:")) - .join("\n"); - await copyTextToClipboard(cookieLines); - toast({ - description: "Copied cookies to clipboard", - duration: 2000, - }); - } catch (err) { - console.error("Failed to copy cookies:", err); - toast({ - description: "Failed to copy to clipboard", - variant: "destructive", - duration: 2000, - }); - } + const { headers } = formatMessage(decodedContent, { + wrap: false, + prettify: false, + }); + const headerLines = headers.split("\n"); + const justHeaders = headerLines.slice(1).join("\n").trim(); + await copyToClipboard(justHeaders, "headers"); }, - payload: async () => { - try { - const { body } = formatMessage(decodedContent, { - wrap: false, - prettify, - }); - await copyTextToClipboard(body); - toast({ - description: "Copied payload to clipboard", - duration: 2000, - }); - } catch (err) { - console.error("Failed to copy payload:", err); - toast({ - description: "Failed to copy to clipboard", - variant: "destructive", - duration: 2000, - }); - } + body: async () => { + const { body } = formatMessage(decodedContent, { + wrap: false, + prettify: false, + }); + await copyToClipboard(body, "body"); }, curl: type === "request" && item ? async () => { - try { - const curl = toCurl(item); - await copyTextToClipboard(curl); - toast({ - description: "Copied curl command to clipboard", - duration: 2000, - }); - } catch (err) { - console.error("Failed to copy curl command:", err); - toast({ - description: "Failed to copy to clipboard", - variant: "destructive", - duration: 2000, - }); - } + const curl = toCurl(item); + await copyToClipboard(curl, "curl command"); } : undefined, }; // Don't attempt to render until mounted if (!isMounted) { - return null; // or a loading state + return null; } return ( diff --git a/src/components/session/SessionViewer/TableContextMenu.tsx b/src/components/session/SessionViewer/TableContextMenu.tsx index 257b0ba..0e93283 100644 --- a/src/components/session/SessionViewer/TableContextMenu.tsx +++ b/src/components/session/SessionViewer/TableContextMenu.tsx @@ -15,7 +15,7 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { useState } from "react"; import { Copy, MessageCircle, Paintbrush2, Link, Globe, Clock, Terminal } from "lucide-react"; -import { useToast } from "@/hooks/use-toast"; +import { useClipboard } from "@/hooks/useClipboard"; import type { BurpItem, HighlightColor } from "@/types/burp"; import { toCurl } from "@/lib/toCurl"; @@ -23,7 +23,7 @@ interface TableContextMenuProps { children: React.ReactNode; item: BurpItem; items?: BurpItem[]; // Optional array of items for bulk operations - onHighlight: (color: HighlightColor) => void; + onHighlight: (color: HighlightColor | null) => void; onUpdateComment: (comment: string) => void; } @@ -47,7 +47,7 @@ export function TableContextMenu({ }: TableContextMenuProps) { const [showCommentDialog, setShowCommentDialog] = useState(false); const [comment, setComment] = useState(item.comment); - const { toast } = useToast(); + const { copyToClipboard, isMounted } = useClipboard(); const isBulkOperation = items && items.length > 1; @@ -62,72 +62,61 @@ export function TableContextMenu({ setShowCommentDialog(false); }; - const copyToClipboard = (text: string, description: string) => { - navigator.clipboard.writeText(text); - toast({ - description: `Copied ${description} to clipboard`, - duration: 2000, - }); - }; - - const handleCopyUrl = () => { + const handleCopyUrl = async () => { const fullUrl = `${item.host.value}${item.url}`; - navigator.clipboard.writeText(fullUrl); + await copyToClipboard(fullUrl, "URL"); }; - const handleCopyCurl = () => { + const handleCopyCurl = async () => { const curl = toCurl(item); - copyToClipboard(curl, "curl command"); + await copyToClipboard(curl, "curl command"); }; + const handleCopyHost = async () => { + await copyToClipboard(item.host.value, "host"); + }; + + const handleCopyTime = async () => { + await copyToClipboard(item.time, "timestamp"); + }; + + if (!isMounted) return null; + return ( <> {children} - {/* Copy Options - Only show in single item mode */} - {!isBulkOperation && ( - <> - - - - Copy - - - - - URL - - copyToClipboard(item.host.ip, "IP")} - > - - IP - - copyToClipboard(item.time, "Time")} - > - - Time - - - - - cURL (bash) - - - + + + + Copy + + + + + URL + + + + Host + + + + Time + - - )} - - {/* Highlight Options */} + + + cURL (bash) + + + + - {isBulkOperation ? "Highlight" : "Highlight"} + Highlight {isBulkOperation ? "All" : ""} onHighlight(null)}> @@ -154,11 +143,7 @@ export function TableContextMenu({ onClick={() => setShowCommentDialog(true)} > - {isBulkOperation - ? "Add Comment" - : item.comment - ? "Edit Comment" - : "Add Comment"} + {isBulkOperation ? "Comment All" : "Comment"} @@ -166,17 +151,16 @@ export function TableContextMenu({ - {isBulkOperation ? "Add Comment" : "Add Comment"} + + {isBulkOperation ? "Add Comment to All" : "Add Comment"} +
setComment(e.target.value)} onKeyDown={handleKeyDown} - autoFocus + placeholder="Enter comment..." />