From 00c59426d3e915c51033369af6505169502d88fb Mon Sep 17 00:00:00 2001 From: dhairyashil Date: Mon, 13 Jan 2025 02:37:21 +0530 Subject: [PATCH 1/3] Added functionality to highlight searched messages --- packages/markups/src/elements/BoldSpan.js | 5 + packages/markups/src/elements/CodeElement.js | 20 ++- .../markups/src/elements/InlineElements.js | 7 +- packages/markups/src/elements/ItalicSpan.js | 4 + packages/markups/src/elements/LinkSpan.js | 4 + packages/markups/src/elements/StrikeSpan.js | 4 + .../markups/src/elements/elements.styles.js | 7 + .../markups/src/elements/highlightText.js | 21 +++ .../MessageAggregators/SearchMessages.js | 1 + .../common/MessageAggregator.js | 152 +++++++++++++++++- 10 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 packages/markups/src/elements/highlightText.js diff --git a/packages/markups/src/elements/BoldSpan.js b/packages/markups/src/elements/BoldSpan.js index 36f1ee945..8cf2c6ba9 100644 --- a/packages/markups/src/elements/BoldSpan.js +++ b/packages/markups/src/elements/BoldSpan.js @@ -4,6 +4,7 @@ import PlainSpan from './PlainSpan'; import ItalicSpan from './ItalicSpan'; import StrikeSpan from './StrikeSpan'; import LinkSpan from './LinkSpan'; +import HighlightText from './highlightText'; const BoldSpan = ({ contents }) => ( @@ -17,6 +18,7 @@ const BoldSpan = ({ contents }) => ( case 'ITALIC': return ; + case 'LINK': return ( ( /> ); + case 'HIGHLIGHT_TEXT': + return ; + default: return null; } diff --git a/packages/markups/src/elements/CodeElement.js b/packages/markups/src/elements/CodeElement.js index 8a2483f17..d91eaad27 100644 --- a/packages/markups/src/elements/CodeElement.js +++ b/packages/markups/src/elements/CodeElement.js @@ -2,16 +2,34 @@ import React from 'react'; import PropTypes from 'prop-types'; import PlainSpan from './PlainSpan'; import { InlineElementsStyles } from './elements.styles'; +import HighlightText from './highlightText'; const CodeElement = ({ contents }) => { const styles = InlineElementsStyles(); + + if (!Array.isArray(contents)) { + return null; // or return a fallback UI if `contents` isn't an array + } + return ( - + {contents.map((content, index) => { + switch (content.type) { + case 'PLAIN_TEXT': + return ; + + case 'HIGHLIGHT_TEXT': + return ; + + default: + return null; + } + })} ); }; + export default CodeElement; CodeElement.propTypes = { diff --git a/packages/markups/src/elements/InlineElements.js b/packages/markups/src/elements/InlineElements.js index 0ab5a7fb1..9ac89a26c 100644 --- a/packages/markups/src/elements/InlineElements.js +++ b/packages/markups/src/elements/InlineElements.js @@ -10,6 +10,7 @@ import ChannelMention from '../mentions/ChannelMention'; import ColorElement from './ColorElement'; import LinkSpan from './LinkSpan'; import UserMention from '../mentions/UserMention'; +import HighlightText from './highlightText'; const InlineElements = ({ contents }) => contents.map((content, index) => { @@ -53,7 +54,11 @@ const InlineElements = ({ contents }) => } /> ); - default: + + case 'HIGHLIGHT_TEXT': + return ; + + default: return null; } }); diff --git a/packages/markups/src/elements/ItalicSpan.js b/packages/markups/src/elements/ItalicSpan.js index 3f5d54a43..e966313bf 100644 --- a/packages/markups/src/elements/ItalicSpan.js +++ b/packages/markups/src/elements/ItalicSpan.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import PlainSpan from './PlainSpan'; import BoldSpan from './BoldSpan'; import StrikeSpan from './StrikeSpan'; +import HighlightText from './highlightText'; const ItalicSpan = ({ contents }) => ( @@ -17,6 +18,9 @@ const ItalicSpan = ({ contents }) => ( case 'BOLD': return ; + case 'HIGHLIGHT_TEXT': + return ; + default: return null; } diff --git a/packages/markups/src/elements/LinkSpan.js b/packages/markups/src/elements/LinkSpan.js index b1257f98d..5cc4ce01b 100644 --- a/packages/markups/src/elements/LinkSpan.js +++ b/packages/markups/src/elements/LinkSpan.js @@ -4,6 +4,7 @@ import PlainSpan from './PlainSpan'; import StrikeSpan from './StrikeSpan'; import ItalicSpan from './ItalicSpan'; import BoldSpan from './BoldSpan'; +import HighlightText from './highlightText'; const getBaseURI = () => { if (document.baseURI) { @@ -44,6 +45,9 @@ const LinkSpan = ({ href, label }) => { case 'BOLD': return ; + case 'HIGHLIGHT_TEXT': + return ; + default: return null; } diff --git a/packages/markups/src/elements/StrikeSpan.js b/packages/markups/src/elements/StrikeSpan.js index d8e749008..5d7387abb 100644 --- a/packages/markups/src/elements/StrikeSpan.js +++ b/packages/markups/src/elements/StrikeSpan.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import PlainSpan from './PlainSpan'; import BoldSpan from './BoldSpan'; import ItalicSpan from './ItalicSpan'; +import HighlightText from './highlightText'; const StrikeSpan = ({ contents }) => ( @@ -17,6 +18,9 @@ const StrikeSpan = ({ contents }) => ( case 'BOLD': return ; + case 'HIGHLIGHT_TEXT': + return ; + default: return null; } diff --git a/packages/markups/src/elements/elements.styles.js b/packages/markups/src/elements/elements.styles.js index 2f1f2fb29..a4b0be7cc 100644 --- a/packages/markups/src/elements/elements.styles.js +++ b/packages/markups/src/elements/elements.styles.js @@ -81,6 +81,13 @@ export const EmojiStyles = { `, }; +export const useHighlightTextStyles = { + highlight: css` + background-color: yellow; + font-weight: bold; + `, +}; + const useMentionStyles = (contents, username) => { const { theme } = useTheme(); const styles = { diff --git a/packages/markups/src/elements/highlightText.js b/packages/markups/src/elements/highlightText.js new file mode 100644 index 000000000..9d61d1a82 --- /dev/null +++ b/packages/markups/src/elements/highlightText.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Box } from '@embeddedchat/ui-elements'; +import { useHighlightTextStyles as styles } from './elements.styles'; + +const HighlightText = ({ contents }) => { + return ( + + {contents} + + ); +}; + +HighlightText.propTypes = { + contents: PropTypes.object.isRequired, +}; + +export default HighlightText; diff --git a/packages/react/src/views/MessageAggregators/SearchMessages.js b/packages/react/src/views/MessageAggregators/SearchMessages.js index b4248461c..eb63f1db5 100644 --- a/packages/react/src/views/MessageAggregators/SearchMessages.js +++ b/packages/react/src/views/MessageAggregators/SearchMessages.js @@ -53,6 +53,7 @@ const SearchMessages = () => { searchFiltered={messageList} shouldRender={(msg) => !!msg} viewType={viewType} + searchedText={text} /> ); }; diff --git a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js index 56d52a08c..78a60175b 100644 --- a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js +++ b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js @@ -28,6 +28,7 @@ export const MessageAggregator = ({ searchProps, searchFiltered, fetching, + searchedText, type = 'message', viewType = 'Sidebar', }) => { @@ -44,7 +45,7 @@ export const MessageAggregator = ({ ); const [messageRendered, setMessageRendered] = useState(false); - const { loading, messageList } = useSetMessageList( + let { loading, messageList } = useSetMessageList( fetchedMessageList || searchFiltered || allMessages, shouldRender ); @@ -108,6 +109,155 @@ export const MessageAggregator = ({ const noMessages = messageList?.length === 0 || !messageRendered; const ViewComponent = viewType === 'Popup' ? Popup : Sidebar; + const highlightText = (text, searchTerm) => { + const parts = text.split(searchTerm); + const result = []; + result.push({ type: 'PLAIN_TEXT', value: parts[0] }); + result.push({ type: 'HIGHLIGHT_TEXT', value: searchTerm }); + result.push({ type: 'PLAIN_TEXT', value: parts[1] }); + return result; + }; + + function highlightSearchTerm(messages, searchedWords) { + const searchTerms = Array.isArray(searchedWords) + ? searchedWords + : [searchedWords]; + + return messages.map((message) => { + message.md = message.md.map((paragraphBlock) => { + if (paragraphBlock.type === 'PARAGRAPH') { + const updatedValue = paragraphBlock.value.reduce( + (accumulatedValue, content) => { + if (content.type === 'PLAIN_TEXT') { + let updatedContent = content.value; + searchTerms.forEach((searchTerm) => { + if (updatedContent.includes(searchTerm)) { + // Highlight the search term and clear the updated content + accumulatedValue.push( + ...highlightText(updatedContent, searchTerm) + ); + updatedContent = ''; + } + }); + + if (updatedContent) { + accumulatedValue.push({ + type: 'PLAIN_TEXT', + value: updatedContent, + }); + } + } else if (content.type === 'LINK') { + // Handle LINK elements, applying highlight only to label if it exists + if (content.label && Array.isArray(content.label)) { + console.log('====== 1 ======'); + const updatedLabel = content.label.reduce( + (labelAccumulatedValue, labelContent) => { + console.log('====== 2 ======'); + if (labelContent.type === 'PLAIN_TEXT') { + let updatedContent = labelContent.value; + searchTerms.forEach((searchTerm) => { + if (updatedContent.includes(searchTerm)) { + labelAccumulatedValue.push( + ...highlightText(updatedContent, searchTerm) + ); + updatedContent = ''; + } + }); + + if (updatedContent) { + labelAccumulatedValue.push({ + type: 'PLAIN_TEXT', + value: updatedContent, + }); + } + } else { + labelAccumulatedValue.push(labelContent); // For non-PLAIN_TEXT content, leave as is + } + return labelAccumulatedValue; + }, + [] + ); + + // Push the updated LINK with modified label + accumulatedValue.push({ + ...content, + label: updatedLabel, + }); + } else { + // If no label found, just push the content as is + console.log('No value found'); + accumulatedValue.push(content); + } + } else if ( + content.type === 'STRIKE' || + content.type === 'BOLD' || + content.type === 'ITALIC' + ) { + // For custom formatting tags like STRIKE, BOLD, ITALIC, recursively highlight the inner contents + const updatedContents = content.value.reduce( + (innerAccumulatedValue, innerContent) => { + if (innerContent.type === 'PLAIN_TEXT') { + let updatedContent = innerContent.value; + searchTerms.forEach((searchTerm) => { + if (updatedContent.includes(searchTerm)) { + innerAccumulatedValue.push( + ...highlightText(updatedContent, searchTerm) + ); + updatedContent = ''; + } + }); + + if (updatedContent) { + innerAccumulatedValue.push({ + type: 'PLAIN_TEXT', + value: updatedContent, + }); + } + } else { + innerAccumulatedValue.push(innerContent); // For non-PLAIN_TEXT content, keep unchanged + } + + return innerAccumulatedValue; + }, + [] + ); + + // Push the modified formatting tags with updated contents + accumulatedValue.push({ + ...content, + value: updatedContents, + }); + } else { + accumulatedValue.push(content); // For non-PLAIN_TEXT or non-formatted content, add as is + } + + return accumulatedValue; + }, + [] + ); + + // Return the updated paragraph block + return { + ...paragraphBlock, + value: updatedValue, + }; + } + return paragraphBlock; // For non-PARAGRAPH blocks, return unchanged + }); + + return message; + }); + } + + if (title === 'Search Messages') { + if (messageList) { + console.log('messageList: ' + JSON.stringify(messageList)); + let highlightedMessages = highlightSearchTerm(messageList, searchedText); + messageList = highlightedMessages; + console.log('after messageList: ' + JSON.stringify(messageList)); + } + } + return ( Date: Mon, 13 Jan 2025 04:07:10 +0530 Subject: [PATCH 2/3] added functionality to highlight the searched message --- packages/markups/src/elements/BoldSpan.js | 2 +- packages/markups/src/elements/CodeElement.js | 20 +----------------- .../markups/src/elements/InlineElements.js | 6 +++--- packages/markups/src/elements/ItalicSpan.js | 2 +- packages/markups/src/elements/LinkSpan.js | 2 +- packages/markups/src/elements/StrikeSpan.js | 2 +- .../markups/src/elements/elements.styles.js | 8 +++---- .../markups/src/elements/highlightText.js | 15 +++++-------- .../common/MessageAggregator.js | 21 ++++++++++++------- 9 files changed, 31 insertions(+), 47 deletions(-) diff --git a/packages/markups/src/elements/BoldSpan.js b/packages/markups/src/elements/BoldSpan.js index 8cf2c6ba9..3e01a3353 100644 --- a/packages/markups/src/elements/BoldSpan.js +++ b/packages/markups/src/elements/BoldSpan.js @@ -32,7 +32,7 @@ const BoldSpan = ({ contents }) => ( /> ); - case 'HIGHLIGHT_TEXT': + case 'HIGHLIGHT_TEXT': return ; default: diff --git a/packages/markups/src/elements/CodeElement.js b/packages/markups/src/elements/CodeElement.js index d91eaad27..8a2483f17 100644 --- a/packages/markups/src/elements/CodeElement.js +++ b/packages/markups/src/elements/CodeElement.js @@ -2,34 +2,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import PlainSpan from './PlainSpan'; import { InlineElementsStyles } from './elements.styles'; -import HighlightText from './highlightText'; const CodeElement = ({ contents }) => { const styles = InlineElementsStyles(); - - if (!Array.isArray(contents)) { - return null; // or return a fallback UI if `contents` isn't an array - } - return ( - {contents.map((content, index) => { - switch (content.type) { - case 'PLAIN_TEXT': - return ; - - case 'HIGHLIGHT_TEXT': - return ; - - default: - return null; - } - })} + ); }; - export default CodeElement; CodeElement.propTypes = { diff --git a/packages/markups/src/elements/InlineElements.js b/packages/markups/src/elements/InlineElements.js index 9ac89a26c..dfb6fa588 100644 --- a/packages/markups/src/elements/InlineElements.js +++ b/packages/markups/src/elements/InlineElements.js @@ -55,10 +55,10 @@ const InlineElements = ({ contents }) => /> ); - case 'HIGHLIGHT_TEXT': + case 'HIGHLIGHT_TEXT': return ; - - default: + + default: return null; } }); diff --git a/packages/markups/src/elements/ItalicSpan.js b/packages/markups/src/elements/ItalicSpan.js index e966313bf..c82410ab4 100644 --- a/packages/markups/src/elements/ItalicSpan.js +++ b/packages/markups/src/elements/ItalicSpan.js @@ -18,7 +18,7 @@ const ItalicSpan = ({ contents }) => ( case 'BOLD': return ; - case 'HIGHLIGHT_TEXT': + case 'HIGHLIGHT_TEXT': return ; default: diff --git a/packages/markups/src/elements/LinkSpan.js b/packages/markups/src/elements/LinkSpan.js index 5cc4ce01b..39c5a6479 100644 --- a/packages/markups/src/elements/LinkSpan.js +++ b/packages/markups/src/elements/LinkSpan.js @@ -45,7 +45,7 @@ const LinkSpan = ({ href, label }) => { case 'BOLD': return ; - case 'HIGHLIGHT_TEXT': + case 'HIGHLIGHT_TEXT': return ; default: diff --git a/packages/markups/src/elements/StrikeSpan.js b/packages/markups/src/elements/StrikeSpan.js index 5d7387abb..855a7912a 100644 --- a/packages/markups/src/elements/StrikeSpan.js +++ b/packages/markups/src/elements/StrikeSpan.js @@ -18,7 +18,7 @@ const StrikeSpan = ({ contents }) => ( case 'BOLD': return ; - case 'HIGHLIGHT_TEXT': + case 'HIGHLIGHT_TEXT': return ; default: diff --git a/packages/markups/src/elements/elements.styles.js b/packages/markups/src/elements/elements.styles.js index 28b48ab7d..f3f10889d 100644 --- a/packages/markups/src/elements/elements.styles.js +++ b/packages/markups/src/elements/elements.styles.js @@ -82,10 +82,10 @@ export const EmojiStyles = { }; export const useHighlightTextStyles = { - highlight: css` - background-color: yellow; - font-weight: bold; - `, + highlight: css` + background-color: yellow; + font-weight: bold; + `, }; const useMentionStyles = (contents, username) => { diff --git a/packages/markups/src/elements/highlightText.js b/packages/markups/src/elements/highlightText.js index 9d61d1a82..dd163660c 100644 --- a/packages/markups/src/elements/highlightText.js +++ b/packages/markups/src/elements/highlightText.js @@ -3,16 +3,11 @@ import PropTypes from 'prop-types'; import { Box } from '@embeddedchat/ui-elements'; import { useHighlightTextStyles as styles } from './elements.styles'; -const HighlightText = ({ contents }) => { - return ( - - {contents} - - ); -}; +const HighlightText = ({ contents }) => ( + + {contents} + +); HighlightText.propTypes = { contents: PropTypes.object.isRequired, diff --git a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js index bdfcf234e..61cb1af4f 100644 --- a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js +++ b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js @@ -1,5 +1,5 @@ import React, { useState, useMemo } from 'react'; -import { isSameDay, format, set } from 'date-fns'; +import { isSameDay, format } from 'date-fns'; import { Box, Sidebar, @@ -46,7 +46,11 @@ export const MessageAggregator = ({ ); const [messageRendered, setMessageRendered] = useState(false); - let { loading, messageList } = useSetMessageList( + let { messageList } = useSetMessageList( + fetchedMessageList || searchFiltered || allMessages, + shouldRender + ); + const { loading } = useSetMessageList( fetchedMessageList || searchFiltered || allMessages, shouldRender ); @@ -119,12 +123,12 @@ export const MessageAggregator = ({ return result; }; - function highlightSearchTerm(messages, searchedWords) { + function highlightSearchTerm(messagesArr, searchedWords) { const searchTerms = Array.isArray(searchedWords) ? searchedWords : [searchedWords]; - return messages.map((message) => { + return messagesArr.map((message) => { message.md = message.md.map((paragraphBlock) => { if (paragraphBlock.type === 'PARAGRAPH') { const updatedValue = paragraphBlock.value.reduce( @@ -252,10 +256,13 @@ export const MessageAggregator = ({ if (title === 'Search Messages') { if (messageList) { - console.log('messageList: ' + JSON.stringify(messageList)); - let highlightedMessages = highlightSearchTerm(messageList, searchedText); + // console.log('messageList: ' + JSON.stringify(messageList)); + const highlightedMessages = highlightSearchTerm( + messageList, + searchedText + ); messageList = highlightedMessages; - console.log('after messageList: ' + JSON.stringify(messageList)); + // console.log('after messageList: ' + JSON.stringify(messageList)); } } From 9156655e2e8a9ba3ba719c1b71c8b738d28303df Mon Sep 17 00:00:00 2001 From: dhairyashil Date: Tue, 14 Jan 2025 10:02:47 +0530 Subject: [PATCH 3/3] added functionality to highlight the searched message, Covered All cases --- packages/markups/src/elements/CodeElement.js | 29 +++- .../markups/src/elements/highlightText.js | 2 +- packages/react/src/lib/highlightUtils.js | 151 ++++++++++++++++++ .../common/MessageAggregator.js | 143 +---------------- 4 files changed, 180 insertions(+), 145 deletions(-) create mode 100644 packages/react/src/lib/highlightUtils.js diff --git a/packages/markups/src/elements/CodeElement.js b/packages/markups/src/elements/CodeElement.js index 8a2483f17..6f5eaba7a 100644 --- a/packages/markups/src/elements/CodeElement.js +++ b/packages/markups/src/elements/CodeElement.js @@ -1,13 +1,27 @@ import React from 'react'; import PropTypes from 'prop-types'; import PlainSpan from './PlainSpan'; +import HighlightText from './highlightText'; import { InlineElementsStyles } from './elements.styles'; const CodeElement = ({ contents }) => { const styles = InlineElementsStyles(); + + const contentsArray = Array.isArray(contents) ? contents : [contents]; return ( - + {contentsArray.map((content, index) => { + switch (content.type) { + case 'PLAIN_TEXT': + return ; + + case 'HIGHLIGHT_TEXT': + return ; + + default: + return null; + } + })} ); }; @@ -15,5 +29,16 @@ const CodeElement = ({ contents }) => { export default CodeElement; CodeElement.propTypes = { - contents: PropTypes.any, + contents: PropTypes.oneOfType([ + PropTypes.arrayOf( + PropTypes.shape({ + type: PropTypes.string.isRequired, + value: PropTypes.any.isRequired, + }) + ), + PropTypes.shape({ + type: PropTypes.string.isRequired, + value: PropTypes.any.isRequired, + }), + ]).isRequired, }; diff --git a/packages/markups/src/elements/highlightText.js b/packages/markups/src/elements/highlightText.js index dd163660c..30d3d48e5 100644 --- a/packages/markups/src/elements/highlightText.js +++ b/packages/markups/src/elements/highlightText.js @@ -10,7 +10,7 @@ const HighlightText = ({ contents }) => ( ); HighlightText.propTypes = { - contents: PropTypes.object.isRequired, + contents: PropTypes.string.isRequired, }; export default HighlightText; diff --git a/packages/react/src/lib/highlightUtils.js b/packages/react/src/lib/highlightUtils.js new file mode 100644 index 000000000..a8292d9c4 --- /dev/null +++ b/packages/react/src/lib/highlightUtils.js @@ -0,0 +1,151 @@ +const highlightText = (text, searchTerm) => { + const parts = text.split(new RegExp(`(${searchTerm})`, 'gi')); + const result = []; + + parts.forEach((part) => { + if (part.toLowerCase() === searchTerm.toLowerCase()) { + result.push({ type: 'HIGHLIGHT_TEXT', value: part }); + } else { + result.push({ type: 'PLAIN_TEXT', value: part }); + } + }); + + return result; +}; + +export function highlightSearchTerm(messagesArr, searchedWords) { + const searchTerms = Array.isArray(searchedWords) + ? searchedWords + : [searchedWords]; + + return messagesArr.map((message) => { + if (message.md) { + message.md = message.md.map((paragraphBlock) => { + if (paragraphBlock.type === 'PARAGRAPH') { + const updatedValue = paragraphBlock.value.reduce( + (accumulatedValue, content) => { + if (content.type === 'PLAIN_TEXT') { + let updatedContent = content.value; + searchTerms.forEach((searchTerm) => { + if (searchTerm && updatedContent) { + accumulatedValue.push( + ...highlightText(updatedContent, searchTerm) + ); + updatedContent = ''; + } + }); + + if (updatedContent) { + accumulatedValue.push({ + type: 'PLAIN_TEXT', + value: updatedContent, + }); + } + } else if (content.type === 'LINK') { + const updatedLabel = content.value.label.reduce( + (labelAccumulatedValue, labelContent) => { + if (labelContent.type === 'PLAIN_TEXT') { + let updatedLabelContent = labelContent.value; + searchTerms.forEach((searchTerm) => { + if (searchTerm && updatedLabelContent) { + labelAccumulatedValue.push( + ...highlightText(updatedLabelContent, searchTerm) + ); + updatedLabelContent = ''; + } + }); + + if (updatedLabelContent) { + labelAccumulatedValue.push({ + type: 'PLAIN_TEXT', + value: updatedLabelContent, + }); + } + } else { + labelAccumulatedValue.push(labelContent); + } + + return labelAccumulatedValue; + }, + [] + ); + + accumulatedValue.push({ + ...content, + value: { + ...content.value, + label: updatedLabel, + }, + }); + } else if (content.type === 'INLINE_CODE') { + let updatedContent = content.value.value; + searchTerms.forEach((searchTerm) => { + if (searchTerm && updatedContent) { + accumulatedValue.push( + ...highlightText(updatedContent, searchTerm) + ); + updatedContent = ''; + } + }); + + if (updatedContent) { + accumulatedValue.push({ + type: 'INLINE_CODE', + value: { type: 'PLAIN_TEXT', value: updatedContent }, + }); + } + } else if (['STRIKE', 'BOLD', 'ITALIC'].includes(content.type)) { + const updatedContents = content.value.reduce( + (innerAccumulatedValue, innerContent) => { + if (innerContent.type === 'PLAIN_TEXT') { + let updatedContent = innerContent.value; + searchTerms.forEach((searchTerm) => { + if (searchTerm && updatedContent) { + innerAccumulatedValue.push( + ...highlightText(updatedContent, searchTerm) + ); + updatedContent = ''; + } + }); + + if (updatedContent) { + innerAccumulatedValue.push({ + type: 'PLAIN_TEXT', + value: updatedContent, + }); + } + } else { + innerAccumulatedValue.push(innerContent); + } + + return innerAccumulatedValue; + }, + [] + ); + + accumulatedValue.push({ + ...content, + value: updatedContents, + }); + } else { + accumulatedValue.push(content); + } + + return accumulatedValue; + }, + [] + ); + + return { + ...paragraphBlock, + value: updatedValue, + }; + } + + return paragraphBlock; + }); + } + + return message; + }); +} diff --git a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js index 61cb1af4f..e4858bb91 100644 --- a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js +++ b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js @@ -18,6 +18,7 @@ import NoMessagesIndicator from './NoMessageIndicator'; import FileDisplay from '../../FileMessage/FileMessage'; import useSetExclusiveState from '../../../hooks/useSetExclusiveState'; import { useRCContext } from '../../../context/RCInstance'; +import { highlightSearchTerm } from '../../../lib/highlightUtils'; export const MessageAggregator = ({ title, @@ -114,155 +115,13 @@ export const MessageAggregator = ({ const noMessages = messageList?.length === 0 || !messageRendered; const ViewComponent = viewType === 'Popup' ? Popup : Sidebar; - const highlightText = (text, searchTerm) => { - const parts = text.split(searchTerm); - const result = []; - result.push({ type: 'PLAIN_TEXT', value: parts[0] }); - result.push({ type: 'HIGHLIGHT_TEXT', value: searchTerm }); - result.push({ type: 'PLAIN_TEXT', value: parts[1] }); - return result; - }; - - function highlightSearchTerm(messagesArr, searchedWords) { - const searchTerms = Array.isArray(searchedWords) - ? searchedWords - : [searchedWords]; - - return messagesArr.map((message) => { - message.md = message.md.map((paragraphBlock) => { - if (paragraphBlock.type === 'PARAGRAPH') { - const updatedValue = paragraphBlock.value.reduce( - (accumulatedValue, content) => { - if (content.type === 'PLAIN_TEXT') { - let updatedContent = content.value; - searchTerms.forEach((searchTerm) => { - if (updatedContent.includes(searchTerm)) { - // Highlight the search term and clear the updated content - accumulatedValue.push( - ...highlightText(updatedContent, searchTerm) - ); - updatedContent = ''; - } - }); - - if (updatedContent) { - accumulatedValue.push({ - type: 'PLAIN_TEXT', - value: updatedContent, - }); - } - } else if (content.type === 'LINK') { - // Handle LINK elements, applying highlight only to label if it exists - if (content.label && Array.isArray(content.label)) { - console.log('====== 1 ======'); - const updatedLabel = content.label.reduce( - (labelAccumulatedValue, labelContent) => { - console.log('====== 2 ======'); - if (labelContent.type === 'PLAIN_TEXT') { - let updatedContent = labelContent.value; - searchTerms.forEach((searchTerm) => { - if (updatedContent.includes(searchTerm)) { - labelAccumulatedValue.push( - ...highlightText(updatedContent, searchTerm) - ); - updatedContent = ''; - } - }); - - if (updatedContent) { - labelAccumulatedValue.push({ - type: 'PLAIN_TEXT', - value: updatedContent, - }); - } - } else { - labelAccumulatedValue.push(labelContent); // For non-PLAIN_TEXT content, leave as is - } - return labelAccumulatedValue; - }, - [] - ); - - // Push the updated LINK with modified label - accumulatedValue.push({ - ...content, - label: updatedLabel, - }); - } else { - // If no label found, just push the content as is - console.log('No value found'); - accumulatedValue.push(content); - } - } else if ( - content.type === 'STRIKE' || - content.type === 'BOLD' || - content.type === 'ITALIC' - ) { - // For custom formatting tags like STRIKE, BOLD, ITALIC, recursively highlight the inner contents - const updatedContents = content.value.reduce( - (innerAccumulatedValue, innerContent) => { - if (innerContent.type === 'PLAIN_TEXT') { - let updatedContent = innerContent.value; - searchTerms.forEach((searchTerm) => { - if (updatedContent.includes(searchTerm)) { - innerAccumulatedValue.push( - ...highlightText(updatedContent, searchTerm) - ); - updatedContent = ''; - } - }); - - if (updatedContent) { - innerAccumulatedValue.push({ - type: 'PLAIN_TEXT', - value: updatedContent, - }); - } - } else { - innerAccumulatedValue.push(innerContent); // For non-PLAIN_TEXT content, keep unchanged - } - - return innerAccumulatedValue; - }, - [] - ); - - // Push the modified formatting tags with updated contents - accumulatedValue.push({ - ...content, - value: updatedContents, - }); - } else { - accumulatedValue.push(content); // For non-PLAIN_TEXT or non-formatted content, add as is - } - - return accumulatedValue; - }, - [] - ); - - // Return the updated paragraph block - return { - ...paragraphBlock, - value: updatedValue, - }; - } - return paragraphBlock; // For non-PARAGRAPH blocks, return unchanged - }); - - return message; - }); - } - if (title === 'Search Messages') { if (messageList) { - // console.log('messageList: ' + JSON.stringify(messageList)); const highlightedMessages = highlightSearchTerm( messageList, searchedText ); messageList = highlightedMessages; - // console.log('after messageList: ' + JSON.stringify(messageList)); } }