Vereinform: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) |
Admin (Diskussion | Beiträge) |
||
| Zeile 139: | Zeile 139: | ||
Der Kopieren-Knopf nutzt die Clipboard-API mit <code>document.execCommand</code> als Rückfallebene für ältere Browser. Beides setzt eine gesicherte Verbindung voraus – über <code>http://</code> ohne TLS bleibt das Textfeld nur markiert und muss von Hand kopiert werden. | Der Kopieren-Knopf nutzt die Clipboard-API mit <code>document.execCommand</code> als Rückfallebene für ältere Browser. Beides setzt eine gesicherte Verbindung voraus – über <code>http://</code> ohne TLS bleibt das Textfeld nur markiert und muss von Hand kopiert werden. | ||
==MediaWiki:Gadget-vereinform== | |||
<pre> | |||
/** | |||
* 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(); | |||
} | |||
}() ); | |||
</pre> | |||
Version vom 31. Juli 2026, 14:00 Uhr
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.jsonmuss 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.pngDatei:CoMaps.pngDatei: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
- Seite
VereinFormaufrufen. - 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.
- Den Wikicode kopieren (Knopf oder Strg+C) und auf der Vereinsseite einfügen.
- Den auskommentierten JSON-Block aus dem eingefügten Text herausschneiden und in
MediaWiki:WikiMap-Orte.jsonin die Listeorteeinfü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.00583oder 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_PAGEentspricht und ob die Zeile inMediaWiki:Gadgets-definitionim richtigen Format steht (Dateiname ohne das PräfixGadget-).
- Karte bleibt leer
- Der Ort steht noch nicht in
MediaWiki:WikiMap-Orte.json, oderidunddata-aktiv-ortweichen 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();
}
}() );