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)} />
highlights: { [field: string]: [number, number][] };
longestTerm: string;
page: DocumentationEntry;
+ query: string;
}
interface Props {
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)} />
});
});
- return { page, highlights, longestTerm, exactMatch };
+ return { exactMatch, highlights, longestTerm, page, query };
})
.filter(result => result.page) as SearchResult[];
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();
});
});
).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
+ };
+}
"title": "Foobar",
"url": "/foo/bar",
},
+ "query": "",
}
}
/>
"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"
"title": "Where does Foobar come from?",
"url": "/foobar",
},
+ "query": "simply text",
}
}
/>
"title": "Where does it come from?",
"url": "/lorem/origin",
},
+ "query": "simply text",
}
}
/>