Note: After saving, changes may not occur immediately. Click here to learn how to bypass your browser's cache.
  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Cmd-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (Cmd-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences

For details and instructions about other browsers, see Wikipedia:Bypass your cache.

/**
 * Simple replace-on-load-or-save framework
 *
 * Configurations:
 *     enable:      disable the entire gadget on a page
 *     debug:       enable some ill-defined debug logging
 *     namespaces:  enable the gadget only on the given namespaces
 *     saveHooks:   list of callback functions to run, in order in Save or Preview.
 *                   One param is given: the main edit box.
 *     loadHooks:   list of callbacks to run when loaded.
 *     quickReplacements: list of regex/replacement pairs to apply, in order
 *                 on save.
 *     quickReplacements_global: true if all quick replacements should be
 *                 made global regexes. Default: true.
 *     autoRefs:   enable automatic refs insertion. Default: true
 *
 * Configure it like this, from your main JS:
 *
 * mw.hook( 'save_load_actions.config' ).add( function ( cfg ) {
 *
 *   var repls = [
 *     [ /^c,, ?(.*)$/m, '{{center|$1}}' ],
 *   ];
 *
 *   cfg.enabled = true;
 *   cfg.namespaces = [ 'Page', '', 'Author', 'User' ];
 *   cfg.saveHooks = [ yourFunction ];
 *   cfg.quickReplacements = repls;
 * } );
 *
 */

// uncomment when running locally - gadgets get an auto-wrapped closure
( function ( $, mw ) {

	'use strict';

	const gadgetName = 'save_load_actions';

	const SLAState = {
		config: {
			enabled: true,
			debug: false,
			namespaces: [ 'Page' ],
			saveHooks: [],
			loadHooks: [],
			quickReplacements: [],
			autoRefs: true
		}
	};

	function slaLog( text ) {
		if ( SLAState.debug ) {
			// eslint-disable-next-line no-console
			console.log( text );
		}
	}

	function updateTextarea( editbox, content ) {
		// deal with CodeMirror?
		editbox.value = content;
	}

	/*
	 * Iterate the user's regexes and apply them in order, globally to the editbox
	 */
	function doQuickReplacements( editbox ) {
		let content = editbox.value;
		const qReps = SLAState.config.quickReplacements;

		for ( let i = 0; i < qReps.length; i++ ) {

			let flags = qReps[ i ][ 0 ].flags;
			// all quick replacements are global
			flags = 'g' + flags.replace( 'g', '' );
			const re = new RegExp( qReps[ i ][ 0 ].source, flags );
			content = content.replace( re, qReps[ i ][ 1 ] );
		}
		updateTextarea( editbox, content );
	}

	function getPageNum() {
		return parseInt( mw.config.get( 'wgTitle' ).split( '/' ).slice( -1 )[ 0 ] );
	}

	/*
	 * Move things that look like autorefs to the right place
	 */
	function doAutoRefs( editbox ) {

		// first split up the text a pick out ref bodies
		let text = editbox.value;

		const refBodyRe = /(?:^|\n)\* *(r[0-9]+)(?:=?(\w*)) *((?:.|\n)*?)(?=\n\n|\n\*|\n*$)/g;

		// Iterate matchs for footnote bodies, replacing as we go

		// Collect refs we found - we'll delete the bodies at the end
		// because we're going to badly mess with the indexes
		// We don't wish to just delete all of them, in case there's a body
		// and no usage - then deleting it would just delete the text, not move it
		const foundNums = [];

		// Git gud
		// eslint-disable-next-line no-restricted-properties
		const matches = text.matchAll( refBodyRe );

		for ( const match of matches ) {
			const num = match[ 1 ];
			const name = match[ 2 ];
			const body = match[ 3 ];

			// may need an autoname
			const autoName = name || ( 'p' + getPageNum() + '_' + num );

			// this is a follow-ref, place it at the top
			if ( num === 'r0' ) {
				const refMarkup = '<ref follow="' + autoName + '">' + body + '</ref>';

				// prepend to whole text field
				text = refMarkup + '\n' + text;

				foundNums.push( num );
			} else {
			// a normal reference

				const reStr = '<\\s*' + num + '\\s*\\/?\\s*>';

				const usageMatches = text.match( new RegExp( reStr, 'g' ) );

				if ( usageMatches ) {
					if ( usageMatches.length === 1 ) {
						let refMarkup = '';
						if ( name ) {
						// named
							refMarkup += '<ref name="' + name + '">';
						} else {
							refMarkup += '<ref>';
						}
						refMarkup += body + '</ref>';
						text = text.replace( new RegExp( reStr, '' ), refMarkup );

						foundNums.push( num );
					} else if ( usageMatches.length > 1 ) {
						const ref1Markup = '<ref name="' + autoName + '">' + body + '</ref>';
						const ref2Markup = '<ref name="' + autoName + '"/>';

						// first instance with the content
						text = text.replace( new RegExp( reStr, '' ), ref1Markup );
						// And the rest with the named ref only
						text = text.replace( new RegExp( reStr, 'g' ), ref2Markup );

						foundNums.push( num );
					}
				}
			}
		}

		// Finally, strip out the old bodies of the ones we found
		text = text.replace( refBodyRe, ( wholeMatch, num ) => {
			if ( foundNums.indexOf( num ) !== -1 ) {
				return '';
			}
			return wholeMatch;
		} );

		updateTextarea( editbox, text );
	}

	/*
	 * Run user functions and replacements
	 */
	function slaActionOnSave() {

		slaLog( 'On save' );

		const editbox = document.getElementById( 'wpTextbox1' );

		doQuickReplacements( editbox );

		if ( SLAState.config.autoRefs ) {
			doAutoRefs( editbox );
		}

		for ( let i = 0; i < SLAState.config.saveHooks.length; i++ ) {
			SLAState.config.saveHooks[ i ]( editbox );
		}
	}

	/*
	 * Run setup, apply any load functions
	 */
	function slaActionOnLoad() {

		const editbox = document.getElementById( 'wpTextbox1' );

		if ( editbox ) {

			for ( let i = 0; i < SLAState.config.loadHooks.length; i++ ) {
				SLAState.config.loadHooks[ i ]( editbox );
			}
		}

		// Install the on-save hook
		// eslint-disable-next-line no-jquery/no-global-selector
		$( '.editButtons' ).on( 'click', slaActionOnSave );
	}

	function slaActionSetup() {

		mw.hook( gadgetName + '.config' ).fire( SLAState.config );

		if ( !SLAState.config.enabled || SLAState.config.namespaces.indexOf(
			mw.config.get( 'wgCanonicalNamespace' ) ) === -1 ) {
			return;
		}

		if ( [ 'edit', 'submit' ].indexOf( mw.config.get( 'wgAction' ) ) !== -1 ) {
			mw.loader.using( 'ext.proofreadpage.page', function () {
			// mimic code in the extension, there is a conditionnal deps on ext.wikiEditor.
				if ( mw.user.options.get( 'usebetatoolbar' ) &&
					mw.loader.getModuleNames().indexOf( 'ext.wikiEditor' ) !== -1 ) {
					const loadDeps = [ 'ext.wikiEditor' ];
					if ( mw.user.options.get( 'codemirror-syntax-highlight' ) === 1 ) {
						loadDeps.push( 'ext.CodeMirror.lib' );
					}
					mw.loader.using( loadDeps, function () {
						slaActionOnLoad();
					} );
				} else {
					slaActionOnLoad();
				}
			} );
		}
	}
	$( slaActionSetup );

// eslint-disable-next-line no-undef
}( jQuery, mediaWiki ) );