UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

154 lines (141 loc) 4.89 kB
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; }