MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Markierung: Manuelle Zurücksetzung
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(/&quot;/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.');
    }
  });
})();