MediaWiki:Common.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung Markierung: Manuelle Zurücksetzung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 1.244: | Zeile 1.244: | ||
}); | }); | ||
// ============================================================ | // ==========================Scan================================== | ||
(function(){ | |||
if (!window.mw) return; | |||
// Nur auf der Seite "LabelScan" aktivieren | |||
if (mw.config.get('wgPageName') !== 'LabelScan') return; | |||
// Tesseract nur bei Bedarf laden | |||
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(){ reject(new Error('Tesseract konnte nicht geladen werden')); }; | |||
document.head.appendChild(s); | |||
}); | |||
} | |||
// Hilfsfunktionen | |||
function $(sel){ return document.querySelector(sel); } | |||
function setStatus(msg){ $('#ados-scan-status').textContent = msg || ''; } | |||
// Heuristik: sinnvolle Tokens extrahieren | |||
function extractHints(text){ | |||
const raw = (text || '').replace(/\s+/g, ' ').trim(); | |||
// Mögliche Distillery-/Marken-Wörter (großgeschrieben, mind. 4 Zeichen) | |||
const words = Array.from(new Set(raw.match(/\b[A-ZÄÖÜ][A-Za-zÄÖÜäöüß\-]{3,}\b/g) || [])); | |||
// Alter (z.B. 12, 13-year, years, yo) | |||
const ages = Array.from(new Set(raw.match(/\b([1-9]\d?)\s?(?:years?|yo|jahr|jahre)\b/gi) || [])) | |||
.map(s => (s.match(/[1-9]\d?/)||[])[0]); | |||
// Jahrgänge (4-stellig, 19xx/20xx) | |||
const years = Array.from(new Set(raw.match(/\b(19|20)\d{2}\b/g) || [])); | |||
// Serie/Marken, die in deinem Wiki häufig sind – Gewichtung hilft der Suchreihenfolge | |||
const seriesHints = ['ADOS','AdoS','A Dream of Scotland','Friendly','Elfen','Fine','Rumbastic','Cigar','Ireland','Islay','Speyside']; | |||
return { words, ages, years, seriesHints }; | |||
} | |||
// Eine smarte Suchphrase bauen (MediaWiki-Suche + Cargo-Fallback möglich) | |||
function buildSearchQuery(hints){ | |||
const parts = []; | |||
// bevorzuge markante Wörter | |||
(hints.words || []).slice(0, 5).forEach(w => parts.push('"' + w + '"')); | |||
// Alter und Jahr als Booster | |||
(hints.ages || []).forEach(a => parts.push('"' + a + '"')); | |||
(hints.years || []).forEach(y => parts.push('"' + y + '"')); | |||
// Serien-Schlagworte (leichter Boost) | |||
(hints.seriesHints || []).forEach(s => parts.push(s)); | |||
// Fallback | |||
if (!parts.length) parts.push('Whisky'); | |||
// Suchraum einschränken (optional): intitle bevorzugen | |||
const query = parts.join(' '); | |||
return query; | |||
} | |||
// Wiki-Search API abfragen (Top-N Treffer) | |||
function searchWiki(query, limit){ | |||
const api = new mw.Api(); | |||
return api.get({ | |||
action: 'query', | |||
list: 'search', | |||
srsearch: query, | |||
srlimit: limit || 10, | |||
srwhat: 'text', | |||
formatversion: 2 | |||
}).then(res => res.query && res.query.search ? res.query.search : []); | |||
} | |||
// (Optional) Cargo-Fallback nach Titel-Ähnlichkeit | |||
function searchCargoByTitleLike(tokens, limit){ | |||
// nutzt cargoquery (falls Tabelle/Spalten verfügbar sind) | |||
const api = new mw.Api(); | |||
// Aus Tokens "LIKE '%X%'" bauen (vorsichtig) | |||
const likes = tokens.slice(0,3).map(t => `Seitentitel LIKE "%${t.replace(/"/g,'') }%"`).join(' OR '); | |||
if (!likes) return Promise.resolve([]); | |||
return api.get({ | |||
action: 'cargoquery', | |||
tables: 'Abfuellungen', | |||
fields: 'Seitentitel,Serie,Erscheinungsjahr', | |||
where: likes, | |||
limit: limit || 10 | |||
}).then(res => (res.cargoquery || []).map(r => r.title || {})); | |||
} | |||
// UI rendern | |||
function showPreview(file){ | |||
const url = URL.createObjectURL(file); | |||
$('#ados-scan-preview').innerHTML = '<img alt="Vorschau" src="'+url+'">'; | |||
} | |||
function renderResults(items){ | |||
const wrap = $('#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(it=>{ | |||
// it: aus core search (title, snippet) oder cargo (Seitentitel) | |||
const title = it.title || it.Seitentitel || ''; | |||
const link = mw.util.getUrl(title.replace(/ /g,'_')); | |||
const snip = (it.snippet || '').replace(/<\/?span[^>]*>/g,'').replace(/"/g,'"'); | |||
const div = document.createElement('div'); | |||
div.className = 'ados-hit'; | |||
div.innerHTML = '<b><a href="'+link+'">'+mw.html.escape(title)+'</a></b>' + | |||
(snip ? '<div class="meta">'+snip+'</div>' : ''); | |||
wrap.appendChild(div); | |||
}); | |||
} | |||
async function runOCR(file){ | |||
await loadTesseract(); | |||
setStatus('Erkenne Text…'); | |||
const worker = await Tesseract.createWorker('eng+deu'); | |||
const { data } = await worker.recognize(file); | |||
await worker.terminate(); | |||
return data && data.text || ''; | |||
} | |||
// Ereignisse | |||
const input = $('#ados-scan-file'); | |||
$('#ados-scan-run').addEventListener('click', async function(){ | |||
const file = input && input.files && input.files[0]; | |||
if (!file) { alert('Bitte ein Foto auswählen oder aufnehmen.'); return; } | |||
showPreview(file); | |||
try{ | |||
setStatus('Erkenne Label…'); | |||
const text = await runOCR(file); | |||
setStatus('Suche im Wiki…'); | |||
const hints = extractHints(text); | |||
const query = buildSearchQuery(hints); | |||
// 1) Core Suche | |||
const coreHits = await searchWiki(query, 12); | |||
// 2) Cargo-Fallback (optional), Tokens aus markanten Wörtern | |||
const tokens = (hints.words || []).map(w => w.toLowerCase()); | |||
let cargoHits = []; | |||
try { cargoHits = await searchCargoByTitleLike(tokens, 8); } catch(e){ /* Cargo optional */ } | |||
// Merge (einfach) | |||
const titlesSeen = new Set(); | |||
const merged = []; | |||
coreHits.forEach(h => { if (!titlesSeen.has(h.title)){ titlesSeen.add(h.title); merged.push(h); }}); | |||
cargoHits.forEach(h => { | |||
const t = h.Seitentitel || h.title; | |||
if (t && !titlesSeen.has(t)){ titlesSeen.add(t); merged.push({ title: t }); } | |||
}); | |||
renderResults(merged); | |||
setStatus('Fertig.'); | |||
}catch(e){ | |||
console.error(e); | |||
setStatus('Fehler bei der Erkennung/Suche. Bitte erneut versuchen.'); | |||
} | |||
}); | |||
})(); | |||