Jump to content

User:Phlsph7/HighlightUnreferencedPassages.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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.
/*** Highlight unreferenced passages ***/
(function(){
	// finds passages that lack references and marks them
	function markUnreferencedPassages(){
		// stylesheet to color passages lacking references
		function addStylesheet(){
			const stylesheet = document.createElement('style');
			stylesheet.innerHTML = `
			.has-no-references, .Template-Fact {
				background: LightPink;
			}
			`;
			document.head.appendChild(stylesheet);
		}
		
		// check whether references are relevant to the element 
		function isEligible(element, excludedSections){
			// exclude elements that are part of navboxes, sidebars, and the like
			// references do not matter for them
			if(hasParentClass(element, 'navbox')
				|| hasParentClass(element, 'sidebar')
				|| hasParentClass(element, 'infobox')
				|| hasParentClass(element, 'side-box-flex')
				|| hasParentClass(element, 'noprint')
				|| hasParentClass(element, 'refbegin')
				|| hasParentClass(element, 'gallery')
				|| hasParentClass(element, 'toc')
				|| hasParentClass(element, 'reflist')){
				return false;
			}
			
			// exclude elements that belong to certain sections where references do not matter
			const sectionName = getSectionName(element);
			if(excludedSections.indexOf(sectionName) != -1){
				return false;
			}
			
			return true;
		}
		
		// utility function to check whether the elements parents and grand parents have a certain class
		function hasParentClass(element, className){
			return element.closest('.' + className) != null;
		}
		
		// utility function to get the section name to which an element belongs
		function getSectionName(element){
			var mainContainerChildElement = getGrandchildOfMainContainer(element);
			var sectionName = ''; // default section name, this corresponds to the lead
			var previousElement = mainContainerChildElement.previousElementSibling;
			
			// sections always start with an h2 element
			// the script loops back from an element to the previous element until an h2 element is discovered
			while(previousElement != null){
				// check whether it is an h2 element
				if(previousElement.classList.contains('mw-heading2')){
					// extract the section name
					sectionName = previousElement.innerText.split('[edit]').join('');
					break;
				}
				previousElement = previousElement.previousElementSibling;
			}
			
			return sectionName;
		}
		
		// utility function: for any element, return the parent that is a grandchild of the main container
		function getGrandchildOfMainContainer(element){
			const mainContainer = document.getElementById('mw-content-text');
			if(element.parentElement.parentElement == mainContainer){
				return element;
			}
			else{
				return getGrandchildOfMainContainer(element.parentElement);
			}
		}
		
		// utility function to check whether the lead section of the article should be marked
		function shouldMarkLead(){
			// if it is a draft then the lead should be highlighted
			var pageTitleNamespace = document.getElementsByClassName('mw-page-title-namespace')[0];
			if(pageTitleNamespace != null){
				if(pageTitleNamespace.innerText === 'User' || pageTitleNamespace.innerText === 'Draft'){
					return true;
				}
			}
			
			// if it is a stub then the lead should be highlighted
			else if(document.getElementsByClassName('stub').length > 0){
				return true;
			}
			
			// otherwise not
			else {
				return false;
			}
		}
		
		// marks elements that lack references
		function mark(element){
			// mark elements without any reference elements
			if(element.getElementsByClassName('reference').length == 0){
				element.classList.add("has-no-references");
			}
			
			// mark elements with some reference elements
			else{
				// if the last element is not a reference then start marking it
				markUntilPreviousReference(element.lastChild);
				
				// starts from each "citation needed" tag, goes backwards and marks until it reaches a reference 
				var citationNeededTags = element.getElementsByClassName('Template-Fact');
				for(var citationNeededTag of citationNeededTags){
					markUntilPreviousReference(citationNeededTag);
				}
			}
		}
		
		// Function to mark unreferenced passages. It starts with one node and loops back to previous nodes until it hits a reference
		function markUntilPreviousReference(childNode){
			var currentNode = childNode;
			while(currentNode != null){
				// handle nodes that are not HTML elements
				if(currentNode.classList == null){
					// create a span element and classify it
					var span = document.createElement('span');
					span.classList.add("has-no-references");
					
					// copy the node's text into the span element and replace the node with the span element
					span.innerHTML = currentNode.data;
					currentNode.parentElement.replaceChild(span, currentNode);
					currentNode = span;
				}
				
				// handle nodes that are HTML elements
				// if the node is a reference
				else if(currentNode.classList.contains('reference')){
					
					// check whether the node is an actual reference: they contain numbers
					if(currentNode != null && 
						currentNode.innerText != null && 
						/[0-9]/.test(currentNode.innerText) &&
						!currentNode.innerText.toLowerCase().includes('note') &&
						!currentNode.innerText.toLowerCase().includes('nb')){
					
						break;
					}
					// otherwise it is an explanatory footnote and not a reference
					else{
						currentNode.classList.add('has-no-references');
					}
				}
				
				// if the node is an element but not a reference then classify it
				else {
					currentNode.classList.add('has-no-references');
				}
				
				// set the current node to the previous one to continue the loop
				currentNode = currentNode.previousSibling;
			}
		}
		
		// removes the red background from elements that were falsely highlighted
		function excludeFalsePositives(){		
			// exclude references used in nested lists
			var unreferencedElements = document.getElementsByClassName('has-no-references');
			for(let unreferencedElement of unreferencedElements){
				// if the element contains a reference inside then it is not unreferenced, so remove the class
				if(unreferencedElement.getElementsByClassName('reference').length > 0){
					unreferencedElement.classList.remove('has-no-references');
				}
			}
			
			// exclude quoteboxes
			unreferencedElements = document.getElementsByClassName('has-no-references');
			for(let unreferencedElement of unreferencedElements){
				// see if the the element is part of a quotebox that has a citation
				var quoteboxParent = unreferencedElement.closest('.quotebox');
				if(quoteboxParent != null && quoteboxParent.getElementsByTagName('cite').length > 0){
					unreferencedElement.classList.remove('has-no-references');
				}
			}
			
			// do not mark empty elements
			unreferencedElements = document.getElementsByClassName('has-no-references');
			for(let unreferencedElement of unreferencedElements){
					if(unreferencedElement.innerHTML == "\n" || unreferencedElement.innerHTML == " \n"){
						unreferencedElement.classList.remove('has-no-references');
					}
			}
			
			// exclude the template {{rp}}
			var referenceElements = document.getElementsByClassName('reference');
			for(let referenceElement of referenceElements){
				if(referenceElement.classList.contains('has-no-references')){
					referenceElement.classList.remove('has-no-references');
				}
			}
			
			// blockquotes often use a different reference style, so false positives need to be excluded separately
			const unreferencedParagraphsInsideBlockquotes = document.querySelectorAll('blockquote > p.has-no-references');
			for(var unreferencedParagraphInsideBlockquotes of unreferencedParagraphsInsideBlockquotes){
				const parent = unreferencedParagraphInsideBlockquotes.parentElement;
				// check whether the parent blockquote contains a citation element
				if(parent.getElementsByClassName('templatequotecite').length > 0){
					// if it does then the paragraph is not unreferenced
					unreferencedParagraphInsideBlockquotes.classList.remove('has-no-references');
				}
			}
			
			// ignore elements in the template "ombox"
			let unreferencedElementsInOmboxes = document.querySelectorAll(".ombox .has-no-references");
			for (let element of unreferencedElementsInOmboxes) {
			  element.classList.remove("has-no-references");
			}
			
			// for drafts: exclude comments
			var pageTitleNamespace = document.getElementsByClassName('mw-page-title-namespace')[0];
			if(pageTitleNamespace != null){
				if(pageTitleNamespace.innerText === 'User' || pageTitleNamespace.innerText === 'Draft'){
					let unreferencedComments = document.querySelectorAll(".has-no-references:has(.localcomments)");
					for (let unreferencedComment of unreferencedComments) {
					  unreferencedComment.classList.remove("has-no-references");
					}
				}
			}
		}
		
		addStylesheet();
		
		// all paragraphs and list entries should have references
		const paragraphs = document.getElementById('mw-content-text').getElementsByTagName('p');
		const listEntries = document.getElementById('mw-content-text').getElementsByTagName('li');
		const elements = Array.from(paragraphs).concat(Array.from(listEntries));
		
		// these sections are not checked for references
		var excludedSections = ['Plot', 'Plots', 'Plot summary', 'Plot synopsis', 'Synopsis', 'Storylines', 'Further reading', 'See also', 'External links', 'References', 'Bibliography', 'Notes', 'Selected publications', 'Selected works', 'Cited sources', 'Sources', 'Footnotes'];
		
		// dedice whether the lead should be checked for references
		if(!shouldMarkLead()){
			excludedSections.push('');
		}
		
		for(var element of elements){
			// check whether the element should be excluded
			if(isEligible(element, excludedSections)){
				// mark the element if it lacks references
				mark(element);
			}
		}
		
		excludeFalsePositives();
	}
	
	// restrict script to mainspace, userspace, and draftspace
	var namespaceNumber = mw.config.get('wgNamespaceNumber');
	if (namespaceNumber === 0 || namespaceNumber === 2 || namespaceNumber === 118) {
		// add a link to the toolbox
		$.when(mw.loader.using('mediawiki.util'), $.ready).then(function (){
			var portletlink = mw.util.addPortletLink('p-tb', '#', 'Highlight unreferenced passages');
			
			// run the main function when the link is clicked
			portletlink.onclick = function(e) {
				e.preventDefault();
				markUnreferencedPassages();
				const unreferencedElements = document.getElementsByClassName('has-no-references');
				//mw.notify(`${unreferencedElements.length} elements were highlighted`);
				mw.notify('Highlighting finished.');
			};
		});
	}
	if (namespaceNumber === 0 || namespaceNumber === 118) {
		if(typeof highlightUnreferencedPassagesAutomatic != 'undefined' && highlightUnreferencedPassagesAutomatic == true){
			markUnreferencedPassages();
		}
	}
})();