MediaWiki:Common.js: Unterschied zwischen den Versionen

 
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
/* Das folgende JavaScript wird für alle Benutzer geladen. */
/* Das folgende JavaScript wird für alle Benutzer geladen. */
/* =========================
/* =========================
   Site Announcement Modal
   Site Announcement Modal + Image Preview
   =========================
   ========================= */
  - Zeigt ein modales Popup einmal pro Tag pro Nutzer.
  - Mobilfreundlich (funktioniert mit Vector/Vector-2022/Minerva/Timeless).
  - Konfigurierbar über den CONFIG-Block.
  - Kein externes Framework nötig, nutzt jQuery (mit MediaWiki gebündelt).
*/


(function ($, mw) {
(function ($, mw) {
   'use strict';
   'use strict';


  // ---- CONFIG ----
   var CONFIG = {
   var CONFIG = {
     enabled: true,                   // global an/aus
     enabled: true,
     id: 'site_announcement_v1',     // bei inhaltlicher Änderung erhöhen (z.B. v2), damit es wieder gezeigt wird
     id: 'site_announcement_v2', // <- erhöht, damit die neue Version wieder angezeigt wird
     title: 'Ankündigung',
     title: 'Ankündigung',
    // HTML erlaubt. Halte es kurz & barrierearm.
     html:
     html: '<p>🎉 Nächste Woche Wartungsfenster: <strong>Di, 10:00–11:00</strong>. ' +
      '<p>🎉 Nächste Woche Wartungsfenster: <strong>Di, 10:00–11:00</strong>. In dieser Zeit ist das Wiki nur eingeschränkt verfügbar.</p>' +
          'In dieser Zeit ist das Wiki nur eingeschränkt verfügbar.</p>' +
      '<div class="mw-site-announcement-actions">' +
          '<p><a href="/wiki/Projekt:News" class="mw-ui-button">Mehr Infos</a></p>',
        '<a href="https://s1.directupload.eu/images/250823/24cwslu3.png" target="_blank" rel="noopener" class="mw-ui-button">In neuem Tab öffnen</a>' +
     showOnNamespaces: 'all',         // 'all' oder Array von NS-IDs, z.B. [0, 4]
        '<button type="button" class="mw-ui-button mw-ui-progressive" id="mw-site-announcement-more">Mehr Infos</button>' +
     onlyForLoggedOut: false,         // nur für nicht angemeldete zeigen
      '</div>',
     onlyForLoggedIn: false,         // nur für angemeldete zeigen
     showOnNamespaces: 'all',
     startISO: null,                 // z.B. '2025-09-01T00:00:00Z' (null = sofort)
     onlyForLoggedOut: false,
     endISO: null,                   // z.B. '2025-09-30T23:59:59Z' (null = kein Ende)
     onlyForLoggedIn: false,
     dailyLimit: 1,                   // max. Anzeigen pro Kalendertag (idempotent, i.d.R. 1)
     startISO: null,
     closeBehavesAsShown: true,       // Schließen zählt als "heute bereits gesehen"
     endISO: null,
     escToClose: true,               // ESC schließt Modal
     dailyLimit: 1,
     clickBackdropToClose: true      // Klick auf Overlay schließt Modal
     closeBehavesAsShown: true,
     escToClose: true,
    clickBackdropToClose: true,
 
    // Bildquelle für die Vorschau:
     imageSrc: 'https://s1.directupload.eu/images/250823/24cwslu3.png',
    imageAlt: 'Mehr Informationen'
   };
   };


  // ---- GUARD CLAUSES ----
   if (!CONFIG.enabled) return;
   if (!CONFIG.enabled) return;
  if (mw.config.get('wgIsArticle') === false && mw.config.get('skin') === 'minerva') {
    // Minerva lädt Common.js teils auf Spezialseiten eingeschränkt – kein Modal auf reinen Spezialseiten nötig.
  }


  // Login-Filter
   var isAnon = mw.config.get('wgUserName') === null;
   var isAnon = mw.config.get('wgUserName') === null;
   if (CONFIG.onlyForLoggedOut && !isAnon) return;
   if (CONFIG.onlyForLoggedOut && !isAnon) return;
   if (CONFIG.onlyForLoggedIn && isAnon) return;
   if (CONFIG.onlyForLoggedIn && isAnon) return;


  // Namespace-Filter
   var ns = mw.config.get('wgNamespaceNumber');
   var ns = mw.config.get('wgNamespaceNumber');
   if (CONFIG.showOnNamespaces !== 'all' && Array.isArray(CONFIG.showOnNamespaces) && CONFIG.showOnNamespaces.indexOf(ns) === -1) {
   if (CONFIG.showOnNamespaces !== 'all' && Array.isArray(CONFIG.showOnNamespaces) && CONFIG.showOnNamespaces.indexOf(ns) === -1) {
Zeile 49: Zeile 43:
   }
   }


  // Zeitfenster-Filter
   var now = new Date();
   var now = new Date();
   if (CONFIG.startISO && now < new Date(CONFIG.startISO)) return;
   if (CONFIG.startISO && now < new Date(CONFIG.startISO)) return;
   if (CONFIG.endISO && now > new Date(CONFIG.endISO)) return;
   if (CONFIG.endISO && now > new Date(CONFIG.endISO)) return;


  // Storage-Helfer (mit Fallback)
   var storage = (function () {
   var storage = (function () {
     var prefix = 'mw_site_popup__';
     var prefix = 'mw_site_popup__';
     function safeGet(k) {
     function get(k){ try { return localStorage.getItem(prefix + k); } catch(e){ return null; } }
      try { return window.localStorage.getItem(prefix + k); } catch (e) { return null; }
     function set(k,v){ try { localStorage.setItem(prefix + k, v); } catch(e){} }
    }
     return { get:get, set:set };
     function safeSet(k, v) {
      try { window.localStorage.setItem(prefix + k, v); } catch (e) { /* ignore */ }
    }
     return { get: safeGet, set: safeSet };
   })();
   })();


  // Schlüssel berechnen (pro Version + Nutzerstatus)
   var keyBase = CONFIG.id + (isAnon ? ':anon' : ':user');
   var keyBase = CONFIG.id + (isAnon ? ':anon' : ':user');
   var lastShownKey = keyBase + ':lastShown';
   var lastShownKey = keyBase + ':lastShown';
   var countKey = keyBase + ':count:';
   var countKey = keyBase + ':count:';
   var todayStr = (function (d) {
   var todayStr = (function (d){ return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0'); })(now);
    // YYYY-MM-DD in Nutzer-Zeitzone
    var y = d.getFullYear();
    var m = String(d.getMonth() + 1).padStart(2, '0');
    var day = String(d.getDate()).padStart(2, '0');
    return y + '-' + m + '-' + day;
  })(now);


   var lastShown = storage.get(lastShownKey);
   var lastShown = storage.get(lastShownKey);
   var todayCount = parseInt(storage.get(countKey + todayStr) || '0', 10);
   var todayCount = parseInt(storage.get(countKey + todayStr) || '0', 10);
  if (lastShown === todayStr && todayCount >= CONFIG.dailyLimit) return;


   // Bereits heute genug gezeigt?
   function markShown(){
  if (lastShown === todayStr && todayCount >= CONFIG.dailyLimit) {
    storage.set(lastShownKey, todayStr);
     return;
     storage.set(countKey + todayStr, String((todayCount || 0) + 1));
   }
   }


  // Modal-HTML erstellen
   function buildModal(bodyHtml, titleText){
   function buildModal() {
     var $overlay = $('<div>', { class: 'mw-site-announcement-overlay', tabindex: '-1', 'aria-hidden': 'true' });
     var $overlay = $('<div>', {
     var $dialog = $('<div>', { class: 'mw-site-announcement-modal', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'mw-site-announcement-title' });
      class: 'mw-site-announcement-overlay',
      tabindex: '-1',
      'aria-hidden': 'true'
    });
 
     var $dialog = $('<div>', {
      class: 'mw-site-announcement-modal',
      role: 'dialog',
      'aria-modal': 'true',
      'aria-labelledby': 'mw-site-announcement-title'
    });
 
     var $header = $('<div>', { class: 'mw-site-announcement-header' })
     var $header = $('<div>', { class: 'mw-site-announcement-header' })
       .append($('<h2>', { id: 'mw-site-announcement-title', text: CONFIG.title }));
       .append($('<h2>', { id: 'mw-site-announcement-title', text: titleText || CONFIG.title }));
 
     var $close = $('<button>', { type:'button', class:'mw-site-announcement-close', 'aria-label':'Schließen' }).text('×');
     var $close = $('<button>', {
     var $content = $('<div>', { class:'mw-site-announcement-content' }).html(bodyHtml);
      type: 'button',
     var $footer = $('<div>', { class:'mw-site-announcement-footer' });
      class: 'mw-site-announcement-close',
     var $ok = $('<button>', { type:'button', class:'mw-ui-button mw-ui-progressive' }).text('OK');
      'aria-label': 'Schließen'
    }).text('×');
 
     var $content = $('<div>', { class: 'mw-site-announcement-content' }).html(CONFIG.html);
 
     var $footer = $('<div>', { class: 'mw-site-announcement-footer' });
     var $ok = $('<button>', {
      type: 'button',
      class: 'mw-ui-button mw-ui-progressive'
    }).text('OK');


     $header.append($close);
     $header.append($close);
     $footer.append($ok);
     $footer.append($ok);
     $dialog.append($header, $content, $footer);
     $dialog.append($header, $content, $footer);
     return { $overlay: $overlay, $dialog: $dialog, $ok: $ok, $close: $close };
     return { $overlay, $dialog, $close, $ok, $content };
   }
   }


   function markShown() {
   function openModal(bodyHtml, titleText, countAsShownOnOk = true, countAsShownOnClose = CONFIG.closeBehavesAsShown){
    storage.set(lastShownKey, todayStr);
     var parts = buildModal(bodyHtml, titleText);
    storage.set(countKey + todayStr, String((todayCount || 0) + 1));
     var $overlay = parts.$overlay, $dialog = parts.$dialog, $close = parts.$close, $ok = parts.$ok;
  }
 
  function openModal() {
     var parts = buildModal();
     var $overlay = parts.$overlay, $dialog = parts.$dialog, $ok = parts.$ok, $close = parts.$close;


     $('body').append($overlay, $dialog);
     $('body').append($overlay, $dialog);


    // Fokus steuern
     var previouslyFocused = document.activeElement;
     var previouslyFocused = document.activeElement;
     setTimeout(function () {
     setTimeout(function(){ $dialog.attr('tabindex','-1').focus(); }, 0);
      $dialog.attr('tabindex', '-1').focus();
    }, 0);


     function close() {
     function close(count){
       if (CONFIG.closeBehavesAsShown) {
       if (count) markShown();
        markShown();
       $overlay.remove(); $dialog.remove();
      }
       $overlay.remove();
      $dialog.remove();
       if (previouslyFocused && previouslyFocused.focus) previouslyFocused.focus();
       if (previouslyFocused && previouslyFocused.focus) previouslyFocused.focus();
       $(document).off('keydown.mwSiteAnnouncement');
       $(document).off('keydown.mwSiteAnnouncement');
     }
     }


    // Events
     $ok.on('click', function(){ close(countAsShownOnOk); });
     $ok.on('click', function () {
     $close.on('click', function(){ close(countAsShownOnClose); });
      markShown();
     if (CONFIG.clickBackdropToClose) $overlay.on('click', function(){ close(countAsShownOnClose); });
      close();
    });
     $close.on('click', close);
 
     if (CONFIG.clickBackdropToClose) {
      $overlay.on('click', close);
    }


     if (CONFIG.escToClose) {
     if (CONFIG.escToClose) {
       $(document).on('keydown.mwSiteAnnouncement', function (e) {
       $(document).on('keydown.mwSiteAnnouncement', function (e) {
         if (e.key === 'Escape') {
         if (e.key === 'Escape') { e.preventDefault(); close(countAsShownOnClose); }
          e.preventDefault();
          close();
        }
       });
       });
     }
     }
    return { $dialog, $overlay };
  }
  function openImagePreview(){
    var imgHtml =
      '<figure class="mw-site-announcement-figure">' +
        '<img src="'+ CONFIG.imageSrc +'" alt="'+ (CONFIG.imageAlt || '') +'" class="mw-site-announcement-img" loading="lazy">' +
        '<figcaption><a href="'+ CONFIG.imageSrc +'" target="_blank" rel="noopener">Bild in voller Größe öffnen</a></figcaption>' +
      '</figure>';
    // Bildvorschau zählt nicht als "gesehen", damit man das Hauptmodal noch mit OK bestätigen kann:
    openModal(imgHtml, 'Mehr Infos', /*countAsShownOnOk*/ false, /*countAsShownOnClose*/ false);
   }
   }


  // Beim DOM-Ready öffnen
   $(function () {
   $(function () {
     openModal();
     // Hauptmodal öffnen
    var main = openModal(CONFIG.html, CONFIG.title, /*countOnOk*/ true, /*countOnClose*/ CONFIG.closeBehavesAsShown);
 
    // „Mehr Infos“-Button (Bildvorschau) verdrahten
    main.$dialog.on('click', '#mw-site-announcement-more', function(){
      openImagePreview();
    });
   });
   });


})(jQuery, mediaWiki);
})(jQuery, mediaWiki);