MediaWiki:Gadget-addMe.js

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// AddMe gadget, taken from Meta and heavily edited for the Developer Wishlist.
// Supports endorse / join / vote by adding your signature and possibly other text
// See https://meta.wikimedia.org/wiki/Meta:AddMe for usage and description.

/*
 * Common utilities for both the endorse & the join gadget
 */
var gadgetUtilities = function (){
	//A reference to the object
	var that = this;
	//The mw wrapper to access the API
	var api = new mw.Api();
	
	/*
	 * The interface messages or strings are maintained in interfaceMessagesPath
	 */
	this.interfaceMessagesPath = 'MediaWiki:AddMe/InterfaceText';

	//The time taken for the page to scroll to the feedback speech bubble (milliseconds)
	this.feedbackScrollTime = 2000;
	
	//The time taken for the feedback speech bubble to disappear (milliseconds)
	this.feedbackDisappearDelay = 10000;
	/*
	 * This function is used to set a cookie to show the speech bubble
	 * on page reload
	 */
	this.setFeedbackCookie = function(value, domain, section){
		$.cookie('addme-'+value, domain+'|'+section);
	};
	/*
	 * This function is used to check if a has been set by the above function 
	 * to show the speech bubble on page reload
	 */
	this.checkFeedbackCookie = function(value){
		var val = $.cookie('addme-'+value);
		if(val){
			$.cookie('addme-'+value,null);
			return val.split('|');
		}
		else{
			return false;
		}
	};
	/*
	 * To display an error message when an error occurs
	 * in the gadget 
	 */
	this.showErrorMessage =  function(gadget,type){
		var errorAttr = '[localize=error-'+type+']';
		var gadgetID = '.'+gadget;
		$(gadgetID + ' ' + errorAttr).show();
	};
	/*
	 * To remove the error message displayed by the above function 
	 */
	this.removeErrorMessage = function(gadget){
		var gadgetID = '.'+gadget;
		$(gadgetID + ' [localize^="error-"]').hide();
	};
	/*
	 * To detect the users default language
	 */
	this.userLanguage = function(){
		return mw.config.get('wgUserLanguage');
	};
	/*
	 * To detect the language of the page
	 */
	this.contentLanguage = function(){
		return mw.config.get('wgContentLanguage');
	};
	/*
	 * To remove extra spaces & cleanup the comment string
	 */
	this.cleanupText = function(text){
			if ( !text ) return '';
			return $.trim(text).replace(/~{3,5}/, '')+' ';
	};
	/*
	 * The config files which can be translated with the help of the 
	 * translation tool generates the dict with the values having a 
	 * lot of space in the key value pairs. This function strips the 
	 * whitespace.
	 */
	this.stripWhiteSpace = function(dict){
		// for (var key in dict){
		// 	dict[key] = typeof(dict[key]) == 'object' ? that.stripWhiteSpace(dict[key]) : $.trim(dict[key]);
		// }
		return dict;
	};
	/*
	 * To localize the gadget's interface messages based on the user's language setting
	 */
	this.localizeGadget = function ($dialog,localizeDict){
		$dialog.find('[localize]').each(function(){
			var localizeValue = localizeDict[$(this).attr('localize')];
			if($(this).attr('value')){
				$(this).attr('value',localizeValue);
			}
			else if($(this).attr('placeholder')){
				$(this).attr('placeholder',localizeValue);
			}
			else if($(this).attr('data-placeholder')){
				$(this).attr('data-placeholder',localizeValue);
			}
			else{
				$(this).html(localizeValue);
			}
		});
	};
	/*
	 * This function show the feedback speech bubble after an 
	 * endorsement has been made or after joining a project
	 */
	this.showFeedback = function(sectionName,InterfaceMessages){
		var li = $('#'+mw.util.escapeIdForAttribute(sectionName).replace(/\./g, '\\.'))
			.closest('h1,h2,h3,h4,h5,h6').nextAll('ul,ol').eq(0).find('li').eq(-1);
		speechBubble = li.append($('<div class="grantsSpeechBubbleContainer"></div>').html('<div class="grantsSpeechBubble">\
		<span localize="message-feedback">Thank You</span></div><div class="grantsSpeechBubbleArrowDown"></div>')).find('.grantsSpeechBubbleContainer');
		var width = li.css('display','inline-block').width();
		li.css('display','');
		li.css('position','relative');
		speechBubble.css('left',width/2+'px');
		$('[localize=message-feedback]').html(InterfaceMessages['message-feedback']);
		//$("body, html").animate({ scrollTop : li[0].offsetTop}, that.feedbackScrollTime);
		setTimeout(function(){ speechBubble.hide();},that.feedbackDisappearDelay);
	};
};
var Gadget = function(){
	/* Variables */
	var util = new gadgetUtilities();
	var dialogs = [];
	
	var api = new mw.Api();	
	var that = this;

	/*
	 * This function creates the dialog box for the gadget.
	 * It is also where all the dialog related interactions are defined.
	 */ 
	var createDialog = function(){
		var hasFreeText = 'message-description' in that.interfaceMessages;
		
		function updateDisable( $dialog  ) {
			if($dialog.find('.devEndorseComment').val()){
				$dialog.find('.add-endorse').attr('disabled',false);
				$dialog.find('.messageSignature').css('visibility','visible');
			}else{
				if ($dialog.find('.devEndorseComment').length) {
					$dialog.find('.add-endorse').attr('disabled',true);
				}
				$dialog.find('.messageSignature').css('visibility','hidden');
			}
		}
		
		var $dialog = $( "<div class='devEndorseDialog'></div>" )
				.html(
					'<div class="mw-ui-vform">\
					 	<div class="error grantsHide" localize="error-save">An error occurred</div>\
					 	<div class="error grantsHide" localize="error-login">An error occurred</div>\
					 </div>'
					 +(hasFreeText ?
					 '<div localize="message-description" class="messageDescription">Endorse the project</div>' + '\
					 <textarea rows="5" cols="10" placeholder="Please explain why you endorse this project." class="devEndorseComment" class="" localize="placeholder-comment"></textarea>\
					 <span localize="message-signature" class="messageSignature">Your signature will be added automatically</span>'
					 : '')+
					 '<div class="gadgetControls">\
						<a href="#" localize="button-cancel" class="mw-ui-button cancel mw-ui-quiet">Cancel</a>\
						<input type="submit" localize="button-submit" class="mw-ui-button mw-ui-constructive add-endorse" ' + (hasFreeText ? 'disabled ' : '') + 'localize="button" value="Ok"></input>\
					 </div>'
		);
		var dialog = $dialog.dialog({
			dialogClass: 'grantsGadget endorseGadget',
			autoOpen: false,
			title: that.interfaceMessages.title + ': ' + that.title,
			width: '495px',
			modal: true,
			closeOnEscape: true,
			resizable: false,
			draggable: false,
			open: function( event, ui ) {
				updateDisable( $dialog );
			},
			close: function( event, ui ) {
				$('.devEndorseComment').val('');
			}
		});

		$dialog.find('.add-endorse').click(function(){
			var text =  util.cleanupText($dialog.find('.devEndorseComment').val()),
				summary = '/* ' + that.title + ' */ ' + that.interfaceMessages.summary + ' ' + mw.user.getName();
			that.addEndorsement(that.section, text, summary);
		});
		
		$dialog.find('.devEndorseComment').on('change keyup paste',function(){
			util.removeErrorMessage('endorseGadget');
			updateDisable($dialog);
		});

		$dialog.find('.cancel').click(function(){
			dialog.dialog('close');
		});
		
		util.localizeGadget($dialog,that.interfaceMessages);
		
		$dialog.find('.messageSignature').css('visibility','hidden');
		
		return dialog;
	};
	/**
	 * Tells which configuration/i18n subset to use.
	 */
	this.setDomain = function (domain) {
		this.domain = domain;
 		this.interfaceMessages = util.stripWhiteSpace(this.allInterfaceMessages[domain]);
	};
	this.Dialog = function () {
		if (!dialogs[that.domain]){
			dialogs[that.domain] = createDialog();
		}
		dialogs[that.domain].dialog('open');
	};
	/*
	 * The main function to add text + a signature to the page.
	 */
	this.addEndorsement = function( sectionName, text, summary ) {
		var endorseComment = '\n#' + text + '~~' + '~~' + '\n';
		var newText, baseTimestamp, revId, sectionCount;
		api.get({
			'format':'json',
			'action':'parse',
			'prop':'sections',
			'page':mw.config.get('wgPageName')
		}).then(function(result){
			var sections = result.parse.sections;
			sectionCount = 1;
			var sectionFound = false;
			for (var section in sections ){
				if ($.trim(sections[section].line) === sectionName ){
					sectionFound = true;
					break;
				}
				sectionCount++;
			}
			if (sectionFound){
				return api.get({
					'format':'json',
					'action':'parse',
					'prop':'revid|sections|wikitext',
					'page': mw.config.get('wgPageName'),
					'section': sectionCount
				});
			} else {
				return $.Deferred().reject( 'addme-sectionnotfound', 'Did not find the right section to edit!' );
			}
		}).then(function(result){
			if ( result.parse.sections[0].line !== sectionName ) { // sanity
				return $.Deferred().reject( 'addme-sectionchanged', 'Edit conflict, please retry' );
			}
			revId = result.parse.revid;
			var wikitext = result.parse.wikitext['*'];
			newText = wikitext.replace( /\n#\s*$/, '' );
			if ( newText[newText.length - 1] === '\n' ) {
				newText = newText.substr(0, -1);
			}
			newText += endorseComment;
			// fetch the base timestamp
			return api.get({
				'format':'json',
				'action':'query',
				'indexpageids': 1,
				'prop':'revisions',
				'titles': mw.config.get('wgPageName'),
				'rvprop': 'ids|timestamp'
			});
		}).then(function(result){
			var data = result.query.pages[result.query.pageids[0]].revisions[0];
			if ( data.revid !== revId ) { // sanity
				return $.Deferred().reject( 'addme-sectionchanged', 'Edit conflict, please retry' );
			}
			return api.postWithToken( 'edit', {
				'action' : 'edit',
				'title' : mw.config.get('wgPageName'),
				'text' : newText,
				'summary' : summary,
				'section': sectionCount,
				'basetimestamp': baseTimestamp
			});
		}).then(function(){
			console.log('Successfully added endorsement');
			window.location.reload(true);
			util.setFeedbackCookie('feedback', that.domain, that.section);	
		},function(code, data){
			console.log('Error!', code, data);
			util.showErrorMessage('gadget','save');
		});
	};
};

/* End of functions */
if (  $('.wp-addme-button').length) {
mw.loader.using( ['jquery.ui', 'mediawiki.util','mediawiki.api', 'mediawiki.ui','jquery.chosen'], function() {	
	var gadget = new Gadget();		
	var util = new gadgetUtilities();
	var api = new mw.Api();
	var interfaceMessagesUrl = mw.config.get('wgScript')+'?title='+util.interfaceMessagesPath+'.js'+'&action=raw';

	$.get(interfaceMessagesUrl).then(function( interfaceStr ){
		var interfaceData = JSON.parse( interfaceStr );
		var feedbackData = util.checkFeedbackCookie('feedback');

 		gadget.allInterfaceMessages = interfaceData;

		if(feedbackData){
			gadget.setDomain(feedbackData[0]);
			try {
				util.showFeedback(feedbackData[1], gadget.interfaceMessages);
			} catch(e) {
				console.log('[AddMe] showFeedback error', e);
			}
		}
		
		$('.wp-addme-button').click(function(e){
			var $this = $(this),
				section = $this.attr('data-addme-section'),
				domain = $this.attr('data-addme-domain'),
				// hack - proper entity encoding is hard in wikitext
				title = decodeURIComponent($this.attr('data-addme-title'));
			e.preventDefault();
			
			//Checking if the user is logged in
			if(!mw.config.get('wgUserName')){
				util.showErrorMessage('gadget','login');
				return;
			}
			
			gadget.section = section;
			gadget.title = title;
			gadget.setDomain(domain);
			gadget.Dialog();
		});
	});
});
}