Skip to content

Commit

Permalink
fix: search inside content (#295)
Browse files Browse the repository at this point in the history
* search inside content

* sanitizeAllHtmlButMark

* escape disallowedTagsMode

* clean
  • Loading branch information
abernier committed Aug 24, 2024
1 parent dd6a0dd commit 3e82c95
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 27 deletions.
125 changes: 124 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@types/node": "^18.19.44",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/sanitize-html": "^2.13.0",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.5",
Expand Down Expand Up @@ -41,6 +42,7 @@
"rehype-github-alerts": "^3.0.0",
"rehype-prism-plus": "^2.0.0",
"remark-gfm": "^4.0.0",
"sanitize-html": "^2.13.0",
"tailwind-merge": "^2.5.2"
},
"scripts": {
Expand Down
3 changes: 1 addition & 2 deletions src/app/[...slug]/DocsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ export type DocToC = {
id: string
level: number
title: string
description: string
// content: string
content: string
url: string
parent: DocToC | null
label: string
Expand Down
37 changes: 24 additions & 13 deletions src/components/Search/SearchItem.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Icon from '@/components/Icon'
import { highlight } from '@/utils/text'
import Link from 'next/link'
import sanitizeHtml from 'sanitize-html'

export interface SearchResult {
title: string
description: string
content: string
url: string
label: string
image?: string
Expand All @@ -14,6 +15,13 @@ export interface SearchItemProps {
search: string
result: SearchResult
}
function sanitizeAllHtmlButMark(str: string) {
return sanitizeHtml(str, {
allowedTags: ['mark'],
allowedAttributes: false,
disallowedTagsMode: 'escape',
})
}

function SearchItem({ search, result }: SearchItemProps) {
return (
Expand All @@ -24,18 +32,21 @@ function SearchItem({ search, result }: SearchItemProps) {
>
<li className="px-2 py-1">
<div className="interactive-bg-surface-container-high flex items-center justify-between rounded-md p-4 py-5 transition-colors">
<span
className="pr-3"
dangerouslySetInnerHTML={{
__html: `
<span class="block text-xs text-on-surface-variant/50 pb-1">${result.label}</span>
${highlight(result.title, search)}
<span class="block text-sm text-on-surface-variant/50 pt-2">
${highlight(result.description, search)}
</span>
`,
}}
/>
<div className="break-all pr-3">
<div className="block pb-1 text-xs text-on-surface-variant/50">{result.label}</div>
<span
dangerouslySetInnerHTML={{
__html: highlight(sanitizeAllHtmlButMark(result.title), search),
}}
/>
<div className="block pt-2 text-sm text-on-surface-variant/50">
<span
dangerouslySetInnerHTML={{
__html: highlight(sanitizeAllHtmlButMark(result.content), search),
}}
/>
</div>
</div>
{result.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="max-w-[40%] rounded" src={result.image} alt={result.title} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search/SearchModalContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const SearchModalContainer = ({ onClose }: SearchModalContainerProps) =>
({ tableOfContents }) => tableOfContents,
) satisfies SearchResult[]
// console.log('candidateResults', candidateResults)
candidateResults = candidateResults.filter((entry) => entry.description.length > 0)
// candidateResults = candidateResults.filter((entry) => entry.description.length > 0)
// .concat(
// Object.entries(boxes).flatMap(([id, data]) => ({
// ...data,
Expand Down
18 changes: 13 additions & 5 deletions src/components/mdx/Toc/rehypeToc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const slugify = (title: string) => title.toLowerCase().replace(/\s+|-+/g, '-')
/**
* Generates a table of contents from page headings.
*/
export const rehypeToc = (target: DocToC[] = [], url: string, page: string, content: string) => {
export const rehypeToc = (target: DocToC[] = [], url: string, page: string) => {
return () => (root: Node) => {
const previous: Record<number, DocToC> = {}

Expand All @@ -39,19 +39,27 @@ export const rehypeToc = (target: DocToC[] = [], url: string, page: string, cont
const id = slugify(title)
node.properties.id = id

//
// Extract content for each heading
//

let siblingIndex = i + 1
const content: string[] = []
let sibling: Node | undefined = root.children[siblingIndex]
while (sibling?.type === 'text') sibling = root.children[siblingIndex++]
const description = sibling?.tagName === 'p' ? toString(sibling) : ''
while (sibling) {
if (RegExp(`^h${level}$`).test(sibling.tagName)) break // stop at the next (same-level) heading

content.push(toString(sibling))
sibling = root.children[siblingIndex++]
}

const item: DocToC = {
id,
level,
label: page,
url: `${url}#${id}`,
title,
description,
// content, // potentially too big to be in the ToC (perfs issue)
content: content.join(''),
parent: previous[level - 2] ?? null,
}
previous[level - 1] = item
Expand Down
2 changes: 1 addition & 1 deletion src/utils/docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ async function _getDocs(
rehypeGha,
rehypePrismPlus,
rehypeCodesandbox(boxes), // 1. put all Codesandbox[id] into `doc.boxes`
rehypeToc(tableOfContents, url, title, content), // 2. will populate `doc.tableOfContents`
rehypeToc(tableOfContents, url, title), // 2. will populate `doc.tableOfContents`
],
},
},
Expand Down
5 changes: 1 addition & 4 deletions src/utils/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,4 @@ export const escape = (text: string) => text.replace(/[|\\{}()[\]^$+*?.]/g, '\\$
* Bolds matching text, returning HTML.
*/
export const highlight = (text: string, target: string) =>
text.replace(
new RegExp(target, 'gi'),
(match: string) => `<span class="font-bold">${match}</span>`,
)
text.replace(new RegExp(target, 'gi'), (match: string) => `<mark>${match}</mark>`)

0 comments on commit 3e82c95

Please sign in to comment.