User:Svartava/CommentsInLocalTime.js
Appearance
From Meta, a Wikimedia project coordination wiki
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
// Copied from [[w:User:Gary/comments in local time.js]] with minor changes /** * 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]] */ $(()=>{ /** * 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. */ functionaddLeadingZero(number){ constnumberArg=number; if(numberArg<10){ return`0${numberArg}`; } returnnumberArg; } functionconvertMonthToNumber(month){ returnnewDate(`${month} 1, 2001`).getMonth(); } functiongetDates(time){ const[,oldHour,oldMinute,oldDay,oldMonth,oldYear]=time; // Today consttoday=newDate(); // Yesterday constyesterday=newDate(); yesterday.setDate(yesterday.getDate()-1); // Tomorrow consttomorrow=newDate(); tomorrow.setDate(tomorrow.getDate()+1); // Set the date entered. constnewTime=newDate(); 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 */ functionpluralize(term,count,plural=null){ letpluralArg=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){ returnterm; } // There are multiple items, so use the plural word. returnpluralArg; } classCommentsInLocalTime{ 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.wikipedia.org/wiki/Wikipedia:Comments_in_Local_Time#Default_settings this.setDefaultSetting({ dateDifference:true, dateFormat:'dmy', dayOfWeek:true, dropDays:0, dropMonths:0, timeFirst:true, twentyFourHours:false, }); } adjustTime(originalTimestamp,search){ 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,'']; } constdate=this.determineDateText({ time, today, tomorrow, yesterday, }); const{ampm,hour}=this.getHour(time); constminute=addLeadingZero(time.getMinutes()); constfinalTime=`${hour}:${minute}${ampm}`; // Determine the time offset. constutcValue=(-1*time.getTimezoneOffset())/60; constutcOffset= utcValue>=0?`+${utcValue}`:`−${Math.abs(utcValue.toFixed(1))}`; constutcPart=`(UTC${utcOffset})`; constreturnDate=this.LocalComments.timeFirst ?`${finalTime}, ${date}${utcPart}` :`${date}, ${finalTime}${utcPart}`; returnreturnDate; } 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 constdayNames=[ this.language.Sunday, this.language.Monday, this.language.Tuesday, this.language.Wednesday, this.language.Thursday, this.language.Friday, this.language.Saturday, ]; constdayOfTheWeek=dayNames[time.getDay()]; letdescriptiveDifference=''; letlast=''; // Create a relative descriptive difference if(this.LocalComments.dateDifference){ ({descriptiveDifference,last}=this.createRelativeDate( today, time )); } constmonthName=this.convertNumberToMonth(time.getMonth()); // Format the date according to user preferences letformattedDate=''; 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)}`; } constformattedDayOfTheWeek=this.LocalComments.dayOfWeek ?`, ${last}${dayOfTheWeek}` :''; return`${formattedDate}${formattedDayOfTheWeek}${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} */ constmillisecondsAgo=today.getTime()-time.getTime(); /** * The number of days ago, that we will display. It's not necessarily the * total days ago. * * @type {number} */ letdaysAgo=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} */ letmonthsAgo=Math.floor((daysAgo/365)*12); /** * The total amount of time ago, in months. * * @type {number} */ consttotalMonthsAgo=monthsAgo; /** * The number of years ago that we will display. It's not necessarily the * total years ago. * * @type {number} */ letyearsAgo=Math.floor(totalMonthsAgo/12); if(totalMonthsAgo<this.LocalComments.dropMonths){ yearsAgo=0; }elseif(this.LocalComments.dropMonths>0){ monthsAgo=0; }else{ monthsAgo-=yearsAgo*12; } if(daysAgo<this.LocalComments.dropDays){ monthsAgo=0; yearsAgo=0; }elseif(this.LocalComments.dropDays>0&&totalMonthsAgo>=1){ daysAgo=0; }else{ daysAgo-=Math.floor((totalMonthsAgo*365)/12); } constdescriptiveParts=[]; // 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. constyear=time.getFullYear(); constmonth=addLeadingZero(time.getMonth()+1); constday=time.getDate(); // Return 'today' or 'yesterday' if that is the case if( year===today.getFullYear()&& month===addLeadingZero(today.getMonth()+1)&& day===today.getDate() ){ returnthis.language.Today; } if( year===yesterday.getFullYear()&& month===addLeadingZero(yesterday.getMonth()+1)&& day===yesterday.getDate() ){ returnthis.language.Yesterday; } if( year===tomorrow.getFullYear()&& month===addLeadingZero(tomorrow.getMonth()+1)&& day===tomorrow.getDate() ){ returnthis.language.Tomorrow; } returnthis.createDateText({day,month,time,today,year}); } getHour(time){ letampm; lethour=Number.parseInt(time.getHours(),10); if(this.LocalComments.twentyFourHours){ ampm=''; hour=addLeadingZero(hour); }else{ // Output am or pm depending on the date. ampm=hour<=11?' am':' pm'; if(hour>12){ hour-=12; }elseif(hour===0){ hour=12; } } return{ampm,hour}; } relativeText({daysAgo,millisecondsAgo}){ letdifferenceWord=''; letlast=''; // 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){ // Don't continue if this text node's parent tag is one of these. if(['CODE','PRE'].includes(node.parentNode.nodeName)){ return; } constvalue=node.nodeValue; constmatches=value.match(search); if(matches){ // Only act on the first timestamp we found in this node. This is for // the rare occassion that there is more than one timestamp in the // same text node. const[match]=matches; constposition=value.search(search); conststringLength=match.toString().length; // Grab the text content before and after the matching timestamp, // which we'll then wrap in their own SPAN nodes. constbeforeMatch=value.slice(0,position); constafterMatch=value.slice(position+stringLength); constreturnDate=this.adjustTime(match.toString(),search); // Create the code to display the new local comments content. const$span=$( `<span class="localcomments" style="font-size: 95%;" title="${match}">${returnDate}</span>` ); // Replace the existing text node in the page with our new local // comments node. $(node).replaceWith($span); // Replace the text content that appears before the timestamp. if(beforeMatch){ $span.before( `<span class="before-localcomments">${beforeMatch}</span>` ); } // Replace the text content that appears after the timestamp. if(afterMatch){ $span.after( `<span class="after-localcomments">${afterMatch}</span>` ); } } }else{ constchildren=[]; letchild; [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. constisDisabledUrl=['action=history'].some((disabledUrl)=> document.location.href.includes(disabledUrl) ); if(isDisabledUrl){ return; } this.replaceText( document.querySelector('.mw-body-content .mw-parser-output'), /(\d{1,2}):(\d{2}), (\d{1,2}) ([A-Z][a-z]+) (\d{4}) \(UTC\)/ ); } setDefaultSetting(...args){ // There are no arguments. if(args.length===0){ returnfalse; } // The first arg is an object, so just set that data directly onto the // settings object. like {setting 1: true, setting 2: false} if(typeofargs[0]==='object'){ const[settings]=args; // Loop through each setting. Object.keys(settings).forEach((name)=>{ constvalue=settings[name]; if(typeofthis.LocalComments[name]==='undefined'){ this.LocalComments[name]=value; } }); returnsettings; } // 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(typeofthis.LocalComments[name]==='undefined'){ this.LocalComments[name]=setting; } returnthis.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:'Today', Yesterday:'Yesterday', Tomorrow:'Tomorrow', last:'last', this:'this', // Days of the week Sunday:'Sunday', Monday:'Monday', Tuesday:'Tuesday', Wednesday:'Wednesday', Thursday:'Thursday', Friday:'Friday', Saturday:'Saturday', // Months of the year January:'January', February:'February', March:'March', April:'April', May:'May', June:'June', July:'July', August:'August', September:'September', October:'October', November:'November', December:'December', // Difference words ago:'ago', 'from now':'from now', // Date phrases year:'year', years:'years', month:'month', months:'months', day:'day', days:'days', }; } } // Check if we've already ran this script. if(window.commentsInLocalTimeWasRun){ return; } window.commentsInLocalTimeWasRun=true; constcommentsInLocalTime=newCommentsInLocalTime(); commentsInLocalTime.run(); });