Jump to content

User:Mxn/CommentsInLocalTime.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Mxn (talk | contribs) at 00:03, 2 January 2016. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.
/**
 * Comments in local time
 * [[:vi:Wikipedia:Tin nhắn theo giờ địa phương]]
 * 
 * Adjust timestamps in comment signatures to use easy-to-understand, relative
 * local time instead of absolute UTC time.
 * 
 * Inspired by [[Wikipedia:Comments in Local Time]].
 */
$(function () {
	if ([-1, 0, 8, 100, 108, 118].indexOf(mw.config.get("wgNamespaceNumber")) !== -1
		|| ["view", "submit"].indexOf(mw.config.get("wgAction")) === -1
		|| mw.config.get("wgCanonicalSpecialPageName")
		|| mw.util.getParamValue("disable") === "loco")
	{
		return;
	}
	
	//* Abbreviated month names in English.
	var enAbbrMonths = mw.config.get("wgMonthNamesShort").splice(1);
	//* Spelled-out month names in English.
	var enFullMonths = mw.config.get("wgMonthNames").splice(1);
	/**
	 * Regular expression matching all the timestamps inserted by this MediaWiki
	 * installation over the years.
	 * 
	 * Until 2005:
	 * 	18:16, 23 Dec 2004 (UTC)
	 * 2005–present:
	 * 	08:51, 23 November 2015 (UTC)
	 */
	var dateRe = new RegExp("(\\d\\d):(\\d\\d), (\\d\\d?) (" +
		enFullMonths.join("|") + "|" + enAbbrMonths.join("|") +
		") (\\d{4}) \\(UTC\\)");
	//* One-based captured group numbers in dateRe keyed by time unit.
	var dateReGroups = {
		year: 5,
		month: 4,
		dayOfMonth: 3,
		hour: 1,
		minute: 2,
	};
	//* Names of tags that often directly contain timestamps.
	var proseTags = ["DD", "LI", "P", "TD"];
	//* Names of tags that do not contain timestamps either directly or
	//* indirectly. Must contain time to avoid an infinite loop.
	var codeTags = "code, input, pre, textarea, time";
	
	/**
	 * Converts the regular expression search result into a Date object.
	 * 
	 * @param {Object} result The result of calling exec() on a RegExp object.
	 * @returns {Date}
	 */
	function toDate(result) {
		var month = enFullMonths.indexOf(result[dateReGroups.month]);
		if (month === -1) month = enAbbrMonths.indexOf(result[dateReGroups.month]);
		return new Date(Date.UTC(result[dateReGroups.year], month,
			result[dateReGroups.dayOfMonth], result[dateReGroups.hour],
			result[dateReGroups.minute]));
	}
	
	// Look in the content body for DOM text nodes that may contain timestamps.
	// The wiki software has already localized other parts of the page.
	var root = $("#wikiPreview, #mw-content-text")[0];
	if (!root) return;
	var iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, {
		acceptNode: function (node) {
			// We can’t just check the node’s direct parent, because templates
			// like [[Template:Talkback]] and [[Template:Resolved]] may place a
			// signature inside a nondescript <span>.
			var isInProse = proseTags.indexOf(node.parentElement.nodeName) !== -1
				|| !$(node).parents(codeTags).length;
			var isDateNode = isInProse && dateRe.test(node.data);
			return isDateNode ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
		},
	});
	
	// Restructure each timestamp found.
	var prefixNode;
	while ((prefixNode = iter.nextNode())) {
		var result = dateRe.exec(prefixNode.data);
		if (!result) continue;
		
		// Split out the timestamp into a separate text node.
		var dateNode = prefixNode.splitText(result.index);
		var suffixNode = dateNode.splitText(result[0].length);
		
		// Wrap the timestamp inside a <time> element for findability.
		var timeElt = $("<time />");
		// MediaWiki core styles .explain[title] the same way as abbr[title],
		// guiding the user to the tooltip.
		timeElt.addClass("localcomments explain");
		timeElt.attr("datetime", toDate(result).toISOString());
		$(dateNode).wrap(timeElt);
	}
	
	/**
	 * Reformats a timestamp marked up with the <time> element.
	 * 
	 * @param {Number} idx Unused.
	 * @param {Element} elt The <time> element.
	 */
	function formatTimestamp(idx, elt) {
		var iso = $(elt).attr("datetime");
		var theMoment = moment(iso);
		var withinHours = Math.abs(theMoment.diff(moment(), "hours", true))
			<= moment.relativeTimeThreshold("h");
		var text;
		if (withinHours) {
			// Within a day, show a relative time that’s easy to relate to.
			text = theMoment.fromNow();
		}
		else {
			var dayDiff = theMoment.diff(moment().startOf("day"), "days", true);
			if (dayDiff > -7 && dayDiff < 8) {
				// Within a week, show a relative date and specific time, still
				// helpful if the user doesn’t remember today’s date. Don’t show
				// just a relative time, because a discussion may need more
				// context than “Last Friday” on every comment.
				text = theMoment.calendar();
			}
			else {
				// The calendar() method uses an ambiguous “MM/DD/YYYY” format
				// for faraway dates; spell things out for this international
				// audience.
				text = theMoment.format("LLL");
			}
		}
		$(elt).text(text);
		
		// Add a tooltip with multiple formats.
		elt.title = theMoment.fromNow() + "\n" +
			theMoment.format("LLLL[\n]YYYY-MM-DDTHH:mmZ");
		
		// Register for periodic updates.
		$(elt).toggleClass("localcomments-within-hours", withinHours);
		var withinMinutes = withinHours
			&& Math.abs(theMoment.diff(moment(), "minutes", true))
				<= moment.relativeTimeThreshold("m");
		$(elt).toggleClass("localcomments-within-minutes", withinMinutes);
	}
	
	/**
	 * Reformat all marked-up timestamps and start updating timestamps on an
	 * interval as necessary.
	 */
	function formatTimestamps() {
		$(".localcomments").each(function (idx, elt) {
			// Update every timestamp at lease this once.
			formatTimestamp(idx, elt);
			
			// Update this hour’s timestamps every minute.
			if ($(".localcomments-within-minutes").length) {
				setInterval(function () {
					$(".localcomments-within-minutes").each(formatTimestamp);
				}, 60 /* s */ * 1000 /* ms */);
			}
			// Update today’s timestamps every hour.
			if ($(".localcomments-within-hours").length) {
				setInterval(function () {
					$(".localcomments-within-hours").each(formatTimestamp);
				}, 60 /* min */ * 60 /* s */ * 1000 /* ms */);
			}
		});
	}
	mw.loader.using("moment", formatTimestamps);
});