diff --git a/extensions/react-widget/src/components/SearchBar.tsx b/extensions/react-widget/src/components/SearchBar.tsx index e88bdafd1..42262e08c 100644 --- a/extensions/react-widget/src/components/SearchBar.tsx +++ b/extensions/react-widget/src/components/SearchBar.tsx @@ -1,12 +1,11 @@ import React from 'react' -import styled, { keyframes, createGlobalStyle, ThemeProvider } from 'styled-components'; +import styled, { ThemeProvider } from 'styled-components'; import { WidgetCore } from './DocsGPTWidget'; import { SearchBarProps } from '@/types'; import { getSearchResults } from '../requests/searchAPI' import { Result } from '@/types'; import MarkdownIt from 'markdown-it'; -import DOMPurify from 'dompurify'; -import { getOS } from '../utils/helper' +import { getOS, preprocessSearchResultsToHTML } from '../utils/helper' const themes = { dark: { bg: '#000', @@ -116,7 +115,7 @@ const ResultWrapper = styled.div` const Markdown = styled.div` line-height:20px; font-size: 12px; -word-break: break-all; +white-space: pre-wrap; pre { padding: 8px; width: 90%; @@ -147,17 +146,18 @@ word-break: break-all; code:not(pre code) { border-radius: 6px; - padding: 4px 4px; - font-size: 12px; - display: inline-block; + padding: 2px 2px; + margin: 2px; + font-size: 10px; + display: inline; background-color: #646464; color: #fff ; } - + img{ + max-width: 50%; + } code { - white-space: pre-wrap ; - overflow-wrap: break-word; - word-break: break-all; + overflow-x: auto; } a{ color: #007ee6; @@ -291,6 +291,8 @@ export const SearchBar = ({ }, 500); return () => { + console.log(results); + abortController.abort(); clearTimeout(debounceTimeout.current ?? undefined); }; @@ -341,22 +343,27 @@ export const SearchBar = ({ (results.length > 0 ? results.map((res, key) => { const containsSource = res.source !== 'local'; - return ( - { - if (!containsSource) return; - window.open(res.source, '_blank', 'noopener, noreferrer') - }} - className={containsSource ? "contains-source" : ""}> - {res.title} - - - - - ) + const filteredResults = preprocessSearchResultsToHTML(res.text,input) + if (filteredResults) + return ( + { + if (!containsSource) return; + window.open(res.source, '_blank', 'noopener, noreferrer') + }} + className={containsSource ? "contains-source" : ""}> + {res.title} + + + + + ) + else { + setResults((prevItems) => prevItems.filter((_, index) => index !== key)); + } }) : No results diff --git a/extensions/react-widget/src/utils/helper.ts b/extensions/react-widget/src/utils/helper.ts index 39c720e21..d9aa19c34 100644 --- a/extensions/react-widget/src/utils/helper.ts +++ b/extensions/react-widget/src/utils/helper.ts @@ -1,27 +1,87 @@ +import MarkdownIt from "markdown-it"; +import DOMPurify from "dompurify"; export const getOS = () => { - const platform = window.navigator.platform; - const userAgent = window.navigator.userAgent || window.navigator.vendor; - - if (/Mac/i.test(platform)) { - return 'mac'; + const platform = window.navigator.platform; + const userAgent = window.navigator.userAgent || window.navigator.vendor; + + if (/Mac/i.test(platform)) { + return 'mac'; + } + + if (/Win/i.test(platform)) { + return 'win'; + } + + if (/Linux/i.test(platform) && !/Android/i.test(userAgent)) { + return 'linux'; + } + + if (/Android/i.test(userAgent)) { + return 'android'; + } + + if (/iPhone|iPad|iPod/i.test(userAgent)) { + return 'ios'; + } + + return 'other'; +}; + +export const preprocessSearchResultsToHTML = (text: string, keyword: string) => { + const md = new MarkdownIt(); + const htmlString = md.render(text); + + // Container for processed HTML + const filteredResults = document.createElement("div"); + filteredResults.innerHTML = htmlString; + + if (!processNode(filteredResults, keyword.trim())) return null; + + return filteredResults.innerHTML.trim() ? filteredResults.outerHTML : null; +}; + + + +// Recursive function to process nodes +const processNode = (node: Node, keyword: string): boolean => { + + const keywordRegex = new RegExp(`(${keyword})`, "gi"); + if (node.nodeType === Node.TEXT_NODE) { + const textContent = node.textContent || ""; + + if (textContent.toLowerCase().includes(keyword.toLowerCase())) { + const highlightedHTML = textContent.replace( + keywordRegex, + `$1` + ); + const tempContainer = document.createElement("div"); + tempContainer.innerHTML = highlightedHTML; + + // Replace the text node with highlighted content + while (tempContainer.firstChild) { + node.parentNode?.insertBefore(tempContainer.firstChild, node); + } + node.parentNode?.removeChild(node); + + return true; } - - if (/Win/i.test(platform)) { - return 'win'; - } - - if (/Linux/i.test(platform) && !/Android/i.test(userAgent)) { - return 'linux'; - } - - if (/Android/i.test(userAgent)) { - return 'android'; - } - - if (/iPhone|iPad|iPod/i.test(userAgent)) { - return 'ios'; - } - - return 'other'; - }; - \ No newline at end of file + + return false; + } else if (node.nodeType === Node.ELEMENT_NODE) { + + const children = Array.from(node.childNodes); + let hasKeyword = false; + + children.forEach((child) => { + if (!processNode(child, keyword)) { + node.removeChild(child); + } else { + hasKeyword = true; + } + }); + + return hasKeyword; + } + + return false; +}; \ No newline at end of file diff --git a/extensions/react-widget/tsconfig.json b/extensions/react-widget/tsconfig.json index e73dd80f2..88e86216c 100644 --- a/extensions/react-widget/tsconfig.json +++ b/extensions/react-widget/tsconfig.json @@ -21,7 +21,7 @@ /* Linting */ "strict": true, "noUnusedLocals": false, - "noUnusedParameters": true, + "noUnusedParameters": false, "noFallthroughCasesInSwitch": true, /* The "typeRoots" configuration specifies the locations where TypeScript looks for type definitions (.d.ts files) to