MediaWiki:Guidedtour-lib.js

/** * Library to easily create and maintain translatable guided tours. * * Important note: This script must be loaded before the entire page is ready because * otherwise GuidedTours will try to start a tour although it is not defined yet. * * @author Ugochimobi * * Temporary note: * This version has some temporary fixes that will be resolved more thoroughly very * shortly. In the meantime, use `actionBtn2` instead of `actionBtn` in step options * if you want a click action to be completed automatically for a step. */ ( function( $, mw, gt, wb ) {	'use strict';

$.ajax( {		async: false,		url: mw.util.wikiScript + '?title=MediaWiki:JQuery.overlay.js&action=raw&ctype=text/javascript',		dataType: 'script'	} );

mw.util.addCSS(		'.tipsy { z-index: 100000006; } /* tipsy should stay visible */ \		.ui-suggester-list.ui-ooMenu { z-index: 1000000061; } /* suggestions should stay visible */ \		#mw-spinner-guidedtour { position: absolute; height: 100%; background-color: rgba(0, 0, 0, 0.1); z-index: 1000000; } /* the spinner */ \		#bodyContent { z-index: auto; } /* Ugly hack to make the overlay work again */'	);

var defaultStep = { overlay: false, closeOnClickOutside: false, position: 'bottom', // only when attached actionBtn: '', actionBtn2: '', // Hack! Using separate property to prevent default click on action btn triggered in guiders library actionComplete: false, xButton: false };

/**	 * Shows a spinner covering the whole page. */	function showSpinner { $( 'body' ).css( 'overflow', 'hidden' ); mw.loader.using( [ 'jquery.spinner' ], function {			$.createSpinner( { size: 'large', type: 'block', id: 'guidedtour' } ).prependTo( 'body' );		} ); }   /**	 * Removes the data from the current entity and adds the new data. * When finished reloads the site with data=ok. *	 * @param {string} tourName * @param {object} newData */	function removeData( tourName, newData ) { mw.loader.using( 'mediawiki.api', function {			new mw.Api			.postWithEditToken( { action: 'wbeditentity', id: mw.config.get( 'wbEntityId' ), clear: true, data: JSON.stringify( newData ), summary: 'Clearing data for ' + tourName + ' tour' } )			.done( function { location.href += '&data=ok'; } );		} );	}

/**	 * Parses the given page and returns the sections used for the single steps of the tour. *	 * @param {string} pageName * @param {string} language * @return {array} */	function getSections( pageName, language ) { var data = JSON.parse( $.ajax( { dataType: 'json', url: '/w/api.php', // this is ugly but necessary if we want to execute the tour on testwiki data: { 'action': 'parse', 'format': 'json', 'page': pageName + ( language === 'en' ? '' : '/' + language ), 'prop': 'text|sections', 'disablepp': true, 'disabletoc': true },			async: false } ).responseText );

if ( data.error ) { if ( language !== 'en' ) { location.href += '&uselang=en'; } else { console.log( data.error ); return false; }		}

var rawSections = data.parse.text['*'].split( /]*>((?!<\/h2>).)*<\/h2>/gi ), titles = data.parse.sections, sections = [];

for ( var i = 0; i < titles.length; i++ ) { sections.push( {				title: titles[i].line,				description: rawSections[i * 2 + 2]			} ); }

return sections; }

/**	 * Builds the tour's options with steps parsed form the given page. *	 * @param {string} tourName * @param {string} tourEntityId * @param {object} options *	 * @return {object} */   function buildOptionsFromPage( tourName, tourEntityId, options ) { var language = mw.config.get( 'wgUserLanguage' ), sections = getSections( options.pageName, language );

$.extend( options, {			name: tourName,			shouldLog: true		} );

$.each( sections, function( i ) {		   var step = options.steps[i] = $.extend( {}, defaultStep, options.steps[i], sections[i] ),		        _onShow = step.onShow;			$.extend( options.steps[i], { onShow: function { if ( typeof _onShow === 'function' ) { _onShow; }					if (step.actionComplete) { // Action already completed // Hack! Re-add the click handler for moving to next slide. // The default click gets removed when mutation observer is added, so we can wait for DOM before triggering next step $('.guidedtour-next-button').off('click').on('click', function {							mw.libs.guiders.next;						}); } else if ( step.actionBtn2 ) { var nextStep = options.steps[i+1]; // Hack! Intercept the click to prevent going to next slide before DOM is ready. // Instead, trigger the action button click and let the mutation observer detect the change and advance the slide // Proper fix requires small changes to guiders library $('.guidedtour-next-button').off('click').one('click', function {							$(step.actionBtn2).click;						}); if( nextStep && nextStep.attachTo && $(nextStep.attachTo).length === 0) { // The attach element for next step is not in the DOM yet, add mutation observer step.observer = newElementObserver(nextStep.attachTo, function {								step.observer.disconnect;								step.actionComplete = true;								return mw.libs.guiders.next;							}) } else { // step has an action button, but there is no attach in the next slide or it's element is already visible // add the click event to advance the slide from an action button click, as there are no mutations to trigger it							$(step.actionBtn2).one('click', function {								step.actionComplete = true;								mw.libs.guiders.next;							}) }					}					if ( step.overlay ) { console.log($( step.overlay )); $( step.overlay ).overlay; } else { $.overlay.remove; }				}			} );

// change the defaults for the end of the tour if ( i === sections.length - 1 ) { $.extend( options.steps[i], {					overlay: false,					//closeOnClickOutside: true,					buttons: [ { action: 'end' } ]				} ); }		} );

if ( location.href.indexOf( 'uselang=en' ) > -1 ) { options.steps.unshift( $.extend( {}, defaultStep, { title: 'Not your language', description: 'This tour is not available in your language. You may proceed using English or ' + 'translate it into your language.' } ) );		}

return options; }

/**	 * Defines the tour with the given options using the TourBuilder. *	 * @param {object} options */	function defineTour( options ) { var tour = new gt.TourBuilder( {			name: options.name,			shouldLog: true		} );

tour.firstStep( $.extend( { name: 'step0' }, options.steps[0] ) ).next( 'step1' );

for ( var i = 1; i < options.steps.length - 1; i++ ) { tour.step( $.extend( { name: 'step' + i			}, options.steps[i] ) ).next( 'step' + ( i + 1 ) ).back( 'step' + ( i - 1 ) ); }

tour.step( $.extend( { name: 'step' + ( options.steps.length - 1 ) }, options.steps[options.steps.length - 1] ) ).back( 'step' + ( options.steps.length - 2 ) ); }   /**	 * Creates mutation observer which run the callback when an element is added * that matches the selector * 	 * @param {string} selector * @param {function} callback *     * @return {object} */	function newElementObserver(selector, callback) { var observer = new MutationObserver(function (mutationList) {			for(var mutation of mutationList) {				for(var node of mutation.addedNodes) {					if($(node).is(selector) ) {						// The node matching the selector has been added						if (callback) callback;						return;					}				}			}		}); var targetNode = $("body").get(0); var observerOptions = { childList: true, subtree: true }		observer.observe(targetNode, observerOptions); return observer; }

/**	 * Checks if the tour name and entity id are correct. Also checks if the data * has been prepared and clears or adds missing data. If everything is ok, * starts the tour. */	gt.init = function( tourName, tourEntityId, newData, options ) { // check for the correct page if ( !wb || location.href.indexOf( 'tour=' + tourName ) < 0 ||			mw.config.get( 'wbEntityId' ) !== tourEntityId || mw.config.get( 'wbIsEditView' ) !== true		) { return; }

// check for emptiness if ( !gt.hasQuery( { 'data' : 'ok' } ) ) { showSpinner; removeData( tourName, newData ); return; }

// launch the tour defineTour( buildOptionsFromPage( tourName, tourEntityId, options ) ); };

} )( jQuery, mediaWiki, mediaWiki.guidedTour, wikibase );