LabelScan: Unterschied zwischen den Versionen
Erscheinungsbild
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 29: | Zeile 29: | ||
</div> | </div> | ||
</html> | </html> | ||
{{#tag:html| | |||
<script> | |||
(function(){ | |||
document.addEventListener('DOMContentLoaded',function(){ | |||
var run = document.getElementById('ados-scan-run'); | |||
var file = document.getElementById('ados-scan-file'); | |||
var big = document.getElementById('ados-scan-bigbtn'); | |||
var prev = document.getElementById('ados-scan-preview'); | |||
var stat = document.getElementById('ados-scan-status'); | |||
var prog = document.getElementById('ados-scan-progress'); | |||
if(!run || !file){ console.warn('[LabelScan] UI-Elemente fehlen'); return; } | |||
function setStatus(t){ stat.textContent = t || ''; } | |||
function setProgress(p){ | |||
if(p==null){ prog.style.display='none'; prog.value=0; return; } | |||
prog.style.display=''; prog.value=Math.max(0,Math.min(1,p)); | |||
} | |||
function showPreview(f){ | |||
var url = URL.createObjectURL(f); | |||
prev.innerHTML = '<img alt="Vorschau" style="max-width:100%;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,.06)" src="'+url+'">'; | |||
prev.setAttribute('aria-hidden','false'); | |||
} | |||
// großer Button öffnet echte Dateiauswahl | |||
if (big) big.addEventListener('click', function(){ file.click(); }); | |||
// Vorschau bei Auswahl | |||
file.addEventListener('change', function(){ if (this.files && this.files[0]) showPreview(this.files[0]); }); | |||
// Drag&Drop (optional, falls du #ados-scan-drop nutzt) | |||
var drop = document.getElementById('ados-scan-drop'); | |||
if (drop){ | |||
drop.addEventListener('dragover', function(e){ e.preventDefault(); drop.classList.add('dragover'); }); | |||
drop.addEventListener('dragleave', function(){ drop.classList.remove('dragover'); }); | |||
drop.addEventListener('drop', function(e){ | |||
e.preventDefault(); drop.classList.remove('dragover'); | |||
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length){ | |||
file.files = e.dataTransfer.files; | |||
showPreview(file.files[0]); | |||
} | |||
}); | |||
} | |||
// Tesseract lazy-load mit Fallback-CDN | |||
function loadTesseract(){ | |||
return new Promise(function(resolve,reject){ | |||
if (window.Tesseract) return resolve(); | |||
var s=document.createElement('script'); | |||
s.src='https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'; | |||
s.async=true; | |||
s.onload=resolve; | |||
s.onerror=function(){ | |||
var s2=document.createElement('script'); | |||
s2.src='https://unpkg.com/tesseract.js@5/dist/tesseract.min.js'; | |||
s2.async=true; | |||
s2.onload=resolve; | |||
s2.onerror=function(){ reject(new Error('Tesseract konnte nicht geladen werden')); }; | |||
document.head.appendChild(s2); | |||
}; | |||
document.head.appendChild(s); | |||
}); | |||
} | |||
// Heuristik -> Query | |||
function extractHints(text){ | |||
var raw = (text||'').replace(/\s+/g,' ').trim(); | |||
var words = (raw.match(/\b[A-ZÄÖÜ][A-Za-zÄÖÜäöüß\-]{3,}\b/g) || []).slice(0,5); | |||
var ages = (raw.match(/\b([1-9]\d?)\s?(?:years?|yo|jahr|jahre)\b/gi) || []).map(function(s){ var m=s.match(/[1-9]\d?/); return m?m[0]:null; }).filter(Boolean); | |||
var years = (raw.match(/\b(19|20)\d{2}\b/g) || []); | |||
return {words:words, ages:ages, years:years}; | |||
} | |||
function buildSearchQuery(h){ | |||
var parts=[]; | |||
h.words.forEach(function(w){ parts.push('"' + w + '"'); }); | |||
h.ages.forEach(function(a){ parts.push('"' + a + '"'); }); | |||
h.years.forEach(function(y){ parts.push('"' + y + '"'); }); | |||
if (!parts.length) parts.push('Whisky'); | |||
return parts.join(' '); | |||
} | |||
function renderResults(items){ | |||
var wrap = document.getElementById('ados-scan-results'); | |||
wrap.innerHTML = ''; | |||
if (!items.length){ | |||
wrap.innerHTML = '<div class="ados-hit">Keine klaren Treffer. Bitte anderes Foto oder manuell suchen.</div>'; | |||
return; | |||
} | |||
items.slice(0,8).forEach(function(h){ | |||
var title = h.title || h.Seitentitel || ''; | |||
var link = mw.util.getUrl( title.replace(/ /g,'_') ); | |||
var snip = (h.snippet||'').replace(/<\/?span[^>]*>/g,'').replace(/"/g,'"'); | |||
var div = document.createElement('div'); | |||
div.className = 'ados-hit'; | |||
div.style.border='1px solid #eee'; div.style.borderRadius='10px'; div.style.padding='10px'; div.style.background='#fafafa'; | |||
div.innerHTML = '<b><a href="'+link+'">'+mw.html.escape(title)+'</a></b>' + (snip ? '<div class="meta" style="color:#666">'+snip+'</div>' : ''); | |||
wrap.appendChild(div); | |||
}); | |||
} | |||
// Klick-Handler: Erkennen & suchen | |||
run.addEventListener('click', function(ev){ | |||
ev.preventDefault(); | |||
if (!(file.files && file.files[0])){ alert('Bitte ein Foto auswählen oder aufnehmen.'); return; } | |||
var f = file.files[0]; | |||
(async function(){ | |||
try{ | |||
run.disabled=true; run.textContent='Erkenne …'; | |||
setStatus('Erkenne Label …'); | |||
await loadTesseract(); | |||
setProgress(0); | |||
var result = await Tesseract.recognize(f, 'deu+eng', { | |||
logger: function(m){ | |||
if (m && m.status === 'recognizing text' && typeof m.progress === 'number') setProgress(m.progress); | |||
} | |||
}); | |||
setProgress(null); | |||
setStatus('Suche im Wiki …'); | |||
var text = (result && result.data && result.data.text) || ''; | |||
var hints = extractHints(text); | |||
var query = buildSearchQuery(hints); | |||
mw.loader.using('mediawiki.api').then(async function(){ | |||
var api = new mw.Api(); | |||
var r = await api.get({ action:'query', list:'search', srsearch:query, srlimit:10, formatversion:2 }); | |||
var hits = (r.query && r.query.search) || []; | |||
renderResults(hits); | |||
setStatus('Fertig.'); | |||
run.disabled=false; run.textContent='Erkennen & suchen'; | |||
}); | |||
} catch(e){ | |||
console.error('[LabelScan] Fehler:', e); | |||
setProgress(null); | |||
setStatus('Fehler bei Erkennung/Suche. Bitte erneut versuchen.'); | |||
run.disabled=false; run.textContent='Erkennen & suchen'; | |||
} | |||
})(); | |||
}); | |||
console.log('LabelScan inline bound'); | |||
}); | |||
})(); | |||
</script> | |||
}} | |||
Version vom 5. November 2025, 19:06 Uhr
📸 Abfüllung scannen
Foto der Front-Labelseite aufnehmen oder wählen. Die Erkennung läuft vollständig lokal im Browser.
Bereit.