PageSourceSearch

https://www.espazium.ch/modules/custom/esp_search/js/esp-search-shared.js?tluiw1

js espazium.ch collected 2026-09-24 17:52:45 UTC 4,496 bytes, 134 lines download raw bytes

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 { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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.