MediaWiki:Gadget-addMe.js

/* ______________________________________________________________________________________

* | |

* | === WARNING: GADGET FILE === |

* | Changes to this page affect many users. |

* | Please discuss changes on the talk page or on MediaWiki_talk:Gadgets-definition |

* | before editing. |

* |_____________________________________________________________________________________|

*

* "Endorse & Join" feature, to be used by the Wikimedia Foundation's Grants Programme

*/

//

//The stylesheet with all the styles for the endorse & the join gadget

mw.loader.load( '//meta.wikimedia.org/w/index.php?title=MediaWiki:Gadget-addMe.css&action=raw&ctype=text/css', 'text/css' );

/*

* 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 & config values eg,

* section-header, the section where the comments are added etc are maintained in configPath

*/

this.interfaceMessagesPath = 'MediaWiki:Gadget-addMe/InterfaceText';

this.configPath = 'MediaWiki:Gadget-addMe/Config';

//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){

$.cookie(value,true);

};

/*

* 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){

if($.cookie(value)){

$.cookie(value,null);

return true;

}

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 type of grant. IEG,PEG etc

*/

this.grantType = function(config){

var grant = mw.config.get('wgTitle').split('/')[0].replace(/ /g,'_');

if (grant in config){

return config[grant];

}

else{

return config['default'];

}

};

/*

* 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){

text = $.trim(text)+' ';

var indexOf = text.indexOf('~~~~');

if ( indexOf == -1 ){

return text;

}

else{

return text.slice(0,indexOf)+text.slice(indexOf+4);

}

};

/*

* 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 (key in dict){

//Temp fix for section header

if(key == 'section-header'){

dict['section-header-read'] = dict[key].replace(/ /g,'_');

dict['section-header-write'] = dict[key];

}

dict[key] = typeof(dict[key]) == 'object' ? that.stripWhiteSpace(dict[key]) : $.trim(dict[key]);

}

return dict;

};

/*

* The function creates the markup for the link to a

* user's user page

*/

this.addToInfobox = function(username){

return username;

};

/*

* To localize the gadget's interface messages based on the user's language setting

*/

