Menü aufrufen
Toggle preferences menu
Persönliches Menü aufrufen
Nicht angemeldet
Ihre IP-Adresse wird öffentlich sichtbar sein, wenn Sie Änderungen vornehmen.

MediaWiki:Common.js: Unterschied zwischen den Versionen

MediaWiki-Schnittstellenseite
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
 
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt)
Zeile 182: Zeile 182:
  * 3. Konfiguration unten (FEED_URL, MAX_AGE_HOURS, etc.) anpassen.
  * 3. Konfiguration unten (FEED_URL, MAX_AGE_HOURS, etc.) anpassen.
  */
  */
 
( function () {
( function () {
'use strict';
'use strict';
 
// ---- Konfiguration ----
// ---- Konfiguration ----
// Direkter Zugriff auf phpBB scheitert an CORS (andere Subdomain),
// Direkter Zugriff auf phpBB scheitert an CORS (andere Subdomain),
Zeile 198: Zeile 198:
var ROTATE_MS = 6000; // Anzeigedauer je Eintrag
var ROTATE_MS = 6000; // Anzeigedauer je Eintrag
var CACHE_KEY = 'forumNewsTickerCache_v1';
var CACHE_KEY = 'forumNewsTickerCache_v1';
 
function getContainer() {
function getContainer() {
return document.getElementById( 'forum-news-ticker' );
return document.getElementById( 'forum-news-ticker' );
}
}
 
function loadFromCache() {
function loadFromCache() {
try {
try {
Zeile 219: Zeile 219:
}
}
}
}
 
function saveToCache( items ) {
function saveToCache( items ) {
try {
try {
Zeile 230: Zeile 230:
}
}
}
}
 
function fetchFeed() {
function fetchFeed() {
return fetch( FEED_URL )
return fetch( FEED_URL )
Zeile 241: Zeile 241:
.then( function ( xmlText ) {
.then( function ( xmlText ) {
var xml = new DOMParser().parseFromString( xmlText, 'application/xml' );
var xml = new DOMParser().parseFromString( xmlText, 'application/xml' );
 
if ( xml.querySelector( 'parsererror' ) ) {
if ( xml.querySelector( 'parsererror' ) ) {
throw new Error( 'Feed-XML konnte nicht geparst werden' );
throw new Error( 'Feed-XML konnte nicht geparst werden' );
}
}
 
var now = Date.now();
var now = Date.now();
var entries = Array.prototype.slice.call( xml.querySelectorAll( 'entry' ) );
var entries = Array.prototype.slice.call( xml.querySelectorAll( 'entry' ) );
 
var items = entries.map( function ( entry ) {
var items = entries.map( function ( entry ) {
var titleEl = entry.querySelector( 'title' );
var titleEl = entry.querySelector( 'title' );
Zeile 254: Zeile 254:
var updatedEl = entry.querySelector( 'updated' );
var updatedEl = entry.querySelector( 'updated' );
var categoryEl = entry.querySelector( 'category' );
var categoryEl = entry.querySelector( 'category' );
 
// Die Forum-ID steht nicht im Eintrags-Link (der zeigt
// Die Forum-ID steht nicht im Eintrags-Link (der zeigt
// direkt auf viewtopic.php?p=...), sondern im
// direkt auf viewtopic.php?p=...), sondern im
Zeile 266: Zeile 266:
}
}
}
}
 
return {
return {
title: titleEl ? titleEl.textContent : '',
title: titleEl ? titleEl.textContent : '',
Zeile 277: Zeile 277:
return false;
return false;
}
}
 
// News-Feed kann mehrere in der ACP ausgewaehlte Foren
// News-Feed kann mehrere in der ACP ausgewaehlte Foren
// mischen, deshalb hier gezielt auf das gewuenschte
// mischen, deshalb hier gezielt auf das gewuenschte
Zeile 283: Zeile 283:
return item.forumId === null || item.forumId === TARGET_FORUM_ID;
return item.forumId === null || item.forumId === TARGET_FORUM_ID;
} );
} );
 
// Vom Zielforum: erst nach Maximalalter filtern. Wenn das
// Vom Zielforum: erst nach Maximalalter filtern. Wenn das
// weniger als MIN_ITEMS ergibt (z.B. weil im Forum gerade
// weniger als MIN_ITEMS ergibt (z.B. weil im Forum gerade
Zeile 293: Zeile 293:
return ageHours <= MAX_AGE_HOURS;
return ageHours <= MAX_AGE_HOURS;
} );
} );
 
var selected = withinAge.length >= MIN_ITEMS ? withinAge : items.slice( 0, MIN_ITEMS );
var selected = withinAge.length >= MIN_ITEMS ? withinAge : items.slice( 0, MIN_ITEMS );
var finalItems = selected.slice( 0, MAX_ITEMS );
var finalItems = selected.slice( 0, MAX_ITEMS );
 
saveToCache( finalItems );
saveToCache( finalItems );
return finalItems;
return finalItems;
} );
} );
}
}
 
