Keine Bearbeitungszusammenfassung
Markierung: Zurückgesetzt
Änderung 1341 von Admin (Diskussion) rückgängig gemacht.
Markierung: Rückgängigmachung
 
Zeile 1: Zeile 1:
/**
/**
  * Gadget-Mastodon.js
  * Gadget-MakerTube.js
  * ============================================================================
  * ============================================================================
  * Bettet Mastodon-Beiträge (samt Video oder anderen Medien) in Wiki-Seiten
  * Bettet Videos von makertube.net ein (eine PeerTube-Instanz).
* ein – instanzunabhängig, also mastodon.social genauso wie jede andere
* Instanz. Verwendet die eingebaute /embed-Ansicht von Mastodon.
  *
  *
  * KEIN ZUSATZSKRIPT DES ANBIETERS
  * ZWEI-KLICK-SPERRE
  * Die offizielle Einbindung lädt embed.js der Instanz nach, nur um die Höhe
  * Der Player liegt auf makertube.net. Beim Laden erfährt der Server die
  * des Rahmens anzupassen. Das macht dieses Gadget selbst: Mastodon beantwortet
  * IP-Adresse des Besuchers. Deshalb erscheint zuerst eine Vorschaubox; erst
  * eine postMessage mit seiner tatsächlichen Höhe. Es wird also genau eine
  * der Klick baut die Verbindung auf. Auf Wunsch merkt sich der Browser die
  * Adresse der Instanz abgerufen und sonst nichts.
* Freigabe für die laufende Sitzung (sessionStorage – kein Cookie, kein
  * Banner); sie endet mit dem Schließen des Browsers.
  *
  *
  * KEINE ZWEI-KLICK-SPERRE – warum nicht?
  * PEER-TO-PEER (das eigentliche Prinzip von PeerTube)
  * Die /embed-Ansicht ist eine öffentliche, cachebare Seite ohne Werbung,
  * PeerTube verteilt Videos nicht nur vom Server, sondern auch zwischen den
  * ohne Reichweitenmessung und ohne Profilbildung; für anonyme Besucher
  * Zuschauern (WebTorrent). Das entlastet kleine Instanzen – und macht die
* setzt sie keine Cookies. Damit greift die Einwilligungspflicht des
  * IP-Adresse für die anderen Zuschauer desselben Videos sichtbar. Weil das
  * §165 TKG nicht, und für die IP-Übertragung beim Abruf trägt das
  * eine echte Abwägung ist, entscheidet sie der Besucher selbst: in der
  * berechtigte Interesse – wie beim CCC-Gadget. Anders bei fahrplan.oebb.at
  * Vorschaubox steht ein zweites Häkchen dafür.
  * und makertube.net, die ihre Vorschaubox behalten haben.
  *
  *
  * NOTAUSGANG: data-sperre="ja" schaltet für eine einzelne Einbettung doch
  * Voreinstellung je Video über data-p2p:
  * eine Vorschaubox mit Ladeknopf davor gedacht für Instanzen, deren
*  nicht gesetzt / "0"  => Häkchen ist zu Beginn leer (Standard)
  * Betreiber du nicht einschätzen kannst.
"1"                 => Häkchen ist zu Beginn gesetzt; sinnvoll bei
  *                           Videos mit erwartbarem Zulauf, wo das Teilen
*                          tatsächlich etwas bringt
* Der Besucher kann es in beide Richtungen ändern data-p2p legt nur fest,
  * was vorausgewählt ist, nie was passiert.
  *
  *
  * EINBINDUNG AUF EINER SEITE
  * EINBINDUNG AUF EINER SEITE
  *  <div class="mastodon-embed"
  *  <div class="makertube-embed" data-video="a1b2c3d4-"></div>
*        data-post="https://mastodon.social/@benutzer/123456789012345678"></div>
*
* Alternativ getrennt:
*  <div class="mastodon-embed" data-instanz="mastodon.social"
*        data-benutzer="benutzer" data-id="123456789012345678"></div>
  *
  *
  * data-Attribute:
  * data-Attribute:
  *  data-post      Vollständige Adresse des Beitrags (empfohlen).
  *  data-video  Pflicht. UUID oder Kurzkennung aus der Adresse des Videos
  *   data-instanz  Hostname der Instanz, falls data-post nicht genutzt wird.
  *               (der Teil hinter /w/ bzw. /videos/watch/).
*  data-benutzer  Benutzername ohne @, zusammen mit data-instanz/data-id.
  *  data-width   Breite in Pixeln (Standard 560). "100%" ist erlaubt.
*  data-id        Beitrags-ID, zusammen mit data-instanz/data-benutzer.
  *  data-height Höhe in Pixeln (Standard 315).
  *  data-width     Breite (Standard 100%, höchstens 42em).
  *  data-titel   Überschrift der Vorschaubox (Standard: "Video").
  *  data-height   Starthöhe in Pixeln (Standard 600). Die endgültige Höhe
  *  data-p2p    "1" => Teilen-Häkchen ist vorausgewählt (Standard: leer).
*                  meldet Mastodon selbst, sobald der Beitrag geladen ist.
*  data-start  Startzeit in Sekunden.
  *  data-titel     Titelzeile des Rahmens, für Screenreader
*                  (Standard: "Mastodon-Beitrag").
  *  data-sperre    "ja" => Vorschaubox mit Ladeknopf davorschalten.
  * ============================================================================
  * ============================================================================
  */
  */
Zeile 48: Zeile 42:
'use strict';
'use strict';


var STD_HOEHE = 600;
var INSTANZ = 'https://makertube.net';
var STD_BREITE = 560;
var STD_HOEHE = 315;
 
var SPEICHER = 'wiki-freigabe-makertube';
var SPEICHER_P2P = 'wiki-makertube-p2p';
var DATENSCHUTZ = 'https://makertube.net/about/instance';
 
/* Noch gesperrte Einbettungen dieser Seite – damit ein "merken"-Klick
* auch weitere Videos derselben Seite 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 ) {}
}
 
/* Gemerkte P2P-Entscheidung: true, false – oder null, wenn der Besucher
* in dieser Sitzung noch nichts entschieden hat. */
function gemerktesP2p() {
try {
var wert = window.sessionStorage.getItem( SPEICHER_P2P );
if ( wert === 'ja' ) {
return true;
}
if ( wert === 'nein' ) {
return false;
}
} catch ( e ) {}
return null;
}
 
function setzeP2p( ja, merken ) {
try {
if ( !merken ) {
window.sessionStorage.removeItem( SPEICHER_P2P );
} else {
window.sessionStorage.setItem( SPEICHER_P2P, ja ? 'ja' : 'nein' );
}
} catch ( e ) {}
}


/* Laufende Rahmen für die Höhenmeldung: Kennnummer -> iframe */
function gibAlleFrei( p2p ) {
var rahmenListe = {};
var liste = wartende.slice();
var naechsteNummer = 0;
wartende.length = 0;
var hoertZu = false;
liste.forEach( function ( fn ) {
fn( p2p );
} );
}


function stil( el, regeln ) {
function stil( el, regeln ) {
Zeile 65: Zeile 115:
function masszahl( wert, standard ) {
function masszahl( wert, standard ) {
if ( wert === undefined || wert === null || wert === '' ) {
if ( wert === undefined || wert === null || wert === '' ) {
return standard;
return standard + 'px';
}
}
var text = String( wert ).trim();
var text = String( wert ).trim();
Zeile 71: Zeile 121:
}
}


/* ------------------------------------------------------------------ *
function baueUrl( cfg, p2p ) {
* Adresse des Beitrags auswerten.
var url = INSTANZ + '/videos/embed/' + encodeURIComponent( cfg.id ) +
* Erlaubt sind die üblichen Mastodon-Formen:
'?p2p=' + ( p2p ? '1' : '0' ) +
*  https://instanz/@benutzer/123…
'&warningTitle=0';
*  https://instanz/@benutzer@andereinstanz/123…
if ( cfg.start ) {
*  https://instanz/users/benutzer/statuses/123…
url += '&start=' + encodeURIComponent( cfg.start );
* Zurück kommt { host, seite, embed } oder null.
* ------------------------------------------------------------------ */
function leseAdresse( roh ) {
var text = String( roh || '' ).trim();
if ( !/^https:\/\//i.test( text ) ) {
return null;
}
}
var a = document.createElement( 'a' );
return url;
a.href = text;
if ( !a.hostname || a.protocol !== 'https:' ) {
return null;
}
var pfad = a.pathname.replace( /\/+$/, '' ).replace( /\/embed$/, '' );
if ( !/^\/(@[^/]+\/\d+|users\/[^/]+\/statuses\/\d+)$/.test( pfad ) ) {
return null;
}
var seite = 'https://' + a.hostname + pfad;
return { host: a.hostname, seite: seite, embed: seite + '/embed' };
}
}


/* ------------------------------------------------------------------ *
/* --- Vorschaubox (noch nichts geladen) ------------------------------ */
* Höhenmeldung von Mastodon entgegennehmen. Das eingebettete Mastodon
function sperre( container, cfg ) {
* antwortet auf { type: "setHeight", id: … } mit derselben Nachricht
var erledigt = false;
* samt height – so sparen wir uns das embed.js des Anbieters.
* ------------------------------------------------------------------ */
function starteZuhoerer() {
if ( hoertZu ) {
return;
}
hoertZu = true;
window.addEventListener( 'message', function ( e ) {
var daten = e.data || {};
if ( daten.type !== 'setHeight' || !rahmenListe[ daten.id ] ) {
return;
}
var hoehe = parseInt( daten.height, 10 );
if ( isFinite( hoehe ) && hoehe > 0 ) {
rahmenListe[ daten.id ].style.height = hoehe + 'px';
}
} );
}


/* --- Beitrag einhängen ------------------------------------------------ */
function lade( container, cfg ) {
container.textContent = '';
starteZuhoerer();
var nummer = 'wiki-mastodon-' + ( naechsteNummer++ );
var iframe = document.createElement( 'iframe' );
iframe.src = cfg.embed;
iframe.title = cfg.titel + ' (' + cfg.host + ')';
iframe.setAttribute( 'frameborder', '0' );
iframe.setAttribute( 'scrolling', 'no' );
iframe.setAttribute( 'allowfullscreen', 'allowfullscreen' );
iframe.setAttribute( 'allow', 'fullscreen' );
iframe.setAttribute( 'sandbox',
'allow-same-origin allow-scripts allow-popups' );
iframe.setAttribute( 'loading', 'lazy' );
iframe.setAttribute( 'referrerpolicy', 'no-referrer' );
stil( iframe, {
width: cfg.breite,
maxWidth: '42em',
height: cfg.hoehe,
border: '0'
} );
rahmenListe[ nummer ] = iframe;
iframe.addEventListener( 'load', function () {
try {
iframe.contentWindow.postMessage(
{ type: 'setHeight', id: nummer }, 'https://' + cfg.host
);
} catch ( e ) {}
} );
container.appendChild( iframe );
// Rückfallebene: wer das iframe blockiert, bekommt wenigstens den Weg
// zum Beitrag.
var fuss = stil( document.createElement( 'div' ), {
fontSize: '85%',
color: '#54595d',
marginTop: '0.4em'
} );
var link = document.createElement( 'a' );
link.href = cfg.seite;
link.target = '_blank';
link.rel = 'noopener';
link.textContent = 'Beitrag auf ' + cfg.host + ' öffnen';
fuss.appendChild( link );
container.appendChild( fuss );
}
/* --- Notausgang: Vorschaubox statt sofortigem Laden -------------------- */
function sperre( container, cfg ) {
container.textContent = '';
container.textContent = '';


Zeile 183: Zeile 145:
boxSizing: 'border-box'
boxSizing: 'border-box'
} );
} );
box.className = 'mastodon-embed-sperre';
box.className = 'makertube-embed-sperre';


var kopf = stil( document.createElement( 'div' ), {
var kopf = stil( document.createElement( 'div' ), {
Zeile 190: Zeile 152:
marginBottom: '0.4em'
marginBottom: '0.4em'
} );
} );
kopf.textContent = cfg.titel + ' von ' + cfg.host;
kopf.textContent = cfg.titel + ' auf MakerTube';
box.appendChild( kopf );
box.appendChild( kopf );


var text = stil( document.createElement( 'p' ), { margin: '0.4em 0' } );
var text = stil( document.createElement( 'p' ), { margin: '0.4em 0' } );
text.textContent = 'Der Beitrag wird direkt von der Mastodon-Instanz ' +
text.textContent = 'Das Video liegt auf makertube.net, einer unabhängigen ' +
cfg.host + ' geladen – einem Server, der nicht uns gehört. Dabei ' +
'PeerTube-Instanz. Beim Abspielen erfährt dieser Server deine ' +
'erfährt dessen Betreiber deine IP-Adresse und welchen Beitrag du ' +
'IP-Adresse und welches Video du ansiehst. Vor deinem Klick wird ' +
'ansiehst. Vor deinem Klick geht keine Anfrage dorthin.';
'nichts dorthin übertragen.';
box.appendChild( text );
box.appendChild( text );
/* --- Häkchen 1: Peer-to-Peer --- */
var hakenP2p = document.createElement( 'input' );
hakenP2p.type = 'checkbox';
hakenP2p.checked = cfg.p2pVorgewaehlt;
var p2pLabel = stil( document.createElement( 'label' ), {
display: 'block',
margin: '0.9em 0 0.2em 0',
fontSize: '92%'
} );
p2pLabel.appendChild( hakenP2p );
p2pLabel.appendChild( document.createTextNode(
' Bandbreite mit anderen Zuschauern teilen (Peer-to-Peer)'
) );
box.appendChild( p2pLabel );
var p2pHinweis = stil( document.createElement( 'div' ), {
fontSize: '85%',
marginLeft: '1.7em',
marginBottom: '0.3em'
} );
function zeigeP2pHinweis() {
if ( hakenP2p.checked ) {
p2pHinweis.style.color = '#b32424';
p2pHinweis.textContent = 'So ist PeerTube gedacht: du lädst Teile ' +
'des Videos von anderen Zuschauern und gibst sie weiter, was ' +
'die Instanz entlastet. Dabei wird deine IP-Adresse für diese ' +
'anderen Zuschauer sichtbar.';
} else {
p2pHinweis.style.color = '#54595d';
p2pHinweis.textContent = 'Ohne Häkchen lädst du ausschließlich vom ' +
'Server von makertube.net; andere Zuschauer sehen deine ' +
'IP-Adresse nicht.';
}
}
zeigeP2pHinweis();
hakenP2p.addEventListener( 'change', zeigeP2pHinweis );
box.appendChild( p2pHinweis );
/* --- Häkchen 2: für die Sitzung merken --- */
var hakenMerken = document.createElement( 'input' );
hakenMerken.type = 'checkbox';
var merkenLabel = stil( document.createElement( 'label' ), {
display: 'block',
margin: '0.7em 0',
fontSize: '92%'
} );
merkenLabel.appendChild( hakenMerken );
merkenLabel.appendChild( document.createTextNode(
' Diese Entscheidung für die Sitzung merken – gilt dann samt ' +
'Teilen-Einstellung für alle MakerTube-Videos im Wiki und endet, ' +
'sobald du den Browser schließt. Es wird kein Cookie gesetzt.'
) );
box.appendChild( merkenLabel );


var knopf = stil( document.createElement( 'button' ), {
var knopf = stil( document.createElement( 'button' ), {
Zeile 206: Zeile 225:
} );
} );
knopf.type = 'button';
knopf.type = 'button';
knopf.textContent = 'Beitrag laden';
knopf.textContent = 'Video laden und abspielen';
knopf.addEventListener( 'click', function () {
lade( container, cfg );
} );
box.appendChild( stil( document.createElement( 'div' ),
box.appendChild( stil( document.createElement( 'div' ),
{ margin: '0.7em 0' } ) ).appendChild( knopf );
{ margin: '0.7em 0' } ) ).appendChild( knopf );
Zeile 218: Zeile 234:
marginTop: '0.6em'
marginTop: '0.6em'
} );
} );
var link = document.createElement( 'a' );
var dsLink = document.createElement( 'a' );
link.href = cfg.seite;
dsLink.href = DATENSCHUTZ;
link.target = '_blank';
dsLink.target = '_blank';
link.rel = 'noopener';
dsLink.rel = 'noopener';
link.textContent = 'Stattdessen direkt auf ' + cfg.host + ' ansehen';
dsLink.textContent = 'Über die Instanz makertube.net';
fuss.appendChild( link );
fuss.appendChild( dsLink );
fuss.appendChild( document.createTextNode( ' · ' ) );
 
var direkt = document.createElement( 'a' );
direkt.href = INSTANZ + '/w/' + cfg.id;
direkt.target = '_blank';
direkt.rel = 'noopener';
direkt.textContent = 'Stattdessen direkt auf MakerTube ansehen';
fuss.appendChild( direkt );
box.appendChild( fuss );
box.appendChild( fuss );


container.appendChild( box );
container.appendChild( box );
function freigeben( p2p ) {
if ( erledigt ) {
return;
}
erledigt = true;
lade( container, cfg, p2p );
}
wartende.push( freigeben );
knopf.addEventListener( 'click', function () {
var p2p = hakenP2p.checked;
if ( hakenMerken.checked ) {
setzeFreigabe( true );
setzeP2p( p2p, true );
gibAlleFrei( p2p );
} else {
freigeben( p2p );
}
} );
}
}


/* --- Einstieg ---------------------------------------------------------- */
/* --- Geladener Player ----------------------------------------------- */
function lade( container, cfg, p2p ) {
container.textContent = '';
 
var iframe = document.createElement( 'iframe' );
iframe.src = baueUrl( cfg, p2p );
iframe.title = cfg.titel;
iframe.setAttribute( 'frameborder', '0' );
iframe.setAttribute( 'allowfullscreen', 'allowfullscreen' );
iframe.setAttribute( 'allow', 'fullscreen; picture-in-picture' );
iframe.setAttribute( 'sandbox',
'allow-same-origin allow-scripts allow-popups allow-forms' );
iframe.setAttribute( 'referrerpolicy', 'no-referrer' );
stil( iframe, {
width: cfg.breite,
maxWidth: '100%',
height: cfg.hoehe,
border: '0'
} );
container.appendChild( iframe );
 
var fuss = stil( document.createElement( 'div' ), {
fontSize: '85%',
color: '#54595d',
marginTop: '0.4em'
} );
fuss.appendChild( document.createTextNode(
p2p ? 'Peer-to-Peer ist eingeschaltet. · ' :
'Peer-to-Peer ist abgeschaltet. · '
) );
 
var zurueck = document.createElement( 'a' );
zurueck.href = '#';
zurueck.textContent = 'Verbindung zu makertube.net wieder sperren';
zurueck.addEventListener( 'click', function ( e ) {
e.preventDefault();
setzeFreigabe( false );
setzeP2p( false, false );
sperre( container, cfg );
} );
fuss.appendChild( zurueck );
container.appendChild( fuss );
}
 
/* --- Einstieg -------------------------------------------------------- */
function baueEinbettung( container ) {
function baueEinbettung( container ) {
var $c = $( container );
var $c = $( container );
var roh = $.trim( String( $c.data( 'post' ) || '' ) );
var id = $.trim( String( $c.data( 'video' ) || '' ) );
 
if ( !roh ) {
var instanz = $.trim( String( $c.data( 'instanz' ) || '' ) )
.replace( /^https?:\/\//i, '' ).replace( /\/+$/, '' );
var benutzer = $.trim( String( $c.data( 'benutzer' ) || '' ) )
.replace( /^@/, '' );
var id = $.trim( String( $c.data( 'id' ) || '' ) );
if ( instanz && benutzer && id ) {
roh = 'https://' + instanz + '/@' + benutzer + '/' + id;
}
}


var adresse = leseAdresse( roh );
if ( !id ) {
if ( !adresse ) {
container.textContent = 'MakerTube-Einbettung: kein data-video angegeben.';
container.textContent = 'Mastodon-Einbettung: keine gültige ' +
'Beitragsadresse angegeben (erwartet z. B. ' +
'https://mastodon.social/@benutzer/123456789012345678).';
return;
return;
}
}


var cfg = {
var cfg = {
host: adresse.host,
id: id,
seite: adresse.seite,
titel: $.trim( String( $c.data( 'titel' ) || '' ) ) || 'Video',
embed: adresse.embed,
breite: masszahl( $c.data( 'width' ), STD_BREITE ),
titel: $.trim( String( $c.data( 'titel' ) || '' ) ) || 'Mastodon-Beitrag',
hoehe: masszahl( $c.data( 'height' ), STD_HOEHE ),
breite: masszahl( $c.data( 'width' ), '100%' ),
p2pVorgewaehlt: String( $c.data( 'p2p' ) || '' ) === '1',
hoehe: masszahl( $c.data( 'height' ), STD_HOEHE + 'px' )
start: $.trim( String( $c.data( 'start' ) || '' ) )
};
};


if ( String( $c.data( 'sperre' ) || '' ) === 'ja' ) {
if ( freigegeben() ) {
// Gemerkte Entscheidung des Besuchers schlägt die Seiten-Vorauswahl.
var gemerkt = gemerktesP2p();
lade( container, cfg,
gemerkt === null ? cfg.p2pVorgewaehlt : gemerkt );
} else {
sperre( container, cfg );
sperre( container, cfg );
} else {
lade( container, cfg );
}
}
}
}


$( function () {
$( function () {
$( '.mastodon-embed' ).each( function () {
$( '.makertube-embed' ).each( function () {
try {
try {
baueEinbettung( this );
baueEinbettung( this );
} catch ( e ) {
} catch ( e ) {
this.textContent = 'Mastodon-Beitrag konnte nicht geladen werden.';
this.textContent = 'MakerTube-Video konnte nicht geladen werden.';
if ( window.mw && mw.log ) {
if ( window.mw && mw.log ) {
mw.log.error( e );
mw.log.error( e );

Aktuelle Version vom 19. September 2026, 20:11 Uhr

/**
 * Gadget-MakerTube.js
 * ============================================================================
 * Bettet Videos von makertube.net ein (eine PeerTube-Instanz).
 *
 * ZWEI-KLICK-SPERRE
 * Der Player liegt auf makertube.net. Beim Laden erfährt der Server die
 * IP-Adresse des Besuchers. 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 endet mit dem Schließen des Browsers.
 *
 * PEER-TO-PEER (das eigentliche Prinzip von PeerTube)
 * PeerTube verteilt Videos nicht nur vom Server, sondern auch zwischen den
 * Zuschauern (WebTorrent). Das entlastet kleine Instanzen – und macht die
 * IP-Adresse für die anderen Zuschauer desselben Videos sichtbar. Weil das
 * eine echte Abwägung ist, entscheidet sie der Besucher selbst: in der
 * Vorschaubox steht ein zweites Häkchen dafür.
 *
 * Voreinstellung je Video über data-p2p:
 *   nicht gesetzt / "0"  => Häkchen ist zu Beginn leer (Standard)
 *   "1"                  => Häkchen ist zu Beginn gesetzt; sinnvoll bei
 *                           Videos mit erwartbarem Zulauf, wo das Teilen
 *                           tatsächlich etwas bringt
 * Der Besucher kann es in beide Richtungen ändern – data-p2p legt nur fest,
 * was vorausgewählt ist, nie was passiert.
 *
 * EINBINDUNG AUF EINER SEITE
 *   <div class="makertube-embed" data-video="a1b2c3d4-…"></div>
 *
 * data-Attribute:
 *   data-video   Pflicht. UUID oder Kurzkennung aus der Adresse des Videos
 *                (der Teil hinter /w/ bzw. /videos/watch/).
 *   data-width   Breite in Pixeln (Standard 560). "100%" ist erlaubt.
 *   data-height  Höhe in Pixeln (Standard 315).
 *   data-titel   Überschrift der Vorschaubox (Standard: "Video").
 *   data-p2p     "1" => Teilen-Häkchen ist vorausgewählt (Standard: leer).
 *   data-start   Startzeit in Sekunden.
 * ============================================================================
 */
( function () {
	'use strict';

	var INSTANZ = 'https://makertube.net';
	var STD_BREITE = 560;
	var STD_HOEHE = 315;

	var SPEICHER = 'wiki-freigabe-makertube';
	var SPEICHER_P2P = 'wiki-makertube-p2p';
	var DATENSCHUTZ = 'https://makertube.net/about/instance';

	/* Noch gesperrte Einbettungen dieser Seite – damit ein "merken"-Klick
	 * auch weitere Videos derselben Seite 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 ) {}
	}

	/* Gemerkte P2P-Entscheidung: true, false – oder null, wenn der Besucher
	 * in dieser Sitzung noch nichts entschieden hat. */
	function gemerktesP2p() {
		try {
			var wert = window.sessionStorage.getItem( SPEICHER_P2P );
			if ( wert === 'ja' ) {
				return true;
			}
			if ( wert === 'nein' ) {
				return false;
			}
		} catch ( e ) {}
		return null;
	}

	function setzeP2p( ja, merken ) {
		try {
			if ( !merken ) {
				window.sessionStorage.removeItem( SPEICHER_P2P );
			} else {
				window.sessionStorage.setItem( SPEICHER_P2P, ja ? 'ja' : 'nein' );
			}
		} catch ( e ) {}
	}

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

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

	function masszahl( wert, standard ) {
		if ( wert === undefined || wert === null || wert === '' ) {
			return standard + 'px';
		}
		var text = String( wert ).trim();
		return /^\d+$/.test( text ) ? text + 'px' : text;
	}

	function baueUrl( cfg, p2p ) {
		var url = INSTANZ + '/videos/embed/' + encodeURIComponent( cfg.id ) +
			'?p2p=' + ( p2p ? '1' : '0' ) +
			'&warningTitle=0';
		if ( cfg.start ) {
			url += '&start=' + encodeURIComponent( cfg.start );
		}
		return url;
	}

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

		container.textContent = '';

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

		var kopf = stil( document.createElement( 'div' ), {
			fontWeight: 'bold',
			fontSize: '108%',
			marginBottom: '0.4em'
		} );
		kopf.textContent = cfg.titel + ' auf MakerTube';
		box.appendChild( kopf );

		var text = stil( document.createElement( 'p' ), { margin: '0.4em 0' } );
		text.textContent = 'Das Video liegt auf makertube.net, einer unabhängigen ' +
			'PeerTube-Instanz. Beim Abspielen erfährt dieser Server deine ' +
			'IP-Adresse und welches Video du ansiehst. Vor deinem Klick wird ' +
			'nichts dorthin übertragen.';
		box.appendChild( text );

		/* --- Häkchen 1: Peer-to-Peer --- */
		var hakenP2p = document.createElement( 'input' );
		hakenP2p.type = 'checkbox';
		hakenP2p.checked = cfg.p2pVorgewaehlt;

		var p2pLabel = stil( document.createElement( 'label' ), {
			display: 'block',
			margin: '0.9em 0 0.2em 0',
			fontSize: '92%'
		} );
		p2pLabel.appendChild( hakenP2p );
		p2pLabel.appendChild( document.createTextNode(
			' Bandbreite mit anderen Zuschauern teilen (Peer-to-Peer)'
		) );
		box.appendChild( p2pLabel );

		var p2pHinweis = stil( document.createElement( 'div' ), {
			fontSize: '85%',
			marginLeft: '1.7em',
			marginBottom: '0.3em'
		} );

		function zeigeP2pHinweis() {
			if ( hakenP2p.checked ) {
				p2pHinweis.style.color = '#b32424';
				p2pHinweis.textContent = 'So ist PeerTube gedacht: du lädst Teile ' +
					'des Videos von anderen Zuschauern und gibst sie weiter, was ' +
					'die Instanz entlastet. Dabei wird deine IP-Adresse für diese ' +
					'anderen Zuschauer sichtbar.';
			} else {
				p2pHinweis.style.color = '#54595d';
				p2pHinweis.textContent = 'Ohne Häkchen lädst du ausschließlich vom ' +
					'Server von makertube.net; andere Zuschauer sehen deine ' +
					'IP-Adresse nicht.';
			}
		}
		zeigeP2pHinweis();
		hakenP2p.addEventListener( 'change', zeigeP2pHinweis );
		box.appendChild( p2pHinweis );

		/* --- Häkchen 2: für die Sitzung merken --- */
		var hakenMerken = document.createElement( 'input' );
		hakenMerken.type = 'checkbox';

		var merkenLabel = stil( document.createElement( 'label' ), {
			display: 'block',
			margin: '0.7em 0',
			fontSize: '92%'
		} );
		merkenLabel.appendChild( hakenMerken );
		merkenLabel.appendChild( document.createTextNode(
			' Diese Entscheidung für die Sitzung merken – gilt dann samt ' +
			'Teilen-Einstellung für alle MakerTube-Videos im Wiki und endet, ' +
			'sobald du den Browser schließt. Es wird kein Cookie gesetzt.'
		) );
		box.appendChild( merkenLabel );

		var knopf = stil( document.createElement( 'button' ), {
			cursor: 'pointer',
			padding: '0.4em 0.9em',
			fontWeight: 'bold'
		} );
		knopf.type = 'button';
		knopf.textContent = 'Video laden und abspielen';
		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 = 'Über die Instanz makertube.net';
		fuss.appendChild( dsLink );
		fuss.appendChild( document.createTextNode( ' · ' ) );

		var direkt = document.createElement( 'a' );
		direkt.href = INSTANZ + '/w/' + cfg.id;
		direkt.target = '_blank';
		direkt.rel = 'noopener';
		direkt.textContent = 'Stattdessen direkt auf MakerTube ansehen';
		fuss.appendChild( direkt );
		box.appendChild( fuss );

		container.appendChild( box );

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

		wartende.push( freigeben );

		knopf.addEventListener( 'click', function () {
			var p2p = hakenP2p.checked;
			if ( hakenMerken.checked ) {
				setzeFreigabe( true );
				setzeP2p( p2p, true );
				gibAlleFrei( p2p );
			} else {
				freigeben( p2p );
			}
		} );
	}

	/* --- Geladener Player ----------------------------------------------- */
	function lade( container, cfg, p2p ) {
		container.textContent = '';

		var iframe = document.createElement( 'iframe' );
		iframe.src = baueUrl( cfg, p2p );
		iframe.title = cfg.titel;
		iframe.setAttribute( 'frameborder', '0' );
		iframe.setAttribute( 'allowfullscreen', 'allowfullscreen' );
		iframe.setAttribute( 'allow', 'fullscreen; picture-in-picture' );
		iframe.setAttribute( 'sandbox',
			'allow-same-origin allow-scripts allow-popups allow-forms' );
		iframe.setAttribute( 'referrerpolicy', 'no-referrer' );
		stil( iframe, {
			width: cfg.breite,
			maxWidth: '100%',
			height: cfg.hoehe,
			border: '0'
		} );
		container.appendChild( iframe );

		var fuss = stil( document.createElement( 'div' ), {
			fontSize: '85%',
			color: '#54595d',
			marginTop: '0.4em'
		} );
		fuss.appendChild( document.createTextNode(
			p2p ? 'Peer-to-Peer ist eingeschaltet. · ' :
				'Peer-to-Peer ist abgeschaltet. · '
		) );

		var zurueck = document.createElement( 'a' );
		zurueck.href = '#';
		zurueck.textContent = 'Verbindung zu makertube.net wieder sperren';
		zurueck.addEventListener( 'click', function ( e ) {
			e.preventDefault();
			setzeFreigabe( false );
			setzeP2p( false, false );
			sperre( container, cfg );
		} );
		fuss.appendChild( zurueck );
		container.appendChild( fuss );
	}

	/* --- Einstieg -------------------------------------------------------- */
	function baueEinbettung( container ) {
		var $c = $( container );
		var id = $.trim( String( $c.data( 'video' ) || '' ) );

		if ( !id ) {
			container.textContent = 'MakerTube-Einbettung: kein data-video angegeben.';
			return;
		}

		var cfg = {
			id: id,
			titel: $.trim( String( $c.data( 'titel' ) || '' ) ) || 'Video',
			breite: masszahl( $c.data( 'width' ), STD_BREITE ),
			hoehe: masszahl( $c.data( 'height' ), STD_HOEHE ),
			p2pVorgewaehlt: String( $c.data( 'p2p' ) || '' ) === '1',
			start: $.trim( String( $c.data( 'start' ) || '' ) )
		};

		if ( freigegeben() ) {
			// Gemerkte Entscheidung des Besuchers schlägt die Seiten-Vorauswahl.
			var gemerkt = gemerktesP2p();
			lade( container, cfg,
				gemerkt === null ? cfg.p2pVorgewaehlt : gemerkt );
		} else {
			sperre( container, cfg );
		}
	}

	$( function () {
		$( '.makertube-embed' ).each( function () {
			try {
				baueEinbettung( this );
			} catch ( e ) {
				this.textContent = 'MakerTube-Video konnte nicht geladen werden.';
				if ( window.mw && mw.log ) {
					mw.log.error( e );
				}
			}
		} );
	} );

}() );