MediaWiki:Common.js: Unterschied zwischen den Versionen
Aus Hist. Verein Herne / Wanne-Eickel
Inhalt gelöscht Inhalt hinzugefügt
Die Seite wurde neu angelegt: „→Das folgende JavaScript wird für alle Benutzer geladen.: /* ============================================================ * Vorlage:Hauptseite/Neue Artikel * Lädt die letzten 20 neu erstellten Seiten via MediaWiki-API * und rendert pro Seite eine Kachel mit Bild, Titel, Auszug. * * Voraussetzung: Erweiterungen PageImages und TextExtracts * sind aktiviert (im Wiki bereits der Fall). * * Bitte als Ergänzung in MediaWiki:Common.js einfügen.…“ |
KKeine Bearbeitungszusammenfassung |
||
| (4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
/* Das folgende JavaScript wird für alle Benutzer geladen. */ |
|||
/* ============================================================ |
/* ============================================================ |
||
* Vorlage:Hauptseite/Neue Artikel |
* Vorlage:Hauptseite/Neue Artikel |
||
* Version 2 (Lookup per pageid statt per Titel — robust gegen |
|||
* zwischenzeitliche Umbenennungen / move-Operationen) |
|||
* Lädt die letzten 20 neu erstellten Seiten via MediaWiki-API |
* Lädt die letzten 20 neu erstellten Seiten via MediaWiki-API |
||
* und rendert pro Seite eine Kachel mit Bild, Titel, Auszug. |
* und rendert pro Seite eine Kachel mit Bild, Titel, Auszug. |
||
| Zeile 37: | Zeile 38: | ||
} |
} |
||
function renderGrid( container, ordered, pageMap ) { |
function renderGrid( container, ordered, pageMap, platzhalter ) { |
||
container.innerHTML = ''; |
container.innerHTML = ''; |
||
ordered.forEach( function ( item ) { |
ordered.forEach( function ( item ) { |
||
// Lookup per pageid — robust gegen Umbenennungen. |
|||
var p = pageMap[ item.pageid ]; |
|||
if ( !p || p.missing !== undefined ) { return; } |
if ( !p || p.missing !== undefined ) { return; } |
||
// Aktuellen Titel aus der Antwort verwenden, nicht den |
|||
var url = mw.util.getUrl( item.title ); |
|||
// (eventuell veralteten) Titel aus recentchanges. |
|||
var titel = p.title; |
|||
var url = mw.util.getUrl( titel ); |
|||
var card = document.createElement( 'div' ); |
var card = document.createElement( 'div' ); |
||
| Zeile 52: | Zeile 57: | ||
imgWrap.className = 'hv-neue-artikel-bild'; |
imgWrap.className = 'hv-neue-artikel-bild'; |
||
imgWrap.href = url; |
imgWrap.href = url; |
||
imgWrap.title = |
imgWrap.title = titel; |
||
var img = document.createElement( 'img' ); |
|||
img.loading = 'lazy'; |
|||
if ( p.thumbnail && p.thumbnail.source ) { |
if ( p.thumbnail && p.thumbnail.source ) { |
||
var img = document.createElement( 'img' ); |
|||
img.src = p.thumbnail.source; |
img.src = p.thumbnail.source; |
||
img.alt = |
img.alt = titel; |
||
imgWrap.appendChild( img ); |
|||
} else if ( platzhalter ) { |
|||
// Kein Page-Image vorhanden -> Platzhalter |
|||
img.src = platzhalter; |
|||
img.alt = titel; |
|||
imgWrap.classList.add( 'hv-neue-artikel-bild-platzhalter' ); |
|||
imgWrap.appendChild( img ); |
imgWrap.appendChild( img ); |
||
} else { |
} else { |
||
| Zeile 64: | Zeile 75: | ||
card.appendChild( imgWrap ); |
card.appendChild( imgWrap ); |
||
// Titel |
// Titel-Element |
||
var |
var titelDiv = document.createElement( 'div' ); |
||
titelDiv.className = 'hv-neue-artikel-titel'; |
|||
var a = document.createElement( 'a' ); |
var a = document.createElement( 'a' ); |
||
a.href = url; |
a.href = url; |
||
a.textContent = |
a.textContent = titel; |
||
titelDiv.appendChild( a ); |
|||
card.appendChild( |
card.appendChild( titelDiv ); |
||
// Auszug |
// Auszug |
||
| Zeile 100: | Zeile 111: | ||
var limit = parseInt( container.getAttribute( 'data-hv-limit' ) || '20', 10 ); |
var limit = parseInt( container.getAttribute( 'data-hv-limit' ) || '20', 10 ); |
||
var ns = parseInt( container.getAttribute( 'data-hv-namespace' ) || '0', 10 ); |
var ns = parseInt( container.getAttribute( 'data-hv-namespace' ) || '0', 10 ); |
||
// Platzhalterbild für Artikel ohne eigenes Vorschaubild. |
|||
// - Nicht gesetzt (Standard): Datei:Nochkeinbild.png |
|||
// - Leerer String: kein Platzhalter (Streifenmuster). |
|||
// - URL oder Pfad (enthält "/" oder beginnt mit "http"): |
|||
// wird direkt als Bild-URL verwendet. |
|||
// - Sonst: als Wiki-Dateiname interpretiert und per |
|||
// Special:FilePath aufgelöst. |
|||
var platzhalterAttr = container.getAttribute( 'data-hv-platzhalter' ); |
|||
if ( platzhalterAttr === null ) { platzhalterAttr = 'Nochkeinbild.png'; } |
|||
if ( isNaN( limit ) || limit < 1 ) { limit = 20; } |
if ( isNaN( limit ) || limit < 1 ) { limit = 20; } |
||
if ( limit > 50 ) { limit = 50; } |
if ( limit > 50 ) { limit = 50; } |
||
| Zeile 105: | Zeile 125: | ||
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).done( function () { |
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).done( function () { |
||
var api = new mw.Api(); |
var api = new mw.Api(); |
||
var platzhalterUrl = null; |
|||
if ( platzhalterAttr === '' ) { |
|||
platzhalterUrl = null; |
|||
} else if ( /^https?:\/\//.test( platzhalterAttr ) || platzhalterAttr.charAt( 0 ) === '/' ) { |
|||
platzhalterUrl = platzhalterAttr; |
|||
} else { |
|||
platzhalterUrl = mw.util.getUrl( |
|||
'Special:FilePath/' + platzhalterAttr, { width: 320 } |
|||
); |
|||
} |
|||
// 1) Liste neu erstellter Seiten holen |
// 1) Liste neu erstellter Seiten holen — mit ids, damit wir |
||
// im zweiten Schritt per pageid statt per Titel suchen |
|||
// können. Robust gegen Umbenennungen (move-Operationen), |
|||
// bei denen recentchanges weiterhin den alten Titel zeigt. |
|||
api.get( { |
api.get( { |
||
action: 'query', |
action: 'query', |
||
| Zeile 113: | Zeile 146: | ||
rctype: 'new', |
rctype: 'new', |
||
rcshow: '!redirect', |
rcshow: '!redirect', |
||
rcprop: 'title|timestamp', |
rcprop: 'title|timestamp|ids', |
||
rclimit: limit, |
rclimit: limit, |
||
format: 'json', |
format: 'json', |
||
| Zeile 123: | Zeile 156: | ||
return; |
return; |
||
} |
} |
||
// Doppelte |
// Doppelte pageids filtern und nur Einträge mit gültiger |
||
// pageid übernehmen. |
|||
var seen = {}; |
var seen = {}; |
||
var ordered = []; |
var ordered = []; |
||
rc.forEach( function ( e ) { |
rc.forEach( function ( e ) { |
||
if ( !seen[ e. |
if ( !e.pageid || seen[ e.pageid ] ) { return; } |
||
seen[ e.pageid ] = true; |
|||
ordered.push( { pageid: e.pageid, timestamp: e.timestamp } ); |
|||
} |
|||
} ); |
} ); |
||
if ( !ordered.length ) { |
|||
renderEmpty( container, 'Keine neuen Artikel gefunden.' ); |
|||
return; |
|||
} |
|||
// 2) Bilder + Auszüge holen |
// 2) Bilder + Auszüge holen — per pageids (NICHT titles), |
||
// damit wir auch zwischenzeitlich umbenannte Artikel |
|||
// finden. POST aus iOS-Safari-Längengründen. |
|||
api.post( { |
|||
action: 'query', |
action: 'query', |
||
pageids: ordered.map( function ( o ) { return o.pageid; } ).join( '|' ), |
|||
prop: 'pageimages|extracts', |
prop: 'pageimages|extracts', |
||
pithumbsize: 320, |
pithumbsize: 320, |
||
| Zeile 149: | Zeile 188: | ||
var pages = ( data2 && data2.query && data2.query.pages ) || []; |
var pages = ( data2 && data2.query && data2.query.pages ) || []; |
||
var pageMap = {}; |
var pageMap = {}; |
||
pages.forEach( function ( p ) { |
pages.forEach( function ( p ) { |
||
if ( p.pageid ) { pageMap[ p.pageid ] = p; } |
|||
} ); |
|||
renderGrid( container, ordered, pageMap, platzhalterUrl ); |
|||
} ).fail( function () { |
} ).fail( function () { |
||
renderEmpty( container, 'Fehler beim Laden der Seitendaten.' ); |
renderEmpty( container, 'Fehler beim Laden der Seitendaten.' ); |
||
| Zeile 176: | Zeile 217: | ||
} else { |
} else { |
||
document.addEventListener( 'DOMContentLoaded', function () { initAll(); } ); |
document.addEventListener( 'DOMContentLoaded', function () { initAll(); } ); |
||
} |
|||
}() ); |
|||
/* ============================================================ |
|||
* Vorlage:Zufallsbild – Bild-Karussell |
|||
* Version 3 (chunked imageinfo, POST, bessere Fehlermeldungen) |
|||
* Holt eine zufällige Stichprobe aus allen Wiki-Bildern via |
|||
* MediaWiki-API, filtert kleine/dekorative Dateien heraus und |
|||
* blendet die verbliebenen Bilder im 6-Sekunden-Takt durch. |
|||
* Beim Hover pausiert das Karussell. |
|||
* ============================================================ */ |
|||
( function () { |
|||
'use strict'; |
|||
// Dateinamen-Muster, die ausgeschlossen werden (Logos, Icons, Banner). |
|||
// Bewusst konservativ; Hauptfilter ist ohnehin die Bildgröße. |
|||
var AUSSCHLUSS_NAMEN = [ |
|||
/\blogo\b/i, |
|||
/\bicon\b/i, |
|||
/\bwappen\b/i, |
|||
/\bsymbol\b/i, |
|||
/_neu\.(png|svg|gif|jpe?g)$/i, // typische Portal-Icons |
|||
/werdejetzt/i, |
|||
/herne[\s_-]mit[\s_-]respekt/i |
|||
]; |
|||
function istAusgeschlossen( title ) { |
|||
var n = title.replace( /^Datei:|^File:|^Bild:/i, '' ); |
|||
for ( var i = 0; i < AUSSCHLUSS_NAMEN.length; i++ ) { |
|||
if ( AUSSCHLUSS_NAMEN[ i ].test( n ) ) { return true; } |
|||
} |
|||
return false; |
|||
} |
|||
function shuffle( arr ) { |
|||
for ( var i = arr.length - 1; i > 0; i-- ) { |
|||
var j = Math.floor( Math.random() * ( i + 1 ) ); |
|||
var tmp = arr[ i ]; arr[ i ] = arr[ j ]; arr[ j ] = tmp; |
|||
} |
|||
return arr; |
|||
} |
|||
function buildSlide( p, container ) { |
|||
var info = p.imageinfo[ 0 ]; |
|||
var a = document.createElement( 'a' ); |
|||
a.className = 'hv-karussell-bild'; |
|||
a.href = mw.util.getUrl( p.title ); |
|||
a.title = p.title.replace( /^Datei:/, '' ); |
|||
var img = document.createElement( 'img' ); |
|||
img.src = info.thumburl || info.url; |
|||
img.alt = ''; |
|||
img.loading = 'lazy'; |
|||
a.appendChild( img ); |
|||
container.appendChild( a ); |
|||
return a; |
|||
} |
|||
function startRotation( container, slides, intervall ) { |
|||
if ( slides.length < 2 ) { return; } |
|||
slides[ 0 ].classList.add( 'hv-aktiv' ); |
|||
var aktiv = 0; |
|||
var timer = null; |
|||
function naechste() { |
|||
slides[ aktiv ].classList.remove( 'hv-aktiv' ); |
|||
aktiv = ( aktiv + 1 ) % slides.length; |
|||
slides[ aktiv ].classList.add( 'hv-aktiv' ); |
|||
} |
|||
function start() { if ( !timer ) { timer = setInterval( naechste, intervall ); } } |
|||
function stop() { if ( timer ) { clearInterval( timer ); timer = null; } } |
|||
container.addEventListener( 'mouseenter', stop ); |
|||
container.addEventListener( 'mouseleave', start ); |
|||
// Bei Tab-Wechsel pausieren, um keine Zyklen im Hintergrund zu drehen |
|||
document.addEventListener( 'visibilitychange', function () { |
|||
if ( document.hidden ) { stop(); } else { start(); } |
|||
} ); |
|||
start(); |
|||
} |
|||
function ladeKarussell( container ) { |
|||
var pool = parseInt( container.getAttribute( 'data-hv-pool' ) || '60', 10 ); |
|||
var anzahl = parseInt( container.getAttribute( 'data-hv-anzahl' ) || '10', 10 ); |
|||
var minB = parseInt( container.getAttribute( 'data-hv-min-breite' ) || '500', 10 ); |
|||
var minH = parseInt( container.getAttribute( 'data-hv-min-hoehe' ) || '400', 10 ); |
|||
var intervall = parseInt( container.getAttribute( 'data-hv-intervall' ) || '6000', 10 ); |
|||
var thumbBreite = parseInt( container.getAttribute( 'data-hv-thumb-breite' ) || '600', 10 ); |
|||
if ( pool < 10 ) { pool = 10; } if ( pool > 200 ) { pool = 200; } |
|||
if ( anzahl < 1 ) { anzahl = 1; } if ( anzahl > 30 ) { anzahl = 30; } |
|||
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).done( function () { |
|||
var api = new mw.Api(); |
|||
// 1) Pool zufälliger Datei-Titel holen. |
|||
// list=random gibt max. 20 Einträge pro Aufruf zurück. Wir |
|||
// sammeln in mehreren Aufrufen, falls pool > 20. |
|||
var titel = []; |
|||
function holePool( restzahl ) { |
|||
if ( restzahl <= 0 ) { return $.Deferred().resolve().promise(); } |
|||
var step = Math.min( restzahl, 20 ); |
|||
return api.get( { |
|||
action: 'query', |
|||
list: 'random', |
|||
rnnamespace: 6, // Datei-Namensraum |
|||
rnlimit: step, |
|||
format: 'json', |
|||
formatversion: 2 |
|||
} ).then( function ( d ) { |
|||
var rnd = ( d && d.query && d.query.random ) || []; |
|||
rnd.forEach( function ( e ) { titel.push( e.title ); } ); |
|||
return holePool( restzahl - step ); |
|||
} ); |
|||
} |
|||
holePool( pool ).done( function () { |
|||
if ( !titel.length ) { |
|||
container.innerHTML = '<div class="hv-karussell-leer"><em>Keine Bilder gefunden.</em></div>'; |
|||
return; |
|||
} |
|||
// Doppelte und ausgeschlossene direkt aussortieren |
|||
var seen = {}, kandidaten = []; |
|||
titel.forEach( function ( t ) { |
|||
if ( seen[ t ] ) { return; } |
|||
seen[ t ] = true; |
|||
if ( istAusgeschlossen( t ) ) { return; } |
|||
kandidaten.push( t ); |
|||
} ); |
|||
if ( !kandidaten.length ) { |
|||
container.innerHTML = '<div class="hv-karussell-leer"><em>Keine geeigneten Bilder gefunden.</em></div>'; |
|||
return; |
|||
} |
|||
// 2) Bildinfos in kleinen Häppchen holen. |
|||
// Statt einer einzigen großen Anfrage zerlegen wir die |
|||
// Titelliste in Chunks von 25 — so kann weder die URL |
|||
// noch der Body zu groß werden, und im Fehlerfall sehen |
|||
// wir genau, welcher Chunk gescheitert ist. |
|||
holeBildInfosInChunks( api, kandidaten, thumbBreite, 25 ) |
|||
.done( function ( allePages ) { |
|||
var valide = []; |
|||
allePages.forEach( function ( p ) { |
|||
if ( !p.imageinfo || !p.imageinfo[ 0 ] ) { return; } |
|||
var i = p.imageinfo[ 0 ]; |
|||
if ( !i.mime || i.mime.indexOf( 'image/' ) !== 0 ) { return; } |
|||
if ( i.mediatype && i.mediatype !== 'BITMAP' ) { return; } // SVG raus |
|||
if ( !i.width || !i.height ) { return; } |
|||
if ( i.width < minB || i.height < minH ) { return; } |
|||
valide.push( { title: p.title, imageinfo: p.imageinfo } ); |
|||
} ); |
|||
if ( !valide.length ) { |
|||
container.innerHTML = '<div class="hv-karussell-leer"><em>Keine geeigneten Bilder gefunden.</em></div>'; |
|||
return; |
|||
} |
|||
// Mischen und auf gewünschte Anzahl kürzen |
|||
valide = shuffle( valide ).slice( 0, anzahl ); |
|||
container.innerHTML = ''; |
|||
container.classList.add( 'hv-karussell-aktiv' ); |
|||
var slides = valide.map( function ( v ) { return buildSlide( v, container ); } ); |
|||
slides[ 0 ].classList.add( 'hv-aktiv' ); |
|||
startRotation( container, slides, intervall ); |
|||
} ) |
|||
.fail( function ( code, info ) { |
|||
var msg = 'Fehler beim Laden der Bilddaten'; |
|||
if ( code ) { msg += ' (' + code + ')'; } |
|||
if ( info && info.error && info.error.info ) { |
|||
msg += ': ' + info.error.info; |
|||
} |
|||
container.innerHTML = '<div class="hv-karussell-leer"><em>' + |
|||
msg.replace( /[<>&]/g, function ( c ) { return { '<': '<', '>': '>', '&': '&' }[ c ]; } ) + |
|||
'</em></div>'; |
|||
} ); |
|||
} ).fail( function ( code ) { |
|||
var msg = 'Fehler beim Abruf der API'; |
|||
if ( code ) { msg += ' (' + code + ')'; } |
|||
container.innerHTML = '<div class="hv-karussell-leer"><em>' + msg + '</em></div>'; |
|||
} ); |
|||
} ); |
|||
} |
|||
// Bildinfos in Häppchen holen, Ergebnisse zusammenfassen. |
|||
function holeBildInfosInChunks( api, titel, thumbBreite, chunkSize ) { |
|||
var alle = []; |
|||
function holeChunk( start ) { |
|||
var chunk = titel.slice( start, start + chunkSize ); |
|||
if ( !chunk.length ) { |
|||
return $.Deferred().resolve( alle ).promise(); |
|||
} |
|||
return api.post( { |
|||
action: 'query', |
|||
titles: chunk.join( '|' ), |
|||
prop: 'imageinfo', |
|||
iiprop: 'size|url|mime|mediatype', |
|||
iiurlwidth: thumbBreite, |
|||
format: 'json', |
|||
formatversion: 2 |
|||
} ).then( function ( d ) { |
|||
var pages = ( d && d.query && d.query.pages ) || []; |
|||
pages.forEach( function ( p ) { alle.push( p ); } ); |
|||
return holeChunk( start + chunkSize ); |
|||
} ); |
|||
} |
|||
return holeChunk( 0 ); |
|||
} |
|||
function initKarussells( $content ) { |
|||
var root = ( $content && $content[ 0 ] ) || document; |
|||
var nodes = root.querySelectorAll( '.hv-bild-karussell' ); |
|||
Array.prototype.forEach.call( nodes, function ( node ) { |
|||
if ( node.getAttribute( 'data-hv-loaded' ) === '1' ) { return; } |
|||
node.setAttribute( 'data-hv-loaded', '1' ); |
|||
ladeKarussell( node ); |
|||
} ); |
|||
} |
|||
if ( window.mw && mw.hook ) { |
|||
mw.hook( 'wikipage.content' ).add( initKarussells ); |
|||
} else if ( document.readyState !== 'loading' ) { |
|||
initKarussells(); |
|||
} else { |
|||
document.addEventListener( 'DOMContentLoaded', function () { initKarussells(); } ); |
|||
} |
} |
||
Aktuelle Version vom 12. Mai 2026, 19:05 Uhr
/* ============================================================
* Vorlage:Hauptseite/Neue Artikel
* Version 2 (Lookup per pageid statt per Titel — robust gegen
* zwischenzeitliche Umbenennungen / move-Operationen)
* Lädt die letzten 20 neu erstellten Seiten via MediaWiki-API
* und rendert pro Seite eine Kachel mit Bild, Titel, Auszug.
*
* Voraussetzung: Erweiterungen PageImages und TextExtracts
* sind aktiviert (im Wiki bereits der Fall).
*
* Bitte als Ergänzung in MediaWiki:Common.js einfügen.
* ============================================================ */
( function () {
'use strict';
function escapeHtml( s ) {
return String( s )
.replace( /&/g, '&' )
.replace( /</g, '<' )
.replace( />/g, '>' )
.replace( /"/g, '"' )
.replace( /'/g, ''' );
}
function formatDate( ts ) {
if ( !ts ) { return ''; }
var d = new Date( ts );
if ( isNaN( d.getTime() ) ) { return ''; }
var monate = [ 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember' ];
return d.getDate() + '. ' + monate[ d.getMonth() ] + ' ' + d.getFullYear();
}
function renderEmpty( container, text ) {
container.innerHTML = '<div class="hv-neue-artikel-leer"><em>' +
escapeHtml( text ) + '</em></div>';
}
function renderGrid( container, ordered, pageMap, platzhalter ) {
container.innerHTML = '';
ordered.forEach( function ( item ) {
// Lookup per pageid — robust gegen Umbenennungen.
var p = pageMap[ item.pageid ];
if ( !p || p.missing !== undefined ) { return; }
// Aktuellen Titel aus der Antwort verwenden, nicht den
// (eventuell veralteten) Titel aus recentchanges.
var titel = p.title;
var url = mw.util.getUrl( titel );
var card = document.createElement( 'div' );
card.className = 'hv-neue-artikel-kachel';
// Bild (mit Link auf die Seite)
var imgWrap = document.createElement( 'a' );
imgWrap.className = 'hv-neue-artikel-bild';
imgWrap.href = url;
imgWrap.title = titel;
var img = document.createElement( 'img' );
img.loading = 'lazy';
if ( p.thumbnail && p.thumbnail.source ) {
img.src = p.thumbnail.source;
img.alt = titel;
imgWrap.appendChild( img );
} else if ( platzhalter ) {
// Kein Page-Image vorhanden -> Platzhalter
img.src = platzhalter;
img.alt = titel;
imgWrap.classList.add( 'hv-neue-artikel-bild-platzhalter' );
imgWrap.appendChild( img );
} else {
imgWrap.classList.add( 'hv-neue-artikel-bild-leer' );
}
card.appendChild( imgWrap );
// Titel-Element
var titelDiv = document.createElement( 'div' );
titelDiv.className = 'hv-neue-artikel-titel';
var a = document.createElement( 'a' );
a.href = url;
a.textContent = titel;
titelDiv.appendChild( a );
card.appendChild( titelDiv );
// Auszug
if ( p.extract ) {
var ext = document.createElement( 'div' );
ext.className = 'hv-neue-artikel-auszug';
ext.textContent = p.extract;
card.appendChild( ext );
}
// Datum
if ( item.timestamp ) {
var dt = document.createElement( 'div' );
dt.className = 'hv-neue-artikel-datum';
dt.textContent = formatDate( item.timestamp );
card.appendChild( dt );
}
container.appendChild( card );
} );
if ( !container.children.length ) {
renderEmpty( container, 'Keine neuen Artikel gefunden.' );
}
}
function ladeNeueArtikel( container ) {
var limit = parseInt( container.getAttribute( 'data-hv-limit' ) || '20', 10 );
var ns = parseInt( container.getAttribute( 'data-hv-namespace' ) || '0', 10 );
// Platzhalterbild für Artikel ohne eigenes Vorschaubild.
// - Nicht gesetzt (Standard): Datei:Nochkeinbild.png
// - Leerer String: kein Platzhalter (Streifenmuster).
// - URL oder Pfad (enthält "/" oder beginnt mit "http"):
// wird direkt als Bild-URL verwendet.
// - Sonst: als Wiki-Dateiname interpretiert und per
// Special:FilePath aufgelöst.
var platzhalterAttr = container.getAttribute( 'data-hv-platzhalter' );
if ( platzhalterAttr === null ) { platzhalterAttr = 'Nochkeinbild.png'; }
if ( isNaN( limit ) || limit < 1 ) { limit = 20; }
if ( limit > 50 ) { limit = 50; }
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).done( function () {
var api = new mw.Api();
var platzhalterUrl = null;
if ( platzhalterAttr === '' ) {
platzhalterUrl = null;
} else if ( /^https?:\/\//.test( platzhalterAttr ) || platzhalterAttr.charAt( 0 ) === '/' ) {
platzhalterUrl = platzhalterAttr;
} else {
platzhalterUrl = mw.util.getUrl(
'Special:FilePath/' + platzhalterAttr, { width: 320 }
);
}
// 1) Liste neu erstellter Seiten holen — mit ids, damit wir
// im zweiten Schritt per pageid statt per Titel suchen
// können. Robust gegen Umbenennungen (move-Operationen),
// bei denen recentchanges weiterhin den alten Titel zeigt.
api.get( {
action: 'query',
list: 'recentchanges',
rcnamespace: ns,
rctype: 'new',
rcshow: '!redirect',
rcprop: 'title|timestamp|ids',
rclimit: limit,
format: 'json',
formatversion: 2
} ).done( function ( data ) {
var rc = ( data && data.query && data.query.recentchanges ) || [];
if ( !rc.length ) {
renderEmpty( container, 'Keine neuen Artikel gefunden.' );
return;
}
// Doppelte pageids filtern und nur Einträge mit gültiger
// pageid übernehmen.
var seen = {};
var ordered = [];
rc.forEach( function ( e ) {
if ( !e.pageid || seen[ e.pageid ] ) { return; }
seen[ e.pageid ] = true;
ordered.push( { pageid: e.pageid, timestamp: e.timestamp } );
} );
if ( !ordered.length ) {
renderEmpty( container, 'Keine neuen Artikel gefunden.' );
return;
}
// 2) Bilder + Auszüge holen — per pageids (NICHT titles),
// damit wir auch zwischenzeitlich umbenannte Artikel
// finden. POST aus iOS-Safari-Längengründen.
api.post( {
action: 'query',
pageids: ordered.map( function ( o ) { return o.pageid; } ).join( '|' ),
prop: 'pageimages|extracts',
pithumbsize: 320,
pilimit: 'max',
exintro: 1,
explaintext: 1,
exchars: 200,
exlimit: 'max',
format: 'json',
formatversion: 2
} ).done( function ( data2 ) {
var pages = ( data2 && data2.query && data2.query.pages ) || [];
var pageMap = {};
pages.forEach( function ( p ) {
if ( p.pageid ) { pageMap[ p.pageid ] = p; }
} );
renderGrid( container, ordered, pageMap, platzhalterUrl );
} ).fail( function () {
renderEmpty( container, 'Fehler beim Laden der Seitendaten.' );
} );
} ).fail( function () {
renderEmpty( container, 'Fehler beim Abruf der API.' );
} );
} );
}
function initAll( $content ) {
var root = ( $content && $content[ 0 ] ) || document;
var nodes = root.querySelectorAll( '.hv-neue-artikel-grid' );
Array.prototype.forEach.call( nodes, function ( node ) {
if ( node.getAttribute( 'data-hv-loaded' ) === '1' ) { return; }
node.setAttribute( 'data-hv-loaded', '1' );
ladeNeueArtikel( node );
} );
}
if ( window.mw && mw.hook ) {
mw.hook( 'wikipage.content' ).add( initAll );
} else if ( document.readyState !== 'loading' ) {
initAll();
} else {
document.addEventListener( 'DOMContentLoaded', function () { initAll(); } );
}
}() );
/* ============================================================
* Vorlage:Zufallsbild – Bild-Karussell
* Version 3 (chunked imageinfo, POST, bessere Fehlermeldungen)
* Holt eine zufällige Stichprobe aus allen Wiki-Bildern via
* MediaWiki-API, filtert kleine/dekorative Dateien heraus und
* blendet die verbliebenen Bilder im 6-Sekunden-Takt durch.
* Beim Hover pausiert das Karussell.
* ============================================================ */
( function () {
'use strict';
// Dateinamen-Muster, die ausgeschlossen werden (Logos, Icons, Banner).
// Bewusst konservativ; Hauptfilter ist ohnehin die Bildgröße.
var AUSSCHLUSS_NAMEN = [
/\blogo\b/i,
/\bicon\b/i,
/\bwappen\b/i,
/\bsymbol\b/i,
/_neu\.(png|svg|gif|jpe?g)$/i, // typische Portal-Icons
/werdejetzt/i,
/herne[\s_-]mit[\s_-]respekt/i
];
function istAusgeschlossen( title ) {
var n = title.replace( /^Datei:|^File:|^Bild:/i, '' );
for ( var i = 0; i < AUSSCHLUSS_NAMEN.length; i++ ) {
if ( AUSSCHLUSS_NAMEN[ i ].test( n ) ) { return true; }
}
return false;
}
function shuffle( arr ) {
for ( var i = arr.length - 1; i > 0; i-- ) {
var j = Math.floor( Math.random() * ( i + 1 ) );
var tmp = arr[ i ]; arr[ i ] = arr[ j ]; arr[ j ] = tmp;
}
return arr;
}
function buildSlide( p, container ) {
var info = p.imageinfo[ 0 ];
var a = document.createElement( 'a' );
a.className = 'hv-karussell-bild';
a.href = mw.util.getUrl( p.title );
a.title = p.title.replace( /^Datei:/, '' );
var img = document.createElement( 'img' );
img.src = info.thumburl || info.url;
img.alt = '';
img.loading = 'lazy';
a.appendChild( img );
container.appendChild( a );
return a;
}
function startRotation( container, slides, intervall ) {
if ( slides.length < 2 ) { return; }
slides[ 0 ].classList.add( 'hv-aktiv' );
var aktiv = 0;
var timer = null;
function naechste() {
slides[ aktiv ].classList.remove( 'hv-aktiv' );
aktiv = ( aktiv + 1 ) % slides.length;
slides[ aktiv ].classList.add( 'hv-aktiv' );
}
function start() { if ( !timer ) { timer = setInterval( naechste, intervall ); } }
function stop() { if ( timer ) { clearInterval( timer ); timer = null; } }
container.addEventListener( 'mouseenter', stop );
container.addEventListener( 'mouseleave', start );
// Bei Tab-Wechsel pausieren, um keine Zyklen im Hintergrund zu drehen
document.addEventListener( 'visibilitychange', function () {
if ( document.hidden ) { stop(); } else { start(); }
} );
start();
}
function ladeKarussell( container ) {
var pool = parseInt( container.getAttribute( 'data-hv-pool' ) || '60', 10 );
var anzahl = parseInt( container.getAttribute( 'data-hv-anzahl' ) || '10', 10 );
var minB = parseInt( container.getAttribute( 'data-hv-min-breite' ) || '500', 10 );
var minH = parseInt( container.getAttribute( 'data-hv-min-hoehe' ) || '400', 10 );
var intervall = parseInt( container.getAttribute( 'data-hv-intervall' ) || '6000', 10 );
var thumbBreite = parseInt( container.getAttribute( 'data-hv-thumb-breite' ) || '600', 10 );
if ( pool < 10 ) { pool = 10; } if ( pool > 200 ) { pool = 200; }
if ( anzahl < 1 ) { anzahl = 1; } if ( anzahl > 30 ) { anzahl = 30; }
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).done( function () {
var api = new mw.Api();
// 1) Pool zufälliger Datei-Titel holen.
// list=random gibt max. 20 Einträge pro Aufruf zurück. Wir
// sammeln in mehreren Aufrufen, falls pool > 20.
var titel = [];
function holePool( restzahl ) {
if ( restzahl <= 0 ) { return $.Deferred().resolve().promise(); }
var step = Math.min( restzahl, 20 );
return api.get( {
action: 'query',
list: 'random',
rnnamespace: 6, // Datei-Namensraum
rnlimit: step,
format: 'json',
formatversion: 2
} ).then( function ( d ) {
var rnd = ( d && d.query && d.query.random ) || [];
rnd.forEach( function ( e ) { titel.push( e.title ); } );
return holePool( restzahl - step );
} );
}
holePool( pool ).done( function () {
if ( !titel.length ) {
container.innerHTML = '<div class="hv-karussell-leer"><em>Keine Bilder gefunden.</em></div>';
return;
}
// Doppelte und ausgeschlossene direkt aussortieren
var seen = {}, kandidaten = [];
titel.forEach( function ( t ) {
if ( seen[ t ] ) { return; }
seen[ t ] = true;
if ( istAusgeschlossen( t ) ) { return; }
kandidaten.push( t );
} );
if ( !kandidaten.length ) {
container.innerHTML = '<div class="hv-karussell-leer"><em>Keine geeigneten Bilder gefunden.</em></div>';
return;
}
// 2) Bildinfos in kleinen Häppchen holen.
// Statt einer einzigen großen Anfrage zerlegen wir die
// Titelliste in Chunks von 25 — so kann weder die URL
// noch der Body zu groß werden, und im Fehlerfall sehen
// wir genau, welcher Chunk gescheitert ist.
holeBildInfosInChunks( api, kandidaten, thumbBreite, 25 )
.done( function ( allePages ) {
var valide = [];
allePages.forEach( function ( p ) {
if ( !p.imageinfo || !p.imageinfo[ 0 ] ) { return; }
var i = p.imageinfo[ 0 ];
if ( !i.mime || i.mime.indexOf( 'image/' ) !== 0 ) { return; }
if ( i.mediatype && i.mediatype !== 'BITMAP' ) { return; } // SVG raus
if ( !i.width || !i.height ) { return; }
if ( i.width < minB || i.height < minH ) { return; }
valide.push( { title: p.title, imageinfo: p.imageinfo } );
} );
if ( !valide.length ) {
container.innerHTML = '<div class="hv-karussell-leer"><em>Keine geeigneten Bilder gefunden.</em></div>';
return;
}
// Mischen und auf gewünschte Anzahl kürzen
valide = shuffle( valide ).slice( 0, anzahl );
container.innerHTML = '';
container.classList.add( 'hv-karussell-aktiv' );
var slides = valide.map( function ( v ) { return buildSlide( v, container ); } );
slides[ 0 ].classList.add( 'hv-aktiv' );
startRotation( container, slides, intervall );
} )
.fail( function ( code, info ) {
var msg = 'Fehler beim Laden der Bilddaten';
if ( code ) { msg += ' (' + code + ')'; }
if ( info && info.error && info.error.info ) {
msg += ': ' + info.error.info;
}
container.innerHTML = '<div class="hv-karussell-leer"><em>' +
msg.replace( /[<>&]/g, function ( c ) { return { '<': '<', '>': '>', '&': '&' }[ c ]; } ) +
'</em></div>';
} );
} ).fail( function ( code ) {
var msg = 'Fehler beim Abruf der API';
if ( code ) { msg += ' (' + code + ')'; }
container.innerHTML = '<div class="hv-karussell-leer"><em>' + msg + '</em></div>';
} );
} );
}
// Bildinfos in Häppchen holen, Ergebnisse zusammenfassen.
function holeBildInfosInChunks( api, titel, thumbBreite, chunkSize ) {
var alle = [];
function holeChunk( start ) {
var chunk = titel.slice( start, start + chunkSize );
if ( !chunk.length ) {
return $.Deferred().resolve( alle ).promise();
}
return api.post( {
action: 'query',
titles: chunk.join( '|' ),
prop: 'imageinfo',
iiprop: 'size|url|mime|mediatype',
iiurlwidth: thumbBreite,
format: 'json',
formatversion: 2
} ).then( function ( d ) {
var pages = ( d && d.query && d.query.pages ) || [];
pages.forEach( function ( p ) { alle.push( p ); } );
return holeChunk( start + chunkSize );
} );
}
return holeChunk( 0 );
}
function initKarussells( $content ) {
var root = ( $content && $content[ 0 ] ) || document;
var nodes = root.querySelectorAll( '.hv-bild-karussell' );
Array.prototype.forEach.call( nodes, function ( node ) {
if ( node.getAttribute( 'data-hv-loaded' ) === '1' ) { return; }
node.setAttribute( 'data-hv-loaded', '1' );
ladeKarussell( node );
} );
}
if ( window.mw && mw.hook ) {
mw.hook( 'wikipage.content' ).add( initKarussells );
} else if ( document.readyState !== 'loading' ) {
initKarussells();
} else {
document.addEventListener( 'DOMContentLoaded', function () { initKarussells(); } );
}
}() );
