MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1.246: Zeile 1.246:
// ==========================Scan==================================
// ==========================Scan==================================


// === LabelScan: Foto->OCR->Suche ===========================================
// === 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;


   // Hook feuert nach jedem Render (auch VisualEditor, Abschnitte, Mobil)
    var $btnBig = $content.find('#ados-scan-bigbtn');
   mw.hook('wikipage.content').add(async function ( $content ) {
    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');


     // Nur auf der Seite "LabelScan" aktivieren
     if (!$btnBig.length || !$file.length || !$run.length) return;
     if ( mw.config.get('wgPageName') !== 'LabelScan' ) return;
     if ($run.data('bound')) return; // nicht doppelt binden
    $run.data('bound', 1);


     // Elemente innerhalb des aktuellen Inhalts suchen
     function setStatus(t){ $stat.text(t||''); }
    var $btn  = $content.find('#ados-scan-run');
     function setProgress(p){
     var $file  = $content.find('#ados-scan-file');
      if (p==null){ $prog.hide().val(0); return; }
    var $prev  = $content.find('#ados-scan-preview');
      $prog.show().val(Math.max(0, Math.min(1, p)));
    var $stat  = $content.find('#ados-scan-status');
    var $res  = $content.find('#ados-scan-results');
 
    if ( !$btn.length || !$file.length ) {
      console.warn('[LabelScan] UI-Elemente nicht gefunden.');
      return;
     }
     }
    // Mehrfachbindungen verhindern
    if ( $btn.data('bound') ) return;
    $btn.data('bound', 1);
    function setStatus(t){ $stat.text( t || '' ); }
     function showPreview(file){
     function showPreview(file){
       var url = URL.createObjectURL(file);
       var url = URL.createObjectURL(file);
       $prev.html('<img alt="Vorschau" style="max-width:100%;border-radius:8px" src="'+url+'">');
       $prev.html('<img alt="Vorschau" src="'+url+'">').attr('aria-hidden','false');
     }
     }


     // Tesseract nur laden, wenn gebraucht
    // 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(){
           console.error('[LabelScan] Tesseract konnte nicht geladen werden (CDN blockiert?)');
           // Fallback-CDN
           reject(new Error('Tesseract load failed'));
          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:
     }
     }


     // Sehr einfache Heuristik
     // 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(/&quot;/g,'"');
         var snip  = (it.snippet||'').replace(/<\/?span[^>]*>/g,'').replace(/&quot;/g,'"');
         $res.append(
         $res.append(
           '<div class="ados-hit" style="border:1px solid #eee;border-radius:10px;padding:10px;background:#fafafa;margin:.25rem 0;">'
           '<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();
       // Einfache API-Form: Sprache "deu+eng"
       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) || [];
     }
     }


     // Klick-Handler
     // Start
     $btn.on('click', async function(ev){
     $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{
         const file = $file[0].files && $file[0].files[0];
         $run.prop('disabled', true).text('Erkenne …');
        if (!file){ alert('Bitte ein Foto auswählen oder aufnehmen.'); return; }
        showPreview(file);
         setStatus('Erkenne Label …');
         setStatus('Erkenne Label …');
 
         const text = await runOCR(file);
         const text = await runOCR(file);
        console.log('[LabelScan] OCR:', text.slice(0,200), '…');


         setStatus('Suche im Wiki …');
         setStatus('Suche im Wiki …');
         const hints = extractHints(text);
         const hints = extractHints(text);
         const query = buildSearchQuery(hints);
         const query = buildSearchQuery(hints);
         console.log('[LabelScan] Query:', query);
         const hits  = await searchWiki(query, 12);


        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');
       }
       }
     });
     });