1/** 2 * @file 3 * Shared helpers for the Espazium Algolia surfaces (result page + overlay). 4 * 5 * Exposed on window.espSearch so the two plain (non-module) scripts can reuse 6 * the same empty-query short-circuit and excerpt composition. 7 */ 8 9/* global algoliasearch */ 10/* eslint-disable func-names -- anonymous callbacks are idiomatic here. */ 11(function (window) { 12 // Body/content attributes whose Algolia _snippetResult we stitch into 13 // hit._excerpt. Deliberately EXCLUDES the displayed title attributes 14 // (`title`, `title_1`, `title_2`) and the teaser title â the title already 15 // shows in the <h3>, and letting a title match land here would (a) repeat the 16 // title as the excerpt and (b) suppress the title-only fallback below. 17 const SNIPPET_ATTRS = [ 18 "field_teaser_text", 19 "field_lead_text", 20 "processed_1", 21 "processed_2", 22 "processed_3", 23 "processed", 24 "body_dossier", 25 "name_1", 26 "name_2", 27 "name", 28 ]; 29 30 // Resolve an empty result payload â short-circuits an Algolia call when the 31 // query is blank, so a surface doesn't show "all" records by default. 32 function emptyResults(requests) { 33 return Promise.resolve({ 34 results: requests.map(function () { 35 return { 36 hits: [], 37 nbHits: 0, 38 nbPages: 0, 39 page: 0, 40 processingTimeMS: 0, 41 exhaustiveNbHits: true, 42 query: "", 43 params: "", 44 }; 45 }), 46 }); 47 } 48 49 // Wrap an algoliasearch client so blank-query searches return no hits. 50 function wrapClient(appId, apiKey) { 51 const realClient = algoliasearch(appId, apiKey); 52 return { 53 search(requests) { 54 const allEmpty = requests.every(function (r) { 55 return !r.params || !r.params.query; 56 }); 57 return allEmpty ? emptyResults(requests) : realClient.search(requests); 58 }, 59 searchForFacetValues: realClient.searchForFacetValues 60 ? realClient.searchForFacetValues.bind(realClient) 61 : undefined, 62 }; 63 } 64 65 // Attributes used, in order, to build a fallback description when the query 66 // matched only the title (no body snippet) â so a result is never title-only. 67 const FALLBACK_ATTRS = [ 68 "field_teaser_text", 69 "field_lead_text", 70 "processed_1", 71 "body_dossier", 72 "processed", 73 "field_teaser_title", 74 ]; 75 76 // transformItems callback: compose hit._excerpt from matched snippets. Each 77 // Algolia snippet already carries its own `â¦` boundaries and <em> highlights; 78 // we only stitch the matching ones together. When nothing matched in the body 79 // (e.g. the term is only in the title), fall back to the plain lead/teaser so 80 // the result still shows a description instead of a bare title. 81 function composeExcerpts(items) { 82 // Localized singular type label for the result card. esp_bundle_label is 83 // baked into the index (untranslated) at index time, so override it here 84 // with the per-interface-language map from drupalSettings; unmapped bundles 85 // keep the baked value. 86 const singular = 87 (window.drupalSettings && 88 window.drupalSettings.espAlgolia && 89 window.drupalSettings.espAlgolia.contentTypeSingularLabels) || 90 {}; 91 return items.map(function (hit) { 92 const typeLabel = singular[hit.esp_content_type]; 93 if (typeLabel) { 94 hit.esp_bundle_label = typeLabel; 95 } 96 const sr = hit._snippetResult || {}; 97 const parts = []; 98 SNIPPET_ATTRS.forEach(function (attr) { 99 const s = sr[attr]; 100 if (s && s.matchLevel && s.matchLevel !== "none" && s.value) { 101 parts.push(s.value); 102 } 103 }); 104 if (parts.length === 0) { 105 for (let i = 0; i < FALLBACK_ATTRS.length; i++) { 106 const raw = hit[FALLBACK_ATTRS[i]]; 107 const text = Array.isArray(raw) ? raw[0] : raw; 108 if (text && String(text).trim()) { 109 hit._excerpt = escapeHtml(String(text).slice(0, 220)); 110 return hit; 111 } 112 } 113 } 114 hit._excerpt = parts.join(" "); 115 return hit; 116 }); 117 } 118 119 // Escape text for safe insertion into innerHTML (labels come from 120 // admin/translator-controlled Drupal strings). 121 function escapeHtml(str) { 122 return String(str).replace(/[&<>"]/g, function (c) { 123 return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; 124 }); 125 } 126 127 window.espSearch = { 128 SNIPPET_ATTRS, 129 emptyResults, 130 wrapClient, 131 composeExcerpts, 132 escapeHtml, 133 }; 134})(window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.