User:Unready/ui.refresh.js

From Wikipedia, the free encyclopedia
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.
//__NOINDEX__
/*
 * Description:
 * Refresh page content periodically
 *
 * Version 1.0: 27 April 2015
 *   Original version for Wikipedia use
 * Version 1.1: 28 April 2015
 *   Use jQuery
 * Version 1.2: 29 April 2015
 *   Dynamically determine selector
 *   Allow interval configuration
 * Version 1.3: 30 April 2015
 *   Support action=history
 * Version 1.4: 3 May 2015
 *   Add programmatic toggle
 *
 * License: CC-BY-SA
 *   http://creativecommons.org/licenses/by-sa/3.0/
 */

((window.user = window.user || {}).ui = window.user.ui || {}).refresh =
(function (mw, $)
{
  'use strict';

  var g_self,
      g_selector,
      g_jqContent, g_jqInput, g_jqImg, g_jqLi,
      g_wAction = mw.config.get('wgAction'),
      g_interval = 120, // default refresh interval (seconds)
      g_hTimeout = -1;  // cannot run = -1; okay to run = 0; running > 0

  // process click events on radio buttons for action=history
  // mostly "borrowed" from MediaWiki 1.26wmf3
  //   example:
  //   $._data( $('li > input[type="radio"]')[0] ).events.click[0].handler
  // it's not possible to attach the existing handler, because
  //   1. the existing handler cannot deal with
  //      changing the number of list items
  //   2. if the previous page had only one list item,
  //      there's no reference to the handler available
  function updateDiffRadios()
  {
    var li, inputs, oldidRadio, diffRadio,
        nextState = 'before';

    if (!g_jqLi.length)
    {
      return;
    }
    g_jqLi.each(function()
    {
      li = $(this);
      inputs = li.find('input[type="radio"]');
      oldidRadio = inputs.filter('[name="oldid"]').eq(0);
      diffRadio = inputs.filter('[name="diff"]').eq(0);
      li.removeClass('selected between before after');
      if (!oldidRadio.length || !diffRadio.length)
      {
        return;
      }
      if (oldidRadio.prop('checked'))
      {
        li.addClass('selected after');
        nextState = 'after';
      }
      else if (diffRadio.prop('checked'))
      {
        li.addClass('selected ' + nextState);
        nextState = 'between';
      }
      else
      {
        li.addClass(nextState);
      }
    });
  }

  // get interval (sec) from module properties
  //   and convert it to msec
  function getInterval()
  {
    if ((typeof g_self.interval === 'number') &&
      (g_self.interval > 0) &&
      (g_self.interval < 604801 )) // 7 days + 1 sec
    {
      g_interval = g_self.interval;
    }
    else
    {
      g_self.interval = g_interval;
    }
    return g_interval * 1000;
  }

  // process checkbox events
  function onCheck()
  {
    if (g_jqInput.prop('checked'))
    {
      if (g_hTimeout === 0)
      { // schedule a tick
        g_jqImg.hide(); // in case it was showing after an error
        g_hTimeout = window.setTimeout(onTick, getInterval());
        g_self.message = 'OK';
      }
    }
    else
    {
      if (g_hTimeout > 0)
      { // stop the scheduled tick
        window.clearTimeout(g_hTimeout);
        g_hTimeout = 0;
        g_self.message = 'Stopped';
      }
    }
  }

  // process Ajax done event
  function onDone(htmlString)
  {
    var jqNewContent = $(htmlString).find(g_selector);

    if (jqNewContent.length !== 1)
    {
      g_self.message = 'onDone :: ' + jqNewContent.length +
        ' elements found for (' + g_selector + ')';
      g_jqInput.prop('checked', false);
      return;
    }
    // refresh content
    g_jqContent.replaceWith(jqNewContent);
    g_jqContent = jqNewContent;
    // for action=history ...
    if (g_wAction === 'history')
    { // ... attach click listeners and fake a click
      g_jqLi = g_jqContent.find('li');
      g_jqLi.find('input[type="radio"]')
        .click(updateDiffRadios);
      updateDiffRadios();
    }
    // go back to sleep
    g_jqImg.hide();
    g_hTimeout = window.setTimeout(onTick, getInterval());
  }

  // process Ajax fail event
  function onFail(jqDummy, textStatus, errorThrown)
  {
    g_self.message = 'onFail :: ' + textStatus + ' ' + errorThrown;
    g_jqInput.prop('checked', false);
  }

  // handle timer events, 
  function onTick()
  {
    g_hTimeout = 0;
    g_jqImg.show();
    // ajax defaults to window.location.href
    $.ajax({dataType: 'html'})
      .done(onDone)
      .fail(onFail);
  }

  // turn refresh on and off programmatically
  function toggle(state)
  {
    if (!!g_jqInput &&
      (typeof state === 'boolean') &&
      (g_jqInput.prop('checked') !== state))
    {
      g_jqInput.click();
    }
  }

  // init g_self before document.ready
  //   in case document.ready executes immediately
  g_self =
  {
    interval : g_interval,
    message : 'Initializing',
    toggle : toggle,
    version : 'Version 1.4.1: 15 May 2015'
  };

  $(function main()
  {
    var jqSpan, jqH1,
        i, done,
        actions =
        [
          'view',
          'history'
        ],
        selectors =
        [
          '.mw-changeslist', // RecentChanges & Watchlist, all skins
          '#pagehistory',    // action=history, all skins
          '#mw-content-text' // all pages, all skins
        ],
        uriData =
        [
          'data:image/gif;base64,',
          'R0lGODlhKwALAMIAAP///wAAAIKCggAAAP///////////////yH/C05FVFNDQVBF',
          'Mi4wAwEAAAAh+QQFCgADACwAAAAAKwALAAADNDiyzPNQtRbhpHfWTCP/mgduYEl+',
          'Z8mlGauG1ii+7bzadBejeL64sIfvAtQJR7yioHJsJQAAIfkEBQoAAgAsAAAAAAsA',
          'CwAAAw0os8zaMMpJq70YPykSACH5BAUKAAEALAAAAAAbAAsAAAMtGLLM8TCMSalq',
          'WERZ+8jY5nVgI45U6URoqmps+71n+8KQPKs1evejC2jzaAUSACH5BAEKAAEALBAA',
          'AAAbAAsAAAMtGLLM8TCMSalqWERZ+8jY5nVgI45U6URoqmps+71n+8KQPKs1evej',
          'C2jzaAUSADs='
        ].join(''),
        htmlString =
        [
          '<span id="ui-refresh">',
          '<label for="ui-refresh-input"',
          ' title="Enable auto-refresh">Auto-refresh:</label>',
          '<input id="ui-refresh-input" type="checkbox">',
          '<img src="' + uriData + '" alt="Refreshing page">',
          '</span>'
        ].join('');

    // allow listed actions, else deny
    done = false;
    for ( i = 0 ; (i < actions.length) && !done ; ++i )
    {
      done = (actions[i] === g_wAction);
    }
    if (!done)
    {
      g_self.message = 'main :: unsupported action : ' + g_wAction;
      return;
    }
    // determine the "best" selector
    done = false;
    for ( i = 0 ; (i < selectors.length) && !done ; ++i )
    {
      g_selector = selectors[i];
      g_jqContent = $(g_selector);
      done = (g_jqContent.length === 1);
    }
    if (!done)
    {
      g_self.message = 'main :: No suitable selector';
      return;
    }
    // span with all the new DOM elements
    jqSpan = $(htmlString);
    // checkbox checked, with handler
    g_jqInput = jqSpan.children('input')
      .prop('checked', true)
      .click(onCheck);
    // throbber, initially hidden
    g_jqImg = jqSpan.children('img')
      .hide();
    // add span to h1
    jqH1 = $('h1:first:visible'); // Minerva made me do it
    if (jqH1.length !== 1)
    {
      g_self.message = 'main :: Problem with (h1:first:visible)';
      g_jqInput = undefined;
      g_jqImg = undefined;
      return;
    }
    jqH1.append(jqSpan);
    // done with DOM
    g_hTimeout = window.setTimeout(onTick, getInterval());
    g_self.message = 'OK';
  });

  return g_self;
}(mediaWiki, jQuery));