@@ -50,9 +50,32 @@ export function SearchResultTitle({ result }) { | |||
export function SearchResultText({ result }) { | |||
const textHighlights = result.highlights.text; | |||
if (textHighlights && textHighlights.length > 0) { | |||
const { text } = result.page; | |||
const tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||
const { text } = result.page; | |||
let tokens = []; | |||
if (result.exactMatch) { | |||
const pageText = result.page.text.toLowerCase(); | |||
const highlights = []; | |||
let start = 0; | |||
let index; | |||
let loopCount = 0; | |||
while ((index = pageText.indexOf(result.query, start)) > -1 && loopCount < 10) { | |||
loopCount++; | |||
highlights.push({ from: index, to: index + result.query.length }); | |||
start = index + 1; | |||
} | |||
if (highlights.length) { | |||
tokens = highlightMarks(text, highlights); | |||
} | |||
} | |||
if (tokens.length === 0 && textHighlights && textHighlights.length > 0) { | |||
tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||
} | |||
if (tokens.length) { | |||
return ( | |||
<div className="note"> | |||
<SearchResultTokens tokens={cutWords(tokens)} /> |
@@ -27,6 +27,7 @@ export interface SearchResult { | |||
highlights: { [field: string]: [number, number][] }; | |||
longestTerm: string; | |||
page: DocumentationEntry; | |||
query: string; | |||
} | |||
interface Props { | |||
@@ -69,9 +70,36 @@ export function SearchResultTitle({ result }: { result: SearchResult }) { | |||
export function SearchResultText({ result }: { result: SearchResult }) { | |||
const textHighlights = result.highlights.text; | |||
if (textHighlights && textHighlights.length > 0) { | |||
const { text } = result.page; | |||
const tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||
const { text } = result.page; | |||
let tokens: { | |||
text: string; | |||
marked: boolean; | |||
}[] = []; | |||
if (result.exactMatch) { | |||
const pageText = result.page.text.toLowerCase(); | |||
const highlights: { from: number; to: number }[] = []; | |||
let start = 0; | |||
let index = pageText.indexOf(result.query, start); | |||
let loopCount = 0; | |||
while (index > -1 && loopCount < 10) { | |||
loopCount++; | |||
highlights.push({ from: index, to: index + result.query.length }); | |||
start = index + 1; | |||
index = pageText.indexOf(result.query, start); | |||
} | |||
if (highlights.length) { | |||
tokens = highlightMarks(text, highlights); | |||
} | |||
} | |||
if (tokens.length === 0 && textHighlights && textHighlights.length > 0) { | |||
tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||
} | |||
if (tokens.length) { | |||
return ( | |||
<div className="note"> | |||
<SearchResultTokens tokens={cutWords(tokens)} /> |
@@ -89,7 +89,7 @@ export default class SearchResults extends React.PureComponent<Props> { | |||
}); | |||
}); | |||
return { page, highlights, longestTerm, exactMatch }; | |||
return { exactMatch, highlights, longestTerm, page, query }; | |||
}) | |||
.filter(result => result.page) as SearchResult[]; | |||
@@ -25,54 +25,45 @@ import SearchResultEntry, { | |||
SearchResultTokens | |||
} from '../SearchResultEntry'; | |||
const page = { | |||
content: '', | |||
relativeName: 'foo/bar', | |||
url: '/foo/bar', | |||
text: 'Foobar is a universal variable understood to represent whatever is being discussed.', | |||
title: 'Foobar', | |||
navTitle: undefined | |||
}; | |||
describe('SearchResultEntry', () => { | |||
it('should render', () => { | |||
expect( | |||
shallow( | |||
<SearchResultEntry active={true} result={{ page, highlights: {}, longestTerm: '' }} /> | |||
) | |||
shallow(<SearchResultEntry active={true} result={mockSearchResult()} />) | |||
).toMatchSnapshot(); | |||
}); | |||
}); | |||
describe('SearchResultText', () => { | |||
it('should render with highlights', () => { | |||
expect( | |||
shallow(<SearchResultText result={mockSearchResult({ highlights: { text: [[12, 9]] } })} />) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should correctly extract exact matches', () => { | |||
expect( | |||
shallow( | |||
<SearchResultText result={{ page, highlights: { text: [[12, 9]] }, longestTerm: '' }} /> | |||
<SearchResultText | |||
result={mockSearchResult({ exactMatch: true, query: 'variable understood' })} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render without highlights', () => { | |||
expect( | |||
shallow(<SearchResultText result={{ page, highlights: {}, longestTerm: '' }} />) | |||
).toMatchSnapshot(); | |||
expect(shallow(<SearchResultText result={mockSearchResult()} />)).toMatchSnapshot(); | |||
}); | |||
}); | |||
describe('SearchResultTitle', () => { | |||
it('should render with highlights', () => { | |||
expect( | |||
shallow( | |||
<SearchResultTitle result={{ page, highlights: { title: [[0, 6]] }, longestTerm: '' }} /> | |||
) | |||
shallow(<SearchResultTitle result={mockSearchResult({ highlights: { title: [[0, 6]] } })} />) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render not without highlights', () => { | |||
expect( | |||
shallow(<SearchResultTitle result={{ page, highlights: {}, longestTerm: '' }} />) | |||
).toMatchSnapshot(); | |||
expect(shallow(<SearchResultTitle result={mockSearchResult()} />)).toMatchSnapshot(); | |||
}); | |||
}); | |||
@@ -94,3 +85,20 @@ describe('SearchResultTokens', () => { | |||
).toMatchSnapshot(); | |||
}); | |||
}); | |||
function mockSearchResult(overrides = {}) { | |||
return { | |||
page: { | |||
content: '', | |||
relativeName: 'foo/bar', | |||
url: '/foo/bar', | |||
text: 'Foobar is a universal variable understood to represent whatever is being discussed.', | |||
title: 'Foobar', | |||
navTitle: undefined | |||
}, | |||
highlights: {}, | |||
longestTerm: '', | |||
query: '', | |||
...overrides | |||
}; | |||
} |
@@ -20,6 +20,7 @@ exports[`SearchResultEntry should render 1`] = ` | |||
"title": "Foobar", | |||
"url": "/foo/bar", | |||
}, | |||
"query": "", | |||
} | |||
} | |||
/> | |||
@@ -36,12 +37,38 @@ exports[`SearchResultEntry should render 1`] = ` | |||
"title": "Foobar", | |||
"url": "/foo/bar", | |||
}, | |||
"query": "", | |||
} | |||
} | |||
/> | |||
</Link> | |||
`; | |||
exports[`SearchResultText should correctly extract exact matches 1`] = ` | |||
<div | |||
className="note" | |||
> | |||
<SearchResultTokens | |||
tokens={ | |||
Array [ | |||
Object { | |||
"marked": false, | |||
"text": "Foobar is a universal ", | |||
}, | |||
Object { | |||
"marked": true, | |||
"text": "variable understood", | |||
}, | |||
Object { | |||
"marked": false, | |||
"text": " to represent whatever is being discussed.", | |||
}, | |||
] | |||
} | |||
/> | |||
</div> | |||
`; | |||
exports[`SearchResultText should render with highlights 1`] = ` | |||
<div | |||
className="note" |
@@ -35,6 +35,7 @@ exports[`should search 1`] = ` | |||
"title": "Where does Foobar come from?", | |||
"url": "/foobar", | |||
}, | |||
"query": "simply text", | |||
} | |||
} | |||
/> | |||
@@ -71,6 +72,7 @@ exports[`should search 1`] = ` | |||
"title": "Where does it come from?", | |||
"url": "/lorem/origin", | |||
}, | |||
"query": "simply text", | |||
} | |||
} | |||
/> |