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
/* eslint-disable max-len */
// <nowiki>
/******************************************************************************
 * Zukunft.js
 * Gebündelte Anzeige aller mit {{Zukunft}} als veraltet markierten Informationen.
 *
 * Depends on: .voy-zukunft[data-note], .voy-zukunft[data-timestamp]
 * License: CC0
 * Maintainer: nw520
 ******************************************************************************/

mw.loader.using( 'mw.html', 'mw.util' ).then( function () {
	mw.hook( 'wikipage.content' ).add( function ( $container ) {
		/**
		 * @constant
		 * @type {string}
		 */
		var NOTE_ATTR = 'data-note';

		/**
		 * @constant
		 * @type {string}
		 */
		var TIMESTAMP_ATTR = 'data-timestamp';

		/**
		 * @constant
		 * @type {string}
		 */
		var OUTDATED_CLASS = 'voy-zukunft';

		/**
		 * Dauer in Millisekunden, für welche ein Element hervorgehoben wird, wenn ein Benutzer auf die Warnung geklickt hat.
		 * 0 um zu deaktivieren.
		 *
		 * @constant
		 * @type {number}
		 */
		var HIGHLIGHT_DURATION = 5000;

		/**
		 * @class
		 * @param {HTMLElement} element
		 * @param {string} h2
		 * @param {string} h3
		 * @param {string} h4
		 * @param {string} h5
		 * @param {string} h6
		 */
		var SectionedMatch = function ( element, h2, h3, h4, h5, h6 ) {
			this.element = element;
			this.h2 = h2;
			this.h3 = h3;
			this.h4 = h4;
			this.h5 = h5;
			this.h6 = h6;

			return this;
		};

		/**
		 * Gibt einen Brotkrümelpfad für die Abschnitte aus.
		 * @return {string}
		 */
		SectionedMatch.prototype.getSection = function () {
			if ( this.h2 === null && this.h3 === null && this.h4 === null && this.h5 === null && this.h6 === null ) {
				return null;
			} else {
				var out = [ this.h2, this.h3, this.h4, this.h5, this.h6 ];
				out = out.filter( function ( item ) {
					return item !== null;
				} );
				return out.join( ' / ' );
			}
		};

		/**
		 * @class
		 * @param {SectionedMatch} match Zugehöriges Element, für das die Warnung gilt.
		 * @param {number} reason Begründung der Fehlermeldung, siehe {@link WarningItem.reasons}.
		 * @param {string} note Vom Nutzer hinterlegter Hinweis.
		 */
		var WarningItem = function ( match, reason, note ) {
			this.match = match;
			this.reason = reason;
			this.note = note || null;
			return this;
		};

		/**
		 * @enum {number}
		 */
		WarningItem.reasons = {
			UNSPECIFIED: 0,
			OUTDATED: 1,
			ERRORNEOUS_TIMESTAMP: 2
		};

		function main() {
			mw.util.addCSS( '.voy-zukunft { background-color: transparent; transition: background .4s ease-in-out; } .voy-zukunft .mw-redirect { background: none; } .voy-zukunft-highlight { animation: voy-zukunft-pulse .7s infinite alternate; } .voy-zukunft-warnbox { background: #f9f9f9; border: 1px solid #f66; border-left: 10px solid #f66; box-sizing: border-box; margin: .5em 0; overflow: hidden; padding: .5em; text-align: left; width: auto; } @keyframes voy-zukunft-pulse { 0% { background: transparent; } 100% { background: lightsalmon; } }' );

			var warnings = filter( findAndDetermineSection( '.' + OUTDATED_CLASS + '[' + TIMESTAMP_ATTR + ']', $container[ 0 ] ) );

			if ( warnings.length === 0 ) {
				return;
			}
			
			// Cleanup old boxes: Avoid multiple boxes when using RealtimePreview
			document.querySelectorAll( '.voy-zukunft-warnbox' ).forEach( function ( warnbox ) {
				warnbox.remove();
			} );

			var container = document.createElement( 'div' );
			container.classList.add( 'voy-zukunft-warnbox' );
			container.innerHTML = '<p>In diesem Reiseführer finden sich einige veraltete Angaben. Hilf mit, indem du sie aktualisierst:</p>';

			var matchesUl = document.createElement( 'ul' );
			container.appendChild( matchesUl );

			generateWarnings( warnings ).forEach( function ( match ) {
				matchesUl.appendChild( match );
			} );

			document.getElementById( 'bodyContent' ).insertAdjacentElement( 'afterbegin', container );
		}

		/**
		 * Überprüft, ob die Elemente tatsächlich veraltet sind anhand des in {@link TIMESTAMP_ATTR} definierten Attributs und gibt eine Liste an veralteten Elementen aus. Es wird das Format YYYY, YYYY-MM, oder YYYY-MM-DD erwartet.
		 *
		 * @param {Array<SectionedMatch>} matches
		 * @return {Array<WarningItem>} Liste an {@link WarningItem} mit veralteten Werten.
		 */
		function filter( matches ) {
			var out = [];

			matches.forEach( function ( match ) {
				try {
					var splitTimestamp = match.element.getAttribute( TIMESTAMP_ATTR ).split( '-' );
					var date = new Date();
					if ( splitTimestamp.length >= 1 && parseInt( splitTimestamp[ 0 ] ) < date.getFullYear() ) {
						out.push( new WarningItem( match, WarningItem.reasons.OUTDATED, match.element.getAttribute( NOTE_ATTR ) ) );
					} else if ( parseInt( splitTimestamp[ 0 ] ) === date.getFullYear() ) {
						if ( splitTimestamp.length >= 2 && parseInt( splitTimestamp[ 1 ] ) < date.getMonth() + 1 ) {
							out.push( new WarningItem( match, WarningItem.reasons.OUTDATED, match.element.getAttribute( NOTE_ATTR ) ) );
						} else if ( parseInt( splitTimestamp[ 1 ] ) === date.getMonth() + 1 ) {
							if ( splitTimestamp.length >= 3 && parseInt( splitTimestamp[ 2 ] ) < date.getDate() ) {
								out.push( new WarningItem( match, WarningItem.reasons.OUTDATED, match.element.getAttribute( NOTE_ATTR ) ) );
							}
						}
					}
				} catch ( e ) {
					mw.log.warn( e );
					out.push( new WarningItem( match, WarningItem.reasons.ERRORNEOUS_TIMESTAMP, match.element.getAttribute( NOTE_ATTR ) ) );
				}
			} );
			return out;
		}

		/**
		 * @param  {string|Array<string>} selectors Selektoren, mit den Elemente gefunden werden sollen.
		 * @param  {HTMLElement} parent Elternelement, in dem Selektoren gefunden werden sollen.
		 * @return {Array<SectionedMatch>}
		 */
		function findAndDetermineSection( selectors, parent ) {
			var castedSelectors = typeof selectors === 'string' ? selectors : selectors.join( ',' );

			var itemsAndSections = parent.querySelectorAll( 'h2,h3,h4,h5,h6,' + selectors );
			var sections = {
				H6: null,
				H5: null,
				H4: null,
				H3: null,
				H2: null
			};
			var sectionOrder = [ 'H6', 'H5', 'H4', 'H3', 'H2' ];

			var items = [];
			itemsAndSections.forEach( function ( itemOrSection ) {
				if ( itemOrSection.matches( castedSelectors ) ) { // Item
					items.push( new SectionedMatch( itemOrSection, sections.H2, sections.H3, sections.H4, sections.H5, sections.H6 ) );
				} else { // Section
					if ( itemOrSection.closest( '#toc' ) === null ) {
						for ( var i = 0; i < sectionOrder.length; i++ ) {
							var sectionTag = sectionOrder[ i ];
							if ( itemOrSection.tagName === sectionTag && itemOrSection.querySelectorAll( '.mw-headline' ).length > 0 ) { // Section
								// Set this section
								sections[ sectionTag ] = itemOrSection.querySelector( '.mw-headline' ).textContent;
								break;
							} else {
								sections[ sectionTag ] = null;
							}
						}
					}
				}
			} );
			return items;
		}

		/**
		 * Erzeugt aus einer Liste an {@link WarningItem} als Liste von li-Elementen (HTMLElement).
		 *
		 * @param {Array<WarningItem>} warnings Liste an veralteten {@link WarningItem}.
		 * @return {Array<HTMLElement>} Text in Wikimedia-Markup für Ausgabe der Warnungen.
		 */
		function generateWarnings( warnings ) {
			return warnings.map( function ( warning ) {
				var section = warning.match.getSection();
				var location = '<a href="#zukunft">' + ( section === null ? 'In der Einleitung' : 'Im Abschnitt „' + mw.html.escape( section ) + '“' ) + '</a>';
				var note = warning.note !== null ? ': ' + mw.html.escape( warning.note ) : '';

				var matchLi = document.createElement( 'li' );
				if ( warning.reason === WarningItem.reasons.OUTDATED ) {
					matchLi.innerHTML = '<li>' + location + ' ist eine potentiell veraltete Angabe' + note + '</li>';
				} else {
					matchLi.innerHTML = '<li>' + location + ' ist eine fehlerhafte Angabe' + note + '</li>';
				}

				matchLi.querySelector( 'a' ).addEventListener( 'click', function ( e ) {
					e.preventDefault();
					warning.match.element.scrollIntoView({
						behavior: 'smooth',
						block: 'center',
						inline: 'center'
			        } );
					highlightElement( warning.match.element );
				} );

				return matchLi;
			} );
		}

		/**
		 * Hebt das gegebene Element {@link HIGHLIGHT_DURATION} lang hervor.
		 *
		 * @param {HTMLElement} element Element, welches hervorgehoben werden soll.
		 */
		function highlightElement( element ) {
			if ( HIGHLIGHT_DURATION > 0 ) {
				element.classList.add( 'voy-zukunft-highlight' );
				setTimeout( function () {
					element.classList.remove( 'voy-zukunft-highlight' );
				}, HIGHLIGHT_DURATION );
			}
		}
		main();
	} );
} );
// </nowiki>