User:Lectrician1/MediaWiki:Gadget-addMe-WishlistSurvey.js

From Meta, a Wikimedia project coordination wiki

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.
// <nowiki>
/**
 * Fork of [[MediaWiki:Gadget-addMe.js]] for use in the [[Community Wishlist Survey]].
 * This gadget will be default-on until voting ends on December 10.
 * Please direct inquiries/concerns to [[Talk:Community Wishlist Survey]] or [[User talk:MusikAnimal (WMF)]].
 */
/* global RJSON */
/* eslint-disable no-implicit-globals, one-var, vars-on-top, no-jquery/no-global-selector */
/* eslint-disable no-jquery/no-trim, new-cap, no-useless-concat */
/* eslint-disable no-jquery/no-parse-html-literal, no-multi-str */
/* eslint-disable no-console */
/*
 * Common utilities for both the endorse & the join gadget
 */
var gadgetUtilities = function () {
  // A reference to the object
  var that = this;

  /*
   * 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 = 'Community Wishlist Survey/AddMe/InterfaceText';
  this.configPath = 'Community Wishlist Survey/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 () {
    mw.storage.set('showWishlistSurveyThanks', 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 () {
    if (mw.storage.get('showWishlistSurveyThanks')) {
      mw.storage.remove('showWishlistSurveyThanks');
      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('[[User:Lectrician1|Lectrician1]] ([[User talk:Lectrician1|talk]]) 05:26, 6 February 2022 (UTC)');
    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 (var 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) {
    var $li = $('#' + config['section-header-read']).parent().next().find('li').eq(-1);
    var $speechBubble = $li.append($('<div class="grantsSpeechBubbleContainer"></div>').html('<div class="grantsSpeechBubble">\
		<span>Thank you for participating in the survey!</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');
    $('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;

  var supportTemplates = '{{\\s*(support|s|yes|vote+|Sì|賛成|موافق|strong support|weak support)\\s*}}';

  // Define page information variables to be set by Initialize and used by addEndorsement
  var sectionCount = 0;
  var sectionFound = false;
  var wikitext;


  this.Initialize = function () {
    api.get({
      format: 'json',
      action: 'parse',
      prop: 'sections',
      page: that.title,
      uselang: 'en'
    }).then(function (result) {
      var sections = result.parse.sections;
      for (var section in sections) {
        if (sections[section].level > 3) {
          continue;
        }
        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: that.title,
          section: sectionCount
        }).then(function (result) {
          wikitext = result.parse.wikitext['*'];

          // Check if they've already voted.
          var regexp = new RegExp(
            supportTemplates + '.*?' +
            mw.config.get('wgUserName').replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') +
            '.*?\\(UTC\\)'
          );
          if (regexp.test(wikitext)) {
            $('.wp-addme-button').css({
              "background-color": "#c8ccd1",
              "color": "#ffffff",
              "border-color": "#c8ccd1",
              "cursor": "default",
            });
          }

          // They have not voted yet
          else {
            // Prepare dialog to be shown
            Dialog();
            $('.wp-addme-button').off();
            $('.wp-addme-button').on('click', function (e) {
              e.preventDefault();

              // Checking if the user is logged in
              if (!mw.config.get('wgUserName')) {
                util.showErrorMessage('gadget', 'login');
                return;
              }

              // Show dialog
              Dialog();
            });
          }
        });
      }
    });
  };

  /*
   * 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 occurred</div>\
				<div class="error grantsHide" localize="error-login">An error occurred</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" style="color: #333;"></textarea>\
			<span localize="message-signature" class="messageSignature">Your signature will be added automatically</span>\
			<div class="gadgetControls">\
				<span localize="button-cancel" class="mw-ui-button cancel mw-ui-quiet">Cancel</span>\
				<input type="submit" localize="button-submit" class="mw-ui-button mw-ui-progressive addme-support" localize="button" value="Support"></input>\
			</div>'
    ).dialog({
      dialogClass: 'grantsGadget endorseGadget',
      autoOpen: false,
      title: '<span localize="title">Support proposal</span>',
      width: '495px',
      modal: true,
      closeOnEscape: true,
      resizable: false,
      draggable: false,
      close: function () {
        $('#devEndorseComment').val('');
      }
    });

    $('.addme-support').on('click', function () {
      that.addEndorsement(util.cleanupText($('#devEndorseComment').val()));
    });

    $('.endorseGadget .cancel').on('click', function () {
      dialog.dialog('close');
    });

    util.localizeGadget('.endorseGadget', that.interfaceMessages);
  };
  var 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) {
    // Remove support template for comment if present.
    text = text.replace(new RegExp(supportTemplates), '').trim();

    var endorseComment = '\n* {{support}}' + (text.length ? ' ' + text : '') + ' [[User:Lectrician1|Lectrician1]] ([[User talk:Lectrician1|talk]]) 05:26, 6 February 2022 (UTC)' + '\n';
    var proposalUrl = 'https://meta.wikimedia.org' + (new mw.Title(that.title)).getUrl();
    var summary = 'Support proposal' + (text.length > 0 ? ': ' + text : '').trim();

    if (sectionFound) {
      var endorsementSection = wikitext + endorseComment;
      api.post({
        action: 'edit',
        title: that.title,
        text: endorsementSection,
        summary: summary,
        section: sectionCount,
        // 'watchlist':'watch',
        token: mw.user.tokens.get('csrfToken')
      }).then(function () {
        console.log('Successfully added endorsement');
        window.location = proposalUrl;
        util.setFeedbackCookie();
      }, function () {
        util.showErrorMessage('endorseGadget', 'save');
      });
    } else {
      api.get({
        format: 'json',
        action: 'parse',
        prop: 'wikitext',
        page: that.title,
        section: sectionCount
      }).then(function (result) {
        var wikitext = result.parse.wikitext['*'] + '\n=== ' + that.config['section-header-write'] + ' ===\n';

        api.post({
          action: 'edit',
          title: that.title,
          text: wikitext + endorseComment,
          summary: summary,
          section: sectionCount,
          // 'watchlist': 'watch',
          token: mw.user.tokens.get('csrfToken')
        }).then(function () {
          console.log('Successfully added endorsement');
          window.location = proposalUrl;
          util.setFeedbackCookie();
        }, function () {
          util.showErrorMessage('endorseGadget', 'save');
        });
      });
    }
  }, function () {
    util.showErrorMessage('endorseGadget', 'save');
  };
};

/* End of functions */
$(function () {
  if (!$('.wp-addme-button').length) {
    // Ignore this page
    return;
  }
  mw.loader.using(['jquery.ui', 'mediawiki.api', 'mediawiki.ui', 'jquery.chosen'], function () {
    /*
     * 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 (mw.config.get('wgPageContentLanguage') === 'en') {

      var endorse = new endorseGadget();
      var util = new gadgetUtilities();
      var api = new mw.Api();
      var interfaceMessagesFullPath = util.interfaceMessagesPath;
      var configFullPath = util.configPath;

      /*
      * 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 + '/' + util.userLanguage() + '|' + configFullPath + '/' + util.userLanguage(), format: 'json' }).then(function (data) {
        for (var id in data.query.pages) {
          if (data.query.pages[id].title === util.interfaceMessagesPath + '/' + util.userLanguage() && id > 0) {
            interfaceMessagesFullPath = util.interfaceMessagesPath + '/' + util.userLanguage();
          }
          if (data.query.pages[id].title === util.configPath + '/' + util.contentLanguage() && id > 0) {
            configFullPath = util.configPath + '/' + util.userLanguage();
          }
        }

        var interfaceMessagesUrl = 'https://meta.wikimedia.org/w/index.php?title=' + interfaceMessagesFullPath + '&action=raw';
        var configUrl = 'https://meta.wikimedia.org/w/index.php?title=' + configFullPath + '&action=raw';
        // Get the config for the detected language
        $.when($.get(interfaceMessagesUrl), $.get(configUrl)).then(function (interfaceStr, configStr) {
          var interfaceData = RJSON.parse(interfaceStr[0]),
            configData = RJSON.parse(configStr[0]);

          endorse.config = util.stripWhiteSpace(util.grantType(configData.endorse));
          endorse.interfaceMessages = util.stripWhiteSpace(util.grantType(interfaceData.endorse));

          // hack - proper entity encoding is hard in wikitext
          proposal = decodeURIComponent($('.wp-addme-button').attr('data-addme-proposal').replace(/\+/g, '_'));

          endorse.section = $('.wp-addme-button').attr('data-addme-section')
          endorse.title = mw.config.get('wgTitle').split('/')[0] + '/' + proposal;

          // Initialize gadget now that we have config, interfaceMessages, section, and title
          endorse.Initialize();

          if (util.checkFeedbackCookie()) {
            util.showFeedback(endorse.config, endorse.interfaceMessages);
          }
        });
      });

    } else {
      $('.wp-addme-button').hide();
    }
  });
});