function renderTicker( container, items ) {
function renderTicker( container, items ) {
if ( !items || items.length === 0 ) {
if ( !items || items.length === 0 ) {
Zeile 309: Zeile 309:
container.style.display = '';
container.style.display = '';
container.innerHTML = '';
container.innerHTML = '';
 
var slides = items.map( function ( item, index ) {
var slides = items.map( function ( item, index ) {
var slide = document.createElement( 'div' );
var slide = document.createElement( 'div' );
slide.className = 'forum-news-ticker-slide';
slide.className = 'forum-news-ticker-slide' + ( index === 0 ? ' is-active' : '' );
slide.style.opacity = index === 0 ? '1' : '0';
 
slide.style.display = index === 0 ? 'block' : 'none';
var link = document.createElement( 'a' );
var link = document.createElement( 'a' );
link.href = item.link;
link.href = item.link;
Zeile 321: Zeile 319:
link.target = '_blank';
link.target = '_blank';
link.rel = 'noopener';
link.rel = 'noopener';
 
slide.appendChild( link );
slide.appendChild( link );
container.appendChild( slide );
container.appendChild( slide );
return slide;
return slide;
} );
} );
 
if ( slides.length <= 1 ) {
if ( slides.length <= 1 ) {
return;
return;
}
}
 
var current = 0;
var current = 0;
setInterval( function () {
setInterval( function () {
var next = ( current + 1 ) % slides.length;
var next = ( current + 1 ) % slides.length;
slides[ current ].style.display = 'none';
slides[ current ].classList.remove( 'is-active' );
slides[ next ].style.display = 'block';
slides[ next ].classList.add( 'is-active' );
current = next;
current = next;
}, ROTATE_MS );
}, ROTATE_MS );
}
}
 
function init() {
function init() {
var container = getContainer();
var container = getContainer();
Zeile 345: Zeile 343:
return;
return;
}
}
 
var cached = loadFromCache();
var cached = loadFromCache();
if ( cached ) {
if ( cached ) {
Zeile 351: Zeile 349:
return;
return;
}
}
 
fetchFeed()
fetchFeed()
.then( function ( items ) {
.then( function ( items ) {
Zeile 365: Zeile 363:
} );
} );
}
}
 
