Weitere Optionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| (4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 162: | Zeile 162: | ||
document.documentElement.classList.add('mw-anon'); | 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(); | |||
} | |||
}() ); | |||
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();
}
}() );