« MediaWiki:Gadget-tour-loader.js » : différence entre les versions

De Wikimedica
(Ajout fonction pour déplacer les guiders)
(Erreur de nommage de paramètre)
(38 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
mw.tours = { // Hook to the mw object to make the launcher globally accessible.
if(typeof MutationObserver == 'undefined') // If the MutationObserver pattern is not supported.
tours: {
{
basic_navigation: {
console.log("Skipping tour loading, MutationObserver not supported.");
requires: [], // List of tours that must have been completed for this tour to launch.
}
startDelayFromRegistration: 300, // Delay in seconds the tour must wait after registration to lauch.
else if(window.self == window.top) // Prevent tours from being launched within an iframe.
skipDelay: 60 * 24, // Delay in seconds the tour shown again after bein skipped.
{
lauchProbability: 1, // Probability the tour will launch once we are past startDelayFromLogin.
mw.guidedTour.loader = { // Hook to the mw.guidedTour to make the launcher globally accessible.
loggedInUsersOnly: true, // If the tour is only for logged in users.
tours: {
canEditPage: true, // If the tour can only launch on pages a user can edit.
basic_navigation: {
excludePages: ["^Accueil$", "^Wikimedica.", "^Classe.", "^Modèle."] // Exclude pages (regex).
requires: [], // List of tours that must have been completed for this tour to launch.
}
startDelayFromRegistration: 300 * 1000, // Delay in milliseconds the tour must wait after registration to launch.
},
postponeDelay: 60 * 60 * 24 * 1000, // Delay in milliseconds the tour is shown again after being postponed.
loggedInUsersOnly: true, // If the tour is only for logged in users.
/*
canEditPage: true, // If the tour can only launch on pages a user can edit.
* Checks if all conditions for a tour to launch are met.
excludePages: [/^Accueil$/, /^Wikimedica./, /^Classe./, /^Modèle./], // Exclude pages (regex).
*/
debug: false // Tour is in debug mode (not lauched when ready)
canLaunch: function(name, tour)
},
{
visual_edition: {
// Normalize configuration.
requires: ["basic_navigation"], // List of tours that must have been completed for this tour to launch.
if(tour.startDelayFromRegistration === undefined) { tour.startDelayFromRegistration = 0; }
startDelayFromRequiredTours: 24 * 60 * 60 * 1000, // Delay in milliseconds the tour must wait after required tours are completed.
if(tour.skipDelay === undefined) { tour.skipDelay = false; }
postponeDelay: 60 * 60 * 24 * 1000, // Delay in milliseconds the tour shown again after being postponed.
if(tour.launchProbability === undefined) { tour.lauchProbability = 1; }
loggedInUsersOnly: true, // If the tour is only for logged in users.
if(tour.loggedInUsersOnly === undefined) { tour.loggedInUsersOnly = true; }
canEditPage: true, // If the tour can only launch on pages a user can edit.
if(tour.canEditPage === undefined) { tour.canEditPage = true; }
excludePages: [/^Accueil$/, /^Wikimedica./, /^Classe./, /^Modèle./], // Exclude pages (regex).
if(tour.excludePages === undefined) { tour.excludePages = []; }
debug: false // Tour is in debug mode (not lauched when ready)
if(tour.requires === undefined) { tour.requires = []; }
}
},
cookiePrefix = mw.config.get("wgCookiePrefix") + "-mw-tour-";
// If the tour has been completed or abandoned.
optionPrefix: 'userjs-mw-tour-', // Prefix under which tour launch data is saved in the user options.
if($.cookie(cookiePrefix + name + "-completed") || $.cookie(cookiePrefix + name + "-abandoned"))
{
return false;
}
// ------------------- startDelayfromRegistration -----------------------------
// Get the state of a tour from the user options.
d = new Date(mw.user.getRegistration());
getTourState: function(name) { return mw.user.options.get(this.optionPrefix + name + "-state"); },
// Not past the delay yet.
// Get the state of a tour from the user options.
if(d.getTime() + tour.startDelayFromRegistration > Date.now()) { return false; }
getTourTime: function(name) { return mw.user.options.get(this.optionPrefix + name + "-time"); },
// ------------------- skipDelay -----------------------------
// Set a state and time for a tour state in the user options.
if(tour.skipDelay !== false)
setTourState: function(name, state, time)  
{
{
skipped = $.cookie(cookiePrefix + name + "-skipped");
mw.user.options.set(this.optionPrefix + name + "-state", state);
mw.user.options.set(this.optionPrefix + name + "-time", time);
if(skipped && (skipped + tour.skipDelay > Date.now())) { return false; } // Tour has been skipped for now.
api = new mw.Api();
else if(skipped) { $.removeCookie(cookiePrefix + name + "-skipped"); }
api.postWithEditToken({
}
'action': 'options',
'change': this.optionPrefix + name + "-state=" + state + "|" + this.optionPrefix + name + "-time=" + time
// ------------------- launchProbability -----------------------------
}).fail(function(data) { console.log("API options set call failed."); });
if(tour.launchProbability < Math.random()) { return false; }
},
// ------------------- loggedInUsersOnly -----------------------------
// The tour required the user to be logged in.
if(tour.loggedInUsersOnly && mw.user.isAnon()) { return false; }  
// ------------------- canEditPage -----------------------------
// Delete a tour state.
// The tour requires the user to be able to edit the current page.
deleteTourState: function(name, state) { this.setTourState(name, null, null); },
if(tour.canEditPage && !mw.config.get('wgIsProbablyEditable')) { return false; }
// ------------------- excludePages -----------------------------
/*
page = mw.config.get("wgPageName");
* Checks if all conditions for a tour to launch are met.
for(var i; i > tour.excludePages.length; i++)
*/
canLaunch: function(name, tour)
{
{
if(page.test(tour.excludePages[i])) { return false; } // Page is excluded.
// Normalize configuration.
}
if(tour.startDelayFromRegistration === undefined) { tour.startDelayFromRegistration = 0; }
if(tour.startDelayFromRequiredTours === undefined) { tour.startDelayFromRequiredTours = 0; }
if(tour.postponeDelay === undefined) { tour.postponeDelay = false; }
if(tour.launchProbability === undefined) { tour.lauchProbability = 1; }
if(tour.loggedInUsersOnly === undefined) { tour.loggedInUsersOnly = true; }
if(tour.canEditPage === undefined) { tour.canEditPage = true; }
if(tour.excludePages === undefined) { tour.excludePages = []; }
if(tour.requires === undefined) { tour.requires = []; }
// If the tour has been completed or abandoned.
if(this.getTourState(name) == "completed" || this.getTourState(name) == "abandoned")
{
return false;
}
// ------------------- startDelayfromRegistration -----------------------------
d = new Date(mw.user.getRegistration());
// Not past the delay yet.
if(d.getTime() + tour.startDelayFromRegistration > Date.now()) { return false; }
// ------------------- postponeDelay -----------------------------
if(tour.postponeDelay !== false)
{
if(this.getTourState(name) == "postponed" && (this.getTourTime(name) + tour.postponeDelay > Date.now())) { return false; } // Tour has been postponed for now.
}
// ------------------- launchProbability -----------------------------
// if(tour.launchProbability < Math.random()) { return false; }
// ------------------- requires -----------------------------
// ------------------- loggedInUsersOnly -----------------------------
for(var i; i > tour.requires.length; i++)
// The tour required the user to be logged in.
{
if(tour.loggedInUsersOnly && mw.user.isAnon()) { return false; }
if($.cookie(cookiePrefix + tour.requires[i] + "-done") === null)
// ------------------- canEditPage -----------------------------
// The tour requires the user to be able to edit the current page.
if(tour.canEditPage && !mw.config.get('wgIsProbablyEditabled')) { return false; }
// ------------------- excludePages -----------------------------
page = mw.config.get("wgPageName");
for(var i = 0; i < tour.excludePages.length; i++)
{
if(tour.excludePages[i].test(page)) { return false; } // Page is excluded.
}
// ------------------- requires -----------------------------
for(var i = 0; i < tour.requires.length; i++)
{
{
return false; // Tour requires a tour that has not been taken yet.
time = null;
if(this.getTourState(tour.requires[i]) == "completed" || this.getTourState(tour.requires[i]) == "abandoned")
{
time = this.getTourTime(tour.requires[i]);
}
if(time === null)
{
return false; // Tour requires a tour that has not been taken (or abandoned) yet.
}
if(parseInt(time) + tour.startDelayFromRequiredTours > Date.now())
{
return false; // Not enough time has passed since the completion (or abandonment) of this required tour.
}
}
}
}
return true; // The tour can run
},
return true; // The tour can run
/*
},
* Reset all tour states.
 
*/
/*
reset: function()
* Iterates over tours to launch them.
*/
launchTours: function()
{
console.log('Launching tours ... ');
d = $(".guider").css("display");
if(d != undefined && d != "none")
{
{
console.log('currently taking a tour.');
for(const tour in this.tours)
return;
{
}
this.deleteTourState(tour);
}
for(const tour in this.tours)
},
/*
* Iterates over tours to launch them.
*/
launch: function()
{
{
if(this.canLaunch(tour, this.tours[tour]))  
console.log('Launching tours ... ');
d = $(".guider").css("display");
if(d !== undefined && d != "none")
{
{
console.log(tour + ' available for launching');
console.log('currently taking a tour.');
mw.guidedTour.launcher.launchTour(tour);
return;
return; // A tour has launched, stop the loop as we don't want multiple tours to launch.
}
}
for(const tour in this.tours)
{
if(this.canLaunch(tour, this.tours[tour]))
{
if(this.tours[tour].debug) // Tour is in debug mode.
{
console.log(tour + ' would have launched (but is in debug mode)');
continue;
}
console.log(tour + ' available for launching');
mw.guidedTour.launcher.launchTour(tour);
return; // A tour has launched, stop the loop as we don't want multiple tours to launch.
}
}
console.log('none ready to lauch.');
}
}
};
console.log('none ready to lauch.');
}
/* Create an observer instance to look for the addition of a guider element to the interface
};
Source: https://gabrieleromanato.name/jquery-detecting-new-elements-with-the-mutationobserver-object */
 
var observer = new MutationObserver(function( mutations )  
// Load the tour launcher asynchronously.
mw.loader.using("ext.guidedTour.launcher", function()
{
/*  
* Override the mw.guidedTour.launcher to add an override to the guiders library.
* This allows cookie handling wether a tour has been launched using this library, from a URL
* or from a cookie while resuming a tour.
*/
mw.guidedTour.launcher.launchTour = function(tourName, tourId)  
{
{
// Prevent tours from being launched within an iframe.
mutations.forEach(function( mutation )  
if(window.self != window.top) { return; }
mw.loader.using( 'ext.guidedTour.lib', function ()  
{
{
// Override the createGuider method from the guiders library to record tour completion or exit.
var newNodes = mutation.addedNodes; // DOM NodeList
var parent = mw.libs.guiders.createGuider;
mw.libs.guiders.createGuider = function ( passedSettings )
if( newNodes === null ) // If there are no new nodes added.
{
{
parent(passedSettings);
return;
}
$(newNodes).each(function()  
{
var $node = $(this);
if(!$node.hasClass("guider"))
{
return; // Not a guider.
}
prefix = mw.config.get("wgCookiePrefix") + "-mw-tour-" + tourName + '-';
// Deduce the tour name from the id.
// tourName = $node.attr('id').replace('gt-', '').replace('-overlay', '');
tourName = /(?:^|\s)mw-guidedtour-tour-(.*?)(?:\s|$)/g.exec($node.attr('class'))[1]; // Should use class because in id the step can be appended (e.g. basic_navigation-forum)
$.removeCookie(prefix + "skipped");
mw.guidedTour.loader.deleteTourState(tourName); // Reset the state of the tour.
$.removeCookie(prefix + "abandoned");
$.removeCookie(prefix + "completed");
$(".x_button").click(function() {
$(".x_button").click(function() {
Ligne 140 : Ligne 205 :
// This counts as a completion.
// This counts as a completion.
console.log(tourName +  ' tour complete');
console.log(tourName +  ' tour complete');
$.cookie(prefix + "complete", Date.now()); // Tour has been completed.
mw.guidedTour.loader.setTourState(tourName, "completed", Date.now()); // Tour has been completed.
return;
return;
}
}
console.log(tourName +  'tour abandoned');
console.log(tourName +  ' tour abandoned');
$.cookie(prefix + "abandoned", Date.now()); // Tour has been skipped.
mw.guidedTour.loader.setTourState(tourName, "abandoned", Date.now()); // Tour has been abandoned.
});
});
$(".mw-tour-later").click(function() {
$(".mw-tour-later").click(function() {
console.log(tourName +  ' tour skipped');
console.log(tourName +  ' tour postponed');
$.cookie(prefix + "skipped", Date.now()); // Tour has been postponed.
mw.guidedTour.loader.setTourState(tourName, "postponed", Date.now()); // Tour has been postponed.
mw.guidedTour.endTour();
});
});
$(".mw-tour-complete, .guidedtour-end-button").click(function() {
$(".mw-tour-complete, .guidedtour-end-button").click(function() {
console.log(tourName +  ' tour completed');
console.log(tourName +  ' tour completed');
$.cookie(prefix + "completed", Date.now()); // Tour has been completed.
mw.guidedTour.loader.setTourState(tourName, "completed", Date.now()); // Tour has been completed.
});
});
Ligne 162 : Ligne 228 :
// Adapted from: https://www.sanwebe.com/2014/10/draggable-element-with-jquery-no-jquery-ui
// Adapted from: https://www.sanwebe.com/2014/10/draggable-element-with-jquery-no-jquery-ui
$('.guider').
$('.guider').
css('cursor:move').
css('cursor', 'move').
hover(function() { $(this).css('border-width:3px'); }, function () { $(this).css('border-width:1px'); }).
hover(function() { $(this).css('border-width', '3px'); }, function() { $(this).css('border-width', '1px'); }).
on('mousedown', function(e) {
on('mousedown', function(e) {
var dr = $(this).addClass("drag");
var dr = $(this).addClass("drag");
Ligne 179 : Ligne 245 :
dr.removeClass("drag");
dr.removeClass("drag");
});
});
});
});
};
});
});  
mw.guidedTour.launchTour( tourName, tourId );
});
});
};
observer.observe($('body')[0], {
});
attributes: false,
 
