Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* =====================================================================
* Bharatpedia Gadget: RefGenBarefix (v1.6)
* v1.4: also detect malformed refs like <ref>(URL text)</ref>.
* v1.5: also detect bare URLs tagged with {{Bare URL inline}} / {{Bare URL PDF}}.
* v1.6: show the reason next to each skipped URL (e.g. no citation data, dead link).
* v1.3: search suggestions (autocomplete) on the article title input.
* Bare reference fixer tool page (Bharatpedia:Bare_reference_fixer).
*
* Enter an article title: the tool scans the wikitext for bare-URL
* <ref>s, builds formatted citations with the Citation generator
* engine (mw.libs.refGen from ext.gadget.RefGen), shows a review list,
* and publishes the fix as the logged-in user when they click
* "Publish changes". Tracking params (fbclid, utm_*) are stripped.
* Duplicate URLs reuse a named reference.
*
* All user-facing text is in English.
* ===================================================================== */
( function () {
'use strict';
var TOOL_PAGE = 'Bharatpedia:Bare_reference_fixer';
var TRACKING = [ 'fbclid', 'gclid', 'igshid', 'mc_cid', 'mc_eid',
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content' ];
function cleanUrl( url ) {
try {
var u = new URL( url );
TRACKING.forEach( function ( p ) { u.searchParams.delete( p ); } );
return u.toString();
} catch ( e ) {
return url;
}
}
var BARE_RE = /<ref(\s+name\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s/>]+))?)?\s*>\s*(https?:\/\/[^\s<{]+?)\s*({{\s*bare url(?: pdf| inline)?\s*(?:\|[^}]*)?}})?\s*<\/ref>/gi;
var BADREF_RE = /<ref(\s+name\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s/>]+))?)?\s*>\s*[(\[]\s*(https?:\/\/[^\s<)\]]+)\s+([^<)\]]+?)\s*[)\]]\s*<\/ref>/gi;
function findBareRefs( text ) {
var out = [], m;
BARE_RE.lastIndex = 0;
while ( ( m = BARE_RE.exec( text ) ) !== null ) {
var url = m[ 5 ].replace( /[.,;:!?)]+$/, '' );
out.push( {
start: m.index, end: m.index + m[ 0 ].length,
raw: m[ 0 ], name: m[ 2 ] || m[ 3 ] || m[ 4 ] || null,
url: url, kind: 'bare'
} );
}
return out;
}
function findBadRefs( text ) {
var out = [], m;
BADREF_RE.lastIndex = 0;
while ( ( m = BADREF_RE.exec( text ) ) !== null ) {
var url = m[ 5 ].replace( /[.,;:!?)]+$/, '' );
out.push( {
start: m.index, end: m.index + m[ 0 ].length,
raw: m[ 0 ], name: m[ 2 ] || m[ 3 ] || m[ 4 ] || null,
url: url, kind: 'malformed'
} );
}
return out;
}
function esc( s ) {
return String( s == null ? '' : s ).replace( /[&<>"']/g, function ( c ) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ c ];
} );
}
function buildTool() {
var RefGen = mw.libs.refGen;
var api = new mw.Api();
var $root = $( '<div>' ).addClass( 'bp-brf' ).css( {
border: '2px solid #36c', 'border-radius': '10px', padding: '18px',
margin: '0 0 18px 0', background: '#f8f9fa', 'box-sizing': 'border-box'
} );
$root.append( $( '<h2>' ).text( 'Bare reference fixer' )
.css( { margin: '0 0 4px 0', 'font-size': '22px' } ) );
$root.append( $( '<p>' )
.text( 'Enter an article title. The tool finds bare-URL and malformed references, converts them to formatted citations, and lets you review before publishing.' )
.css( { margin: '0 0 12px 0', 'font-size': '13px', color: '#54595d' } ) );
var $row = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '12px' } );
var $inputWrap = $( '<div>' ).css( { flex: '1', position: 'relative', 'box-sizing': 'border-box' } );
var $titleInput = $( '<input>' ).attr( { type: 'text', placeholder: 'Article title (e.g. Abhijit Das)', autocomplete: 'off' } ).css( {
width: '100%', padding: '10px 12px', 'font-size': '15px',
border: '2px solid #36c', 'border-radius': '6px', 'box-sizing': 'border-box'
} );
var $suggest = $( '<div>' ).css( {
display: 'none', position: 'absolute', top: '100%', left: 0, right: 0, 'z-index': 50,
background: '#fff', border: '1px solid #a2a9b1', 'border-top': 'none',
'border-radius': '0 0 6px 6px', 'box-shadow': '0 4px 12px rgba(0,0,0,0.15)',
'max-height': '220px', overflow: 'auto', 'font-size': '14px'
} );
$inputWrap.append( $titleInput, $suggest );
var $scanBtn = $( '<button>' ).text( 'Scan article' ).css( {
padding: '10px 24px', 'font-size': '15px', cursor: 'pointer',
background: '#36c', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold'
} );
$row.append( $inputWrap, $scanBtn );
$root.append( $row );
// Search suggestions (autocomplete) for the article title input
var suggestTimer = null;
function hideSuggest() { $suggest.hide().empty(); }
$titleInput.on( 'input', function () {
var q = $.trim( $titleInput.val() );
if ( suggestTimer ) { clearTimeout( suggestTimer ); }
if ( q.length < 2 ) { hideSuggest(); return; }
suggestTimer = setTimeout( function () {
api.get( { action: 'opensearch', search: q, limit: 8, namespace: 0 } ).done( function ( d ) {
if ( $.trim( $titleInput.val() ) !== q ) { return; }
var titles = ( d && d[ 1 ] ) || [];
if ( !titles.length ) { hideSuggest(); return; }
$suggest.empty();
titles.forEach( function ( t ) {
var $opt = $( '<div>' ).text( t ).css( {
padding: '8px 12px', cursor: 'pointer', 'word-break': 'break-word'
} );
$opt.on( 'mouseenter', function () { $opt.css( 'background', '#eaecf0' ); } );
$opt.on( 'mouseleave', function () { $opt.css( 'background', '' ); } );
$opt.on( 'mousedown', function ( e ) {
e.preventDefault();
$titleInput.val( t );
hideSuggest();
} );
$suggest.append( $opt );
} );
$suggest.show();
} ).fail( function () { hideSuggest(); } );
}, 300 );
} );
$titleInput.on( 'keydown', function ( e ) { if ( e.key === 'Escape' ) { hideSuggest(); } } );
$titleInput.on( 'blur', function () { setTimeout( hideSuggest, 150 ); } );
var $msg = $( '<div>' ).css( { 'font-size': '13px', color: '#d33', 'margin-bottom': '8px' } ).hide();
$root.append( $msg );
var $status = $( '<div>' ).css( { 'font-size': '13px', color: '#54595d', 'margin-bottom': '8px' } ).hide();
$root.append( $status );
var $results = $( '<div>' ).hide();
$root.append( $results );
var state = {
title: '', wikitext: '', timestamp: '', items: []
};
function showMsg( t ) { $msg.text( t ).show(); }
function showStatus( t ) { $status.text( t ).show(); }
function scan() {
var title = $.trim( $titleInput.val() );
$msg.hide(); $results.hide().empty();
if ( !title ) {
showMsg( 'Please enter an article title.' );
return;
}
$scanBtn.prop( 'disabled', true ).text( 'Scanning…' );
showStatus( 'Fetching article…' );
api.get( {
action: 'query', prop: 'revisions', titles: title,
rvprop: 'content|timestamp', rvslots: 'main', formatversion: 2
} ).then( function ( d ) {
var pages = d.query.pages;
var page = pages[ 0 ];
if ( page.missing ) {
throw new Error( 'Article not found: ' + title );
}
state.title = page.title;
state.wikitext = page.revisions[ 0 ].slots.main.content;
state.timestamp = page.revisions[ 0 ].timestamp;
var bare = findBareRefs( state.wikitext );
var bad = findBadRefs( state.wikitext );
var all = bare.concat( bad );
all.sort( function ( a, b ) { return a.start - b.start; } );
if ( !all.length ) {
showStatus( 'No bare-URL or malformed references found in ' + state.title + '.' );
return;
}
showStatus( 'Found ' + all.length + ' reference(s) (' + bare.length + ' bare, ' + bad.length + ' malformed). Fetching citation data…' );
return processItems( all );
} ).catch( function ( err ) {
showMsg( ( err && err.message ) || 'Something went wrong.' );
$status.hide();
} ).always( function () {
$scanBtn.prop( 'disabled', false ).text( 'Scan article' );
} );
}
function processItems( bare ) {
state.items = [];
var queue = bare.slice();
var done = 0;
function next() {
if ( !queue.length ) {
renderResults();
return;
}
var b = queue.shift();
done++;
showStatus( 'Fetching citation data… (' + done + '/' + bare.length + ')' );
var url = cleanUrl( b.url );
RefGen.fetchCitation( url ).done( function ( item ) {
var params = RefGen.mapToParams( item );
params.url = url;
var inner = RefGen.buildRef( params, '', '' );
inner = inner.replace( /^<ref>/, '' ).replace( /<\/ref>$/, '' );
state.items.push( {
bare: b, url: url, ok: true,
template: params._template,
title: params.title || url,
inner: inner
} );
} ).fail( function ( reason ) {
state.items.push( { bare: b, url: url, ok: false, reason: reason || 'No citation data.' } );
} ).always( function () {
setTimeout( next, 300 );
} );
}
next();
}
function renderResults() {
$status.hide();
var okItems = state.items.filter( function ( it ) { return it.ok; } );
var skipItems = state.items.filter( function ( it ) { return !it.ok; } );
$results.append(
$( '<p>' ).html(
'Found <b>' + state.items.length + '</b> bare reference(s) in <b>' +
esc( state.title ) + '</b>: <b>' + okItems.length +
'</b> can be fixed, <b>' + skipItems.length + '</b> skipped (reason below).'
).css( { 'font-size': '14px' } )
);
if ( !okItems.length ) {
$results.append( $( '<p>' ).text( 'Nothing to fix.' ).css( { color: '#54595d' } ) );
} else {
var $list = $( '<div>' ).css( {
'max-height': '320px', overflow: 'auto', border: '1px solid #c8ccd1',
'border-radius': '6px', background: '#fff', padding: '8px', 'margin-bottom': '12px'
} );
okItems.forEach( function ( it ) {
var $item = $( '<div>' ).css( {
display: 'flex', gap: '8px', 'align-items': 'flex-start',
padding: '8px', 'border-bottom': '1px solid #eaecf0', 'font-size': '13px'
} );
var $cb = $( '<input>' ).attr( { type: 'checkbox', checked: true } )
.css( { 'margin-top': '3px' } );
it.checked = $cb;
var $txt = $( '<div>' ).css( { flex: '1', 'word-break': 'break-all' } );
$txt.append( $( '<div>' ).css( { color: '#72777d' } ).text( it.url ) );
$txt.append( $( '<div>' ).html( '→ {{' + esc( it.template ) + '}} — <b>' + esc( it.title ) + '</b>' ) );
$item.append( $cb, $txt );
$list.append( $item );
} );
$results.append( $list );
var $btnRow = $( '<div>' ).css( { display: 'flex', gap: '8px', 'align-items': 'center' } );
var $pubBtn = $( '<button>' ).text( 'Publish changes' ).css( {
padding: '10px 24px', 'font-size': '15px', cursor: 'pointer',
background: '#14866d', color: '#fff', border: 'none',
'border-radius': '6px', 'font-weight': 'bold'
} );
$pubBtn.on( 'click', publish );
var $discardBtn = $( '<button>' ).text( 'Discard' ).css( {
padding: '10px 24px', 'font-size': '15px', cursor: 'pointer',
background: '#72777d', color: '#fff', border: 'none',
'border-radius': '6px', 'font-weight': 'bold'
} );
$discardBtn.on( 'click', function () {
state.items = [];
$results.hide().empty();
showStatus( 'Discarded. Enter an article title to scan again.' );
} );
$btnRow.append( $pubBtn, $discardBtn );
$results.append( $btnRow );
$results.append( $( '<p>' ).text( 'The edit will be saved under your own account. Please review the list above first.' )
.css( { 'font-size': '12px', color: '#54595d', 'margin-top': '8px' } ) );
}
if ( skipItems.length ) {
var $skip = $( '<details>' ).css( { 'margin-top': '12px', 'font-size': '13px' } );
$skip.append( $( '<summary>' ).text( 'Skipped URLs (' + skipItems.length + ')' )
.css( { cursor: 'pointer', 'font-weight': 'bold' } ) );
var $ul = $( '<ul>' ).css( { color: '#72777d', 'word-break': 'break-all' } );
skipItems.forEach( function ( it ) {
var $li = $( '<li>' );
$li.append( $( '<div>' ).text( it.url ) );
if ( it.reason ) {
$li.append( $( '<div>' ).css( { color: '#d33', 'font-size': '12px' } ).text( it.reason ) );
}
$ul.append( $li );
} );
$skip.append( $ul );
$results.append( $skip );
}
$results.show();
}
function publish() {
if ( mw.config.get( 'wgUserName' ) === null ) {
showMsg( 'Please log in to publish changes.' );
return;
}
var okItems = state.items.filter( function ( it ) { return it.ok && it.checked.is( ':checked' ); } );
if ( !okItems.length ) {
showMsg( 'No references selected.' );
return;
}
var urlToName = {};
var usedNames = {};
state.items.forEach( function ( it ) {
if ( it.bare.name ) {
usedNames[ it.bare.name ] = true;
}
} );
var n = 0;
function freshName() {
n++;
while ( usedNames[ 'barefix' + n ] ) {
n++;
}
usedNames[ 'barefix' + n ] = true;
return 'barefix' + n;
}
var reps = [];
state.items.forEach( function ( it ) {
if ( !( it.ok && it.checked.is( ':checked' ) ) ) {
return;
}
var newRef;
if ( it.bare.name ) {
newRef = '<ref name="' + it.bare.name + '">' + it.inner + '</ref>';
} else if ( urlToName[ it.url ] ) {
newRef = '<ref name="' + urlToName[ it.url ] + '" />';
} else {
var name = freshName();
urlToName[ it.url ] = name;
newRef = '<ref name="' + name + '">' + it.inner + '</ref>';
}
reps.push( { start: it.bare.start, end: it.bare.end, text: newRef } );
} );
reps.sort( function ( a, b ) { return b.start - a.start; } );
var text = state.wikitext;
reps.forEach( function ( r ) {
text = text.slice( 0, r.start ) + r.text + text.slice( r.end );
} );
showStatus( 'Publishing…' );
api.postWithToken( 'csrf', {
action: 'edit',
title: state.title,
text: text,
basetimestamp: state.timestamp,
summary: 'Fix bare URLs: convert to formatted citations',
assert: 'user'
} ).then( function ( d ) {
var rev = d.edit && d.edit.newrevid;
$results.empty().append(
$( '<p>' ).html(
'<b>Published!</b> ' + reps.length + ' reference(s) fixed in <b>' +
esc( state.title ) + '</b>.' +
( rev ? ' <a href="' + mw.util.getUrl( 'Special:Diff/' + rev ) + '">View changes</a>' : '' )
).css( { 'font-size': '14px', color: '#14866d' } )
);
$status.hide();
} ).fail( function ( code, err ) {
$status.hide();
showMsg( 'Publish failed: ' + ( err && err.message ? err.message : code ) );
} );
}
$scanBtn.on( 'click', scan );
$titleInput.on( 'keydown', function ( e ) { if ( e.key === 'Enter' ) scan(); } );
return $root;
}
function init() {
try {
var pageName = mw.config.get( 'wgPageName' );
if ( pageName !== TOOL_PAGE && pageName.indexOf( TOOL_PAGE + '/' ) !== 0 ) {
return;
}
if ( !mw.libs.refGen ) {
return;
}
var $static = $( '#bp-brf-static' );
var $tool = buildTool();
if ( $static.length ) {
$static.replaceWith( $tool );
} else {
var $content = $( '#mw-content-text' );
if ( $content.length ) {
$content.prepend( $tool );
}
}
var $link = mw.util.addPortletLink( 'p-tb', mw.util.getUrl( TOOL_PAGE ),
'Fix bare references', 't-barefix', 'Convert bare URLs to formatted citations' );
if ( $link ) {
$( $link ).on( 'click', function ( e ) {
e.preventDefault();
window.open( mw.util.getUrl( TOOL_PAGE ), '_blank' );
} );
}
} catch ( e ) {
if ( window.console && console.error ) {
console.error( 'RefGenBarefix init failed: ' + ( e && e.message ) );
}
}
}
function waitForDeps( callback, attempts ) {
attempts = attempts || 0;
try {
if ( typeof $ !== 'undefined' && typeof mw !== 'undefined' &&
mw.util && mw.libs.refGen ) {
callback();
return;
}
} catch ( e ) {}
if ( attempts < 100 ) {
setTimeout( function () { waitForDeps( callback, attempts + 1 ); }, 100 );
} else {
try { callback(); } catch ( e ) {}
}
}
waitForDeps( function () {
try {
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', init );
} else {
init();
}
} catch ( e ) {}
} );
}() );