Zum Inhalt springen

MediaWiki:Gadget-Descriptions.js

aus AufEinWort, der freien Forschung

Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
/* Gadget: Descriptions – Wikibase-Beschreibung beim Hover (Q/P/L) – nummernbasiert
 * Abhängigkeiten: mediawiki.api, jquery.wikibase.wbtooltip
 * Delegiert Events im Capture-Modus; erkennt Links via ID (Q/P/L) + Namensraum-Nummern.
 */
( function ( mw, $ ) {
	'use strict';

	var CFG = window.DescriptionsConfig || { nsItem: 120, nsProperty: 122, nsLexeme: 146 };
	var DBG = !!window.DescriptionsDebug;
	var cache = Object.create( null ); // id -> { text: string|false, pending: Promise|null }
	var userLang = mw.config.get( 'wgUserLanguage' );
	var nsIds = mw.config.get( 'wgNamespaceIds' ) || {}; // { 'qwasi':120, 'property':122, ... }

	function log() { if ( DBG && window.console ) { console.log.apply( console, arguments ); } }

	function extractId( href ) {
		// …/Q123, …/P12, …/L7 – robust für /entity/, Special:EntityPage, und <NS>:Q123
		var m = ( href || '' ).match( /(?:^|[/:])(Q\d+|P\d+|L\d+)(?:[^\d]|$)/ );
		return m ? m[ 1 ] : null;
	}

	function titleFromHref( href ) {
		// Versuche, den *Seitentitel* aus einem internen Link zu gewinnen, um NS zu bestimmen
		try {
			var a = document.createElement( 'a' );
			a.href = href;
			// Externe Links ignorieren
			if ( a.host && a.host !== location.host ) { return null; }

			// 1) ?title=... (script path)
			var params = new URLSearchParams( a.search || '' );
			if ( params.has( 'title' ) ) {
				return decodeURIComponent( params.get( 'title' ) );
			}

			// 2) ArticlePath (/ $1 oder /wiki/$1)
			var base = ( mw.config.get( 'wgArticlePath' ) || '/$1' ).replace( '$1', '' );
			var path = a.pathname || '';
			if ( base && path.indexOf( base ) === 0 ) {
				return decodeURIComponent( path.slice( base.length ) );
			}

			// 3) Fallback: nichts
			return null;
		} catch ( e ) { return null; }
	}

	function nsIdFromTitle( titleText ) {
		// Nimmt den Teil vor dem ersten ":" und schlägt in wgNamespaceIds nach (nummernbasiert)
		if ( !titleText || titleText.indexOf( ':' ) < 0 ) { return 0; }
		var nsName = titleText.split( ':', 1 )[ 0 ].trim().toLowerCase().replace( / /g, '_' );
		return typeof nsIds[ nsName ] === 'number' ? nsIds[ nsName ] : null;
	}

	function isAllowedEntityLink( a ) {
		if ( !a || a.nodeName !== 'A' ) { return false; }
		var href = a.getAttribute( 'href' ) || '';
		// Schnelle Negativfilter
		if ( href.indexOf( '#' ) === 0 || /^mailto:|^javascript:/i.test( href ) ) {
			return false;
		}
		var id = extractId( href );
		if ( !id ) { return false; }

		// Wenn wir den Titel erkennen, prüfen wir *nummernbasiert* den NS
		var t = titleFromHref( href );
		if ( t ) {
			var ns = nsIdFromTitle( t );
			if ( ns === CFG.nsItem || ns === CFG.nsProperty || ns === CFG.nsLexeme ) {
				return { ok: true, id: id };
			}
			// Titel erkannt, aber NS nicht passend -> kein Entity-Link für uns
			return false;
		}

		// Kein Titel ermittelbar (z. B. /entity/Q123, Special:EntityPage/Q123):
		// Dann verlassen wir uns auf die ID – das ist in der Praxis korrekt für Wikibase-UI-Links.
		return { ok: true, id: id };
	}

	function ensureDescription( id ) {
		if ( cache[ id ] ) {
			return cache[ id ].pending || Promise.resolve( cache[ id ].text || '' );
		}
		var api = new mw.Api( { userAgent: 'Gadget-Descriptions/local-2025-12-02' } );
		cache[ id ] = { text: '', pending: null };
		cache[ id ].pending = api.get( {
			action: 'wbgetentities',
			ids: id,
			props: 'descriptions',
			languages: userLang,
			languagefallback: 1
		} ).then( function ( data ) {
			var ents = ( data && data.entities ) || {};
			var e = ents[ id ];
			var d = e && e.descriptions && ( e.descriptions[ userLang ] || Object.values( e.descriptions )[ 0 ] );
			var text = d && d.value || '';
			cache[ id ].text = text || false;
			cache[ id ].pending = null;
			log( '[Descriptions] loaded', id, '→', text );
			return text;
		}, function ( err ) {
			cache[ id ].text = false;
			cache[ id ].pending = null;
			log( '[Descriptions] api error', id, err );
			return '';
		} );
		return cache[ id ].pending;
	}

	function showTooltip( a, id, text ) {
		var $a = $( a );
		var $content = $( '<span>' );
		$( '<strong>' ).text( id ).appendTo( $content );
		if ( text ) {
			$content.append( document.createTextNode( ' — ' + text ) );
		}
		$a.wbtooltip( {
			content: $content,
			permanent: true,
			gravity: 'nw'
		} );
		var tip = $a.data( 'wbtooltip' );
		if ( tip ) { tip.show(); }
	}

	function hideTooltip( a ) {
		var tip = $( a ).data( 'wbtooltip' );
		if ( tip ) { tip.hide(); }
	}

	// Event-Delegation in der *Capture*-Phase, damit andere Skripte uns nicht blockieren
	function handleOver( ev ) {
		var a = ev.target && ev.target.closest ? ev.target.closest( 'a' ) : null;
		var res = isAllowedEntityLink( a );
		if ( !res || !res.ok ) { return; }
		var id = res.id;

		// Cache direkt nutzen
		if ( cache[ id ] && cache[ id ].text ) {
			showTooltip( a, id, cache[id].text );
			return;
		}
		// ggf. laufende Anfrage anhängen, sonst neu anfordern
		( cache[ id ] && cache[ id ].pending ? cache[ id ].pending : ensureDescription( id ) )
			.then( function ( text ) { showTooltip( a, id, text || '' ); } );
	}

	function handleOut( ev ) {
		var a = ev.target && ev.target.closest ? ev.target.closest( 'a' ) : null;
		if ( !a ) { return; }
		hideTooltip( a );
	}

	// Registrieren (mouseover/mouseout & pointerover/pointerout als Fallbacks)
	document.addEventListener( 'mouseover', handleOver, true );
	document.addEventListener( 'mouseout', handleOut, true );
	document.addEventListener( 'pointerover', handleOver, true );
	document.addEventListener( 'pointerout', handleOut, true );

	if ( DBG ) { log( '[Descriptions] ready. NS:', CFG ); }

}( mediaWiki, jQuery ) );