Note: After saving, changes may not occur immediately. Click here to learn how to bypass your browser's cache.
  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Cmd-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (Cmd-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences

For details and instructions about other browsers, see Wikipedia:Bypass your cache.

/* global $, mw */
/*
 * ocrtoy.js
 *
 * This script adds a toolbar button that replaces the editbox text with OCR
 * text derived by sending the .prp-page-image image through ocrtoy's backend.
 *
 */
(function (mw, $) {
  "use strict";

  mw.loader.using(["mediawiki.api", "mediawiki.util", "user.options"], function () {
    /*
     *  First check that this is a context we should be active in.
     */

    // Only active on Page:-namespace pages.
    if (mw.config.get('wgCanonicalNamespace') !== 'Page') {
      return;
    }

    // Only active on pages with content model 'proofread-page'.
    if (mw.config.get('wgPageContentModel') !== 'proofread-page') {
      return;
    }

    // Only active when in edit/preview/diff mode.
    if ($.inArray(mw.config.get('wgAction'), ['edit', 'submit']) < 0) {
      return;
    }


    /*
     * Set up the globals we'll need.
     */
    var sysMessages = [
      'ocrtoy-button-label',
      'ocrtoy-request-in-progress',
      'ocrtoy-no-text',
      'ocrtoy-image-not-found'
    ];


    /*
     * Add main UI. Preferences UI is handled by a separate gadget.
     */
    var setupUI;
    if (mw.user.options.get('usebetatoolbar') === 1) {
      setupUI = addToolbarButton; // Add button in toolbar if enabled.
    } else {
      setupUI = addSidebarLink; // Add a link in the sidebar.
    }
    new mw.Api()
      .loadMessagesIfMissing(sysMessages)
        .then(setupUI);
    /*
     * Prefetch the OCR results for the current page
     */
    var isInteractive = false; var warmingCache = false;
    requestOCR(isInteractive, warmingCache);

    /*
     * Then warm the cache for the next page.
     */
    isInteractive = false; warmingCache = true;
    requestOCR(isInteractive, warmingCache);
  }); // END: mw.loader.using()


  /**
   * Add the OCR button to the toolbar. This is called in run, and doesn't
   * need to check anything about whether we need to add the button.
   */
  function addToolbarButton() {
    var ocrButtonDetails = {
      type: 'button',
      icon: 'https://upload.wikimedia.org/wikipedia/commons/4/48/Ocrtoy_WikiEditor_button.png',
      label: mw.msg('ocrtoy-button-label'),
      action: {type: 'callback', execute: doOcr}
    };
    var ocrButton = {
      section: 'main',
      group: 'insert',
      tools: {'ocrtoy': ocrButtonDetails}
    };
    mw.loader.using(['ext.wikiEditor'], function () {
      $("#wpTextbox1").wikiEditor('addToToolbar', ocrButton);
      $("a[rel='ocrtoy']").css("width", "36px");
    });
  }

  /**
   * Add the OCR link to the sidebar. This is called in run, and doesn't
   * need to check anything about whether we need to add the link.
   */
  function addSidebarLink() {
    var ocrPortlet = mw.util.addPortletLink(
      'p-tb', '#', 'Get OCR', 'ca-ocrtoy',
      'Get the OCR for this page from ocrtoy.'
    ); 
    $(ocrPortlet).click(function (e) {
      e.preventDefault();
      doOcr();
    });
  }

  function doOcr () {
    var isInteractive = true; var warmingCache = false;
    requestOCR(isInteractive, warmingCache);
  }


  function bailOnError(isInteractive, response) {
    var notifyFunc;
    if (isInteractive) {
      notifyFunc = function (m) {console.log(m)};
    } else {
      notifyFunc = function (m) {mw.notify(m)};
    }
    if (response.responseJSON !== undefined && response.responseJSON.error) {
      var code    = response.responseJSON.error.code;
      var message = response.responseJSON.error.message;
      notifyFunc(mw.msg('error') + ' ' + code + ' ' + message);
      return true;
    }
    if (response.text === undefined || response.text.length === 0) {
      notifyFunc(mw.msg('ocrtoy-no-text'));
      return true;
    }
    return false;
  }


  // Disable or enable the text editor text field and toolbar button
  function disable_input(disable) {
    if (disable) {
      // Install handler for ESC key to abort and re-enable text field and button
      $(document).keyup(function(e) {if (e.which == 27) {disable_input(false);}});

      // Remove click handler from toolbar button.
      $("a[rel='ocrtoy']").off('click');

      // Set HTML form "disabled" property to true.
      $('#wpTextbox1').prop('disabled', true);
    } else {
      // Remove handler for ESC key
      $(document).off('keyup');

      // Add back the click handler on the toolbar button.
      $("a[rel='ocrtoy']").on('click', doOcr);

      // Set HTML form "disabled" property to false.
      $('#wpTextbox1').prop('disabled', false);
    }
  }


  /**
   * Prefetch the OCR text so it is ready when the user requests it,
   * or fetch it on demand if it doesn't already exist.
   * TODO: Make this configurable.
   */
  function requestOCR (isInteractive, warmingCache) {
    var lang = mw.config.get('wgContentLanguage');
    var toolUrl = "https://ocrtoy.wmflabs.org/";

    var prefetch = $("#wpTextbox1").data("ocrtoyPrefetch");
    if (prefetch !== undefined && isInteractive === true) {
      $('#wpTextbox1').val(prefetch);
      console.log("[ocrtoy]: Found data in local cache.");
      return;
    }
    var notifyFunc;
    if (isInteractive) {
      notifyFunc = function (m) {mw.notify(m)};
    } else if (warmingCache === true) {
      notifyFunc = function (m) {return;}; // noop
    } else {
      notifyFunc = function (m) {console.log(m)};
    }
    // Just bail out if the page image can't be found.
    if ($('.prp-page-image img').length === 0) {
      notifyFunc(mw.msg('ocrtoy-image-not-found'));
    }

    // Report progress if interactive.
    if (isInteractive) {
      disable_input(true);
    }

    var fullsize_width = $('.prp-page-image img').data('fileWidth');

    var match = /(.*?)\/(\d+)/.exec(mw.config.get('wgTitle'));
    var file = match[1];
    var page = match[2];
    if (warmingCache === true) {
      page = parseInt(page) + 1;
      console.log("[ocrtoy]: Going to warm cache for page " + page + ".");
    }

    var getOCR = function (isInteractive, warmingCache, data) {
      var thumburl = data.query.pages[0].imageinfo[0].thumburl;
      console.log("ocrtoy thumburl: " + thumburl);
      var requestUrl = toolUrl + "?image=" + thumburl + "&lang=" + lang;
      var processOCR = function (isInteractive, warmingCache, response) {
        if (warmingCache !== true) {
          var shouldBail = bailOnError(isInteractive, response);
          if (shouldBail) {
            return;
          }
        }
        if (isInteractive) {
          console.log("[ocrtoy]: Update text due to interactive request.");
          $('#wpTextbox1').val(response.text);
        } else if (warmingCache === true) {
          console.log("[ocrtoy]: Ignoring returned result whiile warming cache.");
          return; // Ignore it; we're just warming the server-side cache.
        } else {
          console.log("[ocrtoy]: Storing prefetched text for interactive use.");
          $("#wpTextbox1").data("ocrtoy-prefetch", response.text);
        }
      }.bind(null, isInteractive, warmingCache);
      $.getJSON(requestUrl)
        .done(processOCR)
        .fail(processOCR) // Same handler, for simplicity.
        .always(function () {disable_input(false);});
    }.bind(null, isInteractive, warmingCache); // Pass along interactive state

    var api = new mw.Api();
    api.get({
      'action': 'query',
      'prop': 'imageinfo',
      'titles': "File:" + file,
      'formatversion': 2,
      'format': 'json',
      'iiprop': 'url',
      'iiurlwidth': fullsize_width,
      'iiurlparam': "page" + page + "-" + fullsize_width + "px"
    })
    .done(getOCR);
  }
}(mediaWiki, jQuery));