Skip to content

Commit 9a851c3

Browse files
committed
fix(results): support flat hierarchy snippet fields
1 parent fb8f53d commit 9a851c3

3 files changed

Lines changed: 134 additions & 13 deletions

File tree

packages/docsearch-react/src/Results.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ import { Snippet } from './Snippet';
77
import type { InternalDocSearchHit, StoredDocSearchHit } from './types';
88
import { sanitizeUserInput } from './utils/sanitize';
99

10+
type HierarchyLevel = 'lvl0' | 'lvl1' | 'lvl2' | 'lvl3' | 'lvl4' | 'lvl5' | 'lvl6';
11+
type FlatHierarchyKey = `hierarchy.${HierarchyLevel}`;
12+
13+
function getHierarchyValue(item: StoredDocSearchHit, level: HierarchyLevel): string | null {
14+
const fromNested = item.hierarchy?.[level];
15+
const flatKey: FlatHierarchyKey = `hierarchy.${level}`;
16+
const fromFlat = item[flatKey];
17+
18+
if (typeof fromNested === 'string') {
19+
return fromNested;
20+
}
21+
22+
return typeof fromFlat === 'string' ? fromFlat : null;
23+
}
24+
1025
export type ResultsTranslations = Partial<{
1126
askAiPlaceholder: string;
1227
noResultsAskAiPlaceholder: string;
@@ -115,7 +130,13 @@ function Result<TItem extends StoredDocSearchHit>({
115130
<div className="DocSearch-Hit-Container">
116131
{renderIcon({ item, index })}
117132

118-
{item.hierarchy[item.type] && item.type === 'lvl1' && (
133+
{item.type === 'lvl0' && getHierarchyValue(item, 'lvl0') && (
134+
<div className="DocSearch-Hit-content-wrapper">
135+
<Snippet className="DocSearch-Hit-title" hit={item} attribute="hierarchy.lvl0" />
136+
</div>
137+
)}
138+
139+
{item.type === 'lvl1' && getHierarchyValue(item, 'lvl1') && (
119140
<div className="DocSearch-Hit-content-wrapper">
120141
<Snippet className="DocSearch-Hit-title" hit={item} attribute="hierarchy.lvl1" />
121142
{item.content && <Snippet className="DocSearch-Hit-path" hit={item} attribute="content" />}
@@ -128,12 +149,12 @@ function Result<TItem extends StoredDocSearchHit>({
128149
</div>
129150
)}
130151

131-
{item.hierarchy[item.type] &&
132-
(item.type === 'lvl2' ||
152+
{(item.type === 'lvl2' ||
133153
item.type === 'lvl3' ||
134154
item.type === 'lvl4' ||
135155
item.type === 'lvl5' ||
136-
item.type === 'lvl6') && (
156+
item.type === 'lvl6') &&
157+
getHierarchyValue(item, item.type) && (
137158
<div className="DocSearch-Hit-content-wrapper">
138159
<Snippet className="DocSearch-Hit-title" hit={item} attribute={`hierarchy.${item.type}`} />
139160
<Snippet className="DocSearch-Hit-path" hit={item} attribute="hierarchy.lvl1" />
Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,112 @@
11
import { type JSX, createElement } from 'react';
22

3-
import type { StoredDocSearchHit } from './types';
3+
import type { DocSearchHit, StoredDocSearchHit } from './types';
44

5-
function getPropertyByPath(object: Record<string, any>, path: string): any {
6-
const parts = path.split('.');
5+
type HierarchyLevel = 'lvl0' | 'lvl1' | 'lvl2' | 'lvl3' | 'lvl4' | 'lvl5' | 'lvl6';
6+
type SnippetAttribute = 'content' | `hierarchy.${HierarchyLevel}`;
7+
type SnippetHit = StoredDocSearchHit &
8+
Partial<Pick<DocSearchHit, '_highlightResult' | '_snippetResult'>>;
79

8-
return parts.reduce((prev, current) => {
9-
if (prev?.[current]) return prev[current];
10+
function parseHierarchyAttribute(attribute: string): HierarchyLevel | null {
11+
if (!attribute.startsWith('hierarchy.')) {
1012
return null;
11-
}, object);
13+
}
14+
15+
const level = attribute.replace('hierarchy.', '') as HierarchyLevel;
16+
return ['lvl0', 'lvl1', 'lvl2', 'lvl3', 'lvl4', 'lvl5', 'lvl6'].includes(level)
17+
? level
18+
: null;
19+
}
20+
21+
function getRawValue(hit: SnippetHit, attribute: SnippetAttribute): string {
22+
if (attribute === 'content') {
23+
return hit.content ?? '';
24+
}
25+
26+
const level = parseHierarchyAttribute(attribute);
27+
if (!level) {
28+
return '';
29+
}
30+
31+
return hit[attribute] ?? hit.hierarchy[level] ?? '';
32+
}
33+
34+
function getHighlightedValue(hit: SnippetHit, attribute: SnippetAttribute): string {
35+
const highlight = hit._highlightResult;
36+
if (!highlight) {
37+
return '';
38+
}
39+
40+
if (attribute === 'content') {
41+
return highlight.content?.value ?? '';
42+
}
43+
44+
const level = parseHierarchyAttribute(attribute);
45+
if (!level) {
46+
return '';
47+
}
48+
49+
return highlight[attribute]?.value ?? highlight.hierarchy?.[level]?.value ?? '';
50+
}
51+
52+
function getSnippetValue(hit: SnippetHit, attribute: SnippetAttribute): string {
53+
const snippet = hit._snippetResult;
54+
if (!snippet) {
55+
return '';
56+
}
57+
58+
if (attribute === 'content') {
59+
return snippet.content?.value ?? '';
60+
}
61+
62+
const level = parseHierarchyAttribute(attribute);
63+
if (!level) {
64+
return '';
65+
}
66+
67+
return snippet[attribute]?.value ?? snippet.hierarchy?.[level]?.value ?? '';
1268
}
1369

1470
interface SnippetProps<TItem> {
1571
hit: TItem;
16-
attribute: string;
72+
attribute: SnippetAttribute;
1773
tagName?: string;
1874
[prop: string]: unknown;
1975
}
2076

21-
export function Snippet<TItem extends StoredDocSearchHit>({
77+
export function Snippet<TItem extends SnippetHit>({
2278
hit,
2379
attribute,
2480
tagName = 'span',
2581
...rest
2682
}: SnippetProps<TItem>): JSX.Element {
83+
const highlightValue = getHighlightedValue(hit, attribute);
84+
const rawValue = getRawValue(hit, attribute);
85+
const baseValue = highlightValue || rawValue;
86+
const snippetValue = getSnippetValue(hit, attribute);
87+
88+
let displayValue = baseValue;
89+
90+
if (snippetValue && baseValue) {
91+
let formattedSnippet = snippetValue;
92+
if (baseValue.substring(0, 20) !== snippetValue.substring(0, 20)) {
93+
formattedSnippet = `… ${formattedSnippet}`;
94+
}
95+
if (
96+
baseValue.substring(baseValue.length - 20, baseValue.length) !==
97+
snippetValue.substring(snippetValue.length - 20, snippetValue.length)
98+
) {
99+
formattedSnippet = `${formattedSnippet} …`;
100+
}
101+
displayValue = formattedSnippet;
102+
} else if (snippetValue) {
103+
displayValue = snippetValue;
104+
}
105+
27106
return createElement(tagName, {
28107
...rest,
29108
dangerouslySetInnerHTML: {
30-
__html: getPropertyByPath(hit, `_snippetResult.${attribute}.value`) || getPropertyByPath(hit, attribute),
109+
__html: displayValue,
31110
},
32111
});
33112
}

packages/docsearch-react/src/types/DocSearchHit.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ interface DocSearchHitHighlightResult {
2121
content: DocSearchHitAttributeHighlightResult;
2222
hierarchy: DocSearchHitHighlightResultHierarchy;
2323
hierarchy_camel: DocSearchHitHighlightResultHierarchy[];
24+
'hierarchy.lvl0'?: DocSearchHitAttributeHighlightResult;
25+
'hierarchy.lvl1'?: DocSearchHitAttributeHighlightResult;
26+
'hierarchy.lvl2'?: DocSearchHitAttributeHighlightResult;
27+
'hierarchy.lvl3'?: DocSearchHitAttributeHighlightResult;
28+
'hierarchy.lvl4'?: DocSearchHitAttributeHighlightResult;
29+
'hierarchy.lvl5'?: DocSearchHitAttributeHighlightResult;
30+
'hierarchy.lvl6'?: DocSearchHitAttributeHighlightResult;
2431
}
2532

2633
interface DocSearchHitAttributeSnippetResult {
@@ -32,6 +39,13 @@ interface DocSearchHitSnippetResult {
3239
content: DocSearchHitAttributeSnippetResult;
3340
hierarchy: DocSearchHitHighlightResultHierarchy;
3441
hierarchy_camel: DocSearchHitHighlightResultHierarchy[];
42+
'hierarchy.lvl0'?: DocSearchHitAttributeSnippetResult;
43+
'hierarchy.lvl1'?: DocSearchHitAttributeSnippetResult;
44+
'hierarchy.lvl2'?: DocSearchHitAttributeSnippetResult;
45+
'hierarchy.lvl3'?: DocSearchHitAttributeSnippetResult;
46+
'hierarchy.lvl4'?: DocSearchHitAttributeSnippetResult;
47+
'hierarchy.lvl5'?: DocSearchHitAttributeSnippetResult;
48+
'hierarchy.lvl6'?: DocSearchHitAttributeSnippetResult;
3549
}
3650

3751
export declare type DocSearchHit = {
@@ -51,6 +65,13 @@ export declare type DocSearchHit = {
5165
lvl5: string | null;
5266
lvl6: string | null;
5367
};
68+
'hierarchy.lvl0'?: string;
69+
'hierarchy.lvl1'?: string;
70+
'hierarchy.lvl2'?: string | null;
71+
'hierarchy.lvl3'?: string | null;
72+
'hierarchy.lvl4'?: string | null;
73+
'hierarchy.lvl5'?: string | null;
74+
'hierarchy.lvl6'?: string | null;
5475
_highlightResult: DocSearchHitHighlightResult;
5576
_snippetResult: DocSearchHitSnippetResult;
5677
_rankingInfo?: {

0 commit comments

Comments
 (0)