MediaWiki:Gadget-relativetime.js: Difference between revisions
Jump to navigation
Jump to search
Content added Content deleted
No edit summary Tag: Manual revert |
No edit summary |
||
Line 1: | Line 1: | ||
"use strict"; |
|||
// Don't load CommentsInLocalTime for namespaces it is disabled for. |
// Don't load CommentsInLocalTime for namespaces it is disabled for. |
||
if ( |
if ([-1, 0, 8].indexOf(mw.config.get("wgNamespaceNumber")) === -1) { |
||
// [[w:en:User:Mxn/CommentsInLocalTime]] |
|||
// en.wikipedia.org/wiki/User:Mxn/CommentsInLocalTime.js |
|||
/** |
|||
* Comments in local time |
|||
* [[User:Mxn/CommentsInLocalTime]] |
|||
* |
|||
* 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]]. |
|||
* |
|||
* @author [[User:Mxn]] |
|||
*/ |
|||
/** |
|||
* Default settings for this gadget. |
|||
*/ |
|||
window.LocalComments = $.extend({ |
|||
// USER OPTIONS //////////////////////////////////////////////////////////// |
|||
/** |
|||
* When false, this gadget does nothing. |
|||
*/ |
|||
enabled: true, |
|||
/** |
|||
* Formats to display inline for each timestamp, keyed by a few common |
|||
/** |
|||
* cases. |
|||
* Formats to display inline for each timestamp, keyed by a few common |
|||
* |
|||
* If a property of this object is set to a string, the timestamp is |
|||
* |
|||
* formatted according to the documentation at |
|||
* <http://momentjs.com/docs/#/displaying/format/>. |
|||
* formatted according to the documentation at |
|||
* |
|||
* <http://momentjs.com/docs/#/displaying/format/>. |
|||
* If a property of this object is set to a function, it is called to |
|||
* |
|||
* retrieve the formatted timestamp string. See |
|||
* If a property of this object is set to a function, it is called to |
|||
* <http://momentjs.com/docs/#/displaying/> for the various things you can |
|||
* retrieve the formatted timestamp string. See |
|||
* do with the passed-in moment object. |
|||
* <http://momentjs.com/docs/#/displaying/> for the various things you can |
|||
*/ |
|||
* do with the passed-in moment object. |
|||
formats: { |
|||
*/ |
|||
/** |
|||
formats: { |
|||
* Within a day, show a relative time that’s easy to relate to. |
|||
/** |
|||
*/ |
|||
* Within a day, show a relative time that’s easy to relate to. |
|||
day: function day(then) { |
|||
*/ |
|||
return then.fromNow(); |
|||
}, |
|||
/** |
|||
* 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. |
|||
*/ |
|||
week: function week(then) { |
|||
return then.calendar(); |
|||
}, |
|||
/** |
|||
/** |
|||
* The calendar() method uses an ambiguous “MM/DD/YYYY” format for |
|||
* The calendar() method uses an ambiguous “MM/DD/YYYY” format for |
|||
* faraway dates; spell things out for this international audience. |
|||
* faraway dates; spell things out for this international audience. |
|||
*/ |
|||
*/ |
|||
other: "LLL", |
|||
other: "LLL" |
|||
}, |
|||
}, |
|||
/** |
|||
* Formats to display in each timestamp’s tooltip, one per line. |
|||
* |
|||
* If an element of this array is a string, the timestamp is formatted |
|||
* according to the documentation at |
|||
* <http://momentjs.com/docs/#/displaying/format/>. |
|||
* |
|||
* If an element of this array is a function, it is called to retrieve the |
|||
* formatted timestamp string. See <http://momentjs.com/docs/#/displaying/> |
|||
* for the various things you can do with the passed-in moment object. |
|||
*/ |
|||
tooltipFormats: [function (then) { |
|||
return then.fromNow(); |
|||
}, "LLLL", "YYYY-MM-DDTHH:mmZ"], |
|||
"LLLL", |
|||
/** |
|||
"YYYY-MM-DDTHH:mmZ", |
|||
* When true, this gadget refreshes timestamps periodically. |
|||
], |
|||
*/ |
|||
dynamic: true |
|||
/** |
|||
}, { |
|||
* When true, this gadget refreshes timestamps periodically. |
|||
// SITE OPTIONS //////////////////////////////////////////////////////////// |
|||
*/ |
|||
dynamic: true, |
|||
/** |
|||
}, { |
|||
* Numbers of namespaces to completely ignore. See [[Wikipedia:Namespace]]. |
|||
// SITE OPTIONS //////////////////////////////////////////////////////////// |
|||
*/ |
|||
excludeNamespaces: [-1, 0, 8, 100, 108, 118], |
|||
/** |
|||
/** |
|||
* Numbers of namespaces to completely ignore. See [[Wikipedia:Namespace]]. |
|||
* Names of tags that often directly contain timestamps. |
|||
*/ |
|||
* |
|||
excludeNamespaces: [-1, 0, 8, 100, 108, 118], |
|||
* This is merely a performance optimization. This gadget will look at text |
|||
* nodes in any tag other than the codeTags, but adding a tag here ensures |
|||
/** |
|||
* that it gets processed the most efficient way possible. |
|||
* Names of tags that often directly contain timestamps. |
|||
*/ |
|||
* |
|||
proseTags: ["dd", "li", "p", "td"], |
|||
* This is merely a performance optimization. This gadget will look at text |
|||
/** |
|||
* nodes in any tag other than the codeTags, but adding a tag here ensures |
|||
* Names of tags that don’t contain timestamps either directly or |
|||
* that it gets processed the most efficient way possible. |
|||
* indirectly. |
|||
*/ |
|||
*/ |
|||
proseTags: ["dd", "li", "p", "td"], |
|||
codeTags: ["code", "input", "pre", "textarea"], |
|||
/** |
|||
* Expected format or formats of the timestamps in existing wikitext. If |
|||
* very different formats have been used over the course of the wiki’s |
|||
* indirectly. |
|||
* history, specify an array of formats. |
|||
*/ |
|||
* |
|||
codeTags: ["code", "input", "pre", "textarea"], |
|||
* This option expects parsing format strings |
|||
* <http://momentjs.com/docs/#/parsing/string-format/>. |
|||
/** |
|||
*/ |
|||
* Expected format or formats of the timestamps in existing wikitext. If |
|||
parseFormat: "H:m, D MMM YYYY", |
|||
* very different formats have been used over the course of the wiki’s |
|||
/** |
|||
* history, specify an array of formats. |
|||
* Regular expression matching all the timestamps inserted by this MediaWiki |
|||
* |
|||
* installation over the years. This regular expression should more or less |
|||
* This option expects parsing format strings |
|||
* agree with the parseFormat option. |
|||
* <http://momentjs.com/docs/#/parsing/string-format/>. |
|||
* |
|||
* Until 2005: |
|||
parseFormat: "H:m, D MMM YYYY", |
|||
* 18:16, 23 Dec 2004 (UTC) |
|||
* 2005–present: |
|||
/** |
|||
* 08:51, 23 November 2015 (UTC) |
|||
* Regular expression matching all the timestamps inserted by this MediaWiki |
|||
*/ |
|||
* installation over the years. This regular expression should more or less |
|||
parseRegExp: /\d\d:\d\d, \d\d? (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w* \d{4} \(UTC\)/, |
|||
* agree with the parseFormat option. |
|||
/** |
|||
* |
|||
* UTC offset of the wiki's default local timezone. See |
|||
* Until 2005: |
|||
* [[mw:Manual:Timezone]]. |
|||
* 18:16, 23 Dec 2004 (UTC) |
|||
*/ |
|||
* 2005–present: |
|||
utcOffset: 0 |
|||
* 08:51, 23 November 2015 (UTC) |
|||
}, window.LocalComments); |
|||
*/ |
|||
$(function () { |
|||
parseRegExp: /\d\d:\d\d, \d\d? (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w* \d{4} \(UTC\)/, |
|||
if (!LocalComments.enabled || LocalComments.excludeNamespaces.indexOf(mw.config.get("wgNamespaceNumber")) !== -1 || ["view", "submit"].indexOf(mw.config.get("wgAction")) === -1 || mw.util.getParamValue("disable") === "loco") { |
|||
return; |
|||
/** |
|||
} |
|||
* UTC offset of the wiki's default local timezone. See |
|||
var proseTags = LocalComments.proseTags.join("\n").toUpperCase().split("\n"); |
|||
* [[mw:Manual:Timezone]]. |
|||
// Exclude <time> to avoid an infinite loop when iterating over text nodes. |
|||
*/ |
|||
var codeTags = $.merge(LocalComments.codeTags, ["time"]).join(", "); |
|||
utcOffset: 0, |
|||
}, window.LocalComments); |
|||
// Look in the content body for DOM text nodes that may contain timestamps. |
|||
// The wiki software has already localized other parts of the page. |
|||
$(function () { |
|||
var root = $("#wikiPreview, #mw-content-text")[0]; |
|||
if (!LocalComments.enabled |
|||
if (!root || !("createNodeIterator" in document)) return; |
|||
|| LocalComments.excludeNamespaces.indexOf(mw.config.get("wgNamespaceNumber")) !== -1 |
|||
var iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, { |
|||
|| ["view", "submit"].indexOf(mw.config.get("wgAction")) === -1 |
|||
acceptNode: function acceptNode(node) { |
|||
|| mw.util.getParamValue("disable") === "loco") |
|||
// We can’t just check the node’s direct parent, because templates |
|||
{ |
|||
// like [[Template:Talkback]] and [[Template:Resolved]] may place a |
|||
return; |
|||
// signature inside a nondescript <span>. |
|||
} |
|||
var isInProse = proseTags.indexOf(node.parentElement.nodeName) !== -1 || !$(node).parents(codeTags).length; |
|||
var isDateNode = isInProse && LocalComments.parseRegExp.test(node.data); |
|||
return isDateNode ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; |
|||
// Exclude <time> to avoid an infinite loop when iterating over text nodes. |
|||
} |
|||
var codeTags = $.merge(LocalComments.codeTags, ["time"]).join(", "); |
|||
}); |
|||
// Look in the content body for DOM text nodes that may contain timestamps. |
|||
// Mark up each timestamp found. |
|||
// The wiki software has already localized other parts of the page. |
|||
function wrapTimestamps() { |
|||
var root = $("#wikiPreview, #mw-content-text")[0]; |
|||
var prefixNode; |
|||
if (!root || !("createNodeIterator" in document)) return; |
|||
while (prefixNode = iter.nextNode()) { |
|||
var iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, { |
|||
var result = LocalComments.parseRegExp.exec(prefixNode.data); |
|||
acceptNode: function (node) { |
|||
if (!result) continue; |
|||
// We can’t just check the node’s direct parent, because templates |
|||
// like [[Template:Talkback]] and [[Template:Resolved]] may place a |
|||
// Split out the timestamp into a separate text node. |
|||
// signature inside a nondescript <span>. |
|||
var dateNode = prefixNode.splitText(result.index); |
|||
var isInProse = proseTags.indexOf(node.parentElement.nodeName) !== -1 |
|||
var suffixNode = dateNode.splitText(result[0].length); |
|||
|| !$(node).parents(codeTags).length; |
|||
var isDateNode = isInProse && LocalComments.parseRegExp.test(node.data); |
|||
// Determine the represented time. |
|||
return isDateNode ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; |
|||
var then = moment.utc(result[0], LocalComments.parseFormat); |
|||
}, |
|||
if (!then.isValid()) { |
|||
}); |
|||
// Many Wikipedias started out with English as the default |
|||
// localization, so fall back to English. |
|||
// Mark up each timestamp found. |
|||
then = moment.utc(result[0], "H:m, D MMM YYYY", "en"); |
|||
function wrapTimestamps() { |
|||
} |
|||
var prefixNode; |
|||
if (!then.isValid()) continue; |
|||
while ((prefixNode = iter.nextNode())) { |
|||
then.utcOffset(-LocalComments.utcOffset); |
|||
var result = LocalComments.parseRegExp.exec(prefixNode.data); |
|||
if (!result) continue; |
|||
// Wrap the timestamp inside a <time> element for findability. |
|||
var timeElt = $("<time />"); |
|||
// Split out the timestamp into a separate text node. |
|||
// MediaWiki core styles .explain[title] the same way as |
|||
var dateNode = prefixNode.splitText(result.index); |
|||
// abbr[title], guiding the user to the tooltip. |
|||
var suffixNode = dateNode.splitText(result[0].length); |
|||
timeElt.addClass("localcomments explain"); |
|||
timeElt.attr("datetime", then.toISOString()); |
|||
// Determine the represented time. |
|||
$(dateNode).wrap(timeElt); |
|||
var then = moment.utc(result[0], LocalComments.parseFormat); |
|||
} |
|||
if (!then.isValid()) { |
|||
} |
|||
// Many Wikipedias started out with English as the default |
|||
// localization, so fall back to English. |
|||
/** |
|||
then = moment.utc(result[0], "H:m, D MMM YYYY", "en"); |
|||
* Returns a formatted string for the given moment object. |
|||
} |
|||
* |
|||
if (!then.isValid()) continue; |
|||
* @param {Moment} then The moment object to format. |
|||
then.utcOffset(-LocalComments.utcOffset); |
|||
* @param {String} fmt A format string or function. |
|||
* @returns {String} A formatted string. |
|||
// Wrap the timestamp inside a <time> element for findability. |
|||
*/ |
|||
var timeElt = $("<time />"); |
|||
function formatMoment(then, fmt) { |
|||
// MediaWiki core styles .explain[title] the same way as |
|||
return fmt instanceof Function ? fmt(then) : then.format(fmt); |
|||
// abbr[title], guiding the user to the tooltip. |
|||
} |
|||
timeElt.addClass("localcomments explain"); |
|||
timeElt.attr("datetime", then.toISOString()); |
|||
/** |
|||
$(dateNode).wrap(timeElt); |
|||
* Reformats a timestamp marked up with the <time> element. |
|||
} |
|||
* |
|||
} |
|||
* @param {Number} idx Unused. |
|||
* @param {Element} elt The <time> element. |
|||
/** |
|||
*/ |
|||
* Returns a formatted string for the given moment object. |
|||
function formatTimestamp(idx, elt) { |
|||
* |
|||
var iso = $(elt).attr("datetime"); |
|||
* @param {Moment} then The moment object to format. |
|||
var then = moment(iso, moment.ISO_8601); |
|||
* @param {String} fmt A format string or function. |
|||
var now = moment(); |
|||
* @returns {String} A formatted string. |
|||
var withinHours = Math.abs(then.diff(now, "hours", true)) <= moment.relativeTimeThreshold("h"); |
|||
*/ |
|||
var formats = LocalComments.formats; |
|||
function formatMoment(then, fmt) { |
|||
var text; |
|||
return (fmt instanceof Function) ? fmt(then) : then.format(fmt); |
|||
if (withinHours) { |
|||
} |
|||
text = formatMoment(then, formats.day || formats.other); |
|||
} else { |
|||
/** |
|||
var dayDiff = then.diff(moment().startOf("day"), "days", true); |
|||
* Reformats a timestamp marked up with the <time> element. |
|||
if (dayDiff > -6 && dayDiff < 7) { |
|||
* |
|||
text = formatMoment(then, formats.week || formats.other); |
|||
* @param {Number} idx Unused. |
|||
} else text = formatMoment(then, formats.other); |
|||
* @param {Element} elt The <time> element. |
|||
} |
|||
*/ |
|||
$(elt).text(text); |
|||
function formatTimestamp(idx, elt) { |
|||
var iso = $(elt).attr("datetime"); |
|||
// Add a tooltip with multiple formats. |
|||
var then = moment(iso, moment.ISO_8601); |
|||
elt.title = $.map(LocalComments.tooltipFormats, function (fmt, idx) { |
|||
var now = moment(); |
|||
return formatMoment(then, fmt); |
|||
var withinHours = Math.abs(then.diff(now, "hours", true)) |
|||
}).join("\n"); |
|||
<= moment.relativeTimeThreshold("h"); |
|||
var formats = LocalComments.formats; |
|||
// Register for periodic updates. |
|||
var text; |
|||
var withinMinutes = withinHours && Math.abs(then.diff(now, "minutes", true)) <= moment.relativeTimeThreshold("m"); |
|||
if (withinHours) { |
|||
var withinSeconds = withinMinutes && Math.abs(then.diff(now, "seconds", true)) <= moment.relativeTimeThreshold("s"); |
|||
text = formatMoment(then, formats.day || formats.other); |
|||
var unit = withinSeconds ? "seconds" : withinMinutes ? "minutes" : withinHours ? "hours" : "days"; |
|||
} |
|||
$(elt).attr("data-localcomments-unit", unit); |
|||
else { |
|||
} |
|||
var dayDiff = then.diff(moment().startOf("day"), "days", true); |
|||
if (dayDiff > -6 && dayDiff < 7) { |
|||
/** |
|||
text = formatMoment(then, formats.week || formats.other); |
|||
* Reformat all marked-up timestamps and start updating timestamps on an |
|||
} |
|||
* interval as necessary. |
|||
else text = formatMoment(then, formats.other); |
|||
*/ |
|||
} |
|||
function formatTimestamps() { |
|||
$(elt).text(text); |
|||
wrapTimestamps(); |
|||
$(".localcomments").each(function (idx, elt) { |
|||
// Add a tooltip with multiple formats. |
|||
// Update every timestamp at least this once. |
|||
elt.title = $.map(LocalComments.tooltipFormats, function (fmt, idx) { |
|||
formatTimestamp(idx, elt); |
|||
return formatMoment(then, fmt); |
|||
if (!LocalComments.dynamic) return; |
|||
}).join("\n"); |
|||
// Update this minute’s timestamps every second. |
|||
// Register for periodic updates. |
|||
if ($("[data-localcomments-unit='seconds']").length) { |
|||
var withinMinutes = withinHours |
|||
setInterval(function () { |
|||
&& Math.abs(then.diff(now, "minutes", true)) |
|||
$("[data-localcomments-unit='seconds']").each(formatTimestamp); |
|||
<= moment.relativeTimeThreshold("m"); |
|||
}, 1000 /* ms */); |
|||
var withinSeconds = withinMinutes |
|||
} |
|||
&& Math.abs(then.diff(now, "seconds", true)) |
|||
// Update this hour’s timestamps every minute. |
|||
<= moment.relativeTimeThreshold("s"); |
|||
setInterval(function () { |
|||
var unit = withinSeconds ? "seconds" : |
|||
$("[data-localcomments-unit='minutes']").each(formatTimestamp); |
|||
(withinMinutes ? "minutes" : |
|||
}, 60 /* s */ * 1000 /* ms */); |
|||
(withinHours ? "hours" : "days")); |
|||
// Update today’s timestamps every hour. |
|||
$(elt).attr("data-localcomments-unit", unit); |
|||
setInterval(function () { |
|||
} |
|||
$("[data-localcomments-unit='hours']").each(formatTimestamp); |
|||
}, 60 /* min */ * 60 /* s */ * 1000 /* ms */); |
|||
/** |
|||
}); |
|||
* Reformat all marked-up timestamps and start updating timestamps on an |
|||
} |
|||
* interval as necessary. |
|||
mw.loader.using("moment", function () { |
|||
*/ |
|||
wrapTimestamps(); |
|||
function formatTimestamps() { |
|||
formatTimestamps(); |
|||
wrapTimestamps(); |
|||
}); |
|||
$(".localcomments").each(function (idx, elt) { |
|||
}); |
|||
// Update every timestamp at least this once. |
|||
formatTimestamp(idx, elt); |
|||
if (!LocalComments.dynamic) return; |
|||
// Update this minute’s timestamps every second. |
|||
if ($("[data-localcomments-unit='seconds']").length) { |
|||
setInterval(function () { |
|||
$("[data-localcomments-unit='seconds']").each(formatTimestamp); |
|||
}, 1000 /* ms */); |
|||
} |
|||
// Update this hour’s timestamps every minute. |
|||
setInterval(function () { |
|||
$("[data-localcomments-unit='minutes']").each(formatTimestamp); |
|||
}, 60 /* s */ * 1000 /* ms */); |
|||
// Update today’s timestamps every hour. |
|||
setInterval(function () { |
|||
$("[data-localcomments-unit='hours']").each(formatTimestamp); |
|||
}, 60 /* min */ * 60 /* s */ * 1000 /* ms */); |
|||
}); |
|||
} |
|||
mw.loader.using("moment", function () { |
|||
wrapTimestamps(); |
|||
formatTimestamps(); |
|||
}); |
|||
}); |
|||
} |
} |
Latest revision as of 12:06, 20 October 2024
"use strict";
// Don't load CommentsInLocalTime for namespaces it is disabled for.
if ([-1, 0, 8].indexOf(mw.config.get("wgNamespaceNumber")) === -1) {
// [[w:en:User:Mxn/CommentsInLocalTime]]
// en.wikipedia.org/wiki/User:Mxn/CommentsInLocalTime.js
/**
* Comments in local time
* [[User:Mxn/CommentsInLocalTime]]
*
* 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]].
*
* @author [[User:Mxn]]
*/
/**
* Default settings for this gadget.
*/
window.LocalComments = $.extend({
// USER OPTIONS ////////////////////////////////////////////////////////////
/**
* When false, this gadget does nothing.
*/
enabled: true,
/**
* Formats to display inline for each timestamp, keyed by a few common
* cases.
*
* If a property of this object is set to a string, the timestamp is
* formatted according to the documentation at
* <http://momentjs.com/docs/#/displaying/format/>.
*
* If a property of this object is set to a function, it is called to
* retrieve the formatted timestamp string. See
* <http://momentjs.com/docs/#/displaying/> for the various things you can
* do with the passed-in moment object.
*/
formats: {
/**
* Within a day, show a relative time that’s easy to relate to.
*/
day: function day(then) {
return then.fromNow();
},
/**
* 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.
*/
week: function week(then) {
return then.calendar();
},
/**
* The calendar() method uses an ambiguous “MM/DD/YYYY” format for
* faraway dates; spell things out for this international audience.
*/
other: "LLL"
},
/**
* Formats to display in each timestamp’s tooltip, one per line.
*
* If an element of this array is a string, the timestamp is formatted
* according to the documentation at
* <http://momentjs.com/docs/#/displaying/format/>.
*
* If an element of this array is a function, it is called to retrieve the
* formatted timestamp string. See <http://momentjs.com/docs/#/displaying/>
* for the various things you can do with the passed-in moment object.
*/
tooltipFormats: [function (then) {
return then.fromNow();
}, "LLLL", "YYYY-MM-DDTHH:mmZ"],
/**
* When true, this gadget refreshes timestamps periodically.
*/
dynamic: true
}, {
// SITE OPTIONS ////////////////////////////////////////////////////////////
/**
* Numbers of namespaces to completely ignore. See [[Wikipedia:Namespace]].
*/
excludeNamespaces: [-1, 0, 8, 100, 108, 118],
/**
* Names of tags that often directly contain timestamps.
*
* This is merely a performance optimization. This gadget will look at text
* nodes in any tag other than the codeTags, but adding a tag here ensures
* that it gets processed the most efficient way possible.
*/
proseTags: ["dd", "li", "p", "td"],
/**
* Names of tags that don’t contain timestamps either directly or
* indirectly.
*/
codeTags: ["code", "input", "pre", "textarea"],
/**
* Expected format or formats of the timestamps in existing wikitext. If
* very different formats have been used over the course of the wiki’s
* history, specify an array of formats.
*
* This option expects parsing format strings
* <http://momentjs.com/docs/#/parsing/string-format/>.
*/
parseFormat: "H:m, D MMM YYYY",
/**
* Regular expression matching all the timestamps inserted by this MediaWiki
* installation over the years. This regular expression should more or less
* agree with the parseFormat option.
*
* Until 2005:
* 18:16, 23 Dec 2004 (UTC)
* 2005–present:
* 08:51, 23 November 2015 (UTC)
*/
parseRegExp: /\d\d:\d\d, \d\d? (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w* \d{4} \(UTC\)/,
/**
* UTC offset of the wiki's default local timezone. See
* [[mw:Manual:Timezone]].
*/
utcOffset: 0
}, window.LocalComments);
$(function () {
if (!LocalComments.enabled || LocalComments.excludeNamespaces.indexOf(mw.config.get("wgNamespaceNumber")) !== -1 || ["view", "submit"].indexOf(mw.config.get("wgAction")) === -1 || mw.util.getParamValue("disable") === "loco") {
return;
}
var proseTags = LocalComments.proseTags.join("\n").toUpperCase().split("\n");
// Exclude <time> to avoid an infinite loop when iterating over text nodes.
var codeTags = $.merge(LocalComments.codeTags, ["time"]).join(", ");
// 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 || !("createNodeIterator" in document)) return;
var iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, {
acceptNode: function acceptNode(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 && LocalComments.parseRegExp.test(node.data);
return isDateNode ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
});
// Mark up each timestamp found.
function wrapTimestamps() {
var prefixNode;
while (prefixNode = iter.nextNode()) {
var result = LocalComments.parseRegExp.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);
// Determine the represented time.
var then = moment.utc(result[0], LocalComments.parseFormat);
if (!then.isValid()) {
// Many Wikipedias started out with English as the default
// localization, so fall back to English.
then = moment.utc(result[0], "H:m, D MMM YYYY", "en");
}
if (!then.isValid()) continue;
then.utcOffset(-LocalComments.utcOffset);
// 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", then.toISOString());
$(dateNode).wrap(timeElt);
}
}
/**
* Returns a formatted string for the given moment object.
*
* @param {Moment} then The moment object to format.
* @param {String} fmt A format string or function.
* @returns {String} A formatted string.
*/
function formatMoment(then, fmt) {
return fmt instanceof Function ? fmt(then) : then.format(fmt);
}
/**
* 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 then = moment(iso, moment.ISO_8601);
var now = moment();
var withinHours = Math.abs(then.diff(now, "hours", true)) <= moment.relativeTimeThreshold("h");
var formats = LocalComments.formats;
var text;
if (withinHours) {
text = formatMoment(then, formats.day || formats.other);
} else {
var dayDiff = then.diff(moment().startOf("day"), "days", true);
if (dayDiff > -6 && dayDiff < 7) {
text = formatMoment(then, formats.week || formats.other);
} else text = formatMoment(then, formats.other);
}
$(elt).text(text);
// Add a tooltip with multiple formats.
elt.title = $.map(LocalComments.tooltipFormats, function (fmt, idx) {
return formatMoment(then, fmt);
}).join("\n");
// Register for periodic updates.
var withinMinutes = withinHours && Math.abs(then.diff(now, "minutes", true)) <= moment.relativeTimeThreshold("m");
var withinSeconds = withinMinutes && Math.abs(then.diff(now, "seconds", true)) <= moment.relativeTimeThreshold("s");
var unit = withinSeconds ? "seconds" : withinMinutes ? "minutes" : withinHours ? "hours" : "days";
$(elt).attr("data-localcomments-unit", unit);
}
/**
* Reformat all marked-up timestamps and start updating timestamps on an
* interval as necessary.
*/
function formatTimestamps() {
wrapTimestamps();
$(".localcomments").each(function (idx, elt) {
// Update every timestamp at least this once.
formatTimestamp(idx, elt);
if (!LocalComments.dynamic) return;
// Update this minute’s timestamps every second.
if ($("[data-localcomments-unit='seconds']").length) {
setInterval(function () {
$("[data-localcomments-unit='seconds']").each(formatTimestamp);
}, 1000 /* ms */);
}
// Update this hour’s timestamps every minute.
setInterval(function () {
$("[data-localcomments-unit='minutes']").each(formatTimestamp);
}, 60 /* s */ * 1000 /* ms */);
// Update today’s timestamps every hour.
setInterval(function () {
$("[data-localcomments-unit='hours']").each(formatTimestamp);
}, 60 /* min */ * 60 /* s */ * 1000 /* ms */);
});
}
mw.loader.using("moment", function () {
wrapTimestamps();
formatTimestamps();
});
});
}