MediaWiki:Gadget-QuickDiff.js

After saving, you may need to bypass your browser's cache to see the changes. For further information, see Wikipedia:Bypass your cache.

  • In most Windows and Linux browsers: Hold down Ctrl and press F5.
  • In Safari: Hold down ⇧ Shift and click the Reload button.
  • In Chrome and Firefox for Mac: Hold down both ⌘ Cmd+⇧ Shift and press R.
"use strict";

function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
/* <nowiki>
    QuickDiff - quickly view any diff link
    Modified to remove Wikia-specific i18n code; relies on [[MediaWiki:Gadget-QDmodal.js]] and [[MediaWiki:Gadget-QDmodal.css]]

    @author OneTwoThreeFall
    @source <https://dev.fandom.com/wiki/QuickDiff>
    @source <https://dev.fandom.com/wiki/MediaWiki:QuickDiff/code.js>
    @source <https://dev.fandom.com/wiki/MediaWiki:Custom-QuickDiff/i18n.json>
*/

/*jslint browser, long */
/*global jQuery, mediaWiki, dev */

(function ($, mw) {
  "use strict";

  // double-run protection
  if (window.quickDiffLoaded) {
    return;
  }
  window.quickDiffLoaded = true;
  var modal;
  var special = {};
  function isElementOrChildFrontmost(element) {
    var pos = element.getBoundingClientRect();
    var frontmostElement = document.elementFromPoint(pos.left, pos.top);
    return element.contains(frontmostElement);
  }

  // "Special:Diff/12345" and "Special:ComparePages" link detection
  function initSpecialPageStrings() {
    special.diffDefault = mw.util.getUrl("Special:Diff/");
    special.compareDefault = mw.util.getUrl("Special:ComparePages");
    var wiki = mw.config.get("wgDBname");
    var storageKeyDiff = "QuickDiff-specialdiff_" + wiki;
    var storageKeyCompare = "QuickDiff-specialcompare_" + wiki;
    try {
      special.diff = localStorage.getItem(storageKeyDiff);
      special.compare = localStorage.getItem(storageKeyCompare);
    } catch (ignore) {}
    if (special.diff && special.compare) {
      // using stored values - no need for api request
      return;
    }
    $.getJSON(mw.util.wikiScript("api"), {
      action: "parse",
      contentmodel: "wikitext",
      format: "json",
      prop: "text",
      text: "<span class='diff'>[[Special:Diff/]]</span><span class='compare'>[[Special:ComparePages]]</span>",
      disablelimitreport: ""
    }).done(function (data) {
      var $parsed = $(data.parse.text["*"]);
      special.diff = $parsed.find(".diff > a").attr("href");
      special.compare = $parsed.find(".compare > a").attr("href");
      try {
        localStorage.setItem(storageKeyDiff, special.diff);
        localStorage.setItem(storageKeyCompare, special.compare);
      } catch (ignore) {}
    });
  }

  // support for patrolling edits directly from modal
  // ideally this wouldn't be needed and we'd rely on MediaWiki's own handler,
  // but that's run only on document ready and isn't easily reusable
  function initAjaxPatrolHandler() {
    var $spinner = mw.libs.QDmodal.getSpinner();
    $spinner.css({
      "--qdmodal-spinner-size": "2em",
      position: "relative",
      top: "-6px",
      verticalAlign: "top"
    });
    mw.hook("quickdiff.ready").add(function (modal) {
      var $patrolLinks = modal.$element.find(".patrollink[data-mw='interface'] > a");
      $patrolLinks.on("click", function (event) {
        event.preventDefault();
        if ($patrolLinks.is("[disabled]")) {
          return;
        }
        $patrolLinks.find(".qdmodal-spinner-container").remove().end().attr("disabled", "").append(" ", $spinner.clone());
        var $spinners = $patrolLinks.find(".qdmodal-spinner-container");
        mw.loader.using("mediawiki.api").done(function () {
          new mw.Api().postWithToken("patrol", {
            action: "patrol",
            rcid: mw.util.getParamValue("rcid", event.target.href)
          }).done(function (data) {
            $spinners.removeAttr("style").text("✅").parent().wrap("<s>");
          }).fail(function (data) {
            $spinners.removeAttr("style").text("❌").parent().removeAttr("disabled");
          });
        });
      });
    });
  }
  function getDiffTitle($diff) {
    var prevTitle = $diff.find("#mw-diff-otitle1 a").attr("title");
    var currTitle = $diff.find("#mw-diff-ntitle1 a").attr("title");
    if (prevTitle && prevTitle !== currTitle) {
      return "Differences between “" + prevTitle + "” and “" + currTitle + "”";
    }
    return "Differences: " + currTitle;
  }
  function addDiffActions() {
    var prevTitle = modal.$content.find("#mw-diff-otitle1 a").attr("title");
    var currTitle = modal.$content.find("#mw-diff-ntitle1 a").attr("title");

    // collect action links (edit, undo, rollback, patrol) from the diff
    var $actions = modal.$content.find(".diff-ntitle").find(".mw-diff-edit, .mw-diff-undo, .mw-rollback-link, .patrollink, .mw-diff-tool").clone();

    // remove text nodes (the brackets around each link)
    $actions.contents().filter(function (ignore, element) {
      return element.nodeType === 3;
    }).remove();
    $actions.find("a").addClass("qdmodal-button").attr("target", "_blank");

    // if diff is for one page, add a page history action
    if (prevTitle === currTitle) {
      $actions = $actions.add($("<a>").attr({
        "class": "qdmodal-button",
        href: mw.util.getUrl(currTitle, {
          action: "history"
        }),
        target: "_blank"
      }).text("history"));
    }
    modal.$footer.append($actions);
  }
  function loadDiff(url) {
    modal.show({
      loading: true,
      title: !modal.visible && "Loading…"
    });

    // add 'action=render' and 'diffonly' params to save some bytes on each request
    url.extend({
      action: "render",
      diffonly: "1"
    });

    // pass through 'bot' param for rollback links if it's in use on the current page
    if (mw.util.getParamValue("bot")) {
      url.extend({
        bot: "1"
      });
    }
    $.when($.get(url.getRelativePath()), mw.loader.using(["mediawiki.diff.styles", "mediawiki.interface.helpers.styles"])).always(function (response) {
      delete url.query.action;
      delete url.query.diffonly;
      delete url.query.bot;
      var data = {
        url: url,
        buttons: [{
          text: "open link",
          href: url.toString(),
          attr: {
            "data-disable-quickdiff": ""
          }
        }],
        content: "Something went wrong while getting the page at “" + url.toString() + "”."
      };
      var $diff;
      if (typeof response[0] === "string") {
        var $content = $(response[0]);
        $diff = $content.filter("table.diff, #mw-rev-deleted-no-diff");
        if (!$diff.length) {
          // $content is a complete page - see if a diff can be found
          // needed for diffs from special pages as they ignore action=render URL parameter
          $diff = $content.find("table.diff");
        }
      }
      if (!$diff || $diff.length === 0) {
        // default content is error msg
        return modal.show(data);
      }
      data.content = $diff;
      data.hook = "quickdiff.ready";
      data.onBeforeShow = addDiffActions;
      data.title = getDiffTitle($diff);

      // if a diff, fire the standard MW hook
      if ($diff.is("table.diff[data-mw='interface']")) {
        mw.hook("wikipage.diff").fire($diff);
      }
      modal.show(data);
    });
  }
  function keydownHandler(event) {
    // only handle key presses if QuickDiff is frontmost
    if (!isElementOrChildFrontmost(modal.$container[0])) {
      return;
    }
    if (event.key === "ArrowLeft") {
      modal.$content.find("#differences-prevlink").trigger("click");
    } else if (event.key === "ArrowRight") {
      modal.$content.find("#differences-nextlink").trigger("click");
    }
  }
  function linkClickHandler(event) {
    // ignore clicks with modifier keys to avoid overriding browser features
    if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
      return;
    }

    // ignore click if link has "data-disable-quickdiff" attribute set
    if (event.currentTarget.dataset.disableQuickdiff !== undefined) {
      return;
    }

    // stop triggering on links with empty href
    if (event.currentTarget.getAttribute('href') === "") {
      return;
    }

    // stop triggering on OOUI icons
    var targetClasses = event.currentTarget.classList;
    var _iterator = _createForOfIteratorHelper(targetClasses),
      _step;
    try {
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        var targetClass = _step.value;
        if (targetClass.startsWith("oo-ui")) {
          return;
        }
      }
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }
    var url = event.currentTarget.href;
    try {
      url = new mw.Uri(url);
    } catch (ignore) {
      // quit if url couldn't be parsed
      // it wouldn't be a link QuickDiff could handle anyway
      return;
    }

    // cross-domain requests not supported
    if (url.host !== location.hostname) {
      return;
    }

    // no fragment check is to ensure section links/collapsible trigger links on diff pages are ignored
    var hasDiffParam = url.query.diff !== undefined && url.fragment === undefined;
    var isSpecialDiffLink = url.path.indexOf(special.diff) === 0 || url.path.indexOf(special.diffDefault) === 0;
    var isSpecialCompareLink = url.path.indexOf(special.compare) === 0 || url.path.indexOf(special.compareDefault) === 0;
    if (hasDiffParam || isSpecialDiffLink || isSpecialCompareLink) {
      event.preventDefault();
      loadDiff(url);
    }
  }
  function init() {
    var $body = $(document.body);
    modal = new mw.libs.QDmodal("quickdiff-modal");

    // full screen modal
    var css = "#quickdiff-modal { height: 100%; width: 100% }";
    // always show modal footer for UI consistency
    css += "#quickdiff-modal > footer { display: flex }";
    mw.util.addCSS(css);

    // attach to body for compatibility with ajax-loaded content
    // also, one attached event handler is better than hundreds!
    $body.on("click.quickdiff", "a[href]", linkClickHandler);

    // listen for left/right arrow keys, to move between prev/next diff
    $body.on("keydown.quickdiff", keydownHandler);
    initSpecialPageStrings();
    initAjaxPatrolHandler();
  }
  init();
})(jQuery, mediaWiki);
// </nowiki>