import { Avatar, Box, ButtonBase, InputBase, MenuItem, Select, Typography, } from "@mui/material"; import React, { useEffect, useMemo, useRef, useState } from "react"; import SearchIcon from "@mui/icons-material/Search"; import { Spacer } from "../../common/Spacer"; import AlternateEmailIcon from "@mui/icons-material/AlternateEmail"; import CloseIcon from "@mui/icons-material/Close"; import InsertLinkIcon from '@mui/icons-material/InsertLink'; import Highlight from "@tiptap/extension-highlight"; import Mention from "@tiptap/extension-mention"; import StarterKit from "@tiptap/starter-kit"; import Underline from "@tiptap/extension-underline"; import { AppsSearchContainer, AppsSearchLeft, AppsSearchRight, } from "../Apps/Apps-styles"; import IconSearch from "../../assets/svgs/Search.svg"; import IconClearInput from "../../assets/svgs/ClearInput.svg"; import { AutoSizer, CellMeasurer, CellMeasurerCache, List, } from "react-virtualized"; import { getBaseApiReact } from "../../App"; import { MessageDisplay } from "./MessageDisplay"; import { useVirtualizer } from "@tanstack/react-virtual"; import { formatTimestamp } from "../../utils/time"; import { ContextMenuMentions } from "../ContextMenuMentions"; import { convert } from 'html-to-text'; import { generateHTML } from "@tiptap/react"; import ErrorBoundary from "../../common/ErrorBoundary"; const extractTextFromHTML = (htmlString = '') => { return convert(htmlString, { wordwrap: false, // Disable word wrapping })?.toLowerCase(); }; const cache = new CellMeasurerCache({ fixedWidth: true, defaultHeight: 50, }); export const ChatOptions = ({ messages : untransformedMessages, goToMessage, members, myName, selectedGroup, openQManager, isPrivate }) => { const [mode, setMode] = useState("default"); const [searchValue, setSearchValue] = useState(""); const [selectedMember, setSelectedMember] = useState(0); const parentRef = useRef(); const parentRefMentions = useRef(); const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null) const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value const messages = useMemo(()=> { return untransformedMessages?.map((item)=> { if(item?.messageText){ let transformedMessage = item?.messageText try { transformedMessage = generateHTML(item?.messageText, [ StarterKit, Underline, Highlight, Mention ]) return { ...item, messageText: transformedMessage } } catch (error) { // error } } else return item }) }, [untransformedMessages]) const getTimestampMention = async () => { try { return new Promise((res, rej) => { window.sendMessage("getTimestampMention") .then((response) => { if (!response?.error) { if(response && selectedGroup && response[selectedGroup]){ setLastMentionTimestamp(response[selectedGroup]) } res(response); return; } rej(response.error); }) .catch((error) => { rej(error.message || "An error occurred"); }); }); } catch (error) {} }; useEffect(()=> { if(mode === 'mentions' && selectedGroup){ window.sendMessage("addTimestampMention", { timestamp: Date.now(), groupId: selectedGroup }).then((res)=> { getTimestampMention() }).catch((error) => { console.error("Failed to add timestamp:", error.message || "An error occurred"); }); } }, [mode, selectedGroup]) useEffect(()=> { getTimestampMention() }, []) // Debounce logic useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(searchValue); }, 350); // Cleanup timeout if searchValue changes before the timeout completes return () => { clearTimeout(handler); }; }, [searchValue]); // Runs effect when searchValue changes const searchedList = useMemo(() => { if (!debouncedValue?.trim()) { if (selectedMember) { return messages .filter((message) => message?.senderName === selectedMember) ?.sort((a, b) => b?.timestamp - a?.timestamp); } return []; } if (selectedMember) { return messages .filter( (message) => message?.senderName === selectedMember && extractTextFromHTML(isPrivate ? message?.messageText : message?.decryptedData?.message)?.includes( debouncedValue.toLowerCase() ) ) ?.sort((a, b) => b?.timestamp - a?.timestamp); } return messages .filter((message) => extractTextFromHTML(isPrivate === false ? message?.messageText : message?.decryptedData?.message)?.includes(debouncedValue.toLowerCase()) ) ?.sort((a, b) => b?.timestamp - a?.timestamp); }, [debouncedValue, messages, selectedMember, isPrivate]); const mentionList = useMemo(() => { if(!messages || messages.length === 0 || !myName) return [] if(isPrivate === false){ return messages .filter((message) => extractTextFromHTML(message?.messageText)?.includes(`@${myName}`) ) ?.sort((a, b) => b?.timestamp - a?.timestamp); } return messages .filter((message) => extractTextFromHTML(message?.decryptedData?.message)?.includes(`@${myName}`) ) ?.sort((a, b) => b?.timestamp - a?.timestamp); }, [messages, myName, isPrivate]); const rowVirtualizer = useVirtualizer({ count: searchedList.length, getItemKey: React.useCallback( (index) => searchedList[index].signature, [searchedList] ), getScrollElement: () => parentRef.current, estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed overscan: 10, // Number of items to render outside the visible area to improve smoothness }); const rowVirtualizerMentions = useVirtualizer({ count: mentionList.length, getItemKey: React.useCallback( (index) => mentionList[index].signature, [mentionList] ), getScrollElement: () => parentRefMentions.current, estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed overscan: 10, // Number of items to render outside the visible area to improve smoothness }); if (mode === "mentions") { return ( { setMode("default"); }} sx={{ cursor: "pointer", color: "white", }} /> {mentionList?.length === 0 && ( No results )}
{rowVirtualizerMentions.getVirtualItems().map((virtualRow) => { const index = virtualRow.index; let message = mentionList[index]; return (
); })}
); } if (mode === "search") { return ( { setMode("default"); }} sx={{ cursor: "pointer", color: "white", }} /> setSearchValue(e.target.value)} sx={{ ml: 1, flex: 1 }} placeholder="Search chat text" inputProps={{ "aria-label": "Search for apps", fontSize: "16px", fontWeight: 400, }} /> {searchValue && ( { setSearchValue(""); }} > )} {!!selectedMember && ( { setSelectedMember(0); }} sx={{ cursor: "pointer", color: "white", }} /> )} {debouncedValue && searchedList?.length === 0 && ( No results )}
{rowVirtualizer.getVirtualItems().map((virtualRow) => { const index = virtualRow.index; let message = searchedList[index]; return (
Error loading content: Invalid Data } >
); })}
); } return ( { setMode("search") }}> { setMode("default") setSearchValue('') setSelectedMember(0) openQManager() }}> { setMode("mentions") setSearchValue('') setSelectedMember(0) }}> 0 && (!lastMentionTimestamp || lastMentionTimestamp < mentionList[0]?.timestamp) ? 'var(--unread)' : 'white' }} /> ); }; const ShowMessage = ({message, goToMessage, messages})=> { return ( {message?.senderName?.charAt(0)} {message?.senderName} {formatTimestamp(message.timestamp)} { const findMsgIndex = messages.findIndex( (item) => item?.signature === message?.signature ); if (findMsgIndex !== -1) { goToMessage(findMsgIndex); } }} > {message?.messageText && ( )} {message?.decryptedData?.message && (

" } /> )}
) }