import { Avatar, Box, ButtonBase, InputBase, MenuItem, Select, Tooltip, 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) => { chrome?.runtime?.sendMessage( { action: "getTimestampMention", }, (response) => { if (!response?.error && selectedGroup && response[selectedGroup]) { setLastMentionTimestamp(response[selectedGroup]); res(response); } rej(response.error); } ); }); } catch (error) {} }; useEffect(() => { if (mode === "mentions" && selectedGroup) { chrome?.runtime?.sendMessage( { action: "addTimestampMention", payload: { timestamp: Date.now(), groupId: selectedGroup, } }, (response) => { if (!response?.error) { getTimestampMention(); } } ); } }, [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?.toLowerCase()}`) ) ?.sort((a, b) => b?.timestamp - a?.timestamp); } return messages .filter((message) => extractTextFromHTML(message?.decryptedData?.message)?.includes( `@${myName?.toLowerCase()}` ) ) ?.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") }}> SEARCH} placement="left" arrow sx={{ fontSize: "24" }} slotProps={{ tooltip: { sx: { color: "#ffffff", backgroundColor: "#444444", }, }, arrow: { sx: { color: "#444444", }, }, }} > { setMode("default") setSearchValue('') setSelectedMember(0) openQManager() }}> Q-MANAGER} placement="left" arrow sx={{ fontSize: "24" }} slotProps={{ tooltip: { sx: { color: "#ffffff", backgroundColor: "#444444", }, }, arrow: { sx: { color: "#444444", }, }, }} > { setMode("mentions") setSearchValue('') setSelectedMember(0) }}> MENTIONED} placement="left" arrow sx={{ fontSize: "24" }} slotProps={{ tooltip: { sx: { color: "#ffffff", backgroundColor: "#444444", }, }, arrow: { sx: { color: "#444444", }, }, }} > 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 && (

"} /> )}
); };