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.
/* <nowiki> */
/* Bharat Mitra: core library (dialogs, API helpers) */
( function () {
'use strict';
if ( window.BharatMitra && window.BharatMitra.core ) { return; }
var BM = window.BharatMitra = window.BharatMitra || { modules: {}, loaded: {} };
var api = null;
var MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
var SIG = '~~' + '~~';
function getApi() {
if ( !api ) { api = new mw.Api(); }
return api;
}
function esc( s ) {
return String( s === null || s === undefined ? '' : s ).replace( /[&<>"']/g, function ( c ) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ c ];
} );
}
function cleanParam( s ) {
return String( s || '' ).replace( /\s*\|\s*/g, ' ' ).replace( /[\r\n]+/g, ' ' ).replace( /\s+/g, ' ' ).trim();
}
function monthHeading( d ) {
d = d || new Date();
return MONTHS[ d.getMonth() ] + ' ' + d.getFullYear();
}
function getPage( title ) {
return getApi().get( {
action: 'query', prop: 'revisions', titles: title,
rvprop: 'content|ids', rvslots: 'main', rvlimit: 1, formatversion: 2
} ).then( function ( r ) {
var p = r.query.pages[ 0 ];
if ( !p || p.missing || p.invalid ) { return { exists: false, content: '', revid: 0 }; }
var rev = p.revisions && p.revisions[ 0 ];
if ( !rev || !rev.slots || !rev.slots.main ) { return { exists: false, content: '', revid: 0 }; }
return { exists: true, content: rev.slots.main.content, revid: rev.revid };
} );
}
function savePage( title, text, summary, baserevid ) {
var params = { action: 'edit', title: title, text: text, summary: summary, format: 'json' };
if ( baserevid ) { params.baserevid = baserevid; }
return getApi().postWithToken( 'edit', params ).then( function ( r ) {
if ( r && r.edit && r.edit.result === 'Success' ) { return r; }
throw new Error( 'Could not be saved.' );
} );
}
function appendToPage( title, block, summary ) {
return getPage( title ).then( function ( p ) {
var text = p.exists ? p.content.replace( /\s+$/, '' ) + '\n' + block + '\n' : block + '\n';
return savePage( title, text, summary, p.revid );
} );
}
function appendUnderMonth( title, block, summary ) {
return getPage( title ).then( function ( p ) {
var content = p.content || '';
var heading = '== ' + monthHeading() + ' ==';
var cleanBlock = String( block ).replace( /\s+$/, '' );
var idx = content.indexOf( heading );
var out;
if ( idx === -1 ) {
out = content.replace( /\s+$/, '' ) + ( content ? '\n\n' : '' ) + heading + '\n' + cleanBlock + '\n';
} else {
var next = content.indexOf( '\n== ', idx + heading.length );
if ( next === -1 ) {
out = content.replace( /\s+$/, '' ) + '\n' + cleanBlock + '\n';
} else {
out = content.slice( 0, next ).replace( /\s+$/, '' ) + '\n' + cleanBlock + '\n' + content.slice( next );
}
}
return savePage( title, out, summary, p.revid );
} );
}
function addSection( title, heading, body, summary ) {
return getApi().postWithToken( 'edit', {
action: 'edit', title: title, section: 'new', sectiontitle: heading,
text: body, summary: summary, format: 'json'
} ).then( function ( r ) {
if ( r && r.edit && r.edit.result === 'Success' ) { return r; }
throw new Error( 'The section could not be added.' );
} );
}
function parse( text, title ) {
var previewText = String( text ).replace( /\{\{subst:/g, '{{' ).replace( /~{4}/g, '(signature)' );
return getApi().post( {
action: 'parse', text: previewText, title: title || mw.config.get( 'wgPageName' ),
prop: 'text', disableeditsection: true, contentmodel: 'wikitext', format: 'json'
} ).then( function ( r ) { return r.parse.text[ '*' ]; } );
}
function firstEditor( title ) {
return getApi().get( {
action: 'query', prop: 'revisions', titles: title,
rvprop: 'user', rvdir: 'newer', rvlimit: 1, formatversion: 2
} ).then( function ( r ) {
var p = r.query.pages[ 0 ];
if ( p && p.revisions && p.revisions[ 0 ] ) { return p.revisions[ 0 ].user; }
return null;
} );
}
/* Best-effort creator notification. Always resolves with a status string
('sent', 'none', 'skipped', 'failed') and never rejects, so a
notice problem can no longer stall or fail the main action.
Imported pages often have a first editor like "imported>Example" or
"->Example": not a local account, and ">" is not allowed in a page
title, so there is no user talk page to write to ('skipped').
The creator is notified even when the creator is the current user
(self-notification enabled at the user's request, 1 Nov 2026). */
function notifyCreator( pageTitle, block, summary ) {
function failed() { return 'failed'; }
return firstEditor( pageTitle ).then( function ( creator ) {
if ( !creator ) { return 'none'; }
if ( /^(imported>|->)/.test( creator ) || /[<>[\]{}|#]/.test( creator ) ) { return 'skipped'; }
return getApi().get( {
action: 'query', list: 'users', ususers: creator, formatversion: 2
} ).then( function ( r ) {
var u = r.query && r.query.users && r.query.users[ 0 ];
if ( !u || u.invalid || u.missing || !u.userid ) { return 'skipped'; }
return appendUnderMonth( 'User talk:' + creator, block, summary ).then(
function () { return 'sent'; }, failed );
}, failed );
}, failed );
}
var CONFIG_PAGE = 'User:' + ( mw.config.get( 'wgUserName' ) || '' ) + '/BharatMitra-config.json';
var configCache = null;
var CONFIG_DEFAULTS = { warnLevel: 'auto', speedyNotify: true, alwaysPreview: true };
function currentConfig() { return configCache || CONFIG_DEFAULTS; }
function getConfig() {
if ( configCache ) { return $.Deferred().resolve( configCache ).promise(); }
return getPage( CONFIG_PAGE ).then( function ( p ) {
if ( p.exists ) {
try {
configCache = $.extend( {}, CONFIG_DEFAULTS, JSON.parse( p.content ) );
return configCache;
} catch ( e ) { /* use the defaults */ }
}
configCache = $.extend( {}, CONFIG_DEFAULTS );
return configCache;
} ).catch( function () {
configCache = $.extend( {}, CONFIG_DEFAULTS );
return configCache;
} );
}
function saveConfig( cfg ) {
configCache = $.extend( {}, CONFIG_DEFAULTS, cfg );
return getApi().postWithToken( 'edit', {
action: 'edit', title: CONFIG_PAGE, text: JSON.stringify( configCache, null, 2 ),
summary: 'Settings saved (using [[Bharatpedia:Bharat Mitra|Bharat Mitra]])', contentmodel: 'json', format: 'json'
} ).then( function ( r ) {
if ( r && r.edit && r.edit.result === 'Success' ) { return r; }
throw new Error( 'Settings could not be saved.' );
} );
}
function dialog( opts ) {
$( '.bm-overlay' ).remove();
var confirmed = false;
var usePreview = ( opts.preview && currentConfig().alwaysPreview !== false ) ? opts.preview : null;
var overlay = $( '<div>' ).addClass( 'bm-overlay' );
var panel = $( '<div>' ).addClass( 'bm-dialog' ).attr( 'role', 'dialog' );
var head = $( '<div>' ).addClass( 'bm-dialog-head' );
head.append( $( '<span>' ).addClass( 'bm-dialog-title' ).text( opts.title || 'Bharat Mitra' ) );
var closeBtn = $( '<button>' ).attr( 'type', 'button' ).addClass( 'bm-x' ).text( '×' );
head.append( closeBtn );
panel.append( head );
if ( opts.help ) { panel.append( $( '<div>' ).addClass( 'bm-help' ).text( opts.help ) ); }
if ( opts.info ) { panel.append( $( '<div>' ).addClass( 'bm-info' ).text( opts.info ) ); }
var form = $( '<div>' ).addClass( 'bm-form' );
var inputs = {};
( opts.fields || [] ).forEach( function ( f ) {
var row = $( '<div>' ).addClass( 'bm-row' );
var input;
if ( f.type === 'select' ) {
input = $( '<select>' );
( f.options || [] ).forEach( function ( o ) {
if ( o.group ) {
var og = $( '<optgroup>' ).attr( 'label', o.group );
o.options.forEach( function ( oo ) {
og.append( $( '<option>' ).attr( 'value', oo[ 0 ] ).text( oo[ 1 ] ) );
} );
input.append( og );
} else {
input.append( $( '<option>' ).attr( 'value', o[ 0 ] ).text( o[ 1 ] ) );
}
} );
if ( f.value ) { input.val( f.value ); }
} else if ( f.type === 'textarea' ) {
input = $( '<textarea>' ).attr( 'rows', f.rows || 3 );
if ( f.value ) { input.val( f.value ); }
} else if ( f.type === 'checkbox' ) {
input = $( '<input>' ).attr( 'type', 'checkbox' );
if ( f.value ) { input.prop( 'checked', true ); }
} else {
input = $( '<input>' ).attr( 'type', 'text' );
if ( f.value ) { input.val( f.value ); }
if ( f.placeholder ) { input.attr( 'placeholder', f.placeholder ); }
}
input.addClass( 'bm-input' ).attr( 'data-bm-field', f.name );
inputs[ f.name ] = { def: f, el: input };
row.append( $( '<label>' ).addClass( 'bm-label' ).text( f.label ) );
row.append( input );
form.append( row );
} );
panel.append( form );
var previewBox = $( '<div>' ).addClass( 'bm-preview' ).hide();
panel.append( previewBox );
var status = $( '<div>' ).addClass( 'bm-status' );
panel.append( status );
var foot = $( '<div>' ).addClass( 'bm-foot' );
var cancelBtn = $( '<button>' ).attr( 'type', 'button' ).addClass( 'bm-btn' ).text( 'Cancel' );
var submitBtn = $( '<button>' ).attr( 'type', 'button' ).addClass( 'bm-btn bm-primary' )
.text( usePreview ? 'Show preview' : ( opts.submitLabel || 'Submit' ) );
foot.append( cancelBtn, submitBtn );
panel.append( foot );
overlay.append( panel );
$( document.body ).append( overlay );
function values() {
var v = {};
Object.keys( inputs ).forEach( function ( k ) {
var it = inputs[ k ];
v[ k ] = it.def.type === 'checkbox' ? it.el.prop( 'checked' ) : it.el.val();
} );
return v;
}
function close() { overlay.remove(); }
function setBusy( b ) {
submitBtn.prop( 'disabled', b );
cancelBtn.prop( 'disabled', b && !opts.stoppable );
}
form.on( 'input change', function () {
if ( confirmed ) {
confirmed = false;
previewBox.hide();
submitBtn.text( 'Show preview' );
status.text( '' );
}
} );
closeBtn.on( 'click', close );
cancelBtn.on( 'click', function () {
if ( submitBtn.prop( 'disabled' ) && opts.stoppable && opts.onStop ) { opts.onStop(); return; }
close();
} );
overlay.on( 'click', function ( e ) { if ( e.target === overlay[ 0 ] ) { close(); } } );
submitBtn.on( 'click', function () {
var v = values();
var missing = ( opts.fields || [] ).filter( function ( f ) {
return f.required && f.type !== 'checkbox' && !String( v[ f.name ] || '' ).trim();
} );
if ( missing.length ) {
status.text( 'Please fill in the required information: ' + missing.map( function ( f ) { return f.label; } ).join( ', ' ) );
return;
}
if ( usePreview && !confirmed ) {
setBusy( true );
status.text( 'Preparing the preview…' );
var wt;
try { wt = usePreview( v ); } catch ( e ) { setBusy( false ); status.text( e.message ); return; }
var ptitle = opts.previewTitle ? opts.previewTitle( v ) : null;
parse( wt, ptitle ).then( function ( html ) {
previewBox.html( html ).show();
confirmed = true;
submitBtn.text( opts.submitLabel ? 'Confirm: ' + opts.submitLabel : 'Confirm and submit' );
status.text( 'Check the preview and confirm.' );
setBusy( false );
} ).catch( function () {
setBusy( false );
status.text( 'The preview could not be created. Please try again.' );
} );
return;
}
setBusy( true );
status.text( 'Sending…' );
Promise.resolve()
.then( function () { return opts.onSubmit( v ); } )
.then( function () {
close();
mw.notify( opts.success || 'Done.', { type: 'success' } );
if ( opts.reload ) { location.reload(); }
} )
.catch( function ( e ) {
setBusy( false );
var emsg = 'Something went wrong.';
if ( e && e.message ) { emsg = e.message; }
else if ( typeof e === 'string' && e ) { emsg = e; }
status.text( 'Error: ' + emsg );
} );
} );
var first = panel.find( '.bm-input' ).first();
if ( first.length ) { first.focus(); }
return { close: close };
}
BM.register = function ( name, def ) { BM.modules[ name ] = def; };
BM.SIG = SIG;
BM.core = {
api: getApi, esc: esc, cleanParam: cleanParam, monthHeading: monthHeading,
getPage: getPage, savePage: savePage, appendToPage: appendToPage,
appendUnderMonth: appendUnderMonth, addSection: addSection,
parse: parse, firstEditor: firstEditor, notifyCreator: notifyCreator, dialog: dialog,
getConfig: getConfig, saveConfig: saveConfig, currentConfig: currentConfig
};
BM.targetUser = function () {
var u = mw.config.get( 'wgRelevantUserName' );
if ( u ) { return u; }
var ns = mw.config.get( 'wgNamespaceNumber' );
if ( ns === 2 || ns === 3 ) {
return mw.config.get( 'wgTitle' ).split( '/' )[ 0 ];
}
return null;
};
}() );
/* </nowiki> */