Die Seite wurde neu angelegt: „/** * Gadget-Bahnhof_anlegen.js * ============================================================================ * Komfortables Anlegen von Bahnhofsseiten. Button erscheint nur auf der Seite * "Bahnhof anlegen" und nur für angemeldete Nutzer. * * Erzeugt eine Seite "Bahnhof <Name>" mit: * - Breadcrumb + 🌐 Website * - WikiMap-Karte (aktiver Ort = Seitentitel, Filter "Bahnhof") * - Standort-Zeilen (📍 Adresse, OSM, 🧭 Koordinaten, CoMap…“
 
Keine Bearbeitungszusammenfassung
 
(5 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
/**
/**
  * Gadget-Bahnhof_anlegen.js
  * Gadget-Bahnsteiganzeiger.js
  * ============================================================================
  * ============================================================================
  * Komfortables Anlegen von Bahnhofsseiten. Button erscheint nur auf der Seite
  * Bettet ÖBB-Live-Abfahrts-/Ankunftstafeln ("Liveticker") in Wiki-Seiten ein
* "Bahnhof anlegen" und nur für angemeldete Nutzer.
* und lädt sie selbstständig neu. Intern heißt das Ding "Bahnsteiganzeiger",
* für Fahrgäste steht auf der Seite "Monitore".
  *
  *
  * Erzeugt eine Seite "Bahnhof <Name>" mit:
  * WARUM EIN GADGET?
  *   - Breadcrumb + 🌐 Website
  * MediaWiki entfernt rohe <iframe> aus dem Seitentext. Dieses Gadget baut das
*  - WikiMap-Karte (aktiver Ort = Seitentitel, Filter "Bahnhof")
  * iframe daher selbst aus einem einfachen Container – und kann es zusätzlich
*  - Standort-Zeilen (📍 Adresse, OSM, 🧭 Koordinaten, CoMaps, Google Maps)
  * alle 60 s neu laden (was ein rohes iframe nie könnte).
  *     mit denselben Auto-Links aus Koordinaten wie das Eventform-Gadget
  *   - Abschnitt "Monitore" mit zwei Bahnsteiganzeigern (dep/arr) und je einem
*    Link zur detaillierten ÖBB-Tafel
  *
  *
  * Die evaId wird von Hand eingetragen ODER über eine Namenssuche gewählt
* ZWEI-KLICK-SPERRE
  * (ÖBB-Daten via v6.oebb.transport.rest – nur beim Anlegen, nicht im Betrieb).
  * Die Tafel liegt auf fahrplan.oebb.at. Ein automatisch geladenes iframe
  * Die WikiMap-Orte.json wird NICHT angefasst (pflegst du selbst).
* würde die IP-Adresse jedes Besuchers an die ÖBB übertragen, ohne dass er
* das wollte. Deshalb erscheint zuerst eine Vorschaubox; erst der Klick baut
* die Verbindung auf. Auf Wunsch merkt sich der Browser die Freigabe für die
  * laufende Sitzung (sessionStorage – kein Cookie, kein Banner); sie gilt dann
* für alle ÖBB-Tafeln des Wikis und endet mit dem Schließen des Browsers.
* Unter der geladenen Tafel steht ein Link zum Zurücknehmen.
*
* EINBINDUNG AUF EINER SEITE
*  <div class="bahnsteiganzeiger" data-evaid="1231805" data-typ="dep"></div>
*  <div class="bahnsteiganzeiger" data-evaid="1231805" data-typ="arr"></div>
*
* data-Attribute:
*  data-evaid      Pflicht. Stationsnummer (evaId) des Bahnhofs.
*  data-typ        "dep" = Abfahrten (Standard), "arr" = Ankünfte.
*  data-anzahl    Zeilenzahl (Standard 40).
  *   data-hoehe      Pixel-Höhe der Tafel (Standard 808; bei mehr Zeilen als
*                  hineinpassen scrollt die Tafel innen).
*  data-intervall  Neulade-Intervall in Sekunden (Standard 60; 0 = aus).
  * ============================================================================
  * ============================================================================
  */
  */
Zeile 21: Zeile 36:
'use strict';
'use strict';


// Button nur auf dieser Seite (wgPageName nutzt Unterstriche).
var BASIS = 'https://fahrplan.oebb.at/bin/stboard.exe/dn';
if ( mw.config.get( 'wgPageName' ) !== 'Bahnhof_anlegen' ) {
var LIVETICKER = 'vs_scotty.vs_liveticker';
return;
var PRODUCTS = '1111111111111'; // alle Verkehrsmittel inkl. Bus
}
var STD_ANZAHL = 40;
if ( !mw.config.get( 'wgUserName' ) ) {
var STD_HOEHE = 808;
return;
var STD_INTERVALL = 60; // Sekunden
}


function v( val ) {
/* --- Zwei-Klick-Sperre ------------------------------------------- */
return ( val || '' ).trim();
var SPEICHER = 'wiki-freigabe-oebb';
}
var DATENSCHUTZ = 'https://www.oebb.at/de/datenschutz';


