feat: normalize search text (#14449)

#### PR Dependency Tree


* **PR #14449** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Improvements**
* Search text normalization now applied consistently across doc titles,
search results, and highlights for uniform display formatting.

* **Tests**
* Added comprehensive test coverage for search text normalization
utility.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-16 08:07:04 +08:00
committed by GitHub
parent 42f2d2b337
commit e3177e6837
5 changed files with 106 additions and 24 deletions
@@ -0,0 +1,33 @@
import { expect, test } from 'vitest';
import { normalizeSearchText } from '../normalize-search-text';
test('normalizeSearchText should keep plain text unchanged', () => {
expect(normalizeSearchText('hello world')).toBe('hello world');
});
test('normalizeSearchText should decode serialized single-item array', () => {
expect(normalizeSearchText('["hello world"]')).toBe('hello world');
});
test('normalizeSearchText should decode serialized highlighted array', () => {
expect(normalizeSearchText('["<b>hello</b> world"]')).toBe(
'<b>hello</b> world'
);
});
test('normalizeSearchText should join serialized multi-item array', () => {
expect(normalizeSearchText('["hello","world"]')).toBe('hello world');
});
test('normalizeSearchText should decode serialized string', () => {
expect(normalizeSearchText('"hello world"')).toBe('hello world');
});
test('normalizeSearchText should keep invalid serialized text unchanged', () => {
expect(normalizeSearchText('["hello"')).toBe('["hello"');
});
test('normalizeSearchText should support array input', () => {
expect(normalizeSearchText(['hello', 'world'])).toBe('hello world');
});
@@ -2,6 +2,7 @@ export * from './channel';
export * from './create-emotion-cache';
export * from './event';
export * from './extract-emoji-icon';
export * from './normalize-search-text';
export * from './string2color';
export * from './toast';
export * from './unflatten-object';
@@ -0,0 +1,62 @@
interface NormalizeSearchTextOptions {
fallback?: string;
arrayJoiner?: string;
}
function tryParseSerializedText(value: string): unknown | null {
const trimmed = value.trim();
if (!trimmed) {
return null;
}
// Indexer/storage may serialize text arrays as JSON strings like ["foo"].
if (!trimmed.startsWith('[') && !trimmed.startsWith('"')) {
return null;
}
try {
return JSON.parse(trimmed);
} catch {
return null;
}
}
export function normalizeSearchText(
value: unknown,
{ fallback = '', arrayJoiner = ' ' }: NormalizeSearchTextOptions = {}
): string {
if (value === null || value === undefined) {
return fallback;
}
if (Array.isArray(value)) {
const normalized = value
.map(item =>
normalizeSearchText(item, {
fallback: '',
arrayJoiner,
})
)
.filter(Boolean);
return normalized.length > 0 ? normalized.join(arrayJoiner) : fallback;
}
if (typeof value !== 'string') {
return String(value);
}
const parsed = tryParseSerializedText(value);
if (parsed === null) {
return value;
}
if (typeof parsed === 'string' || Array.isArray(parsed)) {
return normalizeSearchText(parsed, {
fallback,
arrayJoiner,
});
}
return value;
}