MediaWiki:Gadget-readableRC-core.js

This is an old revision of this page, as edited by Alex (talk | contribs) at 01:53, 13 October 2024 (Created page with "// <nowiki> // Formats the rows on Special:RecentChanges where all the information runs together // into three columns (page, diff/byte change, and user links) to make it more readable // // @author Iiii_I_I_I ;(function ($, mw) { function runReadableRC($content) { if (!$content.hasClass('mw-changeslist')) { return; } $content.addClass('gadget-rc-enabled'); let rows = document.querySelectorAll( '.mw-changeslist-src-mw-edit,' + '.mw-changeslist-src-mw..."). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

After saving, you may need to bypass your browser's cache to see the changes. For further information, see Wikipedia:Bypass your cache.

  • In most Windows and Linux browsers: Hold down Ctrl and press F5.
  • In Safari: Hold down ⇧ Shift and click the Reload button.
  • In Chrome and Firefox for Mac: Hold down both ⌘ Cmd+⇧ Shift and press R.
// <nowiki>
// Formats the rows on Special:RecentChanges where all the information runs together
// into three columns (page, diff/byte change, and user links) to make it more readable
//
// @author Iiii_I_I_I

;(function ($, mw) {
	function runReadableRC($content) {
		if (!$content.hasClass('mw-changeslist')) {
			return;
		}

		$content.addClass('gadget-rc-enabled');

		let rows = document.querySelectorAll(
			'.mw-changeslist-src-mw-edit,' +
			'.mw-changeslist-src-mw-log,' +
			'.mw-changeslist-src-mw-new'
		);

		for (let row of rows) {
			// hover text on timestamp column
			addRelativeTime(row);

			// nested rows
			if (row.classList.contains('mw-rcfilters-ui-highlights-enhanced-nested')) {
				if (row.classList.contains('mw-changeslist-edit')) {
					cleanNestedEdit(row);
				} else {
					cleanNestedLog(row);
				}
			}
			// top-level rows
			else {
				// grouped row
				if (row.classList.contains('mw-rcfilters-ui-highlights-enhanced-toplevel')) {
					let parent = row.closest('.mw-changeslist-line');

					if (parent.classList.contains('mw-changeslist-edit')) {
						cleanGroupedEdit(row);
					} else {
						cleanGroupedLog(row);
					}
				}
				// single row
				else {
					if (row.classList.contains('mw-changeslist-edit')) {
						cleanSingleEdit(row);
					} else {
						cleanSingleLog(row);
					}
				}
			}
		}

		// fires every time readableRC runs when new edits come in
		mw.hook('ext.gadget.readableRC').fire();
	}

	function cleanNestedLog(row) {
		row.classList.add('gadget-rc-row', 'gadget-rc-log', 'gadget-rc-nested');

		// FIRST COLUMN: log timestamp
		row.querySelector('.mw-enhanced-rc-time').classList.add('gadget-rc-col-1');

		// SECOND COLUMN: placeholder with separator dots
		row.querySelector('.mw-changeslist-separator').classList.add('gadget-rc-col-2');

		// THIRD COLUMN: user info
		let col3 = document.createElement('span');

		col3.classList.add('gadget-rc-col-3');
		col3.append(
			get('.mw-changeslist-log-entry', row),
			get('.mw-tag-markers', row)
		);
		row.querySelector('.gadget-rc-col-2').after(col3);
		cleanUserLinks(row);
	}

	function cleanNestedEdit(row) {
		row.classList.add('gadget-rc-row', 'gadget-rc-edit', 'gadget-rc-nested');

		// FIRST COLUMN: revision link
		row.querySelector('.mw-enhanced-rc-time').classList.add('gadget-rc-col-1');

		// THIRD COLUMN: user info
		let col3 = document.createElement('span');

		col3.classList.add('gadget-rc-col-3');
		col3.append(
			get('.mw-userlink', row),
			get('.mw-usertoollinks', row),
			get('.mw-enhanced-rc-nested > .history-deleted', row), // (username removed)
			get('.comment', row),
			get('.mw-pager-tools', row),
			get('.mw-tag-markers', row)
		);

		// SECOND COLUMN: diff text
		let parent = row.querySelector('td.mw-enhanced-rc-nested');
		let col1 = row.querySelector('.gadget-rc-col-1');

		// detach first column so remaining elements all go in diff column
		parent.removeChild(col1);

		// wrap elements together inside column 2
		let col2 = document.createElement('span');

		col2.classList.add('gadget-rc-col-2');
		while (parent.firstChild) {
			col2.append(parent.firstChild);
		}

		// put everything back together
		parent.append(col1);
		parent.append(col2);
		parent.append(col3);

		cleanUserLinks(row);
	}

	function cleanGroupedLog(row) {
		row.classList.add('gadget-rc-row', 'gadget-rc-log', 'gadget-rc-grouped');

		// FIRST COLUMN: log name
		row.querySelector('.mw-rc-unwatched').classList.add('gadget-rc-col-1');

		// SECOND COLUMN: placeholder with separator dots
		row.querySelector('.mw-changeslist-separator').classList.add('gadget-rc-col-2');

		// THIRD COLUMN: user info
		row.querySelector('.changedby').classList.add('gadget-rc-col-3');

		// remove square brackets from grouped usernames; can't use remove()
		// since there might be other text in the same node, eg. "(4×)]"
		let users = row.querySelector('.gadget-rc-col-3').childNodes;

		users[0].textContent = users[0].textContent.slice(1); // [
		users[users.length - 1].textContent = users[users.length - 1].textContent.slice(0, -1); // ]

		// remove empty text nodes - convert live NodeList to array
		let children = [...row.querySelector('.mw-changeslist-line-inner').childNodes];

		for (let child of children) {
			if (child.nodeType === Node.TEXT_NODE) {
				child.remove();
			}
		}
	}

	function cleanGroupedEdit(row) {
		row.classList.add('gadget-rc-row', 'gadget-rc-edit', 'gadget-rc-grouped');

		// FIRST COLUMN: page name
		row.querySelector('.mw-title').classList.add('gadget-rc-col-1');

		// SECOND COLUMN: diff text
		let col2 = document.createElement('span');

		col2.classList.add('gadget-rc-col-2');
		col2.append(
			get('.mw-changeslist-links', row),
			get('.mw-diff-bytes', row)
		);
		row.querySelector('.gadget-rc-col-1').after(col2);

		// "x changes" -> "diff"
		if (row.querySelector('.mw-changeslist-groupdiff')) {
			row.querySelector('.mw-changeslist-groupdiff').textContent = 'diff';
		}
		// new pages have a text node instead of a link
		else {
			row.querySelector('.mw-changeslist-links span:first-child').textContent = 'diff';
		}

		// "history" -> "hist"
		if (row.querySelector('.mw-changeslist-history')) {
			row.querySelector('.mw-changeslist-history').textContent = 'hist';
		}
		// nonexistent pages (redirect-suppressed move or deleted) have a text node instead of a link
		// @todo check for new classname/structure; no example rn
		else {
			// let newHist = row.querySelector('.mw-changeslist-line-inner').childNodes[4].nodeValue.replace('history', 'hist');
			// row.querySelector('.mw-changeslist-line-inner').childNodes[4].nodeValue = newHist;
		}

		// THIRD COLUMN: user info
		row.querySelector('.changedby').classList.add('gadget-rc-col-3');

		// remove square brackets from grouped usernames; cannot simply use remove()
		// since there might be other text in the same node, eg. "(4×)]"
		let users = row.querySelector('.gadget-rc-col-3').childNodes;

		users[0].textContent = users[0].textContent.slice(1); // [
		users[users.length - 1].textContent = users[users.length - 1].textContent.slice(0, -1); // ]

		// remove empty text nodes - convert live NodeList to array
		let children = [...row.querySelector('.mw-changeslist-line-inner').childNodes];

		for (let child of children) {
			if (child.nodeType === Node.TEXT_NODE) {
				child.remove();
			}
		}
	}

	function cleanSingleLog(row) {
		row.classList.add('gadget-rc-row', 'gadget-rc-log');

		// FIRST COLUMN: log name
		row.querySelector('.mw-changeslist-line-inner-logLink').classList.add('gadget-rc-col-1');

		// SECOND COLUMN: placeholder with separator dots
		row.querySelector('.mw-changeslist-line-inner-separatorAfterLinks').classList.add('gadget-rc-col-2');

		// THIRD COLUMN: user info
		let col3 = document.createElement('span');

		col3.classList.add('gadget-rc-col-3');
		col3.append(
			get('.mw-changeslist-line-inner-logEntry', row),
			get('.mw-changeslist-line-inner-tags', row),
			get('.mw-changeslist-line-inner-watchingUsers', row)
		);
		row.querySelector('.gadget-rc-col-2').after(col3);
		cleanUserLinks(row);
	}

	function cleanSingleEdit(row) {
		row.classList.add('gadget-rc-row', 'gadget-rc-edit');

		// FIRST COLUMN: page name
		row.querySelector('.mw-changeslist-line-inner-articleLink').classList.add('gadget-rc-col-1');

		// SECOND COLUMN: diff text
		let col2 = document.createElement('span');

		col2.classList.add('gadget-rc-col-2');
		col2.append(
			get('.mw-changeslist-line-inner-historyLink', row),
			get('.mw-changeslist-line-inner-characterDiff', row)
		);
		row.querySelector('.gadget-rc-col-1').after(col2);

		// THIRD COLUMN: user info
		let col3 = document.createElement('span');

		col3.classList.add('gadget-rc-col-3');
		col3.append(
			get('.mw-changeslist-line-inner-userLink', row),
			get('.mw-changeslist-line-inner-userTalkLink', row),
			get('.mw-changeslist-line-inner-comment', row),
			get('.mw-changeslist-line-inner-rollback', row),
			get('.mw-changeslist-line-inner-tags', row),
			get('.mw-changeslist-line-inner-watchingUsers', row)
		);
		col2.after(col3);
		cleanUserLinks(row);
	}

	function cleanUserLinks(row) {
		// if username has been revdeled (shows "(username removed)"), it has no links
		if (row.querySelector('.history-deleted')) return;

		// replace links with first letter of each link
		let links = row.querySelectorAll('.mw-usertoollinks a');

		for (let link of links) {
			link.textContent = link.textContent.slice(0, 1);
		}

		// rollback link doesn't exist if page creation or user does not have the right
		if (row.querySelector('.mw-rollback-link')) {
			row.querySelector('.mw-rollback-link a').textContent = 'rollback';
		}
	}

	// add relative time (eg. "25m ago") to timestamp column as hover text
	function addRelativeTime(row) {
		// if row is a single row
		let timestamp = row.getAttribute('data-mw-ts');

		// if row is a grouped row
		if (timestamp === null) {
			timestamp = row.closest('table.mw-enhanced-rc').getAttribute('data-mw-ts');
		}

		// convert mw timestamp (eg. 20240906020749) to Date (equal to 2024-09-06, 02:07:49 UTC)
		let {y, m, d, h, min, s} = timestamp.match(/(?<y>\d{4})(?<m>\d{2})(?<d>\d{2})(?<h>\d{2})(?<min>\d{2})(?<s>\d{2})/).groups;
		let timestampObj = new Date(`${y}-${m}-${d}T${h}:${min}:${s}.000Z`);

		// get time difference, then format into hours and minutes
		let minsAgo = Math.floor((new Date() - timestampObj) / 1000 / 60);
		let timeAgo;

		if (minsAgo === 0) {
			timeAgo = 'Now!';
		} else if (minsAgo < 60) {
			timeAgo = minsAgo + 'm ago';
		} else {
			timeAgo = Math.floor(minsAgo / 60) + 'h ' + minsAgo % 60 + 'm ago';
		}

		get('.mw-enhanced-rc', row).setAttribute('title', timeAgo);
	}

	// return element if it exists; if not, fail silently (unlike querySelector, which returns null)
	function get(selector, scope = document) {
		let element = scope.querySelector(selector);
		return (element) ? element : '';
	}

	function init() {
		mw.hook('structuredChangeFilters.ui.initialized').add(function () {
			// initial load
			runReadableRC($('.mw-changeslist'));

			// page refreshed with new edits / "Live updates" on
			mw.hook('wikipage.content').add(runReadableRC);
		});
	}

	$(init);
})(jQuery, mediaWiki);

// </nowiki>