Pengguna:William Surya Permana/comments in local time.js
Catatan: Setelah menyimpan, Anda harus memintas tembolok (cache) peramban Anda untuk melihat perubahannya. Google Chrome, Firefox, Microsoft Edge dan Safari: Tahan tombol ⇧ Shift dan klik Muat ulang (Reload) di tombol bilah alat. Untuk detail dan instruksi tentang peramban lain, lihat halaman menghapus singgahan (Inggris).
/**
* COMMENTS IN LOCAL TIME
*
* Description:
* Changes [[Coordinated Universal Time|UTC]]-based times and dates,
* such as those used in signatures, to be relative to local time.
*
* Documentation:
* [[Wikipedia:Comments in Local Time]]
*
* Difference from [[:en:User:Gary/comments in local time.js]]:
* - Weekday appears before the date
* - Time separator is dot, instead of colon
* - Timezone is WIB/WITA/WIT, instead of (UTC+7/UTC+8/UTC+9)
* - Supports both formats ("hh:mm, dd mmm yyyy (UTC)" and "dd mmm yyyy hh.mm (UTC)")
*
* Example:
* There: 25 September 2020, Friday (1 year, 3 months ago), 02:04 (UTC+7)
* Here: Jumat, 25 September 2020 (1 tahun, 3 bulan yang lalu), 02.04 WIB
*
* There: Last Sunday, 11:22 (UTC+7)
* Here: Minggu lalu, 11.22 WIB
*/
$(() => {
/**
* Given a number, add a leading zero if necessary, so that the final number
* has two characters.
*
* @param {number} number Number
* @returns {string} The number with a leading zero, if necessary.
*/
function addLeadingZero(number) {
const numberArg = number;
if (numberArg < 10) {
return `0${numberArg}`;
}
return numberArg;
}
function convertMonthToNumber(month) {
return new Date(`${month} 1, 2001`).getMonth();
}
function getDates(time) {
// old format
let [, oldHour, oldMinute, oldDay, oldMonth, oldYear] = time;
// new format
if(oldYear.length != 4) {
[, oldDay, oldMonth, oldYear, oldHour, oldMinute] = time;
}
// Today
const today = new Date();
// Yesterday
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
// Tomorrow
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
// Set the date entered.
const newTime = new Date();
newTime.setUTCFullYear(oldYear, convertMonthToNumber(oldMonth), oldDay);
newTime.setUTCHours(oldHour);
newTime.setUTCMinutes(oldMinute);
return { time: newTime, today, tomorrow, yesterday };
}
/**
* Determine whether to use the singular or plural word, and use that.
*
* @param {string} term Original term
* @param {number} count Count of items
* @param {string} plural Pluralized term
* @returns {string} The word to use
*/
function pluralize(term, count, plural = null) {
let pluralArg = plural;
// No unique pluralized word is found, so just use a general one.
if (!pluralArg) {
pluralArg = `${term}s`;
}
// There's only one item, so just use the singular word.
if (count === 1) {
return term;
}
// There are multiple items, so use the plural word.
return pluralArg;
}
class CommentsInLocalTime {
constructor() {
this.language = '';
this.LocalComments = {};
/**
* Settings
*/
this.settings();
this.language = this.setDefaultSetting(
'language',
this.LocalComments.language
);
// These values are also reflected in the documentation:
// https://en.wiki-indonesia.club/wiki/Wikipedia:Comments_in_Local_Time#Default_settings
this.setDefaultSetting({
dateDifference: true,
dateFormat: 'dmy',
dayOfWeek: true,
dropDays: 93,
dropMonths: 0,
timeFirst: false,
twentyFourHours: true,
});
}
adjustTime(originalTimestamp, search) {
originalTimestamp = originalTimestamp
.replace(this.language.January, 'January')
.replace(this.language.February, 'February')
.replace(this.language.March, 'March')
.replace(this.language.April, 'April')
.replace(this.language.May, 'May')
.replace(this.language.June, 'June')
.replace(this.language.July, 'July')
.replace(this.language.August, 'August')
.replace(this.language.September, 'September')
.replace(this.language.October, 'October')
.replace(this.language.November, 'November')
.replace(this.language.December, 'December');
const { time, today, tomorrow, yesterday } = getDates(
originalTimestamp.match(search)
);
// A string matching the date pattern was found, but it cannot be
// converted to a Date object. Return it with no changes made.
if (Number.isNaN(time)) {
return [originalTimestamp, ''];
}
const date = this.determineDateText({
time,
today,
tomorrow,
yesterday,
});
const { ampm, hour } = this.getHour(time);
const minute = addLeadingZero(time.getMinutes());
const finalTime = `${hour}.${minute}${ampm}`;
let returnDate;
// Determine the time offset.
const utcValue = (-1 * time.getTimezoneOffset()) / 60;
const utcOffset =
utcValue >= 0 ? `+${utcValue}` : `−${Math.abs(utcValue.toFixed(1))}`;
let tz;
tz = `(UTC${utcOffset})`;
switch (utcValue) {
case 7: tz = 'WIB'; break;
case 8: tz = 'WITA'; break;
case 9: tz = 'WIT'; break;
}
if (this.LocalComments.timeFirst) {
returnDate = `${finalTime}, ${date} ${tz}`;
} else {
returnDate = `${date}, ${finalTime} ${tz}`;
}
return { returnDate, time };
}
convertNumberToMonth(number) {
return [
this.language.January,
this.language.February,
this.language.March,
this.language.April,
this.language.May,
this.language.June,
this.language.July,
this.language.August,
this.language.September,
this.language.October,
this.language.November,
this.language.December,
][number];
}
createDateText({ day, month, time, today, year }) {
// Calculate day of week
const dayNames = [
this.language.Sunday,
this.language.Monday,
this.language.Tuesday,
this.language.Wednesday,
this.language.Thursday,
this.language.Friday,
this.language.Saturday,
];
const dayOfTheWeek = dayNames[time.getDay()];
let descriptiveDifference = '';
let last = '';
// Create a relative descriptive difference
if (this.LocalComments.dateDifference) {
({ descriptiveDifference, last } = this.createRelativeDate(
today,
time
));
}
const monthName = this.convertNumberToMonth(time.getMonth());
// Format the date according to user preferences
let formattedDate = '';
switch (this.LocalComments.dateFormat.toLowerCase()) {
case 'dmy':
formattedDate = `${day} ${monthName} ${year}`;
break;
case 'mdy':
formattedDate = `${monthName} ${day}, ${year}`;
break;
default:
formattedDate = `${year}-${month}-${addLeadingZero(day)}`;
}
let formattedDayOfTheWeek = '';
if (this.LocalComments.dayOfWeek) {
formattedDayOfTheWeek = `${dayOfTheWeek}${last}, `;
}
return formattedDayOfTheWeek + formattedDate + descriptiveDifference;
}
/**
* Create relative date data.
*
* @param {Date} today Today
* @param {Date} time The timestamp from a comment
* @returns {Object.<string, *>} Relative date data
*/
createRelativeDate(today, time) {
/**
* The time difference from today, in milliseconds.
*
* @type {number}
*/
const millisecondsAgo = today.getTime() - time.getTime();
/**
* The number of days ago, that we will display. It's not necessarily the
* total days ago.
*
* @type {number}
*/
let daysAgo = Math.abs(Math.round(millisecondsAgo / 1000 / 60 / 60 / 24));
const { differenceWord, last } = this.relativeText({
daysAgo,
millisecondsAgo,
});
// This method of computing the years and months is not exact. However,
// it's better than the previous method that used 1 January + delta days.
// That was usually quite off because it mapped the second delta month to
// February, which has only 28 days. This method is usually not more than
// one day off, except perhaps over very distant dates.
/**
* The number of months ago, that we will display. It's not necessarily
* the total months ago.
*
* @type {number}
*/
let monthsAgo = Math.floor((daysAgo / 365) * 12);
/**
* The total amount of time ago, in months.
*
* @type {number}
*/
const totalMonthsAgo = monthsAgo;
/**
* The number of years ago that we will display. It's not necessarily the
* total years ago.
*
* @type {number}
*/
let yearsAgo = Math.floor(totalMonthsAgo / 12);
if (totalMonthsAgo < this.LocalComments.dropMonths) {
yearsAgo = 0;
} else if (this.LocalComments.dropMonths > 0) {
monthsAgo = 0;
} else {
monthsAgo -= yearsAgo * 12;
}
if (daysAgo < this.LocalComments.dropDays) {
monthsAgo = 0;
yearsAgo = 0;
} else if (this.LocalComments.dropDays > 0 && totalMonthsAgo >= 1) {
daysAgo = 0;
} else {
daysAgo -= Math.floor((totalMonthsAgo * 365) / 12);
}
const descriptiveParts = [];
// There is years text to add.
if (yearsAgo > 0) {
descriptiveParts.push(
`${yearsAgo} ${pluralize(
this.language.year,
yearsAgo,
this.language.years
)}`
);
}
// There is months text to add.
if (monthsAgo > 0) {
descriptiveParts.push(
`${monthsAgo} ${pluralize(
this.language.month,
monthsAgo,
this.language.months
)}`
);
}
// There is days text to add.
if (daysAgo > 0) {
descriptiveParts.push(
`${daysAgo} ${pluralize(
this.language.day,
daysAgo,
this.language.days
)}`
);
}
return {
descriptiveDifference: ` (${descriptiveParts.join(
', '
)} ${differenceWord})`,
last,
};
}
determineDateText({ time, today, tomorrow, yesterday }) {
// Set the date bits to output.
const year = time.getFullYear();
const month = addLeadingZero(time.getMonth() + 1);
const day = time.getDate();
// Return 'today' or 'yesterday' if that is the case
if (
year === today.getFullYear() &&
month === addLeadingZero(today.getMonth() + 1) &&
day === today.getDate()
) {
return this.language.Today;
}
if (
year === yesterday.getFullYear() &&
month === addLeadingZero(yesterday.getMonth() + 1) &&
day === yesterday.getDate()
) {
return this.language.Yesterday;
}
if (
year === tomorrow.getFullYear() &&
month === addLeadingZero(tomorrow.getMonth() + 1) &&
day === tomorrow.getDate()
) {
return this.language.Tomorrow;
}
return this.createDateText({ day, month, time, today, year });
}
getHour(time) {
let ampm;
let hour = parseInt(time.getHours(), 10);
if (this.LocalComments.twentyFourHours) {
ampm = '';
hour = addLeadingZero(hour);
} else {
// Output am or pm depending on the date.
// ampm = hour <= 11 ? this.language.am : this.language.pm;
if (hour <= 11) ampm = this.language.morning;
else if (hour <= 14) ampm = this.language.afternoon;
else if (hour <= 17) ampm = this.language.lateafternoon;
else ampm = this.language.night;
if (hour > 12) {
hour -= 12;
} else if (hour === 0) {
hour = 12;
}
}
return { ampm, hour };
}
relativeText({ daysAgo, millisecondsAgo }) {
let differenceWord = '';
let last = '';
// The date is in the past.
if (millisecondsAgo >= 0) {
differenceWord = this.language.ago;
if (daysAgo <= 7) {
last = ` ${this.language.last}`;
}
// The date is in the future.
} else {
differenceWord = this.language['from now'];
if (daysAgo <= 7) {
last = ` ${this.language.this}`;
}
}
return { differenceWord, last };
}
replaceText(node, search) {
if (!node) {
return;
}
// Check if this is a text node.
if (node.nodeType === 3) {
let parent = node.parentNode;
const parentNodeName = parent.nodeName;
if (['CODE', 'PRE'].includes(parentNodeName)) {
return;
}
const value = node.nodeValue;
const matches = value.match(search);
// Stick with manipulating the DOM directly rather than using jQuery.
// I've got more than a 100% speed improvement afterward.
if (matches) {
// Only act on the first timestamp we found in this node. This is
// really a temporary fix for the situation in which there are two or
// more timestamps in the same node.
const [match] = matches;
const position = value.search(search);
const stringLength = match.toString().length;
const beforeMatch = value.substring(0, position);
const afterMatch = value.substring(position + stringLength);
const { returnDate, time } = this.adjustTime(
match.toString(),
search
);
const timestamp = time ? time.getTime() : '';
// Is the "timestamp" attribute used for microformats?
const span = document.createElement('span');
span.className = 'localcomments';
span.style.fontSize = '95%';
span.style.whiteSpace = 'nowrap';
span.setAttribute('timestamp', timestamp);
span.title = match;
span.append(document.createTextNode(returnDate));
parent = node.parentNode;
parent.replaceChild(span, node);
const before = document.createElement('span');
before.className = 'before-localcomments';
before.append(document.createTextNode(beforeMatch));
const after = document.createElement('span');
after.className = 'after-localcomments';
after.append(document.createTextNode(afterMatch));
parent.insertBefore(before, span);
parent.insertBefore(after, span.nextSibling);
}
} else {
const children = [];
let child;
[child] = node.childNodes;
while (child) {
children.push(child);
child = child.nextSibling;
}
// Loop through children and run this func on it again, recursively.
children.forEach((child2) => {
this.replaceText(child2, search);
});
}
}
run() {
if (
['', 'MediaWiki', 'Special'].includes(
mw.config.get('wgCanonicalNamespace')
)
) {
return;
}
// Check for disabled URLs.
const isDisabledUrl = ['action=history'].some((disabledUrl) =>
document.location.href.includes(disabledUrl)
);
if (isDisabledUrl) {
return;
}
// old format
this.replaceText(
document.querySelector('.mw-parser-output'),
/(\d{1,2}):(\d{2}), (\d{1,2}) ([A-Z][a-z]+) (\d{4}) \(UTC\)/
);
// new format
this.replaceText(
document.querySelector('.mw-parser-output'),
/(\d{1,2}) ([A-Z][a-z]+) (\d{4}) (\d{1,2})\.(\d{2}) \(UTC\)/
);
}
setDefaultSetting(...args) {
// There are no arguments.
if (args.length === 0) {
return false;
}
// The first arg is an object, so just set that data directly onto the
// settings object. like {setting 1: true, setting 2: false}
if (typeof args[0] === 'object') {
const [settings] = args;
// Loop through each setting.
Object.keys(settings).forEach((name) => {
const value = settings[name];
if (typeof this.LocalComments[name] === 'undefined') {
this.LocalComments[name] = value;
}
});
return settings;
}
// The first arg is a string, so use the first arg as the settings key,
// and the second arg as the value to set it to.
const [name, setting] = args;
if (typeof this.LocalComments[name] === 'undefined') {
this.LocalComments[name] = setting;
}
return this.LocalComments[name];
}
/**
* Set the script's settings.
*
* @returns {undefined}
*/
settings() {
// The user has set custom settings, so use those.
if (window.LocalComments) {
this.LocalComments = window.LocalComments;
}
/**
* Language
*
* LOCALIZING THIS SCRIPT
* To localize this script, change the terms below,
* to the RIGHT of the colons, to the correct term used in that language.
*
* For example, in the French language,
*
* 'Today' : 'Today',
*
* would be
*
* 'Today' : "Aujourd'hui",
*/
this.LocalComments.language = {
// Relative terms
Today: 'Hari ini',
Yesterday: 'Kemarin',
Tomorrow: 'Besok',
last: 'lalu',
this: 'ini',
// Days of the week
Sunday: 'Minggu',
Monday: 'Senin',
Tuesday: 'Selasa',
Wednesday: 'Rabu',
Thursday: 'Kamis',
Friday: 'Jumat',
Saturday: 'Sabtu',
// Months of the year
January: 'Januari',
February: 'Februari',
March: 'Maret',
April: 'April',
May: 'Mei',
June: 'Juni',
July: 'Juli',
August: 'Agustus',
September: 'September',
October: 'Oktober',
November: 'November',
December: 'Desember',
// Difference words
ago: 'yang lalu',
'from now': 'ke depan',
// Date phrases
year: 'tahun',
years: 'tahun',
month: 'bulan',
months: 'bulan',
day: 'hari',
days: 'hari',
// Time phrases
am: 'am',
pm: 'pm',
morning: ' pagi',
afternoon: ' siang',
lateafternoon: ' sore',
night: ' malam'
};
}
}
// Check if we've already ran this script.
if (window.commentsInLocalTimeWasRun) {
return;
}
window.commentsInLocalTimeWasRun = true;
const commentsInLocalTime = new CommentsInLocalTime();
commentsInLocalTime.run();
});