coveo-search-ui
Version:
Coveo JavaScript Search Framework
154 lines (141 loc) • 4.89 kB
text/typescript
import { Options } from '../misc/Options';
import { HighlightUtils } from './HighlightUtils';
import { StringUtils } from './StringUtils';
import { Utils } from './Utils';
import { IHighlight, IHighlightTerm, IHighlightPhrase } from '../rest/Highlight';
import { $$ } from './Dom';
import * as _ from 'underscore';
// \u2011: http://graphemica.com/%E2%80%91
// Used to split terms and phrases. Should match characters that can separate words.
const wordBoundary = "[\\.\\-\\u2011\\s~=,.\\|\\/:'`’;_()!?&+]";
const regexStart = '(' + wordBoundary + '|^)(';
/**
* The possible options when highlighting a stream.
*/
export interface IStreamHighlightOptions {
/**
* The css class that the highlight will generate.
* Defaults to `coveo-highlight`.
*/
cssClass?: string;
/**
* The regex flags that should be applied to generate the highlighting.
* Defaults to `gi`.
*/
regexFlags?: string;
}
export class DefaultStreamHighlightOptions extends Options implements IStreamHighlightOptions {
constructor(public cssClass = 'coveo-highlight', public shorten = 0, public regexFlags = 'gi') {
super();
}
}
export class StreamHighlightUtils {
static highlightStreamHTML(
stream: string,
termsToHighlight: IHighlightTerm,
phrasesToHighlight: IHighlightPhrase,
options?: IStreamHighlightOptions
) {
const opts = new DefaultStreamHighlightOptions().merge(options);
const container = createStreamHTMLContainer(stream);
const allElements = $$(container).findAll('*');
if (allElements.length > 0) {
_.each(allElements, (elem: HTMLElement, i: number) => {
const text = $$(elem).text();
elem.innerHTML = HighlightUtils.highlightString(
text,
getRestHighlightsForAllTerms(text, termsToHighlight, phrasesToHighlight, opts),
[],
opts.cssClass
);
});
} else {
return StreamHighlightUtils.highlightStreamText(stream, termsToHighlight, phrasesToHighlight, options);
}
return container.innerHTML;
}
static highlightStreamText(
stream: string,
termsToHighlight: IHighlightTerm,
phrasesToHighlight: IHighlightPhrase,
options?: IStreamHighlightOptions
) {
const opts = new DefaultStreamHighlightOptions().merge(options);
return HighlightUtils.highlightString(
stream,
getRestHighlightsForAllTerms(stream, termsToHighlight, phrasesToHighlight, opts),
[],
opts.cssClass
);
}
}
export function getRestHighlightsForAllTerms(
toHighlight: string,
termsToHighlight: IHighlightTerm,
phrasesToHighlight: IHighlightPhrase,
opts: IStreamHighlightOptions
): IHighlight[] {
const indexes = [];
const termsFromPhrases = _.chain(phrasesToHighlight).values().map(_.keys).flatten().value();
_.each(termsToHighlight, (terms: string[], term: string) => {
const uniqueTermsToHighlight = _.chain([term])
.concat(terms)
.compact()
.difference(termsFromPhrases)
.map(Utils.escapeRegexCharacter)
.sortBy('length')
.value();
if (uniqueTermsToHighlight.length === 0) {
return;
}
const regex = `${regexStart}${uniqueTermsToHighlight.join('|')})(?=(?:${wordBoundary}|$)+)`;
const indexesFound = StringUtils.getHighlights(toHighlight, new RegExp(regex, opts.regexFlags), term);
if (indexesFound != undefined && Utils.isNonEmptyArray(indexesFound)) {
indexes.push(indexesFound);
}
});
_.each(phrasesToHighlight, (phrase, origPhrase) => {
const split = origPhrase.split(' ');
let regex = regexStart;
_.each(split, (origWord, i) => {
regex += '(?:' + [origWord].concat(phrase[origWord]).join('|') + ')';
if (i == split.length - 1) {
regex += '(?=';
}
regex += wordBoundary;
if (i == split.length - 1) {
regex += '|$)';
}
if (i != split.length - 1) {
regex += '+';
}
});
regex += ')';
const indexesFound = StringUtils.getHighlights(toHighlight, new RegExp(regex, opts.regexFlags), origPhrase);
if (indexesFound != undefined && Utils.isNonEmptyArray(indexesFound)) {
indexes.push(indexesFound);
}
});
return _.chain(indexes)
.flatten()
.compact()
.uniq((highlight: IHighlight) => {
return highlight.offset;
})
.sortBy((highlight: IHighlight) => {
return highlight.offset;
})
.map(highlight => {
const keysFromTerms = _.keys(termsToHighlight);
const keysFromPhrases = _.keys(phrasesToHighlight);
const keys = keysFromTerms.concat(keysFromPhrases);
const group = _.indexOf(keys, highlight.dataHighlightGroupTerm) + 1;
return _.extend(highlight, { dataHighlightGroup: group });
})
.value();
}
function createStreamHTMLContainer(stream: string) {
const container = $$('div').el;
container.innerHTML = stream;
return container;
}