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 uses material from Wikipedia user Ucucha's script "duplinks" ( https://en.wikipedia.org/wiki/User:Ucucha/duplinks.js ), which is released under the Creative Commons Attribution-Share-Alike License 3.0 ( http://creativecommons.org/licenses/by-sa/3.0/ )
//See also https://en.wikipedia.org/wiki/User:Ucucha/duplinks for documentation of the original script
// oh uh it also uses [[User:Evad37/duplinks-alt]] which had the above notice, this thing adds config "autoDuplinks=true" to automatically highlihgt™

function highlightDuplinks(e = new Event("click")){
	e.preventDefault();			
	// Check if VisualEditor is being used - the element surrounding text is different in VE
	let isVisualEditor = window.location.href.search("veaction")>0;
	// Get the element immediately surrounding the article text.
	let $content = isVisualEditor ? $(".ve-ce-documentNode.ve-ce-branchNode") : $($(".mw-parser-output", "#mw-content-text")[0]);
	// Create a separate div to conatin the lead
	$lead = $("<div id='lead'>").prependTo($content);
	// Move the elements containing the lead content into this newly-created div
	$lead.nextAll().each( function() {
		if (this.nodeName.toLowerCase() == 'h2') {
			// Reached the first heading after the lead.
			// Returning false breaks out of the jQuery .each() loop early
			return false;
		}
		$lead.append(this);
		return true;
	});

	// Objects to keep track of whether we've seen a link before, and which links are duplicated
	let seen = {};
	let duplicated = {};
	let hasDuplicatedLinks = false;

	// Styles
	mw.util.addCSS(".duplicate-link { border: 1px solid red; }\n.duplicated-link { border: 1px dashed green; }");
	
	// Detect and mark links which are duplicates
	let finddups = function() {
		let href = this.attributes.href && this.attributes.href.value;
		if (href != undefined && href.indexOf('#') != 0) {
			if (seen[href]) {
				this.classList.add("duplicate-link");
				duplicated[href] = true;
				hasDuplicatedLinks = true;
			}
			else {
				seen[href] = true;
			  }
		}
		return true;
	};
	// Detect and mark the first occurance of duplicated links
	let markdups = function() {
		let href = this.attributes.href && this.attributes.href.value;
		if(href != undefined && href.indexOf('#') != 0) {
			if(duplicated[href]) {
				this.classList.add("duplicated-link");
				duplicated[href] = '';
			}
		}
		return true;
	};

	// Process sections after the lead
	mw.util.$content.find('p a').not('#lead *, .infobox *, .navbox *').each(finddups);
	mw.util.$content.find('p a').not('#lead *, .infobox *, .navbox *').each(markdups);
	
	// Reset tracking objects, process lead section
	seen = {};
	duplicated = {};
	mw.util.$content.find('#lead p a').not('.infobox *, .navbox *').each(finddups);
	mw.util.$content.find('#lead p a').not('.infobox *, .navbox *').each(markdups);

	// Show a notice if no duplicates were found
	if (!hasDuplicatedLinks && !autoDuplinks) {
		mw.notify('No duplicated links were detected');
	}
	
	if(autoDuplinks)
		autoDuplinks=false; // continue toast after button presses after we've auto'd it
}

$( function($) {
	let namespaceNumber = mw.config.get('wgNamespaceNumber');
	// only check links in mainspace, userspace (for userspace drafts), and draftspace
	let isCorrectNamespace = namespaceNumber === 0 || namespaceNumber === 2 || namespaceNumber === 118;
	if (!isCorrectNamespace) {
		return;
	}
	if(typeof autoDuplinks === 'undefined'){
		autoDuplinks = false; //out of scope behavior required
	}
	if(autoDuplinks) highlightDuplinks();
	mw.loader.using('mediawiki.util').then(function(){
		let portletlink = mw.util.addPortletLink('p-tb', '#', 'Highlight duplicate links', 'ca-findduplicatelinks');
		$(portletlink).click( highlightDuplinks );
	});
});