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.
// This script led to [[:phab:T308364]] being filed.
/* jshint esversion: 10, maxerr: 9999, unused: true, quotmark: single */

$(function() {
	var spn = (mw.config.get('wgCanonicalSpecialPageName') || '').toLowerCase();
	var lc = str => str.toLowerCase();
	var switches = function(cases, def) {
		for (let i in cases) {
			if (i.toLowerCase() === spn) {
				return cases[i];
			}
		}
		return def;
	};
	
	// Main functions
	var init = function(object) {
		var {
			links, linkswrapper, linkextend,
			main, parent, preload, preupdate
		} = object;
		var update = (
			object.update !== 'unwrapped' ?
				((linkswrapper || links) && function(content) {
					if (preupdate) preupdate();
					$(linkswrapper || links).each(function(index) {
						$(this).replaceWith(
							$(linkswrapper || links, content)[index]
						);
					});
				})
			:
				function(content) {
					var $links = [
						$(parent || '#mw-content-text').clone(),
						$(parent || '#mw-content-text', content).clone()
					];
					
					$links = $links.map(function($el) {
						$el.find('*').not('a').remove();
						$el.html(
							$el.html().trim().replace(
								/\s*title=".*?(?<!\\)"(\s*)/g,
								'$1'
							).replace(
								/.*?(.+)\s*\1.*?/,
								'$1'
							)
						);
						return $el;
					});
					
					$(links).each(function() {
						$(this).prop(
							'outerHTML',
							$(this).prop('outerHTML').replace(
								/\s*title=".*?"(\s*)/g,
								'$1'
							)
						);
					});
					
					for (let i of [0, 1]) {
						$('#mw-content-text').html(
							$('#mw-content-text').html().replace(
								$links[0].html(),
								$links[1].html()
							)
						);
					}
				}
			);
			
		if (!links) {
			console.warn('No links.');
			return;
		}
		
		// handle navigating between history items pushed by this script
		window.addEventListener('popstate', event => {
			$(main).html(event?.state?.html ?? $(main).html());
		});
		
		$('body').on('click', links, function(e) {
			e.preventDefault();
			if (window.ajaxloaderloading) return;
			
			linktoggle();
			$(main).hide().after(spinner());
		
			var link = new mw.Uri($(this).attr('href'));
			if (linkextend) {
				link.extend(linkextend);
			}
			
			$.ajax({
				url: link.toString(),
				success: function(response) {
					$('#ajaxloader-loader').remove();
					linktoggle();
					update(response);
					$(main).replaceWith($(main, response));
					if (preload) preload();
					window.history.pushState({html: $(main).html()}, '', link); // add new URL to history/urlbar
					mw.hook( 'wikipage.content' ).fire();
				},
				error: function() {
					$('#ajaxloader-loader').remove();
					linktoggle();
					$(main).show();
					alert('Error: Cannot get pages.');
				}
			});
		});
	};
	var includes = function(array, element = spn) {
		array = array instanceof Array ? array : [array];
		return array.some(e => e.toLowerCase() === element.toLowerCase());
	};
	var join = function(links, parent, separator) {
		var s = [];
		for (let l of links) {
			s.push(`${parent}${separator || ' > a'}${l}`);
		}
		return s.join(', ');
	};
	var load = function(server, title) {
		mw.loader.load(`//${server}/w/index.php?title=${title}&action=raw&ctype=text/javascript`);
	};
	var spinner = function() {
		var l = $('<div>').attr('class', 'ajaxloader-loader');
		
		for (let i = 0; i < 12; i++) {
			l.append(
				$('<div>').attr('class', 'ajaxloader-loader-items').css({
					'animation': 'ajaxloader-loader 1.2s linear infinite',
					'transform': `rotate(${i * 30}deg)`,
					'animation-delay': Math.round((-1.1 + (i * 0.1)) * 10) / 10 + 's'
				})
			);
		}
		
		return $('<div>').attr({
			class: 'ajaxloader-loader-wrapper',
			id: 'ajaxloader-loader'
		}).append(l);
	};
	var linktoggle = function() {
		window.ajaxloaderloading = !window.ajaxloaderloading;
	};
	
	// CSS
	// For attribution: //loading.io/css (CC0)
	var css = `
		.ajaxloader-loader-wrapper {
			display: block;
			margin: 3em 0;
			text-align: center;
		}
		.ajaxloader-loader {
			display: inline-block;
			position: relative;
			width: 80px;
			height: 80px;
		}
		.ajaxloader-loader .ajaxloader-loader-items {
			transform-origin: 40px 40px;
		}
		.ajaxloader-loader .ajaxloader-loader-items::after {
			content: ' ';
			display: block;
			position: absolute;
			top: 4px;
			left: 36px;
			width: 6px;
			height: 18px;
			border-radius: 20%;
			background: #000000;
		}
		@keyframes ajaxloader-loader {
			0% {
				opacity: 1;
			}
			100% {
				opacity: 0;
			}
		}
	`;
	try {
		mw.util.addCSS(css);
	} catch (e) {
		mw.loader.addStyleTag(css);
	}
	
	// Data
	var data = [
		{
			name: 'Unwrapped navlinks',
			check: function() {
				return includes([
					'GlobalUsage'
				]);
			},
			links: join([
				'.mw-lastlink', '.mw-firstlink',
				'.mw-prevlink', '.mw-nextlink',
				'.mw-numlink'
			], '#mw-content-text'),
			main: switches({
				'GlobalUsage': '#mw-globalusage-result'
			}),
			update: 'unwrapped'
		},
		{
			name: 'Navlinks inside mw-pager-navigation-bar ([[:phab:T308364]])',
			check: function() {
				return includes([
					'Log', 'Categories', 'AbuseLog', 'ProtectedTitles',
					'NewImages', 'NewPages', 'DeletedContributions', 'WhatLinksHere',
					'GlobalBlockList', 'ListUsers', 'GlobalUsers', 'ActiveUsers',
					'DisambiguationPageLinks', 'DisambiguationPages',
					'UnreviewedPages', 'OrphanedTimedText',
					'BrokenRedirects', 'DoubleRedirects', 'LongPages', 'ShortPages',
					'DeadendPages', 'LonelyPages', 'AncientPages', 'FewestRevisions',
					'UncategorizedCategories', 'UncategorizedFiles',
					'UncategorizedPages', 'UncategorizedTemplates',
					'UnusedCategories', 'UnusedImages', 'UnusedTemplates',
					'WantedCategories', 'WantedFiles', 'WantedPages', 'WantedTemplates',
					'MIMESearch', 'Linksearch', 'LinkSearch', 'Search',
					'MostLinkedCategories', 'MostImages', 'MostLinked', 'MostLinkedTemplates',
					'EntityUsage', 'UnconnectedPages', 'PagesWithBadges', 'PagesWithProp',
					'MostInterwikis', 'WithoutInterwiki', 'MostCategories', 'MostRevisions',
					'UnwatchedPages', 'ListRedirects', 'ListDuplicatedFiles',
					'OAuthManageMyGrants', 'OAuthListConsumers'
				]) ||
				mw.config.get('wgAction') === 'history';
			},
			links: join([
				'.mw-lastlink', '.mw-firstlink',
				'.mw-prevlink', '.mw-nextlink',
				'.mw-numlink'
			], '.mw-pager-navigation-bar'),
			linkswrapper: '.mw-pager-navigation-bar',
			main: switches({
				'Log': '.mw-logevent-loglines',
				'': '#pagehistory',
				'DeletedContributions': '.mw-pager-body',
				'AbuseLog': '#mw-content-text > form',
				'Search': '.mw-search-results-container',
				'WhatLinksHere': '#mw-whatlinkshere-list'
			}, '#mw-content-text ul, #mw-content-text ol'),
			preload: function() {
				if (includes('NewImages')) {
					$('#mw-content-text > ul').before(
						$('.mw-pager-navigation-bar').clone()
					);
				}
			},
			preupdate: function() {
				if (includes('NewImages')) {
					$($('.mw-pager-navigation-bar')[0]).remove();
				}
			}
		},
		{
			name: 'Special pages with -nav classes',
			check: function() {
				return includes([
					'AllPages', 'PrefixIndex'
				]);
			},
			links: '.mw-' + spn + '-nav > a',
			linkswrapper: '.mw-' + spn + '-nav',
			main: '.mw-' + spn + '-body',
		},
		{
			name: 'Abuse filter history',
			check: function() {
				return spn === lc('AbuseFilter') && $('.mw-abusefilter-history-buttons').length > 0;
			},
			links: '.mw-abusefilter-history-buttons a.oo-ui-buttonElement-button',
			linkswrapper: '.mw-abusefilter-history-buttons',
			main: '#mw-content-text > table.wikitable',
			preload: function() {
				$('#mw-content-text > table.wikitable').before(
					$('.mw-abusefilter-history-buttons').clone()
				);
			},
			preupdate: function() {
				$($('.mw-abusefilter-history-buttons')[0]).remove();
			}
		},
		{
			name: 'Modernized special pages with button-like navlinks',
			check: function() {
				return includes([
					'AllMessages', 'ProtectedPages', 
					'BlockList', 'AutoblockList',
					'AbuseFilter', 'ListFiles'
				]);
			},
			links: '.TablePager_nav a',
			linkswrapper: '.TablePager_nav',
			main: switches({
				'AllMessages': '#mw-allmessagestable',
				'ProtectedPages': '.mw-protectedpages',
				'BlockList': '.mw-blocklist',
				'AutoblockList': '.mw-blocklist',
				'AbuseFilter': '.mw-datatable',
				'ListFiles': '.mw-datatable'
			})
		},
		{
			name: 'Contributions',
			check: function() {
				return includes([
					'Contributions'
				]);
			},
			links: '.mw-pager-navigation-bar > a',
			linkswrapper: '.mw-pager-navigation-bar',
			main: '.mw-pager-body'
		},
		{
			name: 'SearchTranslations',
			check: function() {
				return includes([
					'SearchTranslations'
				]);
			},
			links: '.tux-pagination-links > a',
			linkswrapper: '.tux-pagination-links',
			main: '.results'
		},
		{
			name: 'Categories',
			check: function() {
				return mw.config.get('wgNamespaceNumber') === 14;
			},
			exempt: function() {
				load('en.wikipedia.org', 'User:NguoiDungKhongDinhDanh/AjaxCat.js');
			}
		},
		{
			name: 'Translatable pages',
			check: function() {
				return $('ul.mw-pt-languages-list').length;
			},
			links: '.mw-pt-languages-list a.mw-pt-progress',
			linkswrapper: '.mw-pt-languages-list',
			main: '#bodyContent'
		},
		{
			name: 'Files',
			check: function() {
				return mw.config.get('wgNamespaceNumber') === 6;
			},
			links: join([
				'.mw-lastlink', '.mw-firstlink',
				'.mw-prevlink', '.mw-nextlink',
				'.mw-numlink'
			], '.mw-pager-navigation-bar'),
			linkswrapper: '.mw-pager-navigation-bar',
			main: '.filehistory'
		}
	];
	
	// Init.
	for (let i of data) {
		if (i.check()) {
			console.log(i.name);
			if (!i.exempt) {
				if (i.preload) i.preload();
				init(i);
			} else {
				i.exempt();
			}
			break;
		}
	}
});