MediaWiki:Common.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 1.246: | Zeile 1.246: | ||
// ==========================Scan================================== | // ==========================Scan================================== | ||
// === LabelScan | // === LabelScan – Foto->OCR->Suche (UI+Progress) ============================= | ||
mw.loader.using( ['mediawiki.api'] ).then(function () { | mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function(){ | ||
mw.hook('wikipage.content').add(function($content){ | |||
if (mw.config.get('wgPageName') !== 'LabelScan') return; | |||
var $btnBig = $content.find('#ados-scan-bigbtn'); | |||
var $file = $content.find('#ados-scan-file'); | |||
var $run = $content.find('#ados-scan-run'); | |||
var $drop = $content.find('#ados-scan-drop'); | |||
var $prev = $content.find('#ados-scan-preview'); | |||
var $stat = $content.find('#ados-scan-status'); | |||
var $prog = $content.find('#ados-scan-progress'); | |||
var $res = $content.find('#ados-scan-results'); | |||
if (!$btnBig.length || !$file.length || !$run.length) return; | |||
if ( | if ($run.data('bound')) return; // nicht doppelt binden | ||
$run.data('bound', 1); | |||
function setStatus(t){ $stat.text(t||''); } | |||
function setProgress(p){ | |||
if (p==null){ $prog.hide().val(0); return; } | |||
$prog.show().val(Math.max(0, Math.min(1, p))); | |||
} | } | ||
function showPreview(file){ | function showPreview(file){ | ||
var url = URL.createObjectURL(file); | var url = URL.createObjectURL(file); | ||
$prev.html('<img alt="Vorschau | $prev.html('<img alt="Vorschau" src="'+url+'">').attr('aria-hidden','false'); | ||
} | } | ||
// Tesseract | // Datei wählen | ||
$btnBig.on('click', function(){ $file.trigger('click'); }); | |||
// Drag&Drop | |||
$drop.on('dragover', function(e){ e.preventDefault(); $drop.addClass('dragover'); }); | |||
$drop.on('dragleave', function(e){ $drop.removeClass('dragover'); }); | |||
$drop.on('drop', function(e){ | |||
e.preventDefault(); $drop.removeClass('dragover'); | |||
if (e.originalEvent && e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length){ | |||
$file[0].files = e.originalEvent.dataTransfer.files; | |||
showPreview($file[0].files[0]); | |||
} | |||
}); | |||
// Vorschau bei Auswahl | |||
$file.on('change', function(){ if (this.files && this.files[0]) showPreview(this.files[0]); }); | |||
// Tesseract lazy-load | |||
function loadTesseract(){ | function loadTesseract(){ | ||
return new Promise(function(resolve, reject){ | return new Promise(function(resolve, reject){ | ||
if ( window.Tesseract ) return resolve(); | if (window.Tesseract) return resolve(); | ||
var s = document.createElement('script'); | var s = document.createElement('script'); | ||
s.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'; | s.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'; | ||
| Zeile 1.286: | Zeile 1.298: | ||
s.onload = resolve; | s.onload = resolve; | ||
s.onerror = function(){ | s.onerror = function(){ | ||
// Fallback-CDN | |||
reject(new Error('Tesseract | 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); | document.head.appendChild(s); | ||
| Zeile 1.293: | Zeile 1.310: | ||
} | } | ||
// | // Heuristik -> Query | ||
function extractHints(text){ | function extractHints(text){ | ||
const raw = (text||'').replace(/\s+/g,' ').trim(); | const raw = (text||'').replace(/\s+/g,' ').trim(); | ||
| Zeile 1.302: | Zeile 1.319: | ||
return {words, ages, years}; | return {words, ages, years}; | ||
} | } | ||
function buildSearchQuery(h){ | function buildSearchQuery(h){ | ||
const parts = []; | const parts = []; | ||
| Zeile 1.323: | Zeile 1.339: | ||
var snip = (it.snippet||'').replace(/<\/?span[^>]*>/g,'').replace(/"/g,'"'); | var snip = (it.snippet||'').replace(/<\/?span[^>]*>/g,'').replace(/"/g,'"'); | ||
$res.append( | $res.append( | ||
'<div class="ados-hit | '<div class="ados-hit">' | ||
+ '<b><a href="'+link+'">'+mw.html.escape(title)+'</a></b>' | + '<b><a href="'+link+'">'+mw.html.escape(title)+'</a></b>' | ||
+ (snip ? '<div class="meta" style="color:#666">'+snip+'</div>' : '') | + (snip ? '<div class="meta" style="color:#666">'+snip+'</div>' : '') | ||
| Zeile 1.333: | Zeile 1.349: | ||
async function runOCR(file){ | async function runOCR(file){ | ||
await loadTesseract(); | await loadTesseract(); | ||
setProgress(0); | |||
const result = await Tesseract.recognize(file, 'deu+eng'); | const result = await Tesseract.recognize(file, 'deu+eng', { | ||
logger: function(m){ | |||
if (m && m.status === 'recognizing text' && typeof m.progress === 'number'){ | |||
setProgress(m.progress); | |||
} | |||
} | |||
}); | |||
setProgress(null); | |||
return (result && result.data && result.data.text) || ''; | return (result && result.data && result.data.text) || ''; | ||
} | } | ||
| Zeile 1.341: | Zeile 1.364: | ||
const api = new mw.Api(); | const api = new mw.Api(); | ||
const res = await api.get({ | const res = await api.get({ | ||
action: 'query', list: 'search', srsearch: query, | action:'query', list:'search', srsearch:query, | ||
srlimit: limit || 10, srwhat: 'text', formatversion: 2 | srlimit:limit||10, srwhat:'text', formatversion:2 | ||
}); | }); | ||
return (res.query && res.query.search) || []; | return (res.query && res.query.search) || []; | ||
} | } | ||
// | // Start | ||
$ | $run.on('click', async function(ev){ | ||
ev.preventDefault(); | ev.preventDefault(); | ||
if (!($file[0].files && $file[0].files[0])){ alert('Bitte ein Foto auswählen oder aufnehmen.'); return; } | |||
const file = $file[0].files[0]; | |||
try{ | try{ | ||
$run.prop('disabled', true).text('Erkenne …'); | |||
setStatus('Erkenne Label …'); | setStatus('Erkenne Label …'); | ||
const text = await runOCR(file); | |||
const text | |||
setStatus('Suche im Wiki …'); | setStatus('Suche im Wiki …'); | ||
const hints = extractHints(text); | const hints = extractHints(text); | ||
const query = buildSearchQuery(hints); | const query = buildSearchQuery(hints); | ||
const hits = await searchWiki(query, 12); | |||
renderResults(hits); | renderResults(hits); | ||
setStatus('Fertig.'); | setStatus('Fertig.'); | ||
| Zeile 1.370: | Zeile 1.391: | ||
console.error('[LabelScan] Fehler:', e); | console.error('[LabelScan] Fehler:', e); | ||
setStatus('Fehler bei Erkennung/Suche. Bitte erneut versuchen.'); | setStatus('Fehler bei Erkennung/Suche. Bitte erneut versuchen.'); | ||
} finally { | |||
$run.prop('disabled', false).text('Erkennen & suchen'); | |||
} | } | ||
}); | }); | ||