/**
* 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 ) );