import React, { useState } from 'react'; import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuShortcut, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Eye, EyeClosed } from "lucide-react" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { Button } from "@/components/ui/button" // Internal Badge component to avoid import errors const Badge = ({ children, className }) => ( {children} ); function MessageWithHighlight({ message, dic, show }) { if (!message) return null; const [localMessage, setLocalMessage] = useState(message); const [open, setOpen] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); const [selectionData, setSelectionData] = useState(null); const handleMouseUp = () => { const selection = window.getSelection(); // 1. Check basic validity if (!selection || selection.rangeCount === 0 || selection.isCollapsed) { // If just a click (collapsed), we assume menu shouldn't open or should close // But we don't want to close immediately if clicking inside the menu, handled by DropdownMenu logic return; } // 2. Expand Selection to Word Boundaries const anchorNode = selection.anchorNode; const focusNode = selection.focusNode; // Only support single text node selection for simplicity to map back to data-index if (anchorNode !== focusNode || anchorNode.nodeType !== 3) { setOpen(false); return; } const textContent = anchorNode.textContent; let start = Math.min(selection.anchorOffset, selection.focusOffset); let end = Math.max(selection.anchorOffset, selection.focusOffset); // Expand left while (start > 0 && /\S/.test(textContent[start - 1])) { start--; } // Expand right while (end < textContent.length && /\S/.test(textContent[end])) { end++; } // Update visual selection const newRange = document.createRange(); newRange.setStart(anchorNode, start); newRange.setEnd(anchorNode, end); selection.removeAllRanges(); selection.addRange(newRange); const newText = textContent.slice(start, end); if (!newText.trim()) return; // 3. Validation Logic // Check 1: Less than 4 words const words = newText.split(/\s+/); if (words.length >= 4) { setOpen(false); return; } // Check 2: Does not contain highlighted word (brackets) if (newText.includes('[') || newText.includes(']')) { setOpen(false); return; } // Check 3: Check if selection is inside a Badge const parentElement = anchorNode.parentElement; if (parentElement.closest('.bg-pii')) { setOpen(false); return; } // Identify Part Index const partIndex = parseInt(parentElement.getAttribute('data-index'), 10); if (isNaN(partIndex)) { setOpen(false); return; } const rect = newRange.getBoundingClientRect(); setPosition({ x: rect.left, y: rect.bottom }); setSelectionData({ partIndex, start, end, text: newText }); setOpen(true); }; const handleAnonymize = () => { if (!selectionData) return; const { partIndex, start, end, text } = selectionData; // Split by brackets to reconstruct parts array as seen in render const parts = localMessage.split(/(\[[^\]]+\])/g); if (parts[partIndex] === undefined) return; const originalPart = parts[partIndex]; const newPart = originalPart.substring(0, start) + `[ANONYME_${text}]` + originalPart.substring(end); parts[partIndex] = newPart; const newMessage = parts.join(''); setLocalMessage(newMessage); setOpen(false); // Clear selection window.getSelection().removeAllRanges(); }; // Split by brackets, keeping the brackets in the split result for now // Regex: split by [content] const parts = localMessage.split(/(\[[^\]]+\])/g); return (

{parts.map((part, index) => { // Check if part is [something] const match = part.match(/^\[(\w+)_(.*?)\]$/); if (match && dic[match[2]]) { return ( show ? {dic[match[2]].value} :

{dic[match[2]].value}

); } return {part}; })}

Anonymiser
); } export default function Message() { const [show, setShow] = useState(true); return (
{/* */} {/* */} {/*
{props.message}
*/}
); }