Jump to content

User:Shirik/guidebook.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <source lang="javascript">
/**
 * Guidebook v0.1
 * By Matthew "Shirik" Del Buono
 * 
 * This script adjusts the "diff" window so that it shows relevant guidelines
 * associated with the edit. 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Some semaphore-like things that allow us to implement a barrier waiting for all other modules to load.
var guidebook_mootoolsLoaded = false;
var guidebook_patternsLoaded = false;
var guidebook_configLoaded = false;
var guidebook_onLoadOccurred = false;

function guidebook_checkLoaded()
{
   if (guidebook_mootoolsLoaded && guidebook_patternsLoaded && guidebook_configLoaded && guidebook_onLoadOccurred)
   {
      guidebook_onReady();
   }
}

function guidebook_loadDefaults()
{
   mw.log("Loading guidebook defaults");
   window.GUIDEBOOK_MAXDISPLAYEDPOLICIES = 3;
   guidebook_configLoaded = true;
}

// Import libs
jQuery.getScript('//meta.wikimedia.org/w/index.php?title=User:Pathoschild/Scripts/MooTools.js&action=raw&ctype=text/javascript&9',
   function() { guidebook_mootoolsLoaded = true; guidebook_checkLoaded(); });

// Load configuration
jQuery.getScript('//en.wikipedia.org/w/index.php?title=User:Shirik/guidebook_patterns.js&action=raw&ctype=text/javascript',
   function() { guidebook_patternsLoaded = true; guidebook_checkLoaded(); });

function guidebook_onReady()
{
   // If AJAX not supported, bail out
   if(!wfSupportsAjax())
      return;

   // If this is a diff, then let's actually do something
   var GET = guidebook_getUrlVars();
   if (mw.config.get('wgAction') == 'view' && 'diff' in GET && (GET['diff'] > 0 || GET['diff'] == 'prev' || GET['diff'] == 'next'))
   {
      // This is a diff page. Do stuff!
      var added = guidebook_getLines('diff-addedline');
      var removed = guidebook_getLines('diff-deletedline');
      var summary = guidebook_getSummary();
      var guidelines = guidebook_lookupGuidelines(added, removed, summary);
      if (guidelines.length > 0)
      {
         var editSummary = document.getElementById('mw-diff-ntitle3');
         if (editSummary == undefined)
         {
            mw.log("Guidebook error: could not find edit summary block");
         }
 
         var block = new Element("div");
         block.setProperty('style', 'font-style: italic');
         block.appendText("Policies/Guidelines: ");
         for (var i = 0; i < guidelines.length; ++i)
         {
            if (i > 0)  
            {
               block.appendText(", ");
            }
 
            if (guidelines[i].href != undefined)
            {
               var link = new Element("a", {href: "/wiki/" + guidelines[i].href});
               link.appendText(guidelines[i].text);
               block.adopt(link);           
            }
            else 
            {
               block.appendText("...");
            }
         }

         editSummary.adopt(block);
      }
   }  
}

function guidebook_getUrlVars() 
{
    var vars = {};
    var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value)
    {
        vars[key] = value;
    });
    return vars;
}

/// Gets the lines that were added in the current diff.
/// Parameters: lookupClass [string] - a class for the td element to identify (e.g., 'diff-deletedline'
/// Preconditions: Current page is a diff
/// Postconditions: Nothing has changed
/// Return value: An array of strings, each line that was added in the diff
function guidebook_getLines(lookupClass)
{
   var content = document.getElementById('mw-content-text');
   if (content == undefined)
   {
      // Error: Could not get content text
      return;
   }

   var result = new Array();

   var diffTable = content.getElement('table');       // should always be the first table
   var diffTableBody = diffTable.getElement('tbody'); // a table BETTER only have one body
   var rows = diffTableBody.getElements('tr');
   for (var i = 0; i < rows.length; ++i)              // iterate over each row
   {
       var cols = rows[i].getElements('td');
       for (var j = 0; j < cols.length; ++j)          // iterate over each cell
       {
           // Look for an line (has class 'diff-addedline', etc.)
           if (cols[j].get('class') == lookupClass)
           {
              // This is an added line. Record it.
              var div = cols[j].getElement('div');    // There should only be one div inside
              if (div == undefined)
              {
                  mw.log("Guidebook warning: Could not find line with class '" + lookupClass + "'");
              }
              else 
              {
                 result.push(div.textContent);
              }
           }
       }
   }
   return result;
}

/// Gets the edit summary for the new revision
/// Preconditions: Current page is a diff
/// Postconditions: Nothing has changed
/// Return value: A string representation of the edit summary text
function guidebook_getSummary()
{
   var content = document.getElementById('mw-diff-ntitle3');
   if (content == undefined) return; // likely no edit summary
 
   var text = (content.getElementsByClassName('comment').length > 0 ? content.getElementsByClassName('comment')[0] : undefined);
   if (text == undefined) return; // likely no edit summary
   
   return text.textContent.slice(1, -1); // Remove the ( and ) from the edit summary text
}

function guidebook_lookupGuidelines(addedLines, removedLines, summary)
{
   var result = new Array();
   for (var i = 0; i < GUIDEBOOK_PATTERNS.length; ++i)
   {
      // Compile the patterns first to improve performance since it might do a lot of tests
      var modifiers = (GUIDEBOOK_PATTERNS[i].modifiers == undefined ? '' : GUIDEBOOK_PATTERNS[i].modifiers);

      // Each pattern defaults to '' if it is undefined. That allows us to "always match" so that we ignore that pattern.
      // Slightly slower in performance, but it really shouldn't be that big of a deal. It will look at the first character and finish.
      // The alternative was some ugly-looking code, and this is just too much more elegant to pass up.
      addedRegex = new RegExp(GUIDEBOOK_PATTERNS[i].added_regex == undefined ? '' : GUIDEBOOK_PATTERNS[i].added_regex, modifiers);
      removedRegex = new RegExp(GUIDEBOOK_PATTERNS[i].removed_regex == undefined ? '' : GUIDEBOOK_PATTERNS[i].removed_regex, modifiers);
      summaryRegex = new RegExp(GUIDEBOOK_PATTERNS[i].summary_regex == undefined ? '' : GUIDEBOOK_PATTERNS[i].summary_regex, modifiers);

      // Local function to test it against each added line
      function testLines(regex, lineArray)
      {
         if (lineArray.length == 0 && regex.test('')) return true; // No lines still gets a test against the empty string. If pass, successful.

         for (var j = 0; j < lineArray.length; ++j)
         {
            if (regex.test(lineArray[j]))
            {
               return true;
            }
         }
         return false;
      }
      
      if (testLines(addedRegex, addedLines) && testLines(removedRegex, removedLines) && testLines(summaryRegex, [summary]))
      {
         // Add to list of policies/guidelines to return
         result.push({
            href: GUIDEBOOK_PATTERNS[i].href,
            text: GUIDEBOOK_PATTERNS[i].text
         });
 
         if (result.length > GUIDEBOOK_MAXDISPLAYEDPOLICIES)
         {
            mw.log("Guidebook: Too many matched policies. Capping at " + GUIDEBOOK_MAXDISPLAYEDPOLICIES);
            result.pop();               // remove last (we found 1 too many so we know if we should put an ellipsis)
            result.push({text: "..."}); // ellipsis to indicate "there was more"
            return result;
         }
      }
   }

   return result;
}

addOnloadHook(function() { 
   guidebook_onLoadOccurred = true;
   if (mw.user.isAnon() == false)
   {
      jQuery.getScript('//en.wikipedia.org/w/index.php?title=User:' + mw.user.getName() + '/guidebook_prefs.js&action=raw&ctype=text/javascript')
         .done(function() { guidebook_configLoaded = true; guidebook_checkLoaded(); })
         .fail(function() { mw.log("Error loading Guidebook user preferences"); guidebook_loadDefaults(); guidebook_checkLoaded(); }); // Offer a default set of options if page doesn't exist
   }
   else 
   {
      guidebook_configLoaded = true;
   }
});