Core: deprecate highlight on content highlighter

This commit is contained in:
Fuma Nama
2026-03-08 21:33:18 +08:00
parent 9ba67d3052
commit d35f30c0ff
4 changed files with 43 additions and 118 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-core': patch
---
deprecate `highlight` on content highlighter
+26 -27
View File
@@ -1,7 +1,6 @@
import type { Root } from 'mdast';
import type { ReactNode } from 'react';
import { remark } from 'remark';
import type { Transformer } from 'unified';
import { visit } from 'unist-util-visit';
export interface SortedResult<Content = string> {
@@ -43,14 +42,15 @@ function buildRegexFromQuery(q: string): RegExp | null {
return new RegExp(`(${escaped})`, 'gi');
}
const processor = remark();
export function createContentHighlighter(query: string | RegExp) {
let processor = remark();
const regex = typeof query === 'string' ? buildRegexFromQuery(query) : query;
if (regex) {
processor = processor.use(remarkHighlight, regex) as never;
}
return {
/**
* @deprecated use `highlightMarkdown()` instead.
*/
highlight(content: string): HighlightedText[] {
if (!regex) return [{ type: 'text', content }];
const out: HighlightedText[] = [];
@@ -89,34 +89,33 @@ export function createContentHighlighter(query: string | RegExp) {
*/
highlightMarkdown(content: string): string {
if (!regex) return content;
return String(processor.processSync(content).value);
const tree = processor.parse(content);
highlightInTree(tree, regex);
return processor.stringify(tree);
},
};
}
function remarkHighlight(regex: RegExp): Transformer<Root, Root> {
return (tree) => {
visit(tree, 'text', (node) => {
let out = '';
const content = node.value;
function highlightInTree(tree: Root, regex: RegExp) {
visit(tree, 'text', (node) => {
let out = '';
const content = node.value;
let i = 0;
for (const match of content.matchAll(regex)) {
if (i < match.index) {
out += content.substring(i, match.index);
}
out += `<mark>${match[0]}</mark>`;
i = match.index + match[0].length;
let i = 0;
for (const match of content.matchAll(regex)) {
if (i < match.index) {
out += content.substring(i, match.index);
}
if (i < content.length) {
out += content.substring(i);
}
out += `<mark>${match[0]}</mark>`;
i = match.index + match[0].length;
}
node.type = 'html' as never;
node.value = out;
});
};
if (i < content.length) {
out += content.substring(i);
}
node.type = 'html' as never;
node.value = out;
});
}
+10 -89
View File
@@ -4,96 +4,17 @@ import { createContentHighlighter } from '@/search';
test('highlight search results', () => {
const highlighter = createContentHighlighter('hello world helloworld');
expect(highlighter.highlight('oops hello, world hello! worldhello')).toMatchInlineSnapshot(`
[
{
"content": "oops ",
"type": "text",
},
{
"content": "hello",
"styles": {
"highlight": true,
},
"type": "text",
},
{
"content": ", ",
"type": "text",
},
{
"content": "world",
"styles": {
"highlight": true,
},
"type": "text",
},
{
"content": " ",
"type": "text",
},
{
"content": "hello",
"styles": {
"highlight": true,
},
"type": "text",
},
{
"content": "! ",
"type": "text",
},
{
"content": "world",
"styles": {
"highlight": true,
},
"type": "text",
},
{
"content": "hello",
"styles": {
"highlight": true,
},
"type": "text",
},
]
expect(highlighter.highlightMarkdown('oops hello, world hello! worldhello'))
.toMatchInlineSnapshot(`
"oops <mark>hello</mark>, <mark>world</mark> <mark>hello</mark>! <mark>world</mark><mark>hello</mark>
"
`);
expect(highlighter.highlight('helloworld!!!')).toMatchInlineSnapshot(`
[
{
"content": "hello",
"styles": {
"highlight": true,
},
"type": "text",
},
{
"content": "world",
"styles": {
"highlight": true,
},
"type": "text",
},
{
"content": "!!!",
"type": "text",
},
]
expect(highlighter.highlightMarkdown('helloworld!!!')).toMatchInlineSnapshot(`
"<mark>hello</mark><mark>world</mark>!!!
"
`);
expect(highlighter.highlight('wor ld hello')).toMatchInlineSnapshot(`
[
{
"content": "wor ld ",
"type": "text",
},
{
"content": "hello",
"styles": {
"highlight": true,
},
"type": "text",
},
]
expect(highlighter.highlightMarkdown('wor ld hello')).toMatchInlineSnapshot(`
"wor ld <mark>hello</mark>
"
`);
});
+2 -2
View File
@@ -1,4 +1,4 @@
import { createI18nSearchAPI, createSearchAPI } from '@/search/server';
import { createI18nSearchAPI, createSearchAPI, type ExportedData } from '@/search/server';
import { expect, test } from 'vitest';
import { structure } from '@/mdx-plugins';
@@ -101,7 +101,7 @@ test('Search API I18n', async () => {
expect(await api.search('English', { locale: 'en' })).toHaveLength(1);
expect(await api.search('amico', { locale: 'italian' })).toHaveLength(1);
expect(await api.search('italian', { locale: 'en' })).toHaveLength(0);
const exported = await api.export();
const exported = (await api.export()) as ExportedData;
expect(exported.type).toBe('i18n');
if (exported.type === 'i18n')