User:Fenakhay/CommentsInLocalTime.js

Hello, you have come here looking for the meaning of the word User:Fenakhay/CommentsInLocalTime.js. In DICTIOUS you will not only get to know all the dictionary meanings for the word User:Fenakhay/CommentsInLocalTime.js, but we will also tell you about its etymology, its characteristics and you will know how to say User:Fenakhay/CommentsInLocalTime.js in singular and plural. Everything you need to know about the word User:Fenakhay/CommentsInLocalTime.js you have here. The definition of the word User:Fenakhay/CommentsInLocalTime.js will help you to be more precise and correct when speaking or writing your texts. Knowing the definition ofUser:Fenakhay/CommentsInLocalTime.js, as well as those of other words, enriches your vocabulary and provides you with more and better linguistic resources.
$(() => {
	// Always return a two‐digit string
	const addLeadingZero = (num) => String(num).padStart(2, "0");

	// Build a map from month name → month index (0–11)
	const MONTH_NAME_TO_INDEX = {
		January: 0,
		February: 1,
		March: 2,
		April: 3,
		May: 4,
		June: 5,
		July: 6,
		August: 7,
		September: 8,
		October: 9,
		November: 10,
		December: 11,
	};

	// Given a regex match array ,
	// return an object containing:
	//   • `utcDate` (the Date representing that UTC timestamp),
	//   • and Date objects for today/yesterday/tomorrow (at local midnight).
	const parseTimestamp = ([
		,
		rawHour,
		rawMinute,
		rawDay,
		rawMonth,
		rawYear,
	]) => {
		const hour = parseInt(rawHour, 10);
		const minute = parseInt(rawMinute, 10);
		const day = parseInt(rawDay, 10);
		const month = MONTH_NAME_TO_INDEX;
		const year = parseInt(rawYear, 10);

		// Build a Date object interpreted as UTC:
		//   newDateUTC will represent “year‐month‐day hour:minute UTC.”
		const newDateUTC = new Date(Date.UTC(year, month, day, hour, minute));

		// “Today,” “Yesterday,” “Tomorrow” in local time, but pinned to midnight:
		const today = new Date();
		const localMidnight = new Date(
			today.getFullYear(),
			today.getMonth(),
			today.getDate()
		);
		const yesterday = new Date(localMidnight.getTime() - 24 * 60 * 60 * 1000);
		const tomorrow = new Date(localMidnight.getTime() + 24 * 60 * 60 * 1000);

		return { utcDate: newDateUTC, today: localMidnight, yesterday, tomorrow };
	};

	// Singular/Plural helper
	const pluralize = (singular, count, pluralForm = null) => {
		if (count === 1) return singular;
		return pluralForm || `${singular}s`;
	};

	class CommentsInLocalTime {
		constructor() {
			// If user has defined window.LocalComments, use that. Otherwise start fresh.
			this.LocalComments = window.LocalComments || {};

			// Default language strings (can be overridden by window.LocalComments.language)
			const defaultLanguage = {
				Today: "Today",
				Yesterday: "Yesterday",
				Tomorrow: "Tomorrow",
				last: "last",
				this: "this",
				Sunday: "Sunday",
				Monday: "Monday",
				Tuesday: "Tuesday",
				Wednesday: "Wednesday",
				Thursday: "Thursday",
				Friday: "Friday",
				Saturday: "Saturday",
				January: "January",
				February: "February",
				March: "March",
				April: "April",
				May: "May",
				June: "June",
				July: "July",
				August: "August",
				September: "September",
				October: "October",
				November: "November",
				December: "December",
				ago: "ago",
				"from now": "from now",
				year: "year",
				years: "years",
				month: "month",
				months: "months",
				day: "day",
				days: "days",
			};

			// Merge language defaults if not already present
			this.LocalComments.language = Object.assign(
				{},
				defaultLanguage,
				this.LocalComments.language || {}
			);

			// Default user preferences (only set if not already present)
			const defaultSettings = {
				dateDifference: true,
				dateFormat: "dmy",
				dayOfWeek: true,
				dropDays: 0,
				dropMonths: 0,
				timeFirst: true,
				twentyFourHours: false,
			};
			for (const  of Object.entries(defaultSettings)) {
				if (this.LocalComments === undefined) {
					this.LocalComments = val;
				}
			}

			// Cache month‐name and weekday‐name arrays, based on chosen language
			this.monthNames = [
				this.LocalComments.language.January,
				this.LocalComments.language.February,
				this.LocalComments.language.March,
				this.LocalComments.language.April,
				this.LocalComments.language.May,
				this.LocalComments.language.June,
				this.LocalComments.language.July,
				this.LocalComments.language.August,
				this.LocalComments.language.September,
				this.LocalComments.language.October,
				this.LocalComments.language.November,
				this.LocalComments.language.December,
			];
			this.weekdayNames = [
				this.LocalComments.language.Sunday,
				this.LocalComments.language.Monday,
				this.LocalComments.language.Tuesday,
				this.LocalComments.language.Wednesday,
				this.LocalComments.language.Thursday,
				this.LocalComments.language.Friday,
				this.LocalComments.language.Saturday,
			];
		}

		// Main entry: find every “HH:MM, D Month YYYY (UTC)” and replace it.
		run() {
			const namespace = mw.config.get("wgCanonicalNamespace");
			if (.includes(namespace)) return;

			if (location.href.includes("action=history")) return;

			const container = document.querySelector(
				".mw-body-content .mw-parser-output"
			);
			if (container) {
				// Regex: 1–2 digits for hour, :, 2 digits for minute, comma, space,
				// 1–2 digits day, space, capitalized month, space, 4-digit year, space, “(UTC)”.
				const timestampRegex =
					/(\d{1,2}):(\d{2}), (\d{1,2}) (+) (\d{4}) \(UTC\)/g;
				this.replaceInNode(container, timestampRegex);
			}
		}

		// Recursively traverse text nodes to find and replace matches for `regex`.
		replaceInNode(node, regex) {
			if (!node) return;

			if (node.nodeType === Node.TEXT_NODE) {
				// Skip code/pre blocks
				const parentTag = node.parentNode && node.parentNode.nodeName;
				if (parentTag === "CODE" || parentTag === "PRE") return;

				const text = node.nodeValue;
				regex.lastIndex = 0; // reset
				const match = regex.exec(text);
				if (match) {
					const matchStr = match;
					const index = match.index;
					const before = text.slice(0, index);
					const after = text.slice(index + matchStr.length);
					const localTimeString = this.adjustTime(match);

					// Create a <span> for the converted local time
					const span = document.createElement("span");
					span.className = "localcomments";
					span.style.fontSize = "95%";
					span.title = matchStr;
					span.textContent = localTimeString;

					// Replace with: 
					const fragment = document.createDocumentFragment();
					if (before) fragment.appendChild(document.createTextNode(before));
					fragment.appendChild(span);
					if (after) fragment.appendChild(document.createTextNode(after));
					node.parentNode.replaceChild(fragment, node);
				}
			} else {
				// Recurse into child nodes
				for (const child of Array.from(node.childNodes)) {
					this.replaceInNode(child, regex);
				}
			}
		}

		// Given a single regex match array (),
		// return the formatted “HH:MMam/pm, DATE (UTC+Offset)” or “DATE, HH:MMam/pm (UTC+Offset)”
		adjustTime(matchArray) {
			const { utcDate, today, yesterday, tomorrow } =
				parseTimestamp(matchArray);

			if (isNaN(utcDate.getTime())) {
				// Invalid date, return original string
				return matchArray;
			}

			// Determine how to print the date portion (“Today”, “Yesterday”, etc. or full date)
			const dateText = this.determineDateText(
				utcDate,
				today,
				yesterday,
				tomorrow
			);

			// Determine hour/minute
			let hours = utcDate.getHours();
			hours = addLeadingZero(hours);
			const minutes = addLeadingZero(utcDate.getMinutes());
			const timeString = `${hours}:${minutes}`;

			// Compute UTC offset (hours, possibly with fraction). E.g. +2, −7.5, etc.
			const offsetHours = -utcDate.getTimezoneOffset() / 60;
			// toFixed(1) only if decimal part exists
			const offsetStr =
				offsetHours >= 0
					? `+${offsetHours.toFixed(offsetHours % 1 === 0 ? 0 : 1)}`
					: `−${Math.abs(offsetHours).toFixed(offsetHours % 1 === 0 ? 0 : 1)}`;
			const utcPart = `(UTC${offsetStr})`;

			// final ordering: either “time, date (UTC±X)” or “date, time (UTC±X)”
			return this.LocalComments.timeFirst
				? `${timeString}, ${dateText} ${utcPart}`
				: `${dateText}, ${timeString} ${utcPart}`;
		}

		// Decide between “Today”, “Yesterday”, “Tomorrow”, or a full date string
		determineDateText(utcDate, today, yesterday, tomorrow) {
			const year = utcDate.getFullYear();
			const month = utcDate.getMonth() + 1; // 1–12
			const day = utcDate.getDate();

			// Helper to format “YYYY→MM→DD” as strings, since today/yesterday/tomorrow are at local midnight
			const padMonth = addLeadingZero(month);
			const padDay = day; // day will be used as number in comparisons
			const compareY = today.getFullYear();
			const compareM = addLeadingZero(today.getMonth() + 1);
			const compareD = today.getDate();

			// Today?
			if (year === compareY && padMonth === compareM && padDay === compareD) {
				return this.LocalComments.language.Today;
			}
			// Yesterday?
			const yY = yesterday.getFullYear();
			const yM = addLeadingZero(yesterday.getMonth() + 1);
			const yD = yesterday.getDate();
			if (year === yY && padMonth === yM && padDay === yD) {
				return this.LocalComments.language.Yesterday;
			}
			// Tomorrow?
			const tY = tomorrow.getFullYear();
			const tM = addLeadingZero(tomorrow.getMonth() + 1);
			const tD = tomorrow.getDate();
			if (year === tY && padMonth === tM && padDay === tD) {
				return this.LocalComments.language.Tomorrow;
			}

			// Otherwise, build full date text + optional “, last Friday (2 days ago)”
			return this.createFullDateText(utcDate, day, month, year, today);
		}

		// Construct “DD Month YYYY” or “Month DD, YYYY” or “YYYY-MM-DD” plus optional “, last Wednesday (X days ago)”
		createFullDateText(utcDate, day, month, year, today) {
			const lang = this.LocalComments.language;
			const monthName = this.monthNames;

			let mainDate;
			switch (this.LocalComments.dateFormat.toLowerCase()) {
				case "mdy":
					mainDate = `${monthName} ${day}, ${year}`;
					break;
				case "iso":
					mainDate = `${year}-${addLeadingZero(month)}-${addLeadingZero(day)}`;
					break;
				default: // 'dmy'
					mainDate = `${day} ${monthName} ${year}`;
			}

			// Add weekday if desired (“, last Tuesday” or “, this Monday”)
			let weekdayText = "";
			if (this.LocalComments.dayOfWeek) {
				const wd = this.weekdayNames;
				// Compute how many days difference
				const msDelta =
					today.getTime() -
					new Date(
						utcDate.getFullYear(),
						utcDate.getMonth(),
						utcDate.getDate()
					).getTime();
				const dayDelta = Math.round(msDelta / (1000 * 60 * 60 * 24));
				let prefix = "";
				if (this.LocalComments.dateDifference) {
					if (dayDelta >= 0 && dayDelta <= 7) {
						prefix = `${lang.last} `;
					} else if (dayDelta < 0 && Math.abs(dayDelta) <= 7) {
						prefix = `${lang.this} `;
					}
				}
				weekdayText = `, ${prefix}${wd}`;
			}

			// If relative dates (“(2 days ago, 1 month ago)”) are enabled, append them
			let relativeSuffix = "";
			if (this.LocalComments.dateDifference) {
				const suffixText = this.buildRelativeSuffix(utcDate, today);
				if (suffixText) {
					relativeSuffix = ` (${suffixText})`;
				}
			}

			return `${mainDate}${weekdayText}${relativeSuffix}`;
		}

		// Build a string like “2 days ago, 1 month ago” (respecting dropDays/dropMonths)
		buildRelativeSuffix(utcDate, today) {
			const lang = this.LocalComments.language;
			const msAgo =
				today.getTime() -
				new Date(
					utcDate.getFullYear(),
					utcDate.getMonth(),
					utcDate.getDate()
				).getTime();
			const daysAgo = Math.abs(Math.round(msAgo / (1000 * 60 * 60 * 24)));

			// Calculate rough months and years
			const totalMonths = Math.floor((daysAgo / 365) * 12);
			let years = Math.floor(totalMonths / 12);
			let months = totalMonths - years * 12;
			let days = daysAgo - Math.floor((totalMonths * 365) / 12);

			// Apply dropDays / dropMonths rules
			if (daysAgo < this.LocalComments.dropDays) {
				months = 0;
				years = 0;
			} else if (this.LocalComments.dropDays > 0 && totalMonths >= 1) {
				days = 0;
			}

			if (totalMonths < this.LocalComments.dropMonths) {
				years = 0;
			} else if (this.LocalComments.dropMonths > 0) {
				months = 0;
			}

			// Build parts array
			const parts = ;
			if (years > 0) {
				parts.push(`${years} ${pluralize(lang.year, years, lang.years)}`);
			}
			if (months > 0) {
				parts.push(`${months} ${pluralize(lang.month, months, lang.months)}`);
			}
			if (days > 0) {
				parts.push(`${days} ${pluralize(lang.day, days, lang.days)}`);
			}
			if (!parts.length) return "";

			// Determine “ago” vs “from now”
			const suffixWord = msAgo >= 0 ? lang.ago : lang;
			return `${parts.join(", ")} ${suffixWord}`;
		}
	}

	// Avoid running more than once
	if (!window.commentsInLocalTimeWasRun) {
		window.commentsInLocalTimeWasRun = true;
		new CommentsInLocalTime().run();
	}
});