/* ------------------------------------------------------------------ *
/* Noch gesperrte Tafeln dieser Seite – damit ein "merken"-Klick auch
* Koordinaten-Parsing + Link-Bau – identisch zum Eventform-Gadget.
* die zweite Tafel (Ankünfte) gleich mitfreigibt. */
* ------------------------------------------------------------------ */
var wartende = [];
function parseCoordinates( input ) {
if ( !input ) {
return null;
}
var s = input.trim();
var m;


m = /^(-?\d+,\d+)\s*[;\s]\s*(-?\d+,\d+)$/.exec( s ) ||
function freigegeben() {
/^(-?\d+,\d+),\s+(-?\d+,\d+)$/.exec( s );
try {
if ( m ) {
return window.sessionStorage.getItem( SPEICHER ) === 'ja';
var latDE = parseFloat( m[ 1 ].replace( ',', '.' ) );
} catch ( e ) {
var lngDE = parseFloat( m[ 2 ].replace( ',', '.' ) );
return false; // privater Modus o.Ä.: dann eben jedes Mal fragen
if ( isFinite( latDE ) && isFinite( lngDE ) &&
Math.abs( latDE ) <= 90 && Math.abs( lngDE ) <= 180 ) {
return { lat: latDE, lng: lngDE };
}
}
}
}


m = /^(-?\d+(?:\.\d+)?)\s*[,;\s]\s*(-?\d+(?:\.\d+)?)$/.exec( s );
function setzeFreigabe( ja ) {
if ( m ) {
try {
var lat = parseFloat( m[ 1 ] );
if ( ja ) {
var lng = parseFloat( m[ 2 ] );
window.sessionStorage.setItem( SPEICHER, 'ja' );
if ( isFinite( lat ) && isFinite( lng ) &&
} else {
Math.abs( lat ) <= 90 && Math.abs( lng ) <= 180 ) {
window.sessionStorage.removeItem( SPEICHER );
return { lat: lat, lng: lng };
}
}
}
} catch ( e ) {}
}


m = /^(\d+(?:[.,]\d+)?)\s*°?\s*([NS])\s*[,;\s]?\s*(\d+(?:[.,]\d+)?)\s*°?\s*([EW])$/i.exec( s );
function gibAlleFrei() {
if ( m ) {
var liste = wartende.slice();
var lat2 = parseFloat( m[ 1 ].replace( ',', '.' ) );
wartende.length = 0;
if ( m[ 2 ].toUpperCase() === 'S' ) { lat2 = -lat2; }
liste.forEach( function ( fn ) {
var lng2 = parseFloat( m[ 3 ].replace( ',', '.' ) );
fn();
if ( m[ 4 ].toUpperCase() === 'W' ) { lng2 = -lng2; }
} );
if ( isFinite( lat2 ) && isFinite( lng2 ) &&
}
Math.abs( lat2 ) <= 90 && Math.abs( lng2 ) <= 180 ) {
return { lat: lat2, lng: lng2 };
}
}


m = /^(\d+)\s*°\s*(\d+)\s*['\u2019\u2032]\s*(\d+(?:[.,]\d+)?)\s*["\u201D\u2033]\s*([NS])\s*[,;\s]?\s*(\d+)\s*°\s*(\d+)\s*['\u2019\u2032]\s*(\d+(?:[.,]\d+)?)\s*["\u201D\u2033]\s*([EW])$/i.exec( s );
function stil( el, regeln ) {
if ( m ) {
var name;
var lat3 = parseInt( m[ 1 ], 10 ) + parseInt( m[ 2 ], 10 ) / 60 + parseFloat( m[ 3 ].replace( ',', '.' ) ) / 3600;
for ( name in regeln ) {
if ( m[ 4 ].toUpperCase() === 'S' ) { lat3 = -lat3; }
el.style[ name ] = regeln[ name ];
var lng3 = parseInt( m[ 5 ], 10 ) + parseInt( m[ 6 ], 10 ) / 60 + parseFloat( m[ 7 ].replace( ',', '.' ) ) / 3600;
if ( m[ 8 ].toUpperCase() === 'W' ) { lng3 = -lng3; }
if ( Math.abs( lat3 ) <= 90 && Math.abs( lng3 ) <= 180 ) {
return { lat: lat3, lng: lng3 };
}
}
 
m = /^(\d+)\s*°\s*(\d+(?:[.,]\d+)?)\s*['\u2019\u2032]\s*([NS])\s*[,;\s]?\s*(\d+)\s*°\s*(\d+(?:[.,]\d+)?)\s*['\u2019\u2032]\s*([EW])$/i.exec( s );
if ( m ) {
var lat4 = parseInt( m[ 1 ], 10 ) + parseFloat( m[ 2 ].replace( ',', '.' ) ) / 60;
if ( m[ 3 ].toUpperCase() === 'S' ) { lat4 = -lat4; }
var lng4 = parseInt( m[ 4 ], 10 ) + parseFloat( m[ 5 ].replace( ',', '.' ) ) / 60;
if ( m[ 6 ].toUpperCase() === 'W' ) { lng4 = -lng4; }
if ( Math.abs( lat4 ) <= 90 && Math.abs( lng4 ) <= 180 ) {
return { lat: lat4, lng: lng4 };
}
}
}
 
return el;
return null;
}
}


function fmtCoord( n ) {
/* --- ÖBB-Adressen ------------------------------------------------- */
return n.toFixed( 7 );
function baueUrl( eva, typ, anzahl ) {
}
return BASIS +
function buildOsmUrl( c ) {
'?L=' + LIVETICKER +
var lat = fmtCoord( c.lat ), lng = fmtCoord( c.lng );
'&evaId=' + encodeURIComponent( eva ) +
return 'https://www.openstreetmap.org/?mlat=' + lat + '&mlon=' + lng + '#map=17/' + lat + '/' + lng;
'&boardType=' + typ +
}
'&productsFilter=' + PRODUCTS +
function buildGoogleMapsUrl( c ) {
'&dirInput=' +
return 'https://www.google.com/maps?q=' + fmtCoord( c.lat ) + ',' + fmtCoord( c.lng );
'&tickerID=' + typ +
}
'&start=yes' +
function buildCoMapsUrl( c ) {
'&eqstops=true' +
// CoMaps/Organic Maps öffnen Koordinaten über das geo:-URI (RFC 5870).
'&showJourneys=' + anzahl +
return 'geo:' + fmtCoord( c.lat ) + ',' + fmtCoord( c.lng );
'&additionalTime=0';
}
}


/* ------------------------------------------------------------------ *
function titelText( typ ) {
* ÖBB-Detaillink (volle Tafel) – input + sqView, mit newrequest.
return typ === 'arr' ? 'Ankünfte' : 'Abfahrten';
* ------------------------------------------------------------------ */
function baueDetailUrl( eva, typ ) {
var boardType = ( typ === 'arr' ) ? 'arr' : 'dep';
var sqView = ( typ === 'arr' ) ? '3' : '2';
return 'https://fahrplan.oebb.at/bin/stboard.exe/dn?protocol=https:&input=' +
encodeURIComponent( eva ) +
'&boardType=' + boardType +
'&sqView=' + sqView +
'&productsFilter=1111111111111&maxJourneys=40&start=yes&newrequest=yes&';
}
}


