Widget:SearchBox: Unterschied zwischen den Versionen
Aus MediaWiki
Alex (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Alex (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| (Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt) | |||
| Zeile 10: | Zeile 10: | ||
<style> | <style> | ||
#api-search-widget { | #api-search-widget { | ||
max-width: | max-width: 720px; | ||
font-family: -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; | |||
color: #212529; | |||
} | } | ||
#api-search-form { | #api-search-form { | ||
display: flex; | display: flex; | ||
gap: | gap: 0; | ||
margin-bottom: | margin-bottom: 16px; | ||
} | } | ||
#api-search-input { | #api-search-input { | ||
flex: 1; | flex: 1; | ||
padding: | padding: 10px 14px; | ||
font-size: | font-size: 16px; | ||
border: | border: 2px solid #a2a9b1; | ||
border-radius: | border-right: none; | ||
border-radius: 4px 0 0 4px; | |||
outline: none; | |||
transition: border-color 0.2s; | |||
color: #212529; | |||
background: #fff; | |||
} | |||
#api-search-input:focus { | |||
border-color: #355B84; | |||
} | } | ||
#api-search-form button { | #api-search-form button { | ||
padding: | padding: 10px 20px; | ||
font- | font-size: 16px; | ||
font-weight: 500; | |||
cursor: pointer; | cursor: pointer; | ||
background: #355B84; | |||
color: #fff; | |||
border: 2px solid #355B84; | |||
border-radius: 0 4px 4px 0; | |||
transition: background 0.2s; | |||
} | |||
#api-search-form button:hover { | |||
background: #2a4a6b; | |||
border-color: #2a4a6b; | |||
} | } | ||
#api-search-results { | #api-search-results { | ||
display: none; | |||
font- | } | ||
#api-search-results.visible { | |||
display: block; | |||
} | |||
.search-status { | |||
padding: 16px; | |||
text-align: center; | |||
color: #555; | |||
font-size: 15px; | |||
} | |||
.search-status.error { | |||
color: #a00; | |||
background: #fef2f2; | |||
border: 1px solid #fca5a5; | |||
border-radius: 4px; | |||
} | |||
@keyframes pulse { | |||
0%, 100% { opacity: 1; } | |||
50% { opacity: 0.5; } | |||
} | |||
.search-loading { | |||
animation: pulse 1.5s ease-in-out infinite; | |||
} | |||
.search-result-card { | |||
padding: 16px; | |||
margin-bottom: 12px; | |||
border: 1px solid #dee2e6; | |||
border-radius: 4px; | |||
background: #fff; | |||
transition: box-shadow 0.15s; | |||
} | |||
.search-result-card:hover { | |||
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |||
} | |||
.search-result-card h4 { | |||
margin: 0 0 6px 0; | |||
font-size: 17px; | |||
font-weight: 500; | |||
} | |||
.search-result-card h4 a { | |||
color: #1B599B; | |||
text-decoration: none; | |||
} | |||
.search-result-card h4 a:hover { | |||
text-decoration: underline; | |||
} | |||
.search-result-snippet { | |||
font-size: 14px; | |||
line-height: 1.5; | |||
color: #333; | |||
margin-bottom: 8px; | |||
} | |||
.search-result-meta { | |||
display: flex; | |||
gap: 12px; | |||
font-size: 12px; | |||
color: #6c757d; | |||
} | |||
.search-result-meta span { | |||
display: inline-flex; | |||
align-items: center; | |||
gap: 4px; | |||
} | |||
.search-score-bar { | |||
display: inline-block; | |||
width: 40px; | |||
height: 6px; | |||
background: #dee2e6; | |||
border-radius: 3px; | |||
overflow: hidden; | |||
vertical-align: middle; | |||
} | |||
.search-score-fill { | |||
height: 100%; | |||
background: #355B84; | |||
border-radius: 3px; | |||
} | |||
.search-result-count { | |||
font-size: 13px; | font-size: 13px; | ||
color: #6c757d; | |||
margin-bottom: 12px; | |||
} | } | ||
</style> | </style> | ||
| Zeile 47: | Zeile 141: | ||
var results = document.getElementById('api-search-results'); | var results = document.getElementById('api-search-results'); | ||
var apiHost = 'api.' + location.hostname.split('.').slice(1).join('.'); | var apiHost = 'api.' + location.hostname.split('.').slice(1).join('.'); | ||
function escapeHtml(str) { | |||
var d = document.createElement('div'); | |||
d.textContent = str; | |||
return d.innerHTML; | |||
} | |||
function truncate(str, len) { | |||
if (str.length <= len) return str; | |||
return str.substring(0, len).replace(/\s+\S*$/, '') + '…'; | |||
} | |||
function renderResults(data) { | |||
if (!data.results || data.results.length === 0) { | |||
results.innerHTML = '<div class="search-status">Keine Ergebnisse gefunden.</div>'; | |||
return; | |||
} | |||
var html = '<div class="search-result-count">' + data.results.length + ' Ergebnis' + (data.results.length !== 1 ? 'se' : '') + '</div>'; | |||
data.results.forEach(function(r) { | |||
if (typeof r === 'string') { | |||
html += '<div class="search-result-card">' | |||
+ '<div class="search-result-snippet">' + escapeHtml(r) + '</div>' | |||
+ '</div>'; | |||
} else { | |||
var pct = Math.round((r.similarity_score || 0) * 100); | |||
var wikiUrl = '/wiki/' + encodeURIComponent((r.page_title || '').replace(/ /g, '_')); | |||
html += '<div class="search-result-card">' | |||
+ '<h4><a href="' + wikiUrl + '">' + escapeHtml(r.page_title || 'Ohne Titel') + '</a></h4>' | |||
+ '<div class="search-result-snippet">' + escapeHtml(truncate(r.content_text || '', 250)) + '</div>' | |||
+ '<div class="search-result-meta">' | |||
+ '<span>Relevanz: <span class="search-score-bar"><span class="search-score-fill" style="width:' + pct + '%"></span></span> ' + pct + '%</span>' | |||
+ (r.source ? '<span>Quelle: ' + escapeHtml(r.source) + '</span>' : '') | |||
+ '</div></div>'; | |||
} | |||
}); | |||
results.innerHTML = html; | |||
} | |||
form.addEventListener('submit', function(e) { | form.addEventListener('submit', function(e) { | ||
| Zeile 53: | Zeile 184: | ||
if (!query) return; | if (!query) return; | ||
results. | results.className = 'visible'; | ||
results. | results.innerHTML = '<div class="search-status search-loading">Suche läuft…</div>'; | ||
var url = location.protocol + '//' + apiHost + '/search?q=' + encodeURIComponent(query); | var url = location.protocol + '//' + apiHost + '/search?q=' + encodeURIComponent(query); | ||
| Zeile 64: | Zeile 195: | ||
}) | }) | ||
.then(function(data) { | .then(function(data) { | ||
renderResults(data); | |||
}) | }) | ||
.catch(function(err) { | .catch(function(err) { | ||
results. | results.innerHTML = '<div class="search-status error">Fehler: ' + escapeHtml(err.message) + '</div>'; | ||
}); | }); | ||
}); | }); | ||