diff --git a/ee/tabby-ui/app/(home)/components/search.tsx b/ee/tabby-ui/app/(home)/components/search.tsx index 661df0d68ec4..f49ea131b65d 100644 --- a/ee/tabby-ui/app/(home)/components/search.tsx +++ b/ee/tabby-ui/app/(home)/components/search.tsx @@ -12,6 +12,8 @@ import { useState } from 'react' import { Message } from 'ai' +import DOMPurify from 'dompurify' +import { marked } from 'marked' import { nanoid } from 'nanoid' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' @@ -95,8 +97,8 @@ const tabbyFetcher = ((url: string, init?: RequestInit) => { }) as typeof fetch const SOURCE_CARD_STYLE = { - compress: 5, - expand: 7 + compress: 5.3, + expand: 6.3 } export function SearchRenderer({}, ref: ForwardedRef) { @@ -526,29 +528,51 @@ function SourceCard({ showMore: boolean }) { const { hostname } = new URL(source.link) + + // Remove HTML and Markdown format + const normalizedText = (input: string) => { + const sanitizedHtml = DOMPurify.sanitize(input, { + ALLOWED_TAGS: [], + ALLOWED_ATTR: [] + }) + const parsed = marked.parse(sanitizedHtml) as string + const plainText = parsed.replace(/<\/?[^>]+(>|$)/g, '') + + return plainText + } + return (
window.open(source.link)} > -

- {source.title} -

- {showMore && ( -

- {source.snippet} +

+

+ {source.title}

- )} +

+ {normalizedText(source.snippet)} +

+
-
+

- {hostname.replace('www.', '').split('.')[0]} + {hostname.replace('www.', '').split('/')[0]}

diff --git a/ee/tabby-ui/package.json b/ee/tabby-ui/package.json index 2d36ec2bacb6..6d19e7f46839 100644 --- a/ee/tabby-ui/package.json +++ b/ee/tabby-ui/package.json @@ -62,6 +62,7 @@ "compare-versions": "^6.1.0", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", + "dompurify": "^3.1.5", "downshift": "^8.2.2", "eventsource-parser": "^1.1.2", "focus-trap-react": "^10.1.1", @@ -70,6 +71,7 @@ "jwt-decode": "^4.0.0", "lodash-es": "^4.17.21", "lucide-react": "^0.365.0", + "marked": "^13.0.0", "mitt": "^3.0.1", "moment": "^2.29.4", "moment-timezone": "^0.5.45", @@ -116,6 +118,7 @@ "@tailwindcss/typography": "^0.5.9", "@types/aos": "^3.0.7", "@types/color": "^3.0.6", + "@types/dompurify": "^3.0.5", "@types/humanize-duration": "^3.27.4", "@types/lodash-es": "^4.17.10", "@types/node": "^17.0.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93f4a01667fd..abeb7e8f4bca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -529,6 +529,9 @@ importers: date-fns: specifier: ^3.6.0 version: 3.6.0 + dompurify: + specifier: ^3.1.5 + version: 3.1.5 downshift: specifier: ^8.2.2 version: 8.2.2(react@18.2.0) @@ -553,6 +556,9 @@ importers: lucide-react: specifier: ^0.365.0 version: 0.365.0(react@18.2.0) + marked: + specifier: ^13.0.0 + version: 13.0.0 mitt: specifier: ^3.0.1 version: 3.0.1 @@ -686,6 +692,9 @@ importers: '@types/color': specifier: ^3.0.6 version: 3.0.6 + '@types/dompurify': + specifier: ^3.0.5 + version: 3.0.5 '@types/humanize-duration': specifier: ^3.27.4 version: 3.27.4 @@ -3567,6 +3576,9 @@ packages: '@types/diff@5.2.1': resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==} + '@types/dompurify@3.0.5': + resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -3696,6 +3708,9 @@ packages: '@types/tinycolor2@1.4.6': resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.8': resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==} @@ -5217,6 +5232,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.1.5: + resolution: {integrity: sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA==} + domutils@3.1.0: resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} @@ -7140,6 +7158,11 @@ packages: markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + marked@13.0.0: + resolution: {integrity: sha512-VTeDCd9txf4KLLljUZ0nljE/Incb9SrWuueE44QVuU0pkOdh4sfCeW1Z6lPcxyDRSVY6rm8db/0OPaN75RNUmw==} + engines: {node: '>= 18'} + hasBin: true + marked@7.0.4: resolution: {integrity: sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==} engines: {node: '>= 16'} @@ -13564,6 +13587,10 @@ snapshots: '@types/diff@5.2.1': {} + '@types/dompurify@3.0.5': + dependencies: + '@types/trusted-types': 2.0.7 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 8.56.10 @@ -13694,6 +13721,8 @@ snapshots: '@types/tinycolor2@1.4.6': {} + '@types/trusted-types@2.0.7': {} + '@types/unist@2.0.8': {} '@types/unist@3.0.2': {} @@ -15659,6 +15688,8 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.1.5: {} + domutils@3.1.0: dependencies: dom-serializer: 2.0.0 @@ -18098,6 +18129,8 @@ snapshots: markdown-table@3.0.3: {} + marked@13.0.0: {} + marked@7.0.4: {} md-to-react-email@5.0.2(react@18.2.0):