MediaWiki:Gadget-QuickDiff.js: Difference between revisions
Jump to navigation
Jump to search
Content added Content deleted
No edit summary Tag: Reverted |
No edit summary |
||
(One intermediate revision by the same user not shown) | |||
Line 1: | Line 1: | ||
"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> |
/* <nowiki> |
||
QuickDiff - quickly view any diff link |
QuickDiff - quickly view any diff link |
||
Line 13: | Line 18: | ||
(function ($, mw) { |
(function ($, mw) { |
||
"use strict"; |
|||
// double-run protection |
|||
if (window.quickDiffLoaded) { |
|||
return; |
|||
} |
|||
window.quickDiffLoaded = true; |
|||
var modal; |
|||
var special = {}; |
|||
function isElementOrChildFrontmost(element) { |
|||
var special = {}; |
|||
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 isElementOrChildFrontmost(element) { |
|||
function initSpecialPageStrings() { |
|||
var pos = element.getBoundingClientRect(); |
|||
special.diffDefault = mw.util.getUrl("Special:Diff/"); |
|||
var frontmostElement = document.elementFromPoint(pos.left, pos.top); |
|||
special.compareDefault = mw.util.getUrl("Special:ComparePages"); |
|||
return element.contains(frontmostElement); |
|||
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 |
|||
// "Special:Diff/12345" and "Special:ComparePages" link detection |
|||
// ideally this wouldn't be needed and we'd rely on MediaWiki's own handler, |
|||
function initSpecialPageStrings() { |
|||
// but that's run only on document ready and isn't easily reusable |
|||
special.diffDefault = mw.mw.util.getUrl("Special:Diff/"); |
|||
function initAjaxPatrolHandler() { |
|||
special.compareDefault = mw.mw.util.getUrl("Special:ComparePages"); |
|||
var $spinner = mw.libs.QDmodal.getSpinner(); |
|||
$spinner.css({ |
|||
var wiki = mw.config.get("wgDBname"); |
|||
"--qdmodal-spinner-size": "2em", |
|||
var storageKeyDiff = "QuickDiff-specialdiff_" + wiki; |
|||
position: "relative", |
|||
var storageKeyCompare = "QuickDiff-specialcompare_" + wiki; |
|||
top: "-6px", |
|||
verticalAlign: "top" |
|||
}); |
|||
special.diff = localStorage.getItem(storageKeyDiff); |
|||
mw.hook("quickdiff.ready").add(function (modal) { |
|||
special.compare = localStorage.getItem(storageKeyCompare); |
|||
var $patrolLinks = modal.$element.find(".patrollink[data-mw='interface'] > a"); |
|||
} catch (ignore) {} |
|||
$patrolLinks.on("click", function (event) { |
|||
event.preventDefault(); |
|||
if (special.diff && special.compare) { |
|||
if ($patrolLinks.is("[disabled]")) { |
|||
// using stored values - no need for api request |
|||
return; |
|||
} |
} |
||
$patrolLinks.find(".qdmodal-spinner-container").remove().end().attr("disabled", "").append(" ", $spinner.clone()); |
|||
var $spinners = $patrolLinks.find(".qdmodal-spinner-container"); |
|||
$.getJSON(mw.mw.util.wikiScript("api"), { |
|||
mw.loader.using("mediawiki.api").done(function () { |
|||
action: "parse", |
|||
new mw.Api().postWithToken("patrol", { |
|||
action: "patrol", |
|||
rcid: mw.util.getParamValue("rcid", event.target.href) |
|||
}).done(function (data) { |
|||
text: "<span class='diff'>[[Special:Diff/]]</span><span class='compare'>[[Special:ComparePages]]</span>", |
|||
$spinners.removeAttr("style").text("✅").parent().wrap("<s>"); |
|||
disablelimitreport: "" |
|||
}). |
}).fail(function (data) { |
||
$spinners.removeAttr("style").text("❌").parent().removeAttr("disabled"); |
|||
}); |
|||
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) {} |
|||
}); |
}); |
||
}); |
|||
}); |
|||
} |
|||
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(); |
|||
// 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(); |
|||
// remove text nodes (the brackets around each link) |
|||
$spinner.css({ |
|||
$actions.contents().filter(function (ignore, element) { |
|||
"--qdmodal-spinner-size": "2em", |
|||
return element.nodeType === 3; |
|||
position: "relative", |
|||
}).remove(); |
|||
top: "-6px", |
|||
$actions.find("a").addClass("qdmodal-button").attr("target", "_blank"); |
|||
verticalAlign: "top" |
|||
}); |
|||
// if diff is for one page, add a page history action |
|||
mw.hook("quickdiff.ready").add(function (modal) { |
|||
if (prevTitle === currTitle) { |
|||
var $patrolLinks = modal.$element.find(".patrollink[data-mw='interface'] > a"); |
|||
$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 |
|||
$patrolLinks.on("click", function (event) { |
|||
url.extend({ |
|||
event.preventDefault(); |
|||
action: "render", |
|||
diffonly: "1" |
|||
}); |
|||
// pass through 'bot' param for rollback links if it's in use on the current page |
|||
if ($patrolLinks.is("[disabled]")) { |
|||
if (mw.util.getParamValue("bot")) { |
|||
return; |
|||
url.extend({ |
|||
bot: "1" |
|||
}); |
|||
$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.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"); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
} |
||
$.when($.get(url.getRelativePath()), mw.loader.using(["mediawiki.diff.styles", "mediawiki.interface.helpers.styles"])).always(function (response) { |
|||
delete url.query.action; |
|||
function getDiffTitle($diff) { |
|||
delete url.query.diffonly; |
|||
var prevTitle = $diff.find("#mw-diff-otitle1 a").attr("title"); |
|||
delete url.query.bot; |
|||
var currTitle = $diff.find("#mw-diff-ntitle1 a").attr("title"); |
|||
var data = { |
|||
url: url, |
|||
if (prevTitle && prevTitle !== currTitle) { |
|||
buttons: [{ |
|||
return "Differences between “" + prevTitle + "” and “" + currTitle + "”"; |
|||
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 |
|||
function addDiffActions() { |
|||
if (event.currentTarget.dataset.disableQuickdiff !== undefined) { |
|||
var prevTitle = modal.$content.find("#mw-diff-otitle1 a").attr("title"); |
|||
return; |
|||
var currTitle = modal.$content.find("#mw-diff-ntitle1 a").attr("title"); |
|||
} |
|||
// stop triggering on links with empty href |
|||
// collect action links (edit, undo, rollback, patrol) from the diff |
|||
if (event.currentTarget.getAttribute('href') === "") { |
|||
var $actions = modal.$content.find(".diff-ntitle").find( |
|||
return; |
|||
".mw-diff-edit, .mw-diff-undo, .mw-rollback-link, .patrollink, .mw-diff-tool" |
|||
} |
|||
// stop triggering on OOUI icons |
|||
// remove text nodes (the brackets around each link) |
|||
var targetClasses = event.currentTarget.classList; |
|||
$actions.contents().filter(function (ignore, element) { |
|||
var _iterator = _createForOfIteratorHelper(targetClasses), |
|||
return element.nodeType === 3; |
|||
_step; |
|||
try { |
|||
for (_iterator.s(); !(_step = _iterator.n()).done;) { |
|||
$actions.find("a") |
|||
var targetClass = _step.value; |
|||
if (targetClass.startsWith("oo-ui")) { |
|||
return; |
|||
// if diff is for one page, add a page history action |
|||
if (prevTitle === currTitle) { |
|||
$actions = $actions.add( |
|||
$("<a>").attr({ |
|||
"class": "qdmodal-button", |
|||
href: mw.mw.util.getUrl(currTitle, {action: "history"}), |
|||
target: "_blank" |
|||
}).text("history") |
|||
); |
|||
} |
} |
||
} |
|||
} catch (err) { |
|||
modal.$footer.append($actions); |
|||
_iterator.e(err); |
|||
} finally { |
|||
_iterator.f(); |
|||
} |
} |
||
var url = event.currentTarget.href; |
|||
try { |
|||
url = new mw.Uri(url); |
|||
} catch (ignore) { |
|||
loading: true, |
|||
// quit if url couldn't be parsed |
|||
title: !modal.visible && "Loading…" |
|||
// it wouldn't be a link QuickDiff could handle anyway |
|||
}); |
|||
return; |
|||
// 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.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 mw.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); |
|||
}); |
|||
} |
} |
||
// cross-domain requests not supported |
|||
function keydownHandler(event) { |
|||
if (url.host !== location.hostname) { |
|||
// only handle key presses if QuickDiff is frontmost |
|||
return; |
|||
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"); |
|||
} |
|||
} |
} |
||
// no fragment check is to ensure section links/collapsible trigger links on diff pages are ignored |
|||
function linkClickHandler(event) { |
|||
var hasDiffParam = url.query.diff !== undefined && url.fragment === undefined; |
|||
// ignore clicks with modifier keys to avoid overriding browser features |
|||
var isSpecialDiffLink = url.path.indexOf(special.diff) === 0 || url.path.indexOf(special.diffDefault) === 0; |
|||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { |
|||
var isSpecialCompareLink = url.path.indexOf(special.compare) === 0 || url.path.indexOf(special.compareDefault) === 0; |
|||
return; |
|||
if (hasDiffParam || isSpecialDiffLink || isSpecialCompareLink) { |
|||
} |
|||
event.preventDefault(); |
|||
loadDiff(url); |
|||
// 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; |
|||
for (let targetClass of targetClasses) { |
|||
if (targetClass.startsWith("oo-ui")) { |
|||
return; |
|||
} |
|||
} |
|||
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 mw.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 |
|||
function init() { |
|||
var css = "#quickdiff-modal { height: 100%; width: 100% }"; |
|||
var $body = $(document.body); |
|||
// 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 |
|||
// full screen modal |
|||
// also, one attached event handler is better than hundreds! |
|||
var css = "#quickdiff-modal { height: 100%; width: 100% }"; |
|||
$body.on("click.quickdiff", "a[href]", linkClickHandler); |
|||
// always show modal footer for UI consistency |
|||
css += "#quickdiff-modal > footer { display: flex }"; |
|||
mw.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(); |
|||
} |
|||
// listen for left/right arrow keys, to move between prev/next diff |
|||
init(); |
|||
$body.on("keydown.quickdiff", keydownHandler); |
|||
}(jQuery, mediaWiki)); |
|||
initSpecialPageStrings(); |
|||
initAjaxPatrolHandler(); |
|||
} |
|||
init(); |
|||
})(jQuery, mediaWiki); |
|||
// </nowiki> |
// </nowiki> |
Latest revision as of 12:06, 20 October 2024
"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>