if ( document.readyState === 'loading' ) {
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', init );
document.addEventListener( 'DOMContentLoaded', init );

Aktuelle Version vom 29. August 2026, 15:32 Uhr

mw.hook('wikipage.content').add(function($content) {
    const headings = document.querySelectorAll('.citizen-section-heading');
    if (headings.length === 0) return;
    
    const sections = document.querySelectorAll('.citizen-section');
    sections.forEach(function(section, index) {
        if (index === 0) return; // Einleitungstext überspringen
        section.hidden = 'until-found';
    });
});

//Anker aufklappen und Spoiler aufklappen bei Suchen
$(document).ready(function() {
    setTimeout(function() {
        var hash = window.location.hash;
        var ref = document.referrer;
        var searchMatch = ref.match(/[?&]search=([^&]+)/);
        
        // Anker-Link: entsprechenden Abschnitt aufklappen
        if (hash) {
            var target = document.getElementById(hash.substring(1));
            if (target) {
                var h2 = target.closest('h2') ||
                         (target.tagName === 'H2' ? target : null);
                if (!h2) {
                    var section = target.closest('.citizen-section');
                    if (section) h2 = section.previousElementSibling;
                }
                if (h2 && h2.classList.contains('citizen-section-heading')) {
                    var indicator = h2.querySelector('.citizen-section-indicator');
                    if (indicator) indicator.click();
                    setTimeout(function() { target.scrollIntoView(); }, 100);
                }
            }
        }

        // Suche: ersten Abschnitt + verschachtelte Spoiler aufklappen
        if (!searchMatch) return;
        var term = decodeURIComponent(searchMatch[1]).toLowerCase();

        var sections = document.querySelectorAll('.citizen-section');
        for (var i = 0; i < sections.length; i++) {
            if (sections[i].textContent.toLowerCase().includes(term)) {
                var h2 = sections[i].previousElementSibling;
                if (h2 && h2.classList.contains('citizen-section-heading')) {
                    var indicator = h2.querySelector('.citizen-section-indicator');
                    if (indicator) indicator.click();
                }

                // Verschachtelte mw-collapsible aufklappen die den Begriff enthalten
                setTimeout(function(sec) {
                    var collapsibles = sec.querySelectorAll('.mw-collapsible.mw-collapsed');
                    collapsibles.forEach(function(col) {
                        if (col.textContent.toLowerCase().includes(term)) {
                            var toggle = col.querySelector('.mw-collapsible-toggle');
                            if (toggle) toggle.click();
                        }
                    });
                    h2.scrollIntoView();
                }, 200, sections[i]);

                break;
            }
        }
    }, 500);
});
//Hauptseite Grid Ausrichtung
$(document).ready(function() {
    setTimeout(function() {
        document.querySelectorAll('.wiki-overview-grid').forEach(function(grid) {
            var cols = grid.querySelectorAll('.wiki-overview-col');
            var firstHeaders = [];
            cols.forEach(function(col) {
                var h = col.querySelector('h4');
                if (h) firstHeaders.push(h);
            });
            
            // Natürliche Höhen vor minHeight messen
            var naturalHeights = firstHeaders.map(function(h) { return h.offsetHeight; });
            var maxH = Math.max.apply(null, naturalHeights);
            var minH = Math.min.apply(null, naturalHeights);
            
            firstHeaders.forEach(function(h) {
                h.style.minHeight = maxH + 'px';
                h.style.display = 'flex';
                h.style.alignItems = 'flex-end';
                h.style.justifyContent = 'center';
            });

            // Grid um den Leerraum nach oben verschieben
            if (maxH > minH) {
                grid.style.marginTop = '-' + (maxH - minH) + 'px';
            }
        });
    }, 300);
});

// Ausklappen des ersten Abschnitts bei fehlendem Einleitungstext
$(document).ready(function() {
    setTimeout(function() {
        var content = document.querySelector('.mw-parser-output');
        if (!content) return;

        var children = Array.from(content.children);

        // Prüfen ob die erste Section Text enthält (= Einleitungstext)
        var firstSection = children.find(function(el) {
            return el.classList.contains('citizen-section');
        });

        if (firstSection && firstSection.textContent.trim() !== '') return;

        // Erste H2 aufklappen
        var firstH2 = children.find(function(el) {
            return el.tagName === 'H2' && el.classList.contains('citizen-section-heading');
        });

        if (firstH2) {
            var indicator = firstH2.querySelector('.citizen-section-indicator');
            if (indicator) indicator.click();
        }
    }, 500);
});

// Social Icons in Sidebar ersetzen
$(function() {
    $('#n-sidebar-label-contactus').closest('ul').addClass('sgpggb-external-menu');

    var icons = {
        'n-Discord':    'fab fa-discord',
        'n-Facebook':   'fab fa-facebook-f',
        'n-Instagram':  'fab fa-instagram',
        'n-X':          'fab fa-x-twitter',
        'n-YouTube':    'fab fa-youtube',
        'n-Twitch':     'fab fa-twitch',
        'n-E-Mail':     'fas fa-envelope',
        'n-Support-us': 'fas fa-heart'
    };

    $.each(icons, function(id, cls) {
        $('#' + id + ' a').html('<i class="' + cls + '"></i>');
    });
});

var tagline = document.getElementById('footer-tagline');
if (tagline) {
    var original = tagline.innerHTML;
    var shown = localStorage.getItem('footer_gender_dismissed');

    if (!shown) {
        tagline.innerHTML = 'Aus Gründen der besseren Lesbarkeit wird auf diesem Wiki überwiegend das generische Maskulinum verwendet. Es sind stets alle Geschlechter gleichermaßen gemeint. <button id="footer-gender-dismiss" style="background:none; border:none; cursor:pointer; font-size:0.9em; opacity:0.6; padding:0 4px;" title="Verstanden">✕</button>';

        document.getElementById('footer-gender-dismiss').addEventListener('click', function() {
            tagline.innerHTML = original;
            localStorage.setItem('footer_gender_dismissed', '1');
        });
    }
}

// mw-anon Klasse für nicht angemeldete User setzen
if ( !mw.config.get('wgUserId') ) {
    document.documentElement.classList.add('mw-anon');
}


/**
 * Forum-News-Ticker
 * ------------------
 * Zeigt die neuesten Themen aus einem phpBB-Forum (via Atom-Feed) als
 * rotierendes Text-Karussell im Notice-Box-Stil an.
 *
 * VORAUSSETZUNG: In phpBB muss ACP -> Allgemein -> Feed-Einstellungen
 * aktiviert sein, und das Zielforum muss fuer Gaeste lesbar sein.
 *
 * EINBAU:
 * 1. Diesen Code an das Ende von MediaWiki:Common.js anhaengen.
 * 2. An der Stelle, wo die News-Box erscheinen soll, folgendes HTML
 *    einbauen (z.B. ueber eine Vorlage:NewsTicker):
 *
 *      <div id="forum-news-ticker" class="mw-message-box mw-message-box-notice"></div>
 *
 * 3. Konfiguration unten (FEED_URL, MAX_AGE_HOURS, etc.) anpassen.
 */

( function () {
	'use strict';

	// ---- Konfiguration ----
	// Direkter Zugriff auf phpBB scheitert an CORS (andere Subdomain),
	// deshalb ueber einen same-origin Proxy auf dem Wiki-Server selbst
	// (siehe news-proxy.php).
	var FEED_URL = '/wiki/news-proxy.php';
	var TARGET_FORUM_ID = '22';
	var MAX_AGE_HOURS = 168; // 7 Tage
	var MIN_ITEMS = 3; // falls weniger als 7 Tage alt: trotzdem so viele zeigen
	var MAX_ITEMS = 5; // wie viele Themen maximal im Karussell landen
	var CACHE_MINUTES = 10; // wie lange der Feed lokal zwischengespeichert wird
	var ROTATE_MS = 6000; // Anzeigedauer je Eintrag
	var CACHE_KEY = 'forumNewsTickerCache_v1';

	function getContainer() {
		return document.getElementById( 'forum-news-ticker' );
	}

	function loadFromCache() {
		try {
			var raw = localStorage.getItem( CACHE_KEY );
			if ( !raw ) {
				return null;
			}
			var parsed = JSON.parse( raw );
			var ageMinutes = ( Date.now() - parsed.fetchedAt ) / ( 1000 * 60 );
			if ( ageMinutes > CACHE_MINUTES ) {
				return null;
			}
			return parsed.items;
		} catch ( e ) {
			return null;
		}
	}

	function saveToCache( items ) {
		try {
			localStorage.setItem( CACHE_KEY, JSON.stringify( {
				fetchedAt: Date.now(),
				items: items
			} ) );
		} catch ( e ) {
			// localStorage evtl. voll oder deaktiviert - einfach ignorieren
		}
	}

	function fetchFeed() {
		return fetch( FEED_URL )
			.then( function ( response ) {
				if ( !response.ok ) {
					throw new Error( 'Feed nicht erreichbar: ' + response.status );
				}
				return response.text();
			} )
			.then( function ( xmlText ) {
				var xml = new DOMParser().parseFromString( xmlText, 'application/xml' );

				if ( xml.querySelector( 'parsererror' ) ) {
					throw new Error( 'Feed-XML konnte nicht geparst werden' );
				}

				var now = Date.now();
				var entries = Array.prototype.slice.call( xml.querySelectorAll( 'entry' ) );

				var items = entries.map( function ( entry ) {
					var titleEl = entry.querySelector( 'title' );
					var linkEl = entry.querySelector( 'link' );
					var updatedEl = entry.querySelector( 'updated' );
					var categoryEl = entry.querySelector( 'category' );

					// Die Forum-ID steht nicht im Eintrags-Link (der zeigt
					// direkt auf viewtopic.php?p=...), sondern im
					// scheme-Attribut der <category>.
					var forumId = null;
					if ( categoryEl && categoryEl.getAttribute( 'scheme' ) ) {
						try {
							forumId = new URL( categoryEl.getAttribute( 'scheme' ) ).searchParams.get( 'f' );
						} catch ( e ) {
							forumId = null;
						}
					}

					return {
						title: titleEl ? titleEl.textContent : '',
						link: linkEl ? linkEl.getAttribute( 'href' ) : '',
						updated: updatedEl ? new Date( updatedEl.textContent ) : null,
						forumId: forumId
					};
				} ).filter( function ( item ) {
					if ( !item.title || !item.link || !item.updated ) {
						return false;
					}

					// News-Feed kann mehrere in der ACP ausgewaehlte Foren
					// mischen, deshalb hier gezielt auf das gewuenschte
					// Forum filtern.
					return item.forumId === null || item.forumId === TARGET_FORUM_ID;
				} );

				// Vom Zielforum: erst nach Maximalalter filtern. Wenn das
				// weniger als MIN_ITEMS ergibt (z.B. weil im Forum gerade
				// wenig los ist), stattdessen die MIN_ITEMS neuesten
				// Themen nehmen, auch wenn sie aelter als MAX_AGE_HOURS
				// sind - eine leere/einsame Box wirkt sonst kaputt.
				var withinAge = items.filter( function ( item ) {
					var ageHours = ( now - item.updated.getTime() ) / ( 1000 * 60 * 60 );
					return ageHours <= MAX_AGE_HOURS;
				} );

				var selected = withinAge.length >= MIN_ITEMS ? withinAge : items.slice( 0, MIN_ITEMS );
				var finalItems = selected.slice( 0, MAX_ITEMS );

				saveToCache( finalItems );
				return finalItems;
			} );
	}

	function renderTicker( container, items ) {
		if ( !items || items.length === 0 ) {
			container.style.display = 'none';
			return;
		}
		container.style.display = '';
		container.innerHTML = '';

		var slides = items.map( function ( item, index ) {
			var slide = document.createElement( 'div' );
			slide.className = 'forum-news-ticker-slide' + ( index === 0 ? ' is-active' : '' );

			var link = document.createElement( 'a' );
			link.href = item.link;
			link.textContent = item.title;
			link.target = '_blank';
			link.rel = 'noopener';

			slide.appendChild( link );
			container.appendChild( slide );
			return slide;
		} );

		if ( slides.length <= 1 ) {
			return;
		}

		var current = 0;
		setInterval( function () {
			var next = ( current + 1 ) % slides.length;
			slides[ current ].classList.remove( 'is-active' );
			slides[ next ].classList.add( 'is-active' );
			current = next;
		}, ROTATE_MS );
	}

	function init() {
		var container = getContainer();
		if ( !container ) {
			return;
		}

		var cached = loadFromCache();
		if ( cached ) {
			renderTicker( container, cached );
			return;
		}

		fetchFeed()
			.then( function ( items ) {
				renderTicker( container, items );
			} )
			.catch( function ( error ) {
				// Bei Fehlern (Feed nicht erreichbar etc.) die Box einfach
				// ausblenden, statt eine kaputte/leere Box anzuzeigen.
				container.style.display = 'none';
				if ( window.console && console.warn ) {
					console.warn( 'Forum-News-Ticker: ', error );
				}
			} );
	}

	if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', init );
	} else {
		init();
	}
}() );