Files
meilisearch-bundle/src/Resources/contao/templates/frontend_module/mod_meilisearch_search.html.twig
T
Jürgen Mummert 1f6e115ae6 Bugfix
2025-12-31 10:58:33 +01:00

210 lines
6.4 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{#
Meilisearch Frontend Search
Contao 5 Frontend Module Template
#}
<!-- indexer::stop -->
<div
id="topsearch"
class="meilisearch-search"
data-limit="{{ meiliLimit }}"
>
<div class="headersearch">
<form id="search-form" onsubmit="return false;">
<div class="formbody">
<div class="widget widget-text">
<label for="search_input" class="invisible">
{{ 'Suchen'|trans }}
</label>
<div class="search-field">
<input
type="search"
name="keywords"
id="search_input"
class="text"
placeholder="Suchbegriff eingeben…"
autocomplete="off"
>
<button
type="button"
class="clear-button is-hidden"
aria-label="Suche löschen"
>
×
</button>
</div>
</div>
</div>
</form>
<div id="search-results"></div>
<template id="search-result-template">
<div class="search-item">
<div class="siteimage">
<img src="" alt="" loading="lazy">
</div>
<div class="teaser">
<div class="title"></div>
<div class="extract"></div>
<div class="pfad"></div>
</div>
<a class="masterurl" href="#" title=""></a>
</div>
</template>
</div>
</div>
<script type="module">
import MeiliSearch from 'https://cdn.jsdelivr.net/npm/meilisearch@latest/dist/bundles/meilisearch.esm.js';
document.addEventListener('DOMContentLoaded', () => {
const wrapper = document.querySelector('.meilisearch-search');
if (!wrapper) return;
const input = wrapper.querySelector('#search_input');
const clear = wrapper.querySelector('.clear-button');
const results = wrapper.querySelector('#search-results');
const template = wrapper.querySelector('#search-result-template');
if (!input || !clear || !results || !template) {
console.warn('[Meilisearch] Required elements not found');
return;
}
const limit = parseInt(wrapper.dataset.limit, 10) || 50;
const client = new MeiliSearch({
host: '{{ meiliHost }}',
apiKey: '{{ meiliSearchKey }}'
});
const index = client.index('{{ meiliIndex }}');
let abortController = null;
// ----------------------------
// Clear button
// ----------------------------
clear.addEventListener('click', () => {
input.value = '';
results.innerHTML = '';
clear.classList.add('is-hidden');
input.focus();
});
// ----------------------------
// Input handling
// ----------------------------
input.addEventListener('input', async () => {
const query = input.value.trim();
clear.classList.toggle('is-hidden', query.length === 0);
if (query.length < 2) {
results.innerHTML = '';
return;
}
abortController?.abort();
abortController = new AbortController();
try {
const response = await index.search(query, {
limit,
attributesToRetrieve: [
'title',
'url',
'text',
'poster',
'priority'
],
attributesToHighlight: ['text'],
attributesToCrop: ['text'],
cropLength: 50,
cropMarker: '…',
sort: ['startDate:asc', 'priority:desc']
});
renderResults(response.hits);
} catch (e) {
if (e.name !== 'AbortError') {
console.error('[Meilisearch]', e);
}
}
});
// ----------------------------
// Render results via <template>
// ----------------------------
function renderResults(hits) {
results.innerHTML = '';
if (!hits || !hits.length) {
return;
}
for (const hit of hits) {
const node = template.content.cloneNode(true);
const item = node.firstElementChild;
// TYPE → CSS-Klasse
if (hit.type) {
item.classList.add(
String(hit.type)
.toLowerCase()
.replace(/[^a-z0-9_-]/g, '')
);
}
const image = item.querySelector('.siteimage');
const img = image.querySelector('img');
const title = item.querySelector('.title');
const extract = item.querySelector('.extract');
const path = item.querySelector('.pfad');
const link = item.querySelector('.masterurl');
title.textContent = hit.title || '';
link.href = hit.url || '#';
link.title = hit.title || '';
if (hit._formatted?.text) {
extract.innerHTML = hit._formatted.text;
} else {
extract.textContent = '';
}
if (hit.url) {
path.textContent = hit.url.replace(/^https?:\/\//, '');
}
const img = image.querySelector('img');
if (hit.poster) {
img.src = hit.poster;
img.alt = hit.title || '';
image.classList.remove('is-empty');
} else {
img.removeAttribute('src');
img.alt = '';
image.classList.add('is-empty');
}
results.appendChild(node);
}
}
});
</script>
<!-- indexer::continue -->