this.localizeGadget = function (gadgetClass,localizeDict){

$(gadgetClass+' [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(config,InterfaceMessages){

var li = $('#'+config['section-header-read']).parent().next().find('li').eq(-1);

speechBubble = li.append($('

').html('
\

Thank You

')).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);

};

};

/*

* The Endorse Gadget

*/

var endorseGadget = function(){

/* Variables */

var util = new gadgetUtilities();

var dialog = null;

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(){

dialog = $( "

" )

.html(

'

\

An error occured
\

An error occured
\

\

Explaining your endorsement improves process
' + '\

\

Your signature will be added automatically\

\

Cancel\

\

'

).dialog({

dialogClass: 'grantsGadget endorseGadget',

autoOpen: false,

title: 'Endorse Comment',

width: '495px',

modal: true,

closeOnEscape: true,

resizable: false,

draggable: false,

close: function( event, ui ) {

$('#devEndorseComment').val('');

}

});

$('.add-endorse').click(function(){

that.addEndorsement(util.cleanupText($('#devEndorseComment').val()));

});

$('#devEndorseComment').on('change keyup paste',function(){

util.removeErrorMessage('endorseGadget');

if($(this).val()){

$('.add-endorse').attr('disabled',false);

$('.messageSignature').css('visibility','visible');

}

else{

$('.add-endorse').attr('disabled',true);

$('.messageSignature').css('visibility','hidden');

}

});

$('.endorseGadget .ui-dialog-title').attr('localize','title');

$('.endorseGadget .cancel').click(function(){

dialog.dialog('close');

});

util.localizeGadget('.endorseGadget',that.interfaceMessages);

$('.messageSignature').css('visibility','hidden');

};

this.Dialog = function () {

if (dialog === null){

createDialog();

}

else{

dialog.dialog('open');

}

};

/*

* The main function to add the feedback/endorsement to the page. It first checks if the page has an endorsement section.

* If it dosent it creates a new section called Endorsements and appends the feedback/endorsement comment to that section,

* else it appends the feedback/endorsement comment to existing Endorsements section.

* The name of the endorsement section is defined in the config.

*/

this.addEndorsement = function( text ) {

var endorseComment = '\n*' + text + '~~~~' + '\n';

api.get({

'format':'json',

'action':'parse',

'prop':'sections',

'page':mw.config.get('wgPageName'),

}).then(function(result){

var sections = result.parse.sections;

var sectionCount = 1;

var sectionFound = false;

for (var section in sections ){

if ($.trim(sections[section]['anchor']) == that.config['section-header-read'] ){

sectionFound = true;

break;

}

sectionCount++;

}

if (sectionFound){

api.get({

'format':'json',

'action':'parse',

'prop':'wikitext',

'page': mw.config.get('wgPageName'),

'section': sectionCount,

}).then(function(result){

var wikitext = result.parse.wikitext['*'];

var endorsementSection = wikitext + endorseComment;

api.post({

'action' : 'edit',

'title' : mw.config.get('wgPageName'),

'text' : endorsementSection,

'summary' : 'Endorsed by ' + mw.user.getName(),

'section': sectionCount,

'watchlist':'watch',

'token' : mw.user.tokens.get('csrfToken')

}).then(function(){

console.log('Successfully added endorsement');

window.location.reload(true);

util.setFeedbackCookie('endorseFeedback');

},function(){

util.showErrorMessage('endorseGadget','save');

});

});

}

else{

var sectionHeader = that.config['section-header-write'];

api.post({

'action': 'edit',

'title': mw.config.get('wgPageName'),

'section': 'new',

'summary': sectionHeader + ' Endorsed by ' + mw.user.getName(),

'sectiontitle': sectionHeader,

'text': $.trim(endorseComment),

'watchlist':'watch',

token: mw.user.tokens.get('csrfToken')

}).then(function () {

console.log('Successfully added endorsement');

location.reload();

util.setFeedbackCookie('endorseFeedback');

}, function(){

util.showErrorMessage('endorseGadget','save');

});

}

}, function(){

util.showErrorMessage('endorseGadget','save');

});

};

};

/*

* The function the create the join gadget and provides

* all the needed functionality.

*/

var joinGadget = function(){

/* Variables */

var util = new gadgetUtilities();

var dialog = null;

this.config = null ;

this.interfaceMessages = null;

var infobox = '';

var roleDict = {};

var api = new mw.Api();

var that = this;

/*

* A count is maintained of the open '{{' braces

* when a '}}' is encountered the counter is decremented.

* If the counter reaches 0 the end of the infobox has been found.

* Else the syntax is broken or the end of the infobox is not in

* the first section of the page.

*/

var extractInfobox = function(markup){

var startIndex = markup.indexOf('{{Probox');

var counter = 0;

var endIndex = 0;

for (i=startIndex;i

if(markup[i] == '}' && markup[i+1] == '}'){

counter++;

}

if(markup[i] == '{' && markup[i+1] == '{'){

counter--;

}

if(counter == 0){

var endIndex = i+2;

break;

}

}

if (counter != 0){

return '';

}

var infobox = {

'infobox' : markup.slice(startIndex,endIndex),

'before' : markup.slice(0,startIndex),

'after' : markup.slice(endIndex),

};

//return markup.slice(startIndex,endIndex);

return infobox;

};

/*

* This function creates the dialog & defines

* needed interactions in the dialog.

*/

var createDialog = function(){

dialog = $( "

" )

.html(

'

\

An error occured
\

An error occured
\

\

\

Tell us how you would like to help
\

\

Your signature will be added automatically\

\

Cancel\

\

'

).dialog({

dialogClass: 'grantsGadget joinGadget',

autoOpen: false,

title: 'join Comment',

width: '495px',

modal: true,

closeOnEscape: true,

resizable: false,

draggable: false,

close: function( event, ui ) {

$('#devJoinComment').val('');

}

});

$('.add-join').click(function(){

/*

* Creating the comment to add to the participants section. The comment is of the form

* "Role" User comment. Eg, Volunteer I can help out in many ways.

*/

var joinRole = $('.roleSelect').val().replace(/_/,' ');

joinRole=joinRole[0].toUpperCase()+joinRole.slice(1);

joinRole = ""+ joinRole + "" + " ";

that.addjoinment(joinRole+util.cleanupText($('#devJoinComment').val()));

});

$('#devJoinComment').on('change keyup paste',function(){

util.removeErrorMessage('joinGadget');

if($(this).val()){

$('.messageSignature').css('visibility','visible');

if($('.roleSelect').val()){

$('.add-join').attr('disabled',false);

}

}

else{

$('.add-join').attr('disabled',true);

$('.messageSignature').css('visibility','hidden');

}

});

$('.joinGadget .ui-dialog-title').attr('localize','title');

$('.joinGadget .cancel').click(function(){

dialog.dialog('close');

});

util.localizeGadget('.joinGadget',that.interfaceMessages);

$('.messageSignature').css('visibility','hidden');

/*

* The code below gets the infobox, check for open roles,

* makes sure that these roles are available for other to

* join by looking up roles in the config and creates a drop down

* from which a user can select a role.

*/

api.get({

'format':'json',

'action':'parse',

'prop':'wikitext',

'page': mw.config.get('wgPageName'),

'section': 0

}).then(function(result){

var roles = that.interfaceMessages['roles'];

var wikitext = result.parse.wikitext['*'];

var content = extractInfobox(wikitext);

var infobox = that.infobox = content['infobox'];

that.before = content['before'];

that.after = content['after'];

units = infobox.split('\n');

for (unit in units){

var line = units[unit];

var role = line.match(/[a-zA-z]+/g);

if (role){

role = role.join('');

var elements = line.split('=');

var count = elements[0].match(/[0-9]+/)?elements[0].match(/[0-9]+/)[0]:1;

if (role.indexOf('volunteer') != -1){

roleDict['volunteer']=count;

}

if(role in roles && line.indexOf('=') != -1){

roleDict[role]=count;

if(!$('.roleSelect option[value="'+role+'"]').length){

$('.roleSelect').append('');

}

}

}

}

if(!$('.roleSelect option[value="volunteer"]').length){

$('.roleSelect').append('');

}

$('.roleSelect').chosen({

disable_search: true,

placeholder_text_single: 'Select a role',

width: '50%',

});

/* Fix this */

/*

$('.roleSelect').on('chosen:showing_dropdown',function(){

util.removeErrorMessage('endorseGadget');

});

*/

$('.roleSelect').on('change',function(){

util.removeErrorMessage('joinGadget');

if($(this).val() && $('#devJoinComment').val()){

$('.add-join').attr('disabled',false);

}

else{

$('.add-join').attr('disabled',true);

}

});

});

};

this.Dialog = function () {

if (dialog === null){

createDialog();

}

else{

dialog.dialog('open');

}

};

/*

* The main function to add the feedback/join comment to the page. It first checks if the page has an Participants section.

* If it dosent it creates a new section called Participants and appends the fedback/comment to that section,

* else it appends the feedback/comment to existing Participants section.

*/

this.addjoinment = function( text ) {

var joinComment = '\n*' + text + '~~~~' + '\n';

//var joinComment = '*' + text + '~~~~' + '\n';

//Editing the infobox

var userName = mw.config.get('wgUserName')?mw.config.get('wgUserName'):'{{subst:REVISIONUSER}}';

var roleSelected = $('.roleSelect').val();

var units = that.infobox.split('\n');

var emptyRoleAdded = false;

for (unit in units){

if ($.trim(units[unit].split('=')[1]) == ''){

var role = units[unit].match(/[a-zA-z]+/);

if (role){

role = role[0];

if(role == roleSelected){

units[unit] = $.trim(units[unit]) + util.addToInfobox(userName);

emptyRoleAdded = true;

break;

}

}

}

}

var modifiedInfoBox = units.join("\n");

if(!emptyRoleAdded){

var paramCount = roleDict["volunteer"] ? parseInt(roleDict["volunteer"]) + 1 : 1;

var endIndex = modifiedInfoBox.lastIndexOf('}}');

modifiedInfoBox = modifiedInfoBox.slice(0,endIndex)+'|volunteer'+paramCount+'='+util.addToInfobox(userName)+'\n}}';

}

api.post({

'action' : 'edit',

'title' : mw.config.get('wgPageName'),

'text' : that.before + modifiedInfoBox + that.after,

'summary' : 'Joined as ' + roleSelected,

'section': 0,

'watchlist':'watch',

'token' : mw.user.tokens.get('csrfToken')

}).then(function(){

api.get({

'format':'json',

'action':'parse',

'prop':'sections',

'page':mw.config.get('wgPageName'),

}).then(function(result){

var sections = result.parse.sections;

var sectionCount = 1;

var sectionFound = false;

for (var section in sections ){

if ($.trim(sections[section]['anchor']) == that.config['section-header-read'] ){

sectionFound = true;

break;

}

sectionCount++;

}

if (sectionFound){

api.get({

'format':'json',

'action':'parse',

'prop':'wikitext',

'page': mw.config.get('wgPageName'),

'section': sectionCount

}).then(function(result){

var wikitext = result.parse.wikitext['*'];

var joinmentSection = wikitext + joinComment;

api.post({

'action' : 'edit',

'title' : mw.config.get('wgPageName'),

'text' : joinmentSection,

'summary' : 'Adding my name to the participants section',

'section': sectionCount,

'watchlist':'watch',

'token' : mw.user.tokens.get('csrfToken')

}).then(function(){

console.log('Successfully added to participants');

location.reload();

util.setFeedbackCookie('joinFeedback');

}, function(){

util.showErrorMessage('joinGadget','save');

});

});

}

else{

var sectionHeader = that.config['section-header-write'];

api.post({

'action': 'edit',

'title': mw.config.get('wgPageName'),

'section': 'new',

'summary': sectionHeader,

'text': $.trim(joinComment),

'watchlist':'watch',

'token': mw.user.tokens.get('csrfToken')

}).then(function () {

console.log('Successfully added to participants');

location.reload();

util.setFeedbackCookie('joinFeedback');

}, function(){

util.showErrorMessage('joinGadget','save');

});

}

}, function(){

util.showErrorMessage('joinGadget','save');

});

}, function(){

util.showErrorMessage('joinGadget','save');

});

};

};

/* End of functions */

mw.loader.using( ['jquery.ui', 'mediawiki.api', 'mediawiki.ui','jquery.chosen'], function() {

$(function() {

(function(){

var namespace = mw.config.get('wgCanonicalNamespace');

/*

* Fix mw.config.get('wgPageContentLanguage') == 'en') checking with a better solution,

* either when pages can be tagged with arbitary language or when we set langauge markers later on.

*

*/

if ( $('.wp-join-button,.wp-endorse-button').length) {

if(mw.config.get('wgPageContentLanguage') == 'en'){

var endorse = new endorseGadget();

var join = new joinGadget();

var util = new gadgetUtilities();

var api = new mw.Api();

var interfaceMessagesFullPath = util.interfaceMessagesPath+'/'+util.userLanguage();

var configFullPath = util.configPath+'/'+util.contentLanguage();

/*

* To detect if we have the gadget translations and config in the desired languages.

* Currently page language is English always. So the config returned is in en. The InterfaceMessages is

* in the user's language

*/

api.get({'action':'query','titles':interfaceMessagesFullPath+'|'+configFullPath,'format':'json'}).then(function(data){

for(id in data.query.pages){

if (data.query.pages[id].title == util.interfaceMessagesPath+'/'+util.userLanguage() &&id == -1){

interfaceMessagesFullPath = util.interfaceMessagesPath+'/en';

}

if (data.query.pages[id].title == util.configPath+'/'+util.contentLanguage() && id == -1){

configFullPath = util.configPath+'/en';

}

}

var interfaceMessagesUrl = mw.config.get('wgScript')+'?title='+interfaceMessagesFullPath+'&action=raw&ctype=text/javascript';

var configUrl = mw.config.get('wgScript')+'?title='+configFullPath+'&action=raw&ctype=text/javascript';

//Get the config for the detected language

$.when(jQuery.getScript(interfaceMessagesUrl),jQuery.getScript(configUrl)).then(function(){

//Stripping Whitespace

join.config = util.stripWhiteSpace(util.grantType(joinConfig));

join.interfaceMessages = util.stripWhiteSpace(util.grantType(joinInterfaceMessages));

endorse.config = util.stripWhiteSpace(util.grantType(endorseConfig));

endorse.interfaceMessages = util.stripWhiteSpace(util.grantType(endorseInterfaceMessages));

join.Dialog();

$('.wp-join-button').off();

$('.wp-join-button').click(function(e){

e.preventDefault();

join.Dialog();

});

if(util.checkFeedbackCookie('joinFeedback')){

util.showFeedback(join.config, join.interfaceMessages);

}

endorse.Dialog();

$('.wp-endorse-button').off();

$('.wp-endorse-button').click(function(e){

e.preventDefault();

endorse.Dialog();

});

//Checking if the user is logged in

if(!mw.config.get('wgUserName')){

util.showErrorMessage('endorseGadget','login');

util.showErrorMessage('joinGadget','login');

}

if(util.checkFeedbackCookie('endorseFeedback')){

util.showFeedback(endorse.config, endorse.interfaceMessages);

}

});

});

}

else{

$('.wp-join-button').hide();

$('.wp-endorse-button').hide();

}

}

})();

});

});

//