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#Ref duplication highlight tool.
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' }
);
}
} );
} );