Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page.
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.
/* Helper for the "10+ edits in past 30 days" criterion for the Wikipedia Library. Displays how long you can go without editing until access expires, and advice on when to edit. */$(document).ready(function(){constthresholdN=10;// Number of required editsconstwindowDays=30;// Within this many daysconsthoardRateDays=3;// Ideal rate: 1 edit every X daysconstmillisecondsInDay=24*60*60*1000;constfallOffAmount=windowDays*millisecondsInDay;// 30 days in millisecondsconstdateFormatter=newIntl.DateTimeFormat(undefined,{weekday:'short',day:'2-digit',month:'long',year:'numeric'});consttimeFormatter=newIntl.DateTimeFormat(undefined,{hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:true,// For AM/PMtimeZoneName:'short'});/** * Formats a timestamp into a div element with date and time parts. * @param {string} prefixText - Text to prefix the timestamp display. * @param {number} timestampMs - The timestamp in milliseconds. * @returns {HTMLDivElement} - A div element containing the formatted timestamp. */functionformatTimestampToElements(prefixText,timestampMs,strong=false){constdate=newDate(timestampMs);constdatePart=dateFormatter.format(date);consttimePart=timeFormatter.format(date);consttimestampContainerDiv=document.createElement('div');timestampContainerDiv.className='twl-timestamp-display';// Add a class for potential stylingletprefix=document.createTextNode(`${prefixText}:`);if(strong){letprefixS=document.createElement('strong');prefixS.appendChild(prefix);timestampContainerDiv.appendChild(prefixS);}else{timestampContainerDiv.appendChild(prefix);}timestampContainerDiv.appendChild(document.createElement('br'));timestampContainerDiv.appendChild(document.createTextNode(datePart));timestampContainerDiv.appendChild(document.createElement('br'));timestampContainerDiv.appendChild(document.createTextNode(timePart));returntimestampContainerDiv;}/** * Calculates the Earliest Start Time for making an edit to maintain the 1-edit/3-days cadence. * This is the earliest time that an edit will advance the user's access by 3 full days. * @param {number[]} sortedTimestamps - Array of edit timestamps, sorted newest to oldest. * @returns {number} The earliest optimal time to make an edit in milliseconds. */functioncalculateEarliestStartTime(sortedTimestamps){console.assert(sortedTimestamps.length==thresholdN,"must have exactly thresholdN timestamps");letearliestStartTime=Infinity;// Initialize to infinity to find the min time// We only care about the 9 newest edits (indices 0 to 8) for maintaining the cadence.// The 10th edit (index 9) is only relevant for the hard expiration.for(leti=0;i<thresholdN-1;i++){// Loop through up to the 9th newest editconstcurrentEditTimestamp=sortedTimestamps[i];// The deadline for this editletpotentialExpiry=currentEditTimestamp+fallOffAmount;// 30 days after this editletnumEditsToMake=thresholdN-(i+1);// How many more edits are needed to reach 10letrequiredHoardEditTime=potentialExpiry-(numEditsToMake*hoardRateDays*millisecondsInDay);earliestStartTime=Math.min(earliestStartTime,requiredHoardEditTime);}returnearliestStartTime;// + 3 days for new edits shifting - 3 days of offset of deadline of current = just the earliest start time}functionaddPortlet(element){/* todo: complicated skin logic */letportlets=['p-personal','p-personal-sticky-header'];for(letportletIdofportlets){constportletListItem=mw.util.addPortletLink(portletId,"",'','pt-librarylimit','',null,'#pt-logout');if(portletListItem){portletListItem.firstChild.remove();portletListItem.append(element.cloneNode(true));}}}// Ensure that mediawiki.user and mediawiki.api modules are loaded before proceeding.mw.loader.using(['mediawiki.user','mediawiki.api'],function(){// Get the current username from MediaWiki configuration.constusername=mw.config.get('wgUserName');// Check if a username is available. If not, there's no user to query.if(!username){console.log('No user logged in or username not available.');return;}// Initialize a new MediaWiki API object.(newmw.Api()).get({action:'query',list:'usercontribs',ucuser:username,uclimit:thresholdN,// Request exactly thresholdN (10) contributionsucprop:'timestamp'}).done(function(result){if(result.query&&result.query.usercontribs&&result.query.usercontribs.length>=thresholdN){constuserContribs=result.query.usercontribs;lettimestamps=userContribs.map(contrib=>newDate(contrib.timestamp).getTime());window.timestamps=timestamps;// Store timestamps globally for debuggingconstnow=newDate().getTime();// Current time in millisecondsletdisplayContent=document.createElement('div');displayContent.className='twl-widget-content';// --- Core Logic for Edit Window ---// Calculate Expiration Time (ET) - this is the hard deadlineconstexpirationTime=timestamps[thresholdN-1]+fallOffAmount;// 10th newest edit + 30 days// Calculate Earliest Start Time - based on maintaining cadenceconstoptimalStartTime=calculateEarliestStartTime(timestamps);// --- Display Logic ---if(now<optimalStartTime){consteditWindowStartDiv=formatTimestampToElements('Wait',optimalStartTime,false);editWindowStartDiv.style.border='1px solid black';displayContent.appendChild(editWindowStartDiv);}else{leteditsNeededNow=Math.min(thresholdN,Math.ceil((now-optimalStartTime)/(hoardRateDays*millisecondsInDay)));consteditWindowStartDiv=formatTimestampToElements(`Do ${editsNeededNow} edits`,optimalStartTime,true);editWindowStartDiv.style.border='1px solid black';editWindowStartDiv.classList.add((now>=expirationTime)?'mw-plusminus-neg':'mw-plusminus-pos');displayContent.appendChild(editWindowStartDiv);}constexpirationDiv=formatTimestampToElements('Expire',expirationTime,now>=expirationTime);expirationDiv.style.border='1px solid black';expirationDiv.style.borderTop='none';displayContent.appendChild(expirationDiv);addPortlet(displayContent);}else{console.log('Error: No user contributions found or query failed.');displayContent=document.createElement('div');displayContent.innerHTML="<p>Could not retrieve edit history. Please check the console.</p>";addPortlet(displayContent);}}).fail(function(jqXHR,textStatus,errorThrown){console.error('MediaWiki API request failed:',textStatus,errorThrown);constdisplayContent=document.createElement('div');displayContent.innerHTML=`<p>Error fetching edit history: ${textStatus}.</p>`;addPortlet(displayContent);});});});