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 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 { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ 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> */