This commit is contained in:
Jürgen Mummert
2025-12-27 17:03:20 +01:00
parent 973f3f5b3e
commit 2c53b7862e
6 changed files with 199 additions and 0 deletions
@@ -0,0 +1,24 @@
<?php
namespace MummertMedia\ContaoMeilisearchBundle\Controller\FrontendModule;
use Contao\FrontendModule;
class MeilisearchSearchController extends FrontendModule
{
/**
* Template
*/
protected $strTemplate = 'mod_meilisearch_search';
/**
* Compile the frontend module
*/
protected function compile(): void
{
// Fallback, falls das Feld leer ist
$limit = (int) ($this->meiliLimit ?: 50);
$this->Template->meiliLimit = $limit;
}
}
+2
View File
@@ -17,3 +17,5 @@ $GLOBALS['TL_HOOKS']['indexPage'][] = [
'onIndexPage',
];
$GLOBALS['FE_MOD']['search']['meilisearch_search']
= MummertMedia\ContaoMeilisearchBundle\Controller\FrontendModule\MeilisearchSearchController::class;
+19
View File
@@ -0,0 +1,19 @@
<?php
$GLOBALS['TL_DCA']['tl_module']['palettes']['meilisearch_search'] =
'{title_legend},name,type;
{search_legend},meiliLimit;
{protected_legend:hide},protected;
{expert_legend:hide},cssID';
$GLOBALS['TL_DCA']['tl_module']['fields']['meiliLimit'] = [
'label' => &$GLOBALS['TL_LANG']['tl_module']['meiliLimit'],
'inputType' => 'text',
'default' => 50,
'eval' => [
'rgxp' => 'digit',
'mandatory' => true,
'tl_class' => 'w50',
],
'sql' => "int(10) unsigned NOT NULL default 50",
];
@@ -0,0 +1,7 @@
<?php
$GLOBALS['TL_LANG']['FMD']['meilisearch_search'] = [
'Meilisearch-Suche',
'Suchfeld mit Meilisearch-Anbindung.'
];
@@ -0,0 +1,7 @@
<?php
$GLOBALS['TL_LANG']['tl_module']['meiliLimit'] = [
'Treffer-Limit',
'Maximale Anzahl der Suchergebnisse, die Meilisearch zurückliefert.'
];
@@ -0,0 +1,140 @@
{#
Meilisearch Frontend Search
Default Twig template
#}
<div
class="meilisearch-search"
data-limit="{{ meiliLimit }}"
>
<div class="meilisearch-search-field">
<input
type="search"
id="meilisearch-input"
placeholder="Suche …"
autocomplete="off"
>
<button
type="button"
class="meilisearch-clear"
aria-label="Suche löschen"
hidden
>
×
</button>
</div>
<div id="meilisearch-results" class="meilisearch-results"></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('#meilisearch-input');
const clear = wrapper.querySelector('.meilisearch-clear');
const results = wrapper.querySelector('#meilisearch-results');
const limit = parseInt(wrapper.dataset.limit, 10) || 50;
const client = new MeiliSearch({
host: '{{ config("meilisearch_host") }}',
apiKey: '{{ config("meilisearch_api_search") }}'
});
const index = client.index('{{ config("meilisearch_index") }}');
let abortController = null;
clear.addEventListener('click', () => {
input.value = '';
results.innerHTML = '';
clear.hidden = true;
input.focus();
});
input.addEventListener('input', async () => {
const query = input.value.trim();
clear.hidden = query.length === 0;
if (query.length < 2) {
results.innerHTML = '';
return;
}
abortController?.abort();
abortController = new AbortController();
try {
const response = await index.search(query, {
limit: limit,
attributesToRetrieve: [
'title',
'url',
'text',
'poster',
'type'
],
attributesToHighlight: ['text'],
highlightPreTag: '<mark>',
highlightPostTag: '</mark>'
});
renderResults(response.hits);
} catch (e) {
if (e.name !== 'AbortError') {
console.error('Meilisearch error:', e);
}
}
});
function renderResults(hits) {
results.innerHTML = '';
if (!hits.length) {
return;
}
for (const hit of hits) {
const article = document.createElement('article');
article.className = 'meilisearch-result type-' + (hit.type || 'unknown');
const link = document.createElement('a');
link.href = hit.url || '#';
link.className = 'meilisearch-link';
if (hit.poster) {
const img = document.createElement('img');
img.src = hit.poster;
img.alt = '';
img.loading = 'lazy';
link.appendChild(img);
}
const title = document.createElement('h3');
title.textContent = hit.title || '';
link.appendChild(title);
if (hit._formatted?.text) {
const extract = document.createElement('p');
extract.className = 'meilisearch-extract';
extract.innerHTML = hit._formatted.text;
link.appendChild(extract);
}
article.appendChild(link);
results.appendChild(article);
}
}
});
</script>