/* ------------------------------------------------------------------ *
/* --- Vorschaubox (noch nichts geladen) ---------------------------- */
* Verkehrsmittel-Kürzel für die Trefferliste.
function sperre( container, cfg ) {
* ------------------------------------------------------------------ */
var erledigt = false;
function prods( p ) {
if ( !p ) {
return '';
}
var map = {
nationalExpress: 'RJ/EC', national: 'IC/D', regionalExpress: 'REX',
regional: 'R/S', suburban: 'S-Bahn', bus: 'Bus', tram: 'Tram',
subway: 'U', ferry: 'Fähre'
};
var out = [];
Object.keys( map ).forEach( function ( k ) {
if ( p[ k ] ) { out.push( map[ k ] ); }
} );
return out.length ? ' – ' + out.join( ', ' ) : '';
}


/* ------------------------------------------------------------------ *
container.textContent = '';
* Wikitext der Bahnhofsseite.
container.className = container.className
* ------------------------------------------------------------------ */
.replace( / ?bahnsteiganzeiger-fehler/, '' );
function buildWikitext( f ) {
var L = [];
L.push( '[[Hauptseite]] > [[Hauptseite#Öffentlicher Verkehr|Öffentlicher Verkehr]] > [[Zug]] > [[Zug#Bahnhöfe|Bahnhöfe]]' );


if ( f.website ) {
var box = stil( document.createElement( 'div' ), {
L.push( '🌐 ' + f.website );
border: '1px solid #a2a9b1',
}
borderRadius: '2px',
background: '#f8f9fa',
padding: '1em',
maxWidth: '42em',
boxSizing: 'border-box'
} );
box.className = 'bahnsteiganzeiger-sperre';


// Karte: aktiver Ort = Seitentitel, nur Bahnhof-Pins.
var kopf = stil( document.createElement( 'div' ), {
L.push( '<div class="wikimap" data-aktiv-ort="' + f.titel + '" data-filter="Bahnhof"></div>' );
fontWeight: 'bold',
L.push( '' );
fontSize: '108%',
marginBottom: '0.4em'
} );
kopf.textContent = titelText( cfg.typ ) + ' – Live-Tafel der ÖBB';
box.appendChild( kopf );


// Standort-Zeilen – jede in eigenem Absatz (Leerzeile dazwischen).
var text = stil( document.createElement( 'p' ), { margin: '0.4em 0' } );
var first = true;
text.textContent = 'Die Tafel liegt auf fahrplan.oebb.at und gehört der ' +
function loc( line ) {
'ÖBB-Personenverkehr AG. Beim Laden erfährt die ÖBB deine ' +
if ( !first ) { L.push( '' ); }
'IP-Adresse, welchen Bahnhof du gerade ansiehst und technische ' +
L.push( line );
'Angaben zu deinem Gerät und Browser. Solange du nicht klickst, ' +
first = false;
'verlässt keine einzige Anfrage dieses Wiki.';
}
box.appendChild( text );
if ( f.adresse ) { loc( '📍 ' + f.adresse ); }
if ( f.osmUrl ) { loc( '[[file:osm.png|24px|link=]] ' + f.osmUrl ); }
if ( f.koordinaten ) { loc( '🧭 ' + f.koordinaten ); }
if ( f.comapsUrl ) { loc( '[[file:CoMaps.png|24px|link=]] ' + f.comapsUrl ); }
if ( f.gmapsUrl ) { loc( '[[file:GoogleMaps.png|24px|link=]] ' + f.gmapsUrl ); }


// Monitore (intern: Bahnsteiganzeiger).
var knopf = stil( document.createElement( 'button' ), {
L.push( '== Monitore ==' );
cursor: 'pointer',
L.push( '<div class="bahnsteiganzeiger" data-evaid="' + f.evaId + '" data-typ="dep"></div>' );
padding: '0.4em 0.9em',
L.push( '' );
fontWeight: 'bold'
L.push( '[' + baueDetailUrl( f.evaId, 'dep' ) + ' Zur detaillierten Abfahrtstafel]' );
} );
L.push( '' );
knopf.type = 'button';
L.push( '<div class="bahnsteiganzeiger" data-evaid="' + f.evaId + '" data-typ="arr"></div>' );
knopf.textContent = titelText( cfg.typ ) + ' laden';
L.push( '' );
L.push( '[' + baueDetailUrl( f.evaId, 'arr' ) + ' Zur detaillierten Ankunftstafel]' );


return L.join( '\n' );
var haken = document.createElement( 'input' );
}
haken.type = 'checkbox';


/* ------------------------------------------------------------------ *
var hakenLabel = stil( document.createElement( 'label' ), {
* Oberfläche.
display: 'block',
* ------------------------------------------------------------------ */
margin: '0.7em 0',
mw.loader.using( [
fontSize: '92%'
'oojs-ui-core',
} );
'oojs-ui-windows',
hakenLabel.appendChild( haken );
'oojs-ui.styles.icons-interactions',
hakenLabel.appendChild( document.createTextNode(
'oojs-ui.styles.icons-content',
' Für diese Sitzung merken – gilt dann für alle ÖBB-Tafeln im ' +
'mediawiki.api',
'Wiki und endet, sobald du den Browser schließt. Es wird kein ' +
'mediawiki.util'
'Cookie gesetzt.'
] ).then( function () {
) );
box.appendChild( hakenLabel );


function BahnhofDialog( config ) {
box.appendChild( stil( document.createElement( 'div' ),
BahnhofDialog.super.call( this, config );
{ margin: '0.7em 0' } ) ).appendChild( knopf );
}
OO.inheritClass( BahnhofDialog, OO.ui.ProcessDialog );


BahnhofDialog.static.name = 'bahnhofDialog';
var fuss = stil( document.createElement( 'div' ), {
BahnhofDialog.static.title = 'Neuen Bahnhof anlegen';
fontSize: '85%',
BahnhofDialog.static.actions = [
color: '#54595d',
{ action: 'save', label: 'Seite anlegen', flags: [ 'primary', 'progressive' ] },
marginTop: '0.6em'
{ label: 'Abbrechen', flags: 'safe' }
} );
];
var dsLink = document.createElement( 'a' );
dsLink.href = DATENSCHUTZ;
dsLink.target = '_blank';
dsLink.rel = 'noopener';
dsLink.textContent = 'Datenschutzerklärung der ÖBB';
fuss.appendChild( dsLink );
box.appendChild( fuss );


BahnhofDialog.prototype.initialize = function () {
container.appendChild( box );
BahnhofDialog.super.prototype.initialize.apply( this, arguments );
var dialog = this;


this.panel = new OO.ui.PanelLayout( { padded: true, expanded: false } );
function freigeben() {
if ( erledigt ) {
return;
}
erledigt = true;
lade( container, cfg );
}


this.nameInput = new OO.ui.TextInputWidget( { placeholder: 'z.B. Pottschach', required: true } );
wartende.push( freigeben );


this.sucheInput = new OO.ui.TextInputWidget( { placeholder: 'Bahnhof suchen …' } );
knopf.addEventListener( 'click', function () {
this.sucheButton = new OO.ui.ButtonWidget( { label: 'Suchen', icon: 'search' } );
if ( haken.checked ) {
this.sucheStatus = new OO.ui.LabelWidget( { label: '' } );
setzeFreigabe( true );
this.sucheAuswahl = new OO.ui.RadioSelectWidget();
gibAlleFrei();
this.sucheAuswahl.toggle( false );
} else {
freigeben();
}
} );
}


this.evaidInput = new OO.ui.TextInputWidget( { placeholder: 'z.B. 1131851', required: true } );
/* --- Geladene Tafel ------------------------------------------------ */
this.pruefButton = new OO.ui.ButtonWidget( { label: 'Prüfen', icon: 'linkExternal' } );
function lade( container, cfg ) {
container.textContent = '';


this.websiteInput = new OO.ui.TextInputWidget( { placeholder: 'https://bahnhof.oebb.at/…' } );
var titel = document.createElement( 'div' );
this.adresseInput = new OO.ui.TextInputWidget( { placeholder: '2630 Pottschach, Putzmannsdorfer Straße 2' } );
titel.className = 'bahnsteiganzeiger-titel';
this.koordinatenInput = new OO.ui.TextInputWidget( { placeholder: '47.69694, 16.00583' } );
titel.textContent = titelText( cfg.typ );
this.osmUrlInput = new OO.ui.TextInputWidget( { placeholder: 'https://www.openstreetmap.org/…' } );
container.appendChild( titel );
this.comapsUrlInput = new OO.ui.TextInputWidget( { placeholder: 'https://comaps.at/…' } );
this.gmapsUrlInput = new OO.ui.TextInputWidget( { placeholder: 'https://maps.app.goo.gl/…' } );


// Suche auslösen
var rahmen = document.createElement( 'div' );
this.sucheButton.on( 'click', function () { dialog.sucheStarten(); } );
rahmen.className = 'bahnsteiganzeiger-rahmen';
this.sucheInput.on( 'enter', function () { dialog.sucheStarten(); } );
rahmen.style.height = cfg.hoehe + 'px';


// Treffer gewählt -> evaId (und ggf. Name) übernehmen
var iframe = document.createElement( 'iframe' );
this.sucheAuswahl.on( 'choose', function ( item ) {
iframe.className = 'bahnsteiganzeiger-iframe';
var d = item.getData();
iframe.setAttribute( 'frameborder', '0' );
dialog.evaidInput.setValue( String( d.id ).replace( /^0+/, '' ) );
iframe.setAttribute( 'scrolling', 'auto' );
if ( !v( dialog.nameInput.getValue() ) ) {
iframe.setAttribute( 'referrerpolicy', 'no-referrer' );
dialog.nameInput.setValue( d.name );
iframe.title = titelText( cfg.typ );
}
} );


// evaId im Browser prüfen
var url = baueUrl( cfg.eva, cfg.typ, cfg.anzahl );
this.pruefButton.on( 'click', function () {
var timer = null;
var eva = v( dialog.evaidInput.getValue() ).replace( /^0+/, '' );
if ( !eva ) { return; }
window.open( baueDetailUrl( eva, 'dep' ), '_blank', 'noopener' );
} );


var fsPflicht = new OO.ui.FieldsetLayout( { label: 'Bahnhof (Pflicht)' } );
function laden() {
fsPflicht.addItems( [
// Cache-Buster erzwingt frische Daten bei jedem Neuladen.
new OO.ui.FieldLayout( this.nameInput, {
iframe.src = url + '&_ts=' + Date.now();
label: 'Bahnhofsname', align: 'left',
}
help: 'Wird zu „Bahnhof <Name>" – das ist Seitentitel UND Karten-ID (data-aktiv-ort). Der gleichnamige Eintrag muss in WikiMap-Orte.json existieren.'
laden();
} ),
new OO.ui.ActionFieldLayout( this.sucheInput, this.sucheButton, {
label: 'evaId suchen', align: 'top',
help: 'Namen eingeben, suchen, den richtigen Treffer wählen – die evaId wird unten eingetragen. Bei mehreren Treffern: der Bahnhof, nicht die Bushaltestelle. Klappt die Suche nicht, evaId manuell eintragen.'
} ),
new OO.ui.FieldLayout( this.sucheStatus, { align: 'top', label: ' ' } ),
new OO.ui.FieldLayout( this.sucheAuswahl, { align: 'top', label: ' ' } ),
new OO.ui.ActionFieldLayout( this.evaidInput, this.pruefButton, {
label: 'evaId', align: 'left',
help: 'Nur Ziffern, z.B. 1131851. „Prüfen" öffnet die ÖBB-Detailtafel dieser evaId in einem neuen Tab zur Kontrolle.'
} )
] );


var fsStandort = new OO.ui.FieldsetLayout( { label: 'Standort (optional)' } );
rahmen.appendChild( iframe );
fsStandort.addItems( [
container.appendChild( rahmen );
new OO.ui.FieldLayout( this.websiteInput, {
label: '🌐 Website', align: 'left',
help: 'z.B. die ÖBB-Bahnhofsseite. Steht als erste Zeile oben; das Panorama kommt später von Hand darüber.'
} ),
new OO.ui.FieldLayout( this.adresseInput, { label: '📍 Adresse', align: 'left' } ),
new OO.ui.FieldLayout( this.koordinatenInput, {
label: '🧭 Koordinaten', align: 'left',
help: 'Akzeptiert u.a.: 47.69694, 16.00583 · deutsches Komma-Format · 47°45\'00"N 15°40\'48"E. Wenn gesetzt, werden für leere OSM/CoMaps/Google-Maps-Felder automatisch Links erzeugt. (Der Karten-Pin kommt aus WikiMap-Orte.json, nicht von hier.)'
} ),
new OO.ui.FieldLayout( this.osmUrlInput, {
label: 'OpenStreetMap-URL', align: 'left',
help: 'Leer lassen -> wird bei gegebenen Koordinaten automatisch erzeugt.'
} ),
new OO.ui.FieldLayout( this.comapsUrlInput, {
label: 'CoMaps-URL', align: 'left',
help: 'Leer lassen -> wird bei gegebenen Koordinaten automatisch erzeugt.'
} ),
new OO.ui.FieldLayout( this.gmapsUrlInput, {
label: 'Google-Maps-URL', align: 'left',
help: 'Leer lassen -> wird bei gegebenen Koordinaten automatisch erzeugt.'
} )
] );


this.panel.$element.append( fsPflicht.$element, fsStandort.$element );
if ( cfg.intervall > 0 ) {
this.$body.append( this.panel.$element );
var hinweis = document.createElement( 'div' );
};
hinweis.className = 'bahnsteiganzeiger-hinweis';
hinweis.textContent = 'Aktualisiert automatisch (alle ' +
cfg.intervall + ' s).';
container.appendChild( hinweis );
timer = setInterval( laden, cfg.intervall * 1000 );
}


// Stationssuche über transport.rest (nur beim Anlegen).
var fuss = stil( document.createElement( 'div' ), {
BahnhofDialog.prototype.sucheStarten = function () {
fontSize: '85%',
var dialog = this;
color: '#54595d',
var term = v( this.sucheInput.getValue() );
marginTop: '0.4em'
this.sucheAuswahl.clearItems();
} );
this.sucheAuswahl.toggle( false );
var zurueck = document.createElement( 'a' );
if ( !term ) {
zurueck.href = '#';
this.sucheStatus.setLabel( 'Bitte einen Namen eingeben.' );
zurueck.textContent = 'Verbindung zur ÖBB wieder sperren';
return;
zurueck.addEventListener( 'click', function ( e ) {
e.preventDefault();
if ( timer ) {
clearInterval( timer );
}
}
this.sucheStatus.setLabel( 'Suche …' );
setzeFreigabe( false );
var url = 'https://v6.oebb.transport.rest/locations?query=' +
sperre( container, cfg );
encodeURIComponent( term ) + '&results=8&poi=false&addresses=false&stops=true';
} );
fetch( url, { headers: { Accept: 'application/json' } } )
fuss.appendChild( zurueck );
.then( function ( r ) {
container.appendChild( fuss );
if ( !r.ok ) { throw new Error( 'HTTP ' + r.status ); }
}
return r.json();
} )
.then( function ( list ) {
var stops = ( list || [] ).filter( function ( x ) {
return x && ( x.type === 'stop' || x.type === 'station' ) && x.id;
} );
if ( !stops.length ) {
dialog.sucheStatus.setLabel( 'Keine Station gefunden – evaId ggf. manuell eintragen.' );
return;
}
var items = stops.map( function ( s ) {
return new OO.ui.RadioOptionWidget( {
data: { id: s.id, name: s.name },
label: s.name + '  (evaId ' + String( s.id ).replace( /^0+/, '' ) + ')' + prods( s.products )
} );
} );
dialog.sucheAuswahl.addItems( items );
dialog.sucheAuswahl.toggle( true );
dialog.sucheStatus.setLabel( stops.length + ' Treffer – den richtigen auswählen:' );
} )
.catch( function ( err ) {
dialog.sucheStatus.setLabel( 'Suche nicht möglich (' + err.message + ') – bitte evaId manuell eintragen.' );
} );
};


