User:Chlod/Scripts/DuplicatedRefs.js

// DuplicatedRefs // Author: Chlod // Version: 2.0.1

// Highlight equivalent references which are duplicated in the same paragraph. // See Special:Diff/1141909502.

mw.loader.using( [ 'mediawiki.util' ], function {

mw.util.addCSS( '.reference-localduplicated { background-color: #ffff00; }' );

mw.util.addPortletLink(       'p-tb', 'javascript:void', 'Highlight duplicate refs', 't-dupref',         'Highlight all references in the same paragraph which are not interrupted by other references in sequence.'    ).addEventListener( 'click', function ( e ) {        e.preventDefault;

let currentReferenceBundle = [];

let lastReferenceBundle = null; let $lastGrandfatherElement = null; let duplicatesFound = 0;

var references = document.querySelectorAll( '.mw-body-content .mw-parser-output .reference' ); for ( var item of references ) { const $item = $( item ); const $grandfatherElement = $item.parents( '.mw-parser-output >' ); const referenceId = $item.find( 'a' ).attr( 'href' );

if ( referenceId == null ) { // Invalid reference? We'll skip it. continue; }

if ( $lastGrandfatherElement == null ) { // This is only the case if this is the very first reference in the article. // We'll assign the reference ID as well and move on. $lastGrandfatherElement = $grandfatherElement; currentReferenceBundle.push( $item ); continue; }

if ( !$lastGrandfatherElement.is( $grandfatherElement ) ) { // Different paragraph/section. Snip bundle. $lastGrandfatherElement = $grandfatherElement; lastReferenceBundle = null; currentReferenceBundle = []; }

// Checks for the next sibling. The next sibling may be a text node or a DOM element. const hasNextReference = item.nextSibling instanceof HTMLElement && item.nextSibling.classList.contains( 'reference' );

currentReferenceBundle.push( $item ); if ( !hasNextReference ) { // Next sibling is not a reference. Snip bundle and compare. // Get the reference IDs and see if they are the same on both bundles.

const bundleReferenceIds = new Set(                   currentReferenceBundle.map( e => e.find( 'a' ).attr( 'href' ) )               );

if ( lastReferenceBundle != null ) { const lastBundleReferenceIds = new Set(                       lastReferenceBundle.map( e => e.find( 'a' ).attr( 'href' ) )                   );

// Check if the two sets have the same values. // Unordered, and repeating values discarded. const sameIds = bundleReferenceIds.size == lastBundleReferenceIds.size && [...bundleReferenceIds].every( value => lastBundleReferenceIds.has( value ) );

if ( sameIds ) { // Same IDs. Highlight both bundles. currentReferenceBundle.forEach( e => e.addClass( 'reference-localduplicated' ) ); lastReferenceBundle.forEach( e => e.addClass( 'reference-localduplicated' ) ); duplicatesFound += currentReferenceBundle.length; }               }

// Also check for repeated reference within the same bundle.

if ( bundleReferenceIds.size !== currentReferenceBundle.length ) { // Different IDs, but there might still be repeating references in the same bundle. const foundReferenceIds = new Set; const foundDuplicateReferenceIds = new Set; currentReferenceBundle.forEach( e => {                       const foundReferenceId = e.find( 'a' ).attr( 'href' );                        if ( foundReferenceIds.has( foundReferenceId ) ) {                            // Duplicate found!                            foundDuplicateReferenceIds.add( foundReferenceId );                            // Afterwards, mark the ID as found (or do nothing if it's already in).                        }                        // else: no duplicate found, just add it in.                        foundReferenceIds.add( foundReferenceId );                    } ); currentReferenceBundle.forEach( e => {                       if ( foundDuplicateReferenceIds.has( e.find( 'a' ).attr( 'href' ) ) ) {                            e.addClass( 'reference-localduplicated' );                        }                    } ); duplicatesFound += foundDuplicateReferenceIds.size; }

lastReferenceBundle = currentReferenceBundle; currentReferenceBundle = []; } else { // Next sibling is a reference. Move on. continue; }       }

if ( duplicatesFound > 0 ) { const duplicateExample = ' Duplicated references are highlighted like this. ';           mw.notify(                $.parseHTML( 'Found ' + duplicatesFound + ' duplicate reference pairs in the same paragraph. '                       + duplicateExample ),               { title: 'DuplicatedRefs' }            ); } else { mw.notify(               $.parseHTML( 'Congratulations! No duplicate reference pairs within the same paragraph found.' ),               { title: 'DuplicatedRefs' }            ); }   } );

} );