Files
meilisearch-bundle/src/Resources/contao/templates/frontend_module/mod_meilisearch_search.html.twig
T
Jürgen Mummert 99ef883da5 new Twig
2026-01-09 22:01:16 +01:00

223 lines
6.8 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 -->
{% block meilisearch %}
<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',
'type'
],
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) {
document.body.classList.remove('search-active');
return;
}
document.body.classList.add('search-active');
for (const hit of hits) {
// console.log('hit.type =', hit.type); //
const node = template.content.cloneNode(true);
const item = node.children[0]; // 🔒 sicher
// DEBUG einmal prüfen
console.log('TYPE:', hit.type);
// 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 & Link
title.textContent = hit.title || '';
link.href = hit.url || '#';
link.title = hit.title || '';
// Extract
if (hit._formatted?.text) {
extract.innerHTML = hit._formatted.text;
} else {
extract.textContent = '';
}
// Path
if (hit.url) {
path.textContent = hit.url.replace(/^https?:\/\//, '');
}
// Image
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>
{% endblock %}
<!-- indexer::continue -->