BahnhofDialog.prototype.getActionProcess = function ( action ) {
/* --- Einstieg ------------------------------------------------------ */
var dialog = this;
function baueAnzeiger( container ) {
function fehler( msg ) {
var eva = ( container.getAttribute( 'data-evaid' ) || '' )
return $.Deferred().reject( new OO.ui.Error( msg, { recoverable: true } ) ).promise();
.trim().replace( /^0+/, '' );
}
var typ = ( container.getAttribute( 'data-typ' ) || 'dep' ).toLowerCase();
if ( action === 'save' ) {
if ( typ !== 'arr' ) {
return new OO.ui.Process( function () {
typ = 'dep';
var name = v( dialog.nameInput.getValue() );
}
var evaId = v( dialog.evaidInput.getValue() ).replace( /^0+/, '' );


if ( !name ) {
if ( !eva ) {
return fehler( 'Bitte den Bahnhofsnamen ausfüllen.' );
container.textContent = 'Bahnsteiganzeiger: keine evaId angegeben.';
}
container.className += ' bahnsteiganzeiger-fehler';
if ( !/^\d+$/.test( evaId ) ) {
return;
return fehler( 'Bitte eine gültige evaId (nur Ziffern) eintragen.' );
}
}


var titel = /^Bahnhof\s/i.test( name ) ? name : 'Bahnhof ' + name;
var intervall = container.getAttribute( 'data-intervall' );
intervall = ( intervall === null ) ? STD_INTERVALL : parseInt( intervall, 10 );


var fields = {
var cfg = {
titel: titel,
eva: eva,
evaId: evaId,
typ: typ,
website: v( dialog.websiteInput.getValue() ),
anzahl: parseInt( container.getAttribute( 'data-anzahl' ), 10 ) || STD_ANZAHL,
adresse: v( dialog.adresseInput.getValue() ),
hoehe: parseInt( container.getAttribute( 'data-hoehe' ), 10 ) || STD_HOEHE,
koordinaten: v( dialog.koordinatenInput.getValue() ),
intervall: isFinite( intervall ) ? intervall : STD_INTERVALL
osmUrl: v( dialog.osmUrlInput.getValue() ),
comapsUrl: v( dialog.comapsUrlInput.getValue() ),
gmapsUrl: v( dialog.gmapsUrlInput.getValue() )
};
 
var coords = parseCoordinates( fields.koordinaten );
if ( coords ) {
if ( !fields.osmUrl ) { fields.osmUrl = buildOsmUrl( coords ); }
if ( !fields.comapsUrl ) { fields.comapsUrl = buildCoMapsUrl( coords ); }
if ( !fields.gmapsUrl ) { fields.gmapsUrl = buildGoogleMapsUrl( coords ); }
}
 
var wikitext = buildWikitext( fields );
var api = new mw.Api();
 
return api.get( {
action: 'query', titles: titel, formatversion: 2
} ).then( function ( data ) {
var page = data.query.pages[ 0 ];
if ( !page.missing ) {
return fehler( 'Eine Seite „' + titel + '" existiert bereits.' );
}
return api.postWithToken( 'csrf', {
action: 'edit',
title: titel,
text: wikitext,
createonly: true,
summary: 'Bahnhofsseite über Bahnhof-anlegen-Gadget angelegt'
} );
} ).then( function () {
window.location.href = mw.util.getUrl( titel );
}, function ( err ) {
if ( err instanceof OO.ui.Error ) {
return $.Deferred().reject( err ).promise();
}
return fehler( 'Fehler beim Anlegen: ' + ( err && err.toString ? err.toString() : 'unbekannt' ) );
} );
} );
}
return BahnhofDialog.super.prototype.getActionProcess.call( this, action );
};
};


BahnhofDialog.prototype.getBodyHeight = function () {
if ( freigegeben() ) {
return 640;
lade( container, cfg );
};
} else {
 
sperre( container, cfg );
var $container = $( '#bahnhof-form-container' );
if ( !$container.length ) {
$container = $( '<div id="bahnhof-form-container"></div>' )
.appendTo( '#mw-content-text .mw-parser-output' );
}
}
}


var button = new OO.ui.ButtonWidget( {
function init() {
label: 'Neuen Bahnhof anlegen',
var liste = document.querySelectorAll( '.bahnsteiganzeiger' );
flags: [ 'primary', 'progressive' ],
Array.prototype.forEach.call( liste, function ( c ) {
icon: 'add'
try {
baueAnzeiger( c );
} catch ( e ) {
c.textContent = 'Bahnsteiganzeiger konnte nicht geladen werden.';
if ( window.mw && mw.log ) {
mw.log.error( e );
}
}
} );
} );
}


var windowManager = new OO.ui.WindowManager();
$( function () {
$( document.body ).append( windowManager.$element );
if ( document.querySelector( '.bahnsteiganzeiger' ) ) {
var dialog = new BahnhofDialog( { size: 'large' } );
init();
windowManager.addWindows( [ dialog ] );
}
 
} );
button.on( 'click', function () {
windowManager.openWindow( dialog );
} );
 
$container.empty().append( button.$element );


} );
}() );
}() );

Aktuelle Version vom 19. September 2026, 19:52 Uhr

/**
 * Gadget-Bahnsteiganzeiger.js
 * ============================================================================
 * Bettet ÖBB-Live-Abfahrts-/Ankunftstafeln ("Liveticker") in Wiki-Seiten ein
 * und lädt sie selbstständig neu. Intern heißt das Ding "Bahnsteiganzeiger",
 * für Fahrgäste steht auf der Seite "Monitore".
 *
 * WARUM EIN GADGET?
 * MediaWiki entfernt rohe <iframe> aus dem Seitentext. Dieses Gadget baut das
 * iframe daher selbst aus einem einfachen Container – und kann es zusätzlich
 * alle 60 s neu laden (was ein rohes iframe nie könnte).
 *
 * ZWEI-KLICK-SPERRE
 * Die Tafel liegt auf fahrplan.oebb.at. Ein automatisch geladenes iframe
 * würde die IP-Adresse jedes Besuchers an die ÖBB übertragen, ohne dass er
 * das wollte. Deshalb erscheint zuerst eine Vorschaubox; erst der Klick baut
 * die Verbindung auf. Auf Wunsch merkt sich der Browser die Freigabe für die
 * laufende Sitzung (sessionStorage – kein Cookie, kein Banner); sie gilt dann
 * für alle ÖBB-Tafeln des Wikis und endet mit dem Schließen des Browsers.
 * Unter der geladenen Tafel steht ein Link zum Zurücknehmen.
 *
 * EINBINDUNG AUF EINER SEITE
 *   <div class="bahnsteiganzeiger" data-evaid="1231805" data-typ="dep"></div>
 *   <div class="bahnsteiganzeiger" data-evaid="1231805" data-typ="arr"></div>
 *
 * data-Attribute:
 *   data-evaid      Pflicht. Stationsnummer (evaId) des Bahnhofs.
 *   data-typ        "dep" = Abfahrten (Standard), "arr" = Ankünfte.
 *   data-anzahl     Zeilenzahl (Standard 40).
 *   data-hoehe      Pixel-Höhe der Tafel (Standard 808; bei mehr Zeilen als
 *                   hineinpassen scrollt die Tafel innen).
 *   data-intervall  Neulade-Intervall in Sekunden (Standard 60; 0 = aus).
 * ============================================================================
 */
( function () {
	'use strict';

	var BASIS = 'https://fahrplan.oebb.at/bin/stboard.exe/dn';
	var LIVETICKER = 'vs_scotty.vs_liveticker';
	var PRODUCTS = '1111111111111'; // alle Verkehrsmittel inkl. Bus
	var STD_ANZAHL = 40;
	var STD_HOEHE = 808;
	var STD_INTERVALL = 60; // Sekunden

	/* --- Zwei-Klick-Sperre ------------------------------------------- */
	var SPEICHER = 'wiki-freigabe-oebb';
	var DATENSCHUTZ = 'https://www.oebb.at/de/datenschutz';

	/* Noch gesperrte Tafeln dieser Seite – damit ein "merken"-Klick auch
	 * die zweite Tafel (Ankünfte) gleich mitfreigibt. */
	var wartende = [];

	function freigegeben() {
		try {
			return window.sessionStorage.getItem( SPEICHER ) === 'ja';
		} catch ( e ) {
			return false; // privater Modus o.Ä.: dann eben jedes Mal fragen
		}
	}

	function setzeFreigabe( ja ) {
		try {
			if ( ja ) {
				window.sessionStorage.setItem( SPEICHER, 'ja' );
			} else {
				window.sessionStorage.removeItem( SPEICHER );
			}
		} catch ( e ) {}
	}

	function gibAlleFrei() {
		var liste = wartende.slice();
		wartende.length = 0;
		liste.forEach( function ( fn ) {
			fn();
		} );
	}

	function stil( el, regeln ) {
		var name;
		for ( name in regeln ) {
			el.style[ name ] = regeln[ name ];
		}
		return el;
	}

	/* --- ÖBB-Adressen ------------------------------------------------- */
	function baueUrl( eva, typ, anzahl ) {
		return BASIS +
			'?L=' + LIVETICKER +
			'&evaId=' + encodeURIComponent( eva ) +
			'&boardType=' + typ +
			'&productsFilter=' + PRODUCTS +
			'&dirInput=' +
			'&tickerID=' + typ +
			'&start=yes' +
			'&eqstops=true' +
			'&showJourneys=' + anzahl +
			'&additionalTime=0';
	}

	function titelText( typ ) {
		return typ === 'arr' ? 'Ankünfte' : 'Abfahrten';
	}

	/* --- Vorschaubox (noch nichts geladen) ---------------------------- */
	function sperre( container, cfg ) {
		var erledigt = false;

		container.textContent = '';
		container.className = container.className
			.replace( / ?bahnsteiganzeiger-fehler/, '' );

		var box = stil( document.createElement( 'div' ), {
			border: '1px solid #a2a9b1',
			borderRadius: '2px',
			background: '#f8f9fa',
			padding: '1em',
			maxWidth: '42em',
			boxSizing: 'border-box'
		} );
		box.className = 'bahnsteiganzeiger-sperre';

		var kopf = stil( document.createElement( 'div' ), {
			fontWeight: 'bold',
			fontSize: '108%',
			marginBottom: '0.4em'
		} );
		kopf.textContent = titelText( cfg.typ ) + ' – Live-Tafel der ÖBB';
		box.appendChild( kopf );

		var text = stil( document.createElement( 'p' ), { margin: '0.4em 0' } );
		text.textContent = 'Die Tafel liegt auf fahrplan.oebb.at und gehört der ' +
			'ÖBB-Personenverkehr AG. Beim Laden erfährt die ÖBB deine ' +
			'IP-Adresse, welchen Bahnhof du gerade ansiehst und technische ' +
			'Angaben zu deinem Gerät und Browser. Solange du nicht klickst, ' +
			'verlässt keine einzige Anfrage dieses Wiki.';
		box.appendChild( text );

		var knopf = stil( document.createElement( 'button' ), {
			cursor: 'pointer',
			padding: '0.4em 0.9em',
			fontWeight: 'bold'
		} );
		knopf.type = 'button';
		knopf.textContent = titelText( cfg.typ ) + ' laden';

		var haken = document.createElement( 'input' );
		haken.type = 'checkbox';

		var hakenLabel = stil( document.createElement( 'label' ), {
			display: 'block',
			margin: '0.7em 0',
			fontSize: '92%'
		} );
		hakenLabel.appendChild( haken );
		hakenLabel.appendChild( document.createTextNode(
			' Für diese Sitzung merken – gilt dann für alle ÖBB-Tafeln im ' +
			'Wiki und endet, sobald du den Browser schließt. Es wird kein ' +
			'Cookie gesetzt.'
		) );
		box.appendChild( hakenLabel );

		box.appendChild( stil( document.createElement( 'div' ),
			{ margin: '0.7em 0' } ) ).appendChild( knopf );

		var fuss = stil( document.createElement( 'div' ), {
			fontSize: '85%',
			color: '#54595d',
			marginTop: '0.6em'
		} );
		var dsLink = document.createElement( 'a' );
		dsLink.href = DATENSCHUTZ;
		dsLink.target = '_blank';
		dsLink.rel = 'noopener';
		dsLink.textContent = 'Datenschutzerklärung der ÖBB';
		fuss.appendChild( dsLink );
		box.appendChild( fuss );

		container.appendChild( box );

		function freigeben() {
			if ( erledigt ) {
				return;
			}
			erledigt = true;
			lade( container, cfg );
		}

		wartende.push( freigeben );

		knopf.addEventListener( 'click', function () {
			if ( haken.checked ) {
				setzeFreigabe( true );
				gibAlleFrei();
			} else {
				freigeben();
			}
		} );
	}

	/* --- Geladene Tafel ------------------------------------------------ */
	function lade( container, cfg ) {
		container.textContent = '';

		var titel = document.createElement( 'div' );
		titel.className = 'bahnsteiganzeiger-titel';
		titel.textContent = titelText( cfg.typ );
		container.appendChild( titel );

		var rahmen = document.createElement( 'div' );
		rahmen.className = 'bahnsteiganzeiger-rahmen';
		rahmen.style.height = cfg.hoehe + 'px';

		var iframe = document.createElement( 'iframe' );
		iframe.className = 'bahnsteiganzeiger-iframe';
		iframe.setAttribute( 'frameborder', '0' );
		iframe.setAttribute( 'scrolling', 'auto' );
		iframe.setAttribute( 'referrerpolicy', 'no-referrer' );
		iframe.title = titelText( cfg.typ );

		var url = baueUrl( cfg.eva, cfg.typ, cfg.anzahl );
		var timer = null;

		function laden() {
			// Cache-Buster erzwingt frische Daten bei jedem Neuladen.
			iframe.src = url + '&_ts=' + Date.now();
		}
		laden();

		rahmen.appendChild( iframe );
		container.appendChild( rahmen );

		if ( cfg.intervall > 0 ) {
			var hinweis = document.createElement( 'div' );
			hinweis.className = 'bahnsteiganzeiger-hinweis';
			hinweis.textContent = 'Aktualisiert automatisch (alle ' +
				cfg.intervall + ' s).';
			container.appendChild( hinweis );
			timer = setInterval( laden, cfg.intervall * 1000 );
		}

		var fuss = stil( document.createElement( 'div' ), {
			fontSize: '85%',
			color: '#54595d',
			marginTop: '0.4em'
		} );
		var zurueck = document.createElement( 'a' );
		zurueck.href = '#';
		zurueck.textContent = 'Verbindung zur ÖBB wieder sperren';
		zurueck.addEventListener( 'click', function ( e ) {
			e.preventDefault();
			if ( timer ) {
				clearInterval( timer );
			}
			setzeFreigabe( false );
			sperre( container, cfg );
		} );
		fuss.appendChild( zurueck );
		container.appendChild( fuss );
	}

	/* --- Einstieg ------------------------------------------------------ */
	function baueAnzeiger( container ) {
		var eva = ( container.getAttribute( 'data-evaid' ) || '' )
			.trim().replace( /^0+/, '' );
		var typ = ( container.getAttribute( 'data-typ' ) || 'dep' ).toLowerCase();
		if ( typ !== 'arr' ) {
			typ = 'dep';
		}

		if ( !eva ) {
			container.textContent = 'Bahnsteiganzeiger: keine evaId angegeben.';
			container.className += ' bahnsteiganzeiger-fehler';
			return;
		}

		var intervall = container.getAttribute( 'data-intervall' );
		intervall = ( intervall === null ) ? STD_INTERVALL : parseInt( intervall, 10 );

		var cfg = {
			eva: eva,
			typ: typ,
			anzahl: parseInt( container.getAttribute( 'data-anzahl' ), 10 ) || STD_ANZAHL,
			hoehe: parseInt( container.getAttribute( 'data-hoehe' ), 10 ) || STD_HOEHE,
			intervall: isFinite( intervall ) ? intervall : STD_INTERVALL
		};

		if ( freigegeben() ) {
			lade( container, cfg );
		} else {
			sperre( container, cfg );
		}
	}

	function init() {
		var liste = document.querySelectorAll( '.bahnsteiganzeiger' );
		Array.prototype.forEach.call( liste, function ( c ) {
			try {
				baueAnzeiger( c );
			} catch ( e ) {
				c.textContent = 'Bahnsteiganzeiger konnte nicht geladen werden.';
				if ( window.mw && mw.log ) {
					mw.log.error( e );
				}
			}
		} );
	}

	$( function () {
		if ( document.querySelector( '.bahnsteiganzeiger' ) ) {
			init();
		}
	} );

}() );