childList: true,
console.log('Loaded tour loader gadget');
characterData: false
});
console.log('Loaded tour loader gadget');
mw.guidedTour.loader.launch(); // Launch next available tour.
}

Version du 16 septembre 2020 à 22:32

if(typeof MutationObserver == 'undefined') // If the MutationObserver pattern is not supported.
{
	console.log("Skipping tour loading, MutationObserver not supported.");
}
else if(window.self == window.top) // Prevent tours from being launched within an iframe.
{
	mw.guidedTour.loader = { // Hook to the mw.guidedTour to make the launcher globally accessible.
		tours: {
			basic_navigation: {
				requires: [], // List of tours that must have been completed for this tour to launch.
				startDelayFromRegistration: 300 * 1000, // Delay in milliseconds the tour must wait after registration to launch.
				postponeDelay: 60 * 60 * 24 * 1000, // Delay in milliseconds the tour is shown again after being postponed.
				loggedInUsersOnly: true, // If the tour is only for logged in users.
				canEditPage: true, // If the tour can only launch on pages a user can edit.
				excludePages: [/^Accueil$/, /^Wikimedica./, /^Classe./, /^Modèle./], // Exclude pages (regex).
				debug: false // Tour is in debug mode (not lauched when ready)
			},
			visual_edition: {
				requires: ["basic_navigation"], // List of tours that must have been completed for this tour to launch.
				startDelayFromRequiredTours: 24 * 60 * 60 * 1000, // Delay in milliseconds the tour must wait after required tours are completed.
				postponeDelay: 60 * 60 * 24 * 1000, // Delay in milliseconds the tour shown again after being postponed.
				loggedInUsersOnly: true, // If the tour is only for logged in users.
				canEditPage: true, // If the tour can only launch on pages a user can edit.
				excludePages: [/^Accueil$/, /^Wikimedica./, /^Classe./, /^Modèle./], // Exclude pages (regex).
				debug: false // Tour is in debug mode (not lauched when ready)
			}
		},
		
		
		optionPrefix: 'userjs-mw-tour-', // Prefix under which tour launch data is saved in the user options.
		
		// Get the state of a tour from the user options.		
		getTourState: function(name) { return mw.user.options.get(this.optionPrefix + name + "-state"); },
		
		// Get the state of a tour from the user options.		
		getTourTime: function(name) { return mw.user.options.get(this.optionPrefix + name + "-time"); },
		
		// Set a state and time for a tour state in the user options.
		setTourState: function(name, state, time) 
		{
			mw.user.options.set(this.optionPrefix + name + "-state", state);
			mw.user.options.set(this.optionPrefix + name + "-time", time);
			
			api = new mw.Api();
			api.postWithEditToken({
				'action': 'options',
				'change': this.optionPrefix + name + "-state=" + state + "|" + this.optionPrefix + name + "-time=" + time
			}).fail(function(data) { console.log("API options set call failed."); });
		},
		
		// Delete a tour state.
		deleteTourState: function(name, state) { this.setTourState(name, null, null); },
		
		/*
		 * Checks if all conditions for a tour to launch are met.
		 */
		canLaunch: function(name, tour)
		{
			// Normalize configuration.
			if(tour.startDelayFromRegistration === undefined) { tour.startDelayFromRegistration = 0; }
			if(tour.startDelayFromRequiredTours === undefined) { tour.startDelayFromRequiredTours = 0; }
			if(tour.postponeDelay === undefined) { tour.postponeDelay = false; }
			if(tour.launchProbability === undefined) { tour.lauchProbability = 1; }
			if(tour.loggedInUsersOnly === undefined) { tour.loggedInUsersOnly = true; }
			if(tour.canEditPage === undefined) { tour.canEditPage = true; }
			if(tour.excludePages === undefined) { tour.excludePages = []; }
			if(tour.requires === undefined) { tour.requires = []; }
			
			// If the tour has been completed or abandoned.
			if(this.getTourState(name) == "completed" || this.getTourState(name) == "abandoned")
			{
				return false;
			}
			
			// ------------------- startDelayfromRegistration -----------------------------
			d = new Date(mw.user.getRegistration());
			
			// Not past the delay yet.
			if(d.getTime() + tour.startDelayFromRegistration > Date.now()) { return false; }
			
			// ------------------- postponeDelay -----------------------------
			if(tour.postponeDelay !== false)
			{
				if(this.getTourState(name) == "postponed" && (this.getTourTime(name) + tour.postponeDelay > Date.now())) { return false; } // Tour has been postponed for now.
			}
			
			// ------------------- launchProbability -----------------------------
			// if(tour.launchProbability < Math.random()) { return false; }
		
			// ------------------- loggedInUsersOnly -----------------------------
			// The tour required the user to be logged in.
			if(tour.loggedInUsersOnly && mw.user.isAnon()) { return false; } 
			
			// ------------------- canEditPage -----------------------------
			// The tour requires the user to be able to edit the current page.
			if(tour.canEditPage && !mw.config.get('wgIsProbablyEditabled')) { return false; }
			
			// ------------------- excludePages -----------------------------
			page = mw.config.get("wgPageName");
			for(var i = 0; i < tour.excludePages.length; i++)
			{
				if(tour.excludePages[i].test(page)) { return false; } // Page is excluded.
			}
			
			// ------------------- requires -----------------------------
			for(var i = 0; i < tour.requires.length; i++)
			{
				time = null;
				if(this.getTourState(tour.requires[i]) == "completed" || this.getTourState(tour.requires[i]) == "abandoned")
				{
					time = this.getTourTime(tour.requires[i]);
				}
				
				if(time === null)
				{
					return false; // Tour requires a tour that has not been taken (or abandoned) yet.
				}
				
				if(parseInt(time) + tour.startDelayFromRequiredTours > Date.now())
				{ 
					return false; // Not enough time has passed since the completion (or abandonment) of this required tour.
				}
			}
			
			return true; // The tour can run
		},
		
		/*
		 * Reset all tour states.
		 */
		reset: function()
		{
			for(const tour in this.tours)
			{
				this.deleteTourState(tour);
			}
		},
	
		/*
		 * Iterates over tours to launch them.
		 */
		launch: function()
		{
			console.log('Launching tours ... ');
			
			d = $(".guider").css("display");
			if(d !== undefined && d != "none")
			{
				console.log('currently taking a tour.');
				return;
			}
			
			for(const tour in this.tours)
			{
				if(this.canLaunch(tour, this.tours[tour])) 
				{
					if(this.tours[tour].debug) // Tour is in debug mode.
					{
						console.log(tour + ' would have launched (but is in debug mode)');
						continue;
					}
					
					console.log(tour + ' available for launching');
					mw.guidedTour.launcher.launchTour(tour);
					return; // A tour has launched, stop the loop as we don't want multiple tours to launch.
				}
			}
			
			console.log('none ready to lauch.');
		}
	};
	
	/* Create an observer instance to look for the addition of a guider element to the interface 
	Source: https://gabrieleromanato.name/jquery-detecting-new-elements-with-the-mutationobserver-object */
	var observer = new MutationObserver(function( mutations ) 
	{
		mutations.forEach(function( mutation ) 
		{
			var newNodes = mutation.addedNodes; // DOM NodeList
			
			if( newNodes === null ) // If there are no new nodes added.
			{
				return;
			}
			
			$(newNodes).each(function() 
			{
				var $node = $(this);
				
				if(!$node.hasClass("guider")) 
				{
					return; // Not a guider.
				}
				
				// Deduce the tour name from the id.
				// tourName = $node.attr('id').replace('gt-', '').replace('-overlay', '');
				tourName = /(?:^|\s)mw-guidedtour-tour-(.*?)(?:\s|$)/g.exec($node.attr('class'))[1]; // Should use class because in id the step can be appended (e.g. basic_navigation-forum)
				
				mw.guidedTour.loader.deleteTourState(tourName); // Reset the state of the tour.
				
				$(".x_button").click(function() {
					// If there was a complete button on the step.
					if($(".mw-tour-complete, .guidedtour-end-button").length)
					{
						// This counts as a completion.
						console.log(tourName +  ' tour complete');
						mw.guidedTour.loader.setTourState(tourName, "completed", Date.now()); // Tour has been completed.
						
						return;
					}
					
					console.log(tourName +  ' tour abandoned');
					mw.guidedTour.loader.setTourState(tourName, "abandoned", Date.now()); // Tour has been abandoned.
				});
				
				$(".mw-tour-later").click(function() {
					console.log(tourName +  ' tour postponed');
					mw.guidedTour.loader.setTourState(tourName, "postponed", Date.now()); // Tour has been postponed.
					mw.guidedTour.endTour();
				});
				
				$(".mw-tour-complete, .guidedtour-end-button").click(function() {
					console.log(tourName +  ' tour completed');
					mw.guidedTour.loader.setTourState(tourName, "completed", Date.now()); // Tour has been completed.
				});
				
				// Make the guiders draggable so users can move them when they are interfering with an element.
				// Adapted from: https://www.sanwebe.com/2014/10/draggable-element-with-jquery-no-jquery-ui
				$('.guider').
					css('cursor', 'move').
					hover(function() { $(this).css('border-width', '3px'); }, function() { $(this).css('border-width', '1px'); }).
					on('mousedown', function(e) {
						var dr = $(this).addClass("drag");
						height = dr.outerHeight();
						width = dr.outerWidth();
						ypos = dr.offset().top + height - e.pageY,
						xpos = dr.offset().left + width - e.pageX;
						$(document.body).on('mousemove', function(e){
							var itop = e.pageY + ypos - height;
							var ileft = e.pageX + xpos - width;
							if(dr.hasClass("drag")){
								dr.offset({top: itop,left: ileft});
							}
						}).on('mouseup', function(e){
							dr.removeClass("drag");
						});
					});
			});
		});    
	});
	
	observer.observe($('body')[0], { 
		attributes: false, 
		childList: true, 
		characterData: false 
	});
	
	console.log('Loaded tour loader gadget');
	mw.guidedTour.loader.launch(); // Launch next available tour.
}