import { escape } from './escape-html'; import { isPlainObject } from './isPlainObject'; import type { Hit, FacetHit, EscapedHits } from '../../types'; export const TAG_PLACEHOLDER = { highlightPreTag: '__ais-highlight__', highlightPostTag: '__/ais-highlight__', }; export const TAG_REPLACEMENT = { highlightPreTag: '', highlightPostTag: '', }; function replaceTagsAndEscape(value: string): string { return escape(value) .replace( new RegExp(TAG_PLACEHOLDER.highlightPreTag, 'g'), TAG_REPLACEMENT.highlightPreTag ) .replace( new RegExp(TAG_PLACEHOLDER.highlightPostTag, 'g'), TAG_REPLACEMENT.highlightPostTag ); } function recursiveEscape(input: any): any { if (isPlainObject(input) && typeof input.value !== 'string') { return Object.keys(input).reduce( (acc, key) => ({ ...acc, [key]: recursiveEscape(input[key]), }), {} ); } if (Array.isArray(input)) { return input.map(recursiveEscape); } return { ...input, value: replaceTagsAndEscape(input.value), }; } export function escapeHits( hits: THit[] | EscapedHits ): EscapedHits { if ((hits as EscapedHits).__escaped === undefined) { // We don't override the value on hit because it will mutate the raw results // instead we make a shallow copy and we assign the escaped values on it. hits = hits.map(({ ...hit }) => { if (hit._highlightResult) { hit._highlightResult = recursiveEscape(hit._highlightResult); } if (hit._snippetResult) { hit._snippetResult = recursiveEscape(hit._snippetResult); } return hit; }); (hits as EscapedHits).__escaped = true; } return hits as EscapedHits; } export function escapeFacets(facetHits: FacetHit[]): FacetHit[] { return facetHits.map((h) => ({ ...h, highlighted: replaceTagsAndEscape(h.highlighted), })); }