From 3ce04de161f450e2da2b14ed2c3aae4bf996882e Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 6 Dec 2024 03:31:59 +0530 Subject: [PATCH 1/5] (feat:highlightSearch) recursively filter the DOM --- .../react-widget/src/components/SearchBar.tsx | 6 +- extensions/react-widget/src/utils/helper.ts | 101 +++++++++++++----- 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/extensions/react-widget/src/components/SearchBar.tsx b/extensions/react-widget/src/components/SearchBar.tsx index e88bdafd1..160726434 100644 --- a/extensions/react-widget/src/components/SearchBar.tsx +++ b/extensions/react-widget/src/components/SearchBar.tsx @@ -6,7 +6,7 @@ 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', @@ -291,6 +291,8 @@ export const SearchBar = ({ }, 500); return () => { + console.log(results); + abortController.abort(); clearTimeout(debounceTimeout.current ?? undefined); }; @@ -352,7 +354,7 @@ export const SearchBar = ({ {res.title} diff --git a/extensions/react-widget/src/utils/helper.ts b/extensions/react-widget/src/utils/helper.ts index 39c720e21..eb2f836f4 100644 --- a/extensions/react-widget/src/utils/helper.ts +++ b/extensions/react-widget/src/utils/helper.ts @@ -1,27 +1,76 @@ +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'; - } - - 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 + 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); + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, "text/html"); + + const filteredResults = document.createElement("div") + recursiveFilter(doc.body, keyword, filteredResults) + console.log(filteredResults) + + return DOMPurify.sanitize(filteredResults) + +} + +const recursiveFilter = (element: Node, keyword: string, parent: Node | null) => { + const content = element.textContent?.toLowerCase() ?? null; + const childNodes = element.childNodes + childNodes.forEach((child) => { + if (recursiveFilter(child, keyword, element)) + parent?.appendChild(highlightFilteredContent(child, keyword)) + }) + if (content && content.includes(keyword.toLowerCase())) { + return true + } + return false +} + +const highlightFilteredContent = (element: Node, keyword: string) => { + if (!element.textContent || !keyword.trim()) return element; + + const regex = new RegExp(`(${keyword})`, 'gi'); + const splitted = element.textContent.split(regex); + console.log(splitted); + + // Create a new HTML string with the keyword wrapped in a + const highlightedHTML = splitted + .map((part) => + regex.test(part) + ? `${part}` + : part + ) + .join(""); + if (element instanceof HTMLElement) { + element.innerHTML = highlightedHTML; + } + return element +}; \ No newline at end of file From fdd265f47f074812168c1d8a06d25d3612067adc Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 9 Dec 2024 01:51:06 +0530 Subject: [PATCH 2/5] (feat:filter search result) use node iterator --- extensions/react-widget/src/utils/helper.ts | 64 ++++++++------------- 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/extensions/react-widget/src/utils/helper.ts b/extensions/react-widget/src/utils/helper.ts index eb2f836f4..d9905a371 100644 --- a/extensions/react-widget/src/utils/helper.ts +++ b/extensions/react-widget/src/utils/helper.ts @@ -30,47 +30,29 @@ export const getOS = () => { export const preprocessSearchResultsToHTML = (text: string, keyword: string) => { const md = new MarkdownIt(); const htmlString = md.render(text); - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlString, "text/html"); - const filteredResults = document.createElement("div") - recursiveFilter(doc.body, keyword, filteredResults) - console.log(filteredResults) - - return DOMPurify.sanitize(filteredResults) - -} - -const recursiveFilter = (element: Node, keyword: string, parent: Node | null) => { - const content = element.textContent?.toLowerCase() ?? null; - const childNodes = element.childNodes - childNodes.forEach((child) => { - if (recursiveFilter(child, keyword, element)) - parent?.appendChild(highlightFilteredContent(child, keyword)) - }) - if (content && content.includes(keyword.toLowerCase())) { - return true + filteredResults.innerHTML = htmlString; + console.log(filteredResults); + + //iterator for nodes not including the keyword + const nodeIterator = document.createNodeIterator( + filteredResults, + NodeFilter.SHOW_ELEMENT, + { + acceptNode(node) { + return !node.textContent?.toLowerCase().includes(keyword.toLowerCase()) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + }, + }, + ); + + //remove each node from the DOM not containg the + let currentNode; + while ((currentNode = nodeIterator.nextNode())) { + currentNode.parentElement?.removeChild(currentNode); } - return false + if (!filteredResults.innerHTML.trim()) + return null; + return filteredResults.outerHTML } - -const highlightFilteredContent = (element: Node, keyword: string) => { - if (!element.textContent || !keyword.trim()) return element; - - const regex = new RegExp(`(${keyword})`, 'gi'); - const splitted = element.textContent.split(regex); - console.log(splitted); - - // Create a new HTML string with the keyword wrapped in a - const highlightedHTML = splitted - .map((part) => - regex.test(part) - ? `${part}` - : part - ) - .join(""); - if (element instanceof HTMLElement) { - element.innerHTML = highlightedHTML; - } - return element -}; \ No newline at end of file From 2378548cf1944ceb818fee4ded83dc972f28b262 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 9 Dec 2024 02:41:06 +0530 Subject: [PATCH 3/5] (feat: filer result) remove iterator; optimisation --- extensions/react-widget/src/utils/helper.ts | 77 ++++++++++++++------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/extensions/react-widget/src/utils/helper.ts b/extensions/react-widget/src/utils/helper.ts index d9905a371..cd6504334 100644 --- a/extensions/react-widget/src/utils/helper.ts +++ b/extensions/react-widget/src/utils/helper.ts @@ -30,29 +30,58 @@ export const getOS = () => { export const preprocessSearchResultsToHTML = (text: string, keyword: string) => { const md = new MarkdownIt(); const htmlString = md.render(text); - const filteredResults = document.createElement("div") + + // Container for processed HTML + const filteredResults = document.createElement("div"); filteredResults.innerHTML = htmlString; - console.log(filteredResults); - - //iterator for nodes not including the keyword - const nodeIterator = document.createNodeIterator( - filteredResults, - NodeFilter.SHOW_ELEMENT, - { - acceptNode(node) { - return !node.textContent?.toLowerCase().includes(keyword.toLowerCase()) - ? NodeFilter.FILTER_ACCEPT - : NodeFilter.FILTER_REJECT; - }, - }, - ); - - //remove each node from the DOM not containg the - let currentNode; - while ((currentNode = nodeIterator.nextNode())) { - currentNode.parentElement?.removeChild(currentNode); + + if (!processNode(filteredResults, keyword)) 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; + } + + 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; } - if (!filteredResults.innerHTML.trim()) - return null; - return filteredResults.outerHTML -} + + return false; +}; \ No newline at end of file From ca5eb06de9f8dc66a3a26fffc41675c1eb607089 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 9 Dec 2024 02:43:26 +0530 Subject: [PATCH 4/5] (feat:filter results) improve markdown format, add filter --- .../react-widget/src/components/SearchBar.tsx | 59 ++++++++++--------- extensions/react-widget/tsconfig.json | 2 +- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/extensions/react-widget/src/components/SearchBar.tsx b/extensions/react-widget/src/components/SearchBar.tsx index 160726434..42262e08c 100644 --- a/extensions/react-widget/src/components/SearchBar.tsx +++ b/extensions/react-widget/src/components/SearchBar.tsx @@ -1,11 +1,10 @@ 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, preprocessSearchResultsToHTML } from '../utils/helper' const themes = { dark: { @@ -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; @@ -292,7 +292,7 @@ export const SearchBar = ({ return () => { console.log(results); - + abortController.abort(); clearTimeout(debounceTimeout.current ?? undefined); }; @@ -343,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/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 From 7c3f80f13df8af2c13f8cb16891a94057282a294 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 9 Dec 2024 12:47:13 +0530 Subject: [PATCH 5/5] (fix: filter search) trim keyword pre processing --- extensions/react-widget/src/utils/helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/react-widget/src/utils/helper.ts b/extensions/react-widget/src/utils/helper.ts index cd6504334..d9aa19c34 100644 --- a/extensions/react-widget/src/utils/helper.ts +++ b/extensions/react-widget/src/utils/helper.ts @@ -35,7 +35,7 @@ export const preprocessSearchResultsToHTML = (text: string, keyword: string) => const filteredResults = document.createElement("div"); filteredResults.innerHTML = htmlString; - if (!processNode(filteredResults, keyword)) return null; + if (!processNode(filteredResults, keyword.trim())) return null; return filteredResults.innerHTML.trim() ? filteredResults.outerHTML : null; };