/* ______________________________________________________________________________________
* | |
* | === 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
*/
//<nowiki>
//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($('<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);
};
};
/*
* 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 = $( "<div id='devEndorseDialog'></div>" )
.html(
'<div class="mw-ui-vform">\
<div class="error grantsHide" localize="error-save">An error occured</div>\
<div class="error grantsHide" localize="error-login">An error occured</div>\
</div>\
<div localize="message-description" class="messageDescription">Explaining your endorsement improves process</div>' + '\
<textarea rows="5" cols="10" placeholder="Add your comment" id="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" disabled localize="button" value="Ok"></input>\
</div>'
).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<markup.length;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 = $( "<div id='devJoinDialog'></div>" )
.html(
'<div class="mw-ui-vform">\
<div class="error grantsHide" localize="error-save">An error occured</div>\
<div class="error grantsHide" localize="error-login">An error occured</div>\
</div>\
<select class="roleSelect" localize="placeholder-role" data-placeholder="Select a role">\
<option></option>\
</select>\
<div localize="message-description" class="messageDescription">Tell us how you would like to help</div>\
<textarea rows="5" cols="10" placeholder="Add your comment" id="devJoinComment" 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-join" class="mw-ui-button mw-ui-constructive add-join" disabled localize="button" value="Join"></input>\
</div>'
).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('<option value='+role+'>'+roles[role]+'</option>');
}
}
}
}
if(!$('.roleSelect option[value="volunteer"]').length){
$('.roleSelect').append('<option value="volunteer">'+roles['volunteer']+'</option>');
}
$('.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').unbind();
$('.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').unbind();
$('.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();
}
}
})();
});
});
//</nowiki>