Was das Gadget macht

Das Gadget vereinform erzeugt das Grundgerüst einer Vereinsseite: Brotkrümel-Navigation, Karteneinbindung, Adress- und Kontaktzeilen samt der Links zu OpenStreetMap, CoMaps und Google Maps.

Es speichert nichts und legt keine Seite an. Das Formular gibt reinen Wikicode in ein Textfeld aus; man kopiert ihn und fügt ihn selbst auf der gewünschten Seite ein. Dadurch braucht es keine Schreibrechte, kann nichts überschreiben und funktioniert auch auf bereits bestehenden Seiten.

Zusätzlich erzeugt es den passenden Eintrag für die Ortsdatei der Karte WikiMap und legt ihn als auskommentierten Block mitten in den Wikicode – zum Herauskopieren und anschließenden Löschen.

Voraussetzungen

  • MediaWiki mit aktivierter Extension:Gadgets (bei den meisten Installationen Standard).
  • Keine weiteren Extensions, keine externen JavaScript-Bibliotheken.
  • Das WikiMap-Gadget samt MediaWiki:WikiMap-Orte.json muss vorhanden sein – sonst erzeugt das Formular zwar den Karten-div, es erscheint aber keine Karte.
  • Drei hochgeladene Bilddateien für die Symbole der Kartenlinks:
    • Datei:osm.png
    • Datei:CoMaps.png
    • Datei:GoogleMaps.png
  • Auf der Hauptseite sollte der Abschnittsanker existieren, auf den die Navigation zeigt (standardmäßig #Sport).

Fehlen die Bilddateien, funktioniert alles weiter – auf der Seite stehen dann nur rote Dateilinks.

Installation

Schritt 1: Gadget-Datei anlegen

Den kompletten Code nach MediaWiki:Gadget-vereinform.js kopieren und speichern.

Schritt 2: Gadget registrieren

In MediaWiki:Gadgets-definition eine Zeile ergänzen:

* vereinform[ResourceLoader|default]|vereinform.js

default bedeutet: für alle aktiv, auch für nicht angemeldete Benutzer. Das ist hier unbedenklich, weil das Gadget nichts schreibt. Soll es nur angemeldeten Benutzern angeboten werden, default weglassen – dann muss es jeder in seinen Einstellungen selbst aktivieren.

Sind in der Definitionsdatei Abschnitte (Zeilen wie == Werkzeuge ==) vorhanden, gehört die Zeile in einen davon.

Schritt 3: Auslöserseite anlegen

Eine normale Wiki-Seite mit dem Titel VereinForm anlegen. Das Gadget wird ausschließlich auf dieser Seite geladen. Optional kann man festlegen, wo genau das Formular erscheint:

<div id="verein-form-container"></div>

Ohne diesen Ankerpunkt hängt sich das Formular oben in den Seiteninhalt.

Schritt 4: Prüfen

Seite VereinForm aufrufen und mit Strg+Umschalt+R neu laden (der ResourceLoader hält Gadgets aggressiv im Cache). Das Formular muss sofort sichtbar sein.

Konfiguration

Ganz oben in der Gadget-Datei stehen vier Konstanten. Für eine Übernahme in ein anderes Wiki sind das die einzigen Stellen, die typischerweise angepasst werden:

var TRIGGER_PAGE  = 'VereinForm';      // Seite, auf der das Formular erscheint
var KATEGORIE     = 'Stockschießen';   // Kartenkategorie
var STD_RUBRIK    = 'Sport';           // Anker auf der Hauptseite
var STD_OBERSEITE = 'Stockschießen';   // übergeordnete Seite

TRIGGER_PAGE muss exakt dem Seitentitel entsprechen, inklusive Groß- und Kleinschreibung; Leerzeichen werden dabei als Unterstriche geschrieben.

KATEGORIE steuert zwei Dinge gleichzeitig: das Attribut data-filter im Karten-div und das Feld kategorie im erzeugten JSON-Block. Der Wert muss zeichengenau mit einer Kategorie in MediaWiki:WikiMap-Orte.json übereinstimmen – sonst bleibt der Kartenpin unsichtbar.

STD_RUBRIK und STD_OBERSEITE sind nur Vorbelegungen der beiden Navigationsfelder und lassen sich im Formular jederzeit überschreiben. Für eine andere Sparte – etwa Fußball – genügt es also, diese drei Werte zu ändern und das Gadget unter neuem Namen ein zweites Mal anzulegen.

Bedienung

  1. Seite VereinForm aufrufen.
  2. Felder ausfüllen. Pflicht sind nur Vereinsname und Koordinaten; alles andere ist optional und lässt bei leerem Feld die zugehörige Zeile einfach weg.
  3. Den Wikicode kopieren (Knopf oder Strg+C) und auf der Vereinsseite einfügen.
  4. Den auskommentierten JSON-Block aus dem eingefügten Text herausschneiden und in MediaWiki:WikiMap-Orte.json in die Liste orte einfügen. Die beiden Leerzeilen um den Block gleich mit entfernen, sonst bleibt ein Absatzumbruch mitten im Adressblock stehen.

Der Vereinsname wird an drei Stellen verwendet und muss überall identisch sein: als Seitentitel, als data-aktiv-ort in der Karte und als id in der Ortsdatei.

Bei den drei Kartenlink-Feldern gilt: leer lassen heißt, der Link wird aus den Koordinaten erzeugt. Trägt man etwas ein, wird genau das übernommen – gedacht für gekürzte Links direkt aus den Apps.

Aufbau der erzeugten Seite

[[Hauptseite]] > [[Hauptseite#Sport|Sport]] > [[Stockschießen]] <br>
<br>
<div class="wikimap" data-aktiv-ort="Vereinsname" data-filter="Stockschießen"></div>

(auskommentierter Block für WikiMap-Orte.json)

📍 Adresse<br><br>
[[file:osm.png|24px|link=]] OpenStreetMap-Link<br><br>
🧭 Breitengrad, Längengrad<br><br>
[[file:CoMaps.png|24px|link=]] CoMaps-Link<br><br>
[[file:GoogleMaps.png|24px|link=]] Google-Maps-Link<br><br>
📞 Telefonnummer<br><br>
📧 E-Mail-Adresse<br><br>
🌐 Website

Die eigentlichen Inhaltsabschnitte – Geschichte, Anlage, Mannschaften, Fotos – werden bewusst nicht erzeugt und von Hand ergänzt.

Koordinatenformate

Das Eingabefeld erkennt die gängigen Schreibweisen, egal aus welcher App kopiert:

  • Dezimalgrad: 47.69694, 16.00583 oder mit Leerzeichen getrennt
  • Dezimalgrad mit deutschem Komma: 47,69694 16,00583
  • mit Himmelsrichtung: 47.69694N 16.00583E
  • Grad/Minuten: 47°41.816'N 16°00.350'E
  • Grad/Minuten/Sekunden: 47°41'49.0"N 16°00'21.0"E

Negative Werte sowie S und W werden korrekt als Süd beziehungsweise West gewertet, O gilt wie E als Ost. Wird eine Eingabe nicht erkannt, erscheint über dem Ausgabefeld ein Warnhinweis und die Auto-Links unterbleiben.

Die Ausgabe wird auf sieben Nachkommastellen gerundet – das entspricht etwa einem Zentimeter und ist für jede Kartenanwendung mehr als genau genug.

Fehlersuche

Formular erscheint nicht
Fast immer der Cache – Strg+Umschalt+R. Sonst prüfen, ob der Seitentitel exakt TRIGGER_PAGE entspricht und ob die Zeile in MediaWiki:Gadgets-definition im richtigen Format steht (Dateiname ohne das Präfix Gadget-).
Karte bleibt leer
Der Ort steht noch nicht in MediaWiki:WikiMap-Orte.json, oder id und data-aktiv-ort weichen voneinander ab – schon ein zusätzliches Leerzeichen genügt.
Pin fehlt, Karte ist aber da
Die Kategorie im JSON-Eintrag stimmt nicht mit data-filter überein. Beide Werte kommen aus derselben Konstante, insofern liegt der Fehler meist in einem von Hand nachbearbeiteten JSON-Eintrag.
Alles steht in einer Zeile
Die <br> wurden beim Kopieren verschluckt. Wikitext fügt bei einfachen Zeilenumbrüchen von sich aus keinen Umbruch ein – die Tags sind zwingend nötig.
JSON-Block wird auf der Seite angezeigt
Der Kommentar ist unvollständig kopiert worden; es fehlt <!-- oder -->.

Technisches

Reines JavaScript ohne Abhängigkeiten, kein OOUI, kein jQuery, kein mw.Api. Das Gadget liest lediglich wgPageName aus, um zu entscheiden, ob es sich überhaupt einhängt. Auf allen anderen Seiten bricht es sofort ab und kostet damit praktisch nichts.

Das Stylesheet wird vom Skript selbst eingefügt; eine eigene .css-Seite ist nicht erforderlich.

Der Kopieren-Knopf nutzt die Clipboard-API mit document.execCommand als Rückfallebene für ältere Browser. Beides setzt eine gesicherte Verbindung voraus – über http:// ohne TLS bleibt das Textfeld nur markiert und muss von Hand kopiert werden.


MediaWiki:Gadget-vereinform


/**
 * Gadget-vereinform
 *
 * Formular zum Erzeugen des Grundgerüsts einer Vereinsseite.
 * Es wird NICHTS gespeichert oder angelegt - das Gadget gibt nur Wikicode aus,
 * den man kopiert und selbst auf die Seite einfügt.
 *
 * Aktiv auf der Seite "VereinForm".
 * Ankerpunkt (optional) auf dieser Seite:  <div id="verein-form-container"></div>
 */
( function () {
	'use strict';

	/* ------------------------------------------------------------------ *
	 * Konfiguration - hier und nur hier anpassen
	 * ------------------------------------------------------------------ */

	// Seite, auf der das Formular erscheint
	var TRIGGER_PAGE = 'VereinForm';

	// Kartenkategorie. Steuert BEIDES: data-filter im wikimap-div
	// UND das Feld "kategorie" im JSON-Block.
	// Muss exakt so in MediaWiki:WikiMap-Orte.json stehen.
	var KATEGORIE = 'Stockschießen';

	// Vorbelegung der Brotkrümel-Navigation
	var STD_RUBRIK    = 'Sport';          // Anker auf der Hauptseite
	var STD_OBERSEITE = 'Stockschießen';  // Seite eine Ebene darüber

	/* ------------------------------------------------------------------ */

	if ( mw.config.get( 'wgPageName' ) !== TRIGGER_PAGE ) {
		return;
	}

	/* --- Hilfsfunktionen ---------------------------------------------- */

	function v( val ) {
		return ( val || '' ).trim();
	}

	// Grad/Minuten/Sekunden -> Dezimalgrad
	function dms( g, m, s ) {
		var sign = g < 0 ? -1 : 1;
		return sign * ( Math.abs( g ) + ( m || 0 ) / 60 + ( s || 0 ) / 3600 );
	}

	/**
	 * Parst die gängigen Koordinatenformate:
	 *   47.69694, 16.00583      47.69694 16.00583      47,69694 16,00583
	 *   47.75N 16.08E           N 47.75 E 16.08
	 *   47°45'00.0"N 16°04'48.0"E        47°45.000'N 16°04.800'E
	 * Rückgabe { lat, lon } oder null.
	 */
	function parseKoordinaten( text ) {
		if ( !text ) {
			return null;
		}
		var s = String( text ).trim();

		// deutsche Dezimalkommas ("47,75 16,08") -> Punkte
		if ( /^-?\d+,\d+[\s;]+-?\d+,\d+/.test( s ) ) {
			s = s.replace( /,/g, '.' );
		}

		var hems = ( s.match( /[NSEWOnsewo]/g ) || [] ).map( function ( h ) {
			return h.toUpperCase();
		} );
		var nums = ( s.match( /-?\d+(?:\.\d+)?/g ) || [] ).map( parseFloat );

		if ( nums.length < 2 ) {
			return null;
		}

		var lat, lon;
		if ( nums.length >= 6 ) {           // Grad Minuten Sekunden
			lat = dms( nums[ 0 ], nums[ 1 ], nums[ 2 ] );
			lon = dms( nums[ 3 ], nums[ 4 ], nums[ 5 ] );
		} else if ( nums.length >= 4 ) {    // Grad Minuten
			lat = dms( nums[ 0 ], nums[ 1 ], 0 );
			lon = dms( nums[ 2 ], nums[ 3 ], 0 );
		} else {                            // Dezimalgrad
			lat = nums[ 0 ];
			lon = nums[ 1 ];
		}

		if ( hems.length >= 2 ) {
			if ( hems[ 0 ] === 'S' ) { lat = -Math.abs( lat ); }
			if ( hems[ 0 ] === 'N' ) { lat =  Math.abs( lat ); }
			if ( hems[ 1 ] === 'W' ) { lon = -Math.abs( lon ); }
			if ( hems[ 1 ] === 'E' || hems[ 1 ] === 'O' ) { lon = Math.abs( lon ); }
		}

		if ( !isFinite( lat ) || !isFinite( lon ) ) { return null; }
		if ( Math.abs( lat ) > 90 || Math.abs( lon ) > 180 ) { return null; }

		return { lat: lat, lon: lon };
	}

	// Zahl ohne überflüssige Nullen
	function fmt( n ) {
		return String( parseFloat( n.toFixed( 7 ) ) );
	}

	/* --- Formular aufbauen -------------------------------------------- */

	var felder = {};

	function feld( key, label, platzhalter, hinweis ) {
		var wrap  = document.createElement( 'div' );
		wrap.className = 'vf-feld';

		var lab   = document.createElement( 'label' );
		lab.textContent = label;

		var input = document.createElement( 'input' );
		input.type = 'text';
		input.placeholder = platzhalter || '';
		input.className = 'vf-input';

		lab.appendChild( input );
		wrap.appendChild( lab );

		if ( hinweis ) {
			var kl = document.createElement( 'div' );
			kl.className = 'vf-hinweis';
			kl.textContent = hinweis;
			wrap.appendChild( kl );
		}

		felder[ key ] = input;
		input.addEventListener( 'input', aktualisiere );
		return wrap;
	}

	function baueWikitext() {
		var name    = v( felder.name.value );
		var rubrik  = v( felder.rubrik.value )    || STD_RUBRIK;
		var ober    = v( felder.oberseite.value ) || STD_OBERSEITE;
		var telefon = v( felder.telefon.value );
		var email   = v( felder.email.value );
		var website = v( felder.website.value );
		var adresse = v( felder.adresse.value );
		var koord   = parseKoordinaten( felder.koordinaten.value );

		var osm    = v( felder.osm.value );
		var comaps = v( felder.comaps.value );
		var gmaps  = v( felder.gmaps.value );

		var lat = koord ? fmt( koord.lat ) : '';
		var lon = koord ? fmt( koord.lon ) : '';

		// Automatische Links, wenn die Felder leer gelassen wurden
		if ( koord ) {
			if ( !osm ) {
				osm = 'https://www.openstreetmap.org/?mlat=' + lat + '&mlon=' + lon +
					'#map=17/' + lat + '/' + lon;
			}
			if ( !comaps ) {
				comaps = 'https://comaps.at/?lat=' + lat + '&lon=' + lon;
			}
			if ( !gmaps ) {
				gmaps = 'https://www.google.com/maps?q=' + lat + ',' + lon;
			}
		}

		var z = [];

		// Brotkrümel
		z.push( '[[Hauptseite]] > [[Hauptseite#' + rubrik + '|' + rubrik + ']] > [[' + ober + ']] <br>' );
		z.push( '<br>' );

		// Karte
		z.push( '<div class="wikimap" data-aktiv-ort="' + ( name || 'NAME' ) +
			'" data-filter="' + KATEGORIE + '"></div>' );

		// Ausschneide-Block für MediaWiki:WikiMap-Orte.json
		z.push( '' );
		z.push( '<!-- Eintrag für MediaWiki:WikiMap-Orte.json – dort einfügen, danach diesen Block hier löschen' );
		z.push( '\t\t{' );
		z.push( '\t\t\t"id": ' + JSON.stringify( name || 'NAME' ) + ',' );
		z.push( '\t\t\t"geo": "' + lat + ',' + lon + '",' );
		z.push( '\t\t\t"kategorie": ' + JSON.stringify( KATEGORIE ) );
		z.push( '\t\t},' );
		z.push( '-->' );
		z.push( '' );

		// Standort, danach Kontakt - jeder Punkt durch <br><br> getrennt
		var eintraege = [];
		if ( adresse ) { eintraege.push( '📍 ' + adresse ); }
		if ( osm )     { eintraege.push( '[[file:osm.png|24px|link=]] ' + osm ); }
		if ( koord )   { eintraege.push( '🧭 ' + lat + ', ' + lon ); }
		if ( comaps )  { eintraege.push( '[[file:CoMaps.png|24px|link=]] ' + comaps ); }
		if ( gmaps )   { eintraege.push( '[[file:GoogleMaps.png|24px|link=]] ' + gmaps ); }
		if ( telefon ) { eintraege.push( '📞 ' + telefon ); }
		if ( email )   { eintraege.push( '📧 ' + email ); }
		if ( website ) { eintraege.push( '🌐 ' + website ); }
		if ( eintraege.length ) {
			z.push( eintraege.join( '<br><br>\n' ) );
		}

		return z.join( '\n' ) + '\n';
	}

	var ausgabe, status;

	function aktualisiere() {
		ausgabe.value = baueWikitext();

		var meldungen = [];
		if ( !v( felder.name.value ) ) {
			meldungen.push( 'Vereinsname fehlt' );
		}
		if ( v( felder.koordinaten.value ) && !parseKoordinaten( felder.koordinaten.value ) ) {
			meldungen.push( 'Koordinaten nicht erkannt' );
		} else if ( !v( felder.koordinaten.value ) ) {
			meldungen.push( 'ohne Koordinaten kein Kartenpin und keine Auto-Links' );
		}
		status.textContent = meldungen.length ? '⚠ ' + meldungen.join( ' · ' ) : '';
	}

	function init() {
		var ziel = document.getElementById( 'verein-form-container' ) ||
			document.getElementById( 'mw-content-text' );
		if ( !ziel ) {
			return;
		}

		var style = document.createElement( 'style' );
		style.textContent =
			'.vf-box{border:1px solid #a2a9b1;background:#f8f9fa;padding:1em;margin:1em 0;max-width:60em}' +
			'.vf-feld{margin:0 0 .7em}' +
			'.vf-feld label{display:block;font-weight:bold}' +
			'.vf-input{display:block;width:100%;box-sizing:border-box;padding:.3em;margin-top:.2em}' +
			'.vf-hinweis{font-size:.85em;color:#54595d;margin-top:.15em}' +
			'.vf-trenner{margin:1.2em 0 .6em;font-weight:bold;border-bottom:1px solid #c8ccd1}' +
			'.vf-ausgabe{width:100%;box-sizing:border-box;height:18em;font-family:monospace;font-size:.9em}' +
			'.vf-status{color:#b32424;margin:.4em 0;min-height:1.2em}';
		document.head.appendChild( style );

		var box = document.createElement( 'div' );
		box.className = 'vf-box';

		function trenner( text ) {
			var d = document.createElement( 'div' );
			d.className = 'vf-trenner';
			d.textContent = text;
			return d;
		}

		box.appendChild( trenner( 'Verein' ) );
		box.appendChild( feld( 'name', 'Vereinsname', '1. Neunkirchner ESK',
			'Wird zugleich Seitentitel und id auf der Karte – überall exakt gleich schreiben.' ) );
		box.appendChild( feld( 'rubrik', 'Navigation: Rubrik auf der Hauptseite', STD_RUBRIK ) );
		box.appendChild( feld( 'oberseite', 'Navigation: übergeordnete Seite', STD_OBERSEITE ) );
		box.appendChild( feld( 'telefon', '📞 Telefonnummer', '+43 2635 12345' ) );
		box.appendChild( feld( 'email', '📧 E-Mail-Adresse', 'verein@example.at' ) );
		box.appendChild( feld( 'website', '🌐 Website', 'https://…' ) );

		box.appendChild( trenner( 'Standort' ) );
		box.appendChild( feld( 'koordinaten', '🧭 Koordinaten',
			'47.72109887633516, 16.07317540344262',
			'Format egal – Dezimalgrad, Grad/Minuten/Sekunden, mit oder ohne N/E.' ) );
		box.appendChild( feld( 'adresse', '📍 Adresse', '2620 Neunkirchen, Musterstraße 1' ) );

		box.appendChild( trenner( 'Kartenlinks (leer lassen = aus Koordinaten erzeugen)' ) );
		box.appendChild( feld( 'osm', 'OpenStreetMap', 'leer = automatisch' ) );
		box.appendChild( feld( 'comaps', 'CoMaps', 'leer = automatisch' ) );
		box.appendChild( feld( 'gmaps', 'Google Maps', 'leer = automatisch' ) );

		box.appendChild( trenner( 'Wikicode' ) );

		status = document.createElement( 'div' );
		status.className = 'vf-status';
		box.appendChild( status );

		ausgabe = document.createElement( 'textarea' );
		ausgabe.className = 'vf-ausgabe';
		box.appendChild( ausgabe );

		var knopf = document.createElement( 'button' );
		knopf.type = 'button';
		knopf.textContent = 'In die Zwischenablage kopieren';
		knopf.style.marginTop = '.5em';
		knopf.addEventListener( 'click', function () {
			ausgabe.select();
			if ( navigator.clipboard && navigator.clipboard.writeText ) {
				navigator.clipboard.writeText( ausgabe.value );
			} else {
				document.execCommand( 'copy' );
			}
			knopf.textContent = 'kopiert ✓';
			setTimeout( function () {
				knopf.textContent = 'In die Zwischenablage kopieren';
			}, 1500 );
		} );
		box.appendChild( knopf );

		ziel.insertBefore( box, ziel.firstChild );
		aktualisiere();
	}

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

}() );