MediaWiki:Gadget-SisterWeblinks.js

Aus Wikivoyage

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
  • Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
// <nowiki>
/* eslint-disable max-len */
/******************************************************************************
 * SisterWeblinks
 * Kopiert Links auf einige ausgewählte Schwesterprojekte in den Abschnitt „Weblinks“
 *
 * License: CC0
 * Maintainer: nw520
 ******************************************************************************/

( () => {
	const strings = {
		'voy-sisterweblinks-sisterdescription-commons': {
			de: 'Sammlung von Bildern, Videos und Audiodateien'
		},
		'voy-sisterweblinks-sisterdescription-wikinews': {
			de: 'Nachrichten'
		},
		'voy-sisterweblinks-sisterdescription-wikipedia': {
			de: 'Enzyklopädie'
		}
	};

	function main() {
		mw.hook( 'wikipage.content' ).add( ( $el ) => {
			const parserOutput = $el[ 0 ].querySelector( '.mw-parser-output' );

			if ( parserOutput === null ) {
				return;
			}

			let insertPoint = findWeblinksInsertPoint( parserOutput );
			if ( insertPoint.insertPoint === null ) {
				mw.log.warn( '[SisterWeblinks] Failed to find insert point.' );
				return;
			}

			// Create list after insert point, if needed
			if ( !insertPoint.isList ) {
				const list = document.createElement( 'ul' );
				insertPoint.insertPoint.insertAdjacentElement( 'afterend', list );
				insertPoint.insertPoint = list;
				insertPoint.location = 'beforeend';
			}

			const sitelinks = getSitelinks();
			if ( sitelinks === null ) {
				throw new Error( '[SisterWeblinks] Failed to get insert point.' );
			}

			setupStrings();
			insertSitelinks( sitelinks, insertPoint.insertPoint, insertPoint.location );
		} );
	}

	/**
	 * Creates a list item for a sitelink.
	 *
	 * @param  {Object} sitelink
	 * @return {HTMLElement}
	 */
	function createItem( sitelink ) {
		let projectMeta = getProjectMeta( sitelink.project );
		if ( projectMeta === null ) {
			return null;
		}

		const li = document.createElement( 'li' );
		const a = document.createElement( 'a' );
		a.href = sitelink.url;

		if ( ( projectMeta.logo ?? null ) !== null ) {
			const logo = document.createElement( 'img' );
			logo.height = 16;
			logo.width = 16;
			logo.src = projectMeta.logo;
			a.appendChild( logo );
			a.appendChild( document.createTextNode( ' ' ) );
		}

		a.appendChild( document.createTextNode( projectMeta.name ) );

		li.appendChild( a );

		if ( ( projectMeta.description ?? null ) !== null ) {
			li.appendChild( document.createTextNode( ` – ${projectMeta.description}` ) );
		}

		return li;
	}

	/**
	 * Scans an article for the #Weblinks-section.
	 *
	 * @param  {HTMLElement} parserOutput
	 * @return {Object}
	 */
	function findWeblinksInsertPoint( parserOutput ) {
		let weblinksSectionElement = null;
		let weblinksLastListElement = null;
		let weblinksLastElement = null;

		for ( const el of [ ...parserOutput.querySelectorAll( '.mw-parser-output > *' ) ] ) {
			if ( el.matches( 'h2' ) ) {
				if ( weblinksSectionElement !== null ) {
					// We already found #Weblinks.
					// Since this section is on the same hierarchical level, #Weblinks has just concluded.
					// Therefore, we can terminate.
					break;
				}

				if ( el.querySelector( '.mw-headline' ) !== null && el.querySelector( '.mw-headline' ).textContent === 'Weblinks' ) {
					// We found #Weblinks.
					weblinksSectionElement = el;
				} else {
					// This section is not #Weblinks.
					continue;
				}
			}

			if ( weblinksSectionElement === null ) {
				// #Weblinks was not found yet
				continue;
			}

			if ( el.matches( 'ul' ) ) {
				weblinksLastListElement = el;
			}
			
			if ( el.matches( 'div' ) || el.matches( 'table' ) ) {
				// We don't want to insert below divs or tables, therefore terminate
				break;
			}

			weblinksLastElement = el;
		}

		return {
			insertPoint: weblinksLastListElement?.lastChild ?? weblinksLastElement,
			isList: weblinksLastListElement !== null,
			location: 'afterend',
			sectionElement: weblinksSectionElement,
			lastListElement: weblinksLastListElement,
			lastElement: weblinksLastElement
		};
	}

	/**
	 * Returns meta data for sister projects.
	 *
	 * @param  {string} project
	 * @return {Object}
	 */
	function getProjectMeta( project ) {
		switch ( project ) {
			case 'commons':
				return {
					description: mw.msg( 'voy-sisterweblinks-sisterdescription-commons' ),
					logo: 'https://commons.wikimedia.org/wiki/Special:FilePath/File:Commons-logo.svg',
					name: 'Commons'
				};
			case 'wikinews':
				return {
					description: mw.msg( 'voy-sisterweblinks-sisterdescription-wikinews' ),
					logo: 'https://commons.wikimedia.org/wiki/Special:FilePath/File:Wikinews-logo.svg',
					name: 'Wikinews'
				};
			case 'wikipedia':
				return {
					description: mw.msg( 'voy-sisterweblinks-sisterdescription-wikipedia' ),
					logo: 'https://commons.wikimedia.org/wiki/Special:FilePath/File:Wikipedia-logo-v2.svg',
					name: 'Wikipedia'
				};
			default:
				mw.log.error( `[SisterWeblinks] Unknown sister project ${sitelink.project}` );
				return null;
		}
	}

	/**
	 * Extracts the sister project's name from a portlet.
	 * @param  {HTMLElement} el
	 * @return {string}
	 */
	function getProjectName( el ) {
		if ( el.matches( '.wb-otherproject-commons' ) ) {
			return 'commons';
		} else if ( el.matches( '.wb-otherproject-wikinews' ) ) {
			return 'wikinews';
		} else if ( el.matches( '.wb-otherproject-wikipedia' ) ) {
			return 'wikipedia';
		} else if ( el.matches( '.wb-otherproject-wikiquote' ) ) {
			return 'wikiquote';
		} else if ( el.matches( '.wb-otherproject-wikisource' ) ) {
			return 'wikisource';
		} else {
			return null;
		}
	}

	/**
	 * Returns a list of sitelinks.
	 *
	 * Tested for monobook, timeless, vector-{2020,legacy}.
	 * Does not work for Minerva.
	 *
	 * @return {Object}
	 */
	function getSitelinks() {
		const links = document.querySelectorAll( '.wb-otherproject-link' );

		return [ ...links ].map( ( el ) => {
			return {
				project: getProjectName( el ),
				url: el.querySelector( 'a' ).href
			};
		} );
	}

	/**
	 * Generates and inserts a sister project's sitelink.
	 *
	 * @param  {Object[]} sitelinks
	 * @param  {HTMLElement} insertPoint
	 * @param  {string} location
	 */
	function insertSitelinks( sitelinks, insertPoint, location ) {
		let insertAfter = insertPoint;
		let insertLocation = location;
		const sitelinksOrder = {
			commons: 1,
			wikinews: 2,
			wikipedia: 0
		};

		// Filter and sort
		const orderedSitelinks = sitelinks.filter( ( sitelink ) => {
			return sitelinksOrder[ sitelink.project ] !== undefined;
		} ).sort( ( left, right ) => {
			const l = sitelinksOrder[ left.project ];
			const r = sitelinksOrder[ right.project ];

			if ( l < r ) {
				return -1;
			} else if ( l > r ) {
				return 1;
			} else {
				return 0;
			}
		} );

		for ( const sitelink of orderedSitelinks ) {
			const li = createItem( sitelink );

			if ( li === null ) {
				continue;
			}

			insertAfter.insertAdjacentElement( location, li );
			insertAfter = li;
			location = 'afterend';
		}
	}

	function setupStrings() {
		const lang = mw.config.get( 'wgUserLanguage' );
		mw.messages.set( Object.fromEntries( Object.keys( strings ).map( ( stringKey ) => {
			return [ stringKey, strings[ stringKey ][ lang ] ?? strings[ stringKey ].en ];
		} ) ) );
	}

	main();
} )();
// </nowiki>