/***************************************************************************************************
Unfinished and not working
Forked from [[User:Evad37/TimestampDiffs.js]]
> Links timestamps to diffs on discussion pages and adds link to thank commenter
***************************************************************************************************/
/* jshint esnext:false, laxbreak: true, undef: true, maxerr: 999*/
/* globals console, document, $, mw */
// <nowiki>
$.when(
mw.loader.using(["mediawiki.api"]),
$.ready
).then(function() {
// Pollyfill NodeList.prototype.forEach() per https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
var config = {
version: "1.1.2",
mw: mw.config.get([
"wgNamespaceNumber",
"wgPageName",
"wgRevisionId",
"wgArticleId"
]),
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
};
// Only activate on existing talk pages and project pages
var isExistingPage = config.mw.wgArticleId > 0;
if ( !isExistingPage ) {
return;
}
var isTalkPage = config.mw.wgNamespaceNumber > 0 && config.mw.wgNamespaceNumber%2 === 1;
var isProjectPage = config.mw.wgNamespaceNumber === 4;
if ( !isTalkPage && !isProjectPage ) {
return;
}
mw.util.addCSS(".tsdiffs-timestamp a { color:inherit; text-decoration: underline dotted #6495ED; }" );
/**
* Wraps timestamps within text nodes inside spans (with classes "tsdiffs-timestamp" and "tsdiffs-unlinked").
* Based on "replaceText" method in https://en.wikipedia.org/wiki/User:Gary/comments_in_local_time.js
*
* @param {Node} node Node in which to look for timestamps
*/
var wrapTimestamps = function(node) {
var timestampPatten = /(\d{2}:\d{2}, \d{1,2} \w+ \d{4} \(UTC\))/g;
if (!node) {
return;
}
var isTextNode = node.nodeType === 3;
if (isTextNode) {
var parent = node.parentNode;
var parentNodeName = parent.nodeName;
if (['CODE', 'PRE'].includes(parentNodeName)) {
return;
}
var value = node.nodeValue;
var matches = value.match(timestampPatten);
// Manipulating the DOM directly is much faster than using jQuery.
if (matches) {
// Only act on the first timestamp we found in this node. If
// there are two or more timestamps in the same node, they
// will be dealt with through recursion below
var match = matches[0];
var position = value.search(timestampPatten);
var stringLength = match.toString().length;
var beforeMatch = value.substring(0, position);
var afterMatch = value.substring(position + stringLength);
var span = document.createElement('span');
span.className = 'tsdiffs-timestamp tsdiffs-unlinked';
span.append(document.createTextNode(match.toString()));
parent = node.parentNode;
parent.replaceChild(span, node);
var before = document.createElement('span');
before.className = 'before-tsdiffs';
before.append(document.createTextNode(beforeMatch));
var after = document.createElement('span');
after.className = 'after-tsdiffs';
after.append(document.createTextNode(afterMatch));
parent.insertBefore(before, span);
parent.insertBefore(after, span.nextSibling);
// Look for timestamps to wrap in all subsequent sibling nodes
var next = after;
var nextNodes = [];
while (next) {
nextNodes.push(next);
next = next.nextSibling;
}
nextNodes.forEach(wrapTimestamps);
}
} else {
node.childNodes.forEach(wrapTimestamps);
}
};
wrapTimestamps(document.querySelector(".mw-parser-output"));
// Account for [[Wikipedia:Comments in local time]] gadget
document.querySelectorAll(".localcomments").forEach(function(node) {
node.classList.add("tsdiffs-timestamp", "tsdiffs-unlinked");
});
/**
* Wraps the child nodes of an element within an <a> tag,
* with given href and title attributes, and removes the
* `tsdiffs-unlinked` class from the element.
*
* @param {Element} element
* @param {string} href
* @param {string} title
*/
var linkTimestamp = function(element, href, hrefThanks, title) {
var a = document.createElement("a");
a.setAttribute("href", href);
a.setAttribute("title", title);
element.childNodes.forEach(function(child) {
a.appendChild(child);
});
var thankWrapper = document.createElement( "span" );
thankWrapper.className = "thank-link-wrapper";
var b = document.createElement("a");
thankWrapper.appendChild( document.createTextNode( " (" ) );
thankWrapper.appendChild(b);
thankWrapper.appendChild( document.createTextNode( ")" ) );
b.setAttribute("href", hrefThanks);
b.setAttribute("title", title);
element.appendChild(a);
element.classList.remove("tsdiffs-unlinked");
};
/**
* Formats a JavaScript Date object as a string in the MediaWiki timestamp format:
* hh:mm, dd Mmmm YYYY (UTC)
*
* @param {Date} date
* @returns {string}
*/
var dateToTimestamp = function(date) {
var hours = ("0"+date.getUTCHours()).slice(-2);
var minutes = ("0"+date.getUTCMinutes()).slice(-2);
var day = date.getUTCDate();
var month = config.months[date.getUTCMonth()];
var year = date.getUTCFullYear();
return hours + ":" + minutes + ", " + day + " " + month + " " + year + " (UTC)";
};
var api = new mw.Api( {
ajax: {
headers: {
"Api-User-Agent": "TimestampDiffs/" + config.version +
" ( https://en.wikipedia.org/wiki/User:Evad37/TimestampDiffs.js )"
}
}
} );
// For discussion archives, comments come from the base page
var basePageName = config.mw.wgPageName.replace(/\/Archive..*?$/, "");
var apiQueryCount = 0;
var processTimestamps = function(rvStartId) {
apiQueryCount++;
return api.get({
"action": "query",
"format": "json",
"prop": "revisions",
"titles": basePageName,
"formatversion": "2",
"rvprop": "timestamp|user|comment|ids",
"rvslots": "",
"rvlimit": "5000",
"rvStartId": rvStartId || config.mw.wgRevisionId
}).then(function(response) {
if (!response || !response.query || !response.query.pages || !response.query.pages[0] || !response.query.pages[0].revisions) {
return $.Deferred().reject("API response did not contain any revisions");
}
var pageRevisions = response.query.pages[0].revisions.map(function(revision) {
var revisionDate = new Date(revision.timestamp);
var oneMinutePriorDate = new Date(revisionDate - 1000*60);
revision.timestampText = dateToTimestamp(revisionDate);
revision.oneMinutePriorTimestampText = dateToTimestamp(oneMinutePriorDate);
return revision;
});
document.querySelectorAll(".tsdiffs-unlinked").forEach(function(timestampNode) {
var timestamp;
if (timestampNode.classList.contains("localcomments")) {
timestamp = timestampNode.getAttribute("title");
} else {
timestamp = timestampNode.textContent;
}
// Try finding revisions with an exact timestamp match
var revisions = pageRevisions.filter(function(revision) {
return revision.timestampText === timestamp;
});
if (!revisions.length) {
// Try finding revisions which are off by one miniute
revisions = pageRevisions.filter(function(revision) {
return revision.oneMinutePriorTimestampText === timestamp;
});
}
if (revisions.length) { // One or more revisions had a matching timestamp
// Generate a link of the diff the between newest revision in the array,
// and the parent (previous) of the oldest revision in the array.
var newerRevId = revisions[0].revid;
var olderRevId = revisions[revisions.length-1].parentid || "prev";
var href = "/wiki/Special:Diff/" + olderRevId + "/" + newerRevId;
var hrefThanks = '/wiki/Special:Thanks/' + olderRevId;
// Title attribute for the link can be the revision comment if there was
// only one revision, otherwise use the number of revisions found
var comment = revisions.length === 1 ? revisions[0].comment : revisions.length + " edits";
var title = "Diff (" + comment + ")";
linkTimestamp(timestampNode, href, hrefThanks, title);
}
});
if ( apiQueryCount < 5 && document.getElementsByClassName("tsdiffs-unlinked").length ) {
return processTimestamps(pageRevisions[pageRevisions.length-1].revid);
}
});
};
return processTimestamps()
.catch(function(code, error) {
mw.notify("Error: " + (code || "unknown"), {title:"TimestampDiffs failed to load"});
console.warn("[TimestampDiffs] Error: " + (code || "unknown"), error);
});
});
// </nowiki>