User:Mifield/common/script-installer/main.js

// "use strict";

mw.loader.getScript( mw.libs.xsi.utils.script.path( "./lib.js" ) ).then( => {

// An mw.Api object let api; // Keep "common" at beginning const SKIN = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];

// How many scripts do we need before we show the quick filter? let NUM_SCRIPTS_FOR_SEARCH = 5;

// The master import list, keyed by target. (A "target" is a user J subpage	// where the script is imported, like "common" or "vector".) Set in buildImportList let scripts = {};

// Local scripts, keyed on name; value will be the target. Set in buildImportList. let localScriptsByName = {};

// How many scripts are installed? let scriptCount = 0;

// Goes on the end of edit summaries const EDIT_SUMMARY_POSTFIX = " (script-installer (forked))";

// const EMPTY_STR = "";

const	xsi =   mw.libs.xsi, CHAR =  xsi.CHAR, PREFAB = xsi.PREFAB, REGEX = xsi.REGEX, STR =   xsi.STR, TAG =   xsi.TAG, TEXT =  xsi.TEXT; const UNSPECIFIED = Symbol.for( "function parameter is not specified" ); /* const	traverseObject =   xsi.utils.traverseObject, objectFromKeyMaps = xsi.utils.objectFromKeyMaps, to = traverseObject, okm = objectFromKeyMaps; */

const USERSPACE_LOCAL_NAME = mw.config.get( "wgFormattedNamespaces" )[2];

/**	 * Constructs an Import. An Import is a line in a J file that will import a	 * user script. Properties: *	 * - "page" is a page name, such as "User:Foo/Bar.js". * - "wiki" is a wiki from which the script is loaded, such as * "en.wikipedia". If null, the script is local, on the user's	 * wiki. * - "url" is a URL that can be passed into mw.loader.load. * - "target" is the title of the user subpage where the script is, * without the .js ending: for example, "common". * - "xsi-stopped" is whether this import is commented out. * - "type" is 0 if local, 1 if remotely loaded, and 2 if URL. *	 * EXACTLY one of "page" or "url" are null for every Import. This * constructor should not be used directly; use the factory * functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead. */

// class Import {}

function Import( page, wiki, url, target, stopped ) { this.page = page; this.wiki = wiki; this.url = url; this.target = target; this.stopped = stopped; this.type = this.url ? 2 : ( this.wiki ? 1 : 0 ); // alert(this.type + " " + this.page + " " + this.wiki + " " + this.url ) }

Import.ofLocal = ( page, target, stopped ) => { if ( stopped === undefined ) stopped = false; return new Import( page, null, null, target, stopped ); };

/** URL to Import. Assumes wgScriptPath is "/w" */ Import.ofUrl = ( url, target, stopped ) => { if ( stopped === undefined ) stopped = false; let match = REGEX.url.exec( url ); if ( match ) { let title = decodeURIComponent( match[2].replace( /&$/, STR.empty ) ), wiki = decodeURIComponent( match[1] ); return new Import( title, wiki, null, target, stopped ); }		return new Import( null, null, url, target, stopped ); };

Import.fromJs = ( line, target ) => { let match = REGEX.import.exec( line ); if ( match ) { return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] ); }

match = REGEX.loader.exec( line ); if ( match ) { return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] ); }	};

Import.prototype.getDescription = ( useWikitext ) => { switch( this.type ) { case 0: return useWikitext ? ( STR.wrap.brack( this.page, 2 ) ) : this.page; case 1: return TEXT.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki ); case 2: return this.url; }	};

/**	 * Human-readable (NOT necessarily suitable for ResourceLoader) URL. */	Import.prototype.getHumanUrl = => { switch( this.type ) { case 0: return "/wiki/" + encodeURI( this.page ); case 1: return STR.slashes( 2 ) + this.wiki + ".org/wiki/" + encodeURI( this.page ); case 2: return this.url; }	};

Import.prototype.toJs = => { let dis = this.stopped ? STR.slashes( 2 ) : STR.empty, url = this.url; switch( this.type ) { case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: " + escapeForJsComment( this.page ) + ""; case 1: url = STR.slashes( 2 ) + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" + encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; /* FALL THROUGH */ case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');"; }	};

/**	 * Import the script. */	Import.prototype.import = => { return api.postWithEditToken( {			action: "edit",			title: getFullTarget( this.target ),			summary: TEXT.action.import.summary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + EDIT_SUMMARY_POSTFIX,			appendtext: CHAR.NL + this.toJs		} ); };

/**	 * Get all line numbers from the target page that mention * the specified script. */	Import.prototype.getLineNums = ( targetWikitext ) => { function quoted( string ) { return new RegExp( `(['"])${ escapeForRegex( string ) }\\1` );		}		let toFind;		switch( this.type ) {			case 0: toFind = quoted( escapeForJsString( this.page ) ); break;			case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +							escapeForRegex( encodeURIComponent( this.page ) ) ); break;			case 2: toFind = quoted( escapeForJsString( this.url ) ); break;		}		let lineNums = [], lines = targetWikitext.split( CHAR.NL );		for ( let i = 0; i < lines.length; i++ ) {			if ( toFind.test( lines[i] ) ) {				lineNums.push( i );			}		}		return lineNums;	};

/**	 * "forget"s the given import. That is, delete all lines from the * target page that import the specified script. */	Import.prototype.forget = => { let that = this; return getWikitext( getFullTarget( this.target ) ).then( ( wikitext ) => {			let lineNums = that.getLineNums( wikitext ),				newWikitext = wikitext.split( CHAR.NL ).filter( ( _, idx ) => { return lineNums.indexOf( idx ) < 0; } ).join( CHAR.NL );			return api.postWithEditToken( { action: "edit", title: getFullTarget( that.target ), summary: TEXT.action.forget.summary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + EDIT_SUMMARY_POSTFIX, text: newWikitext } );		} );	};

/**	 * Sets whether the given import is stopped, based on the provided * boolean value. */	Import.prototype.setStopped = ( stopped ) => { let that = this; this.stopped = stopped; return getWikitext( getFullTarget( this.target ) ).then( ( wikitext ) => {			let lineNums = that.getLineNums( wikitext ),				newWikitextLines = wikitext.split( CHAR.NL );

if ( stopped ) { lineNums.forEach( ( lineNum ) => {					if ( newWikitextLines[lineNum].trim.indexOf( STR.slashes( 2 ) ) != 0 ) {						newWikitextLines[lineNum] = STR.slashes( 2 ) + newWikitextLines[lineNum].trim;					}				} ); } else { lineNums.forEach( ( lineNum ) => {					if ( newWikitextLines[lineNum].trim.indexOf( STR.slashes( 2 ) ) == 0 ) {						newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, STR.empty );					}				} ); }

const summary = ( stopped ? TEXT.action.stop.summary : TEXT.action.start.summary ) .replace( "$1", that.getDescription( /* useWikitext */ true ) ) + EDIT_SUMMARY_POSTFIX; return api.postWithEditToken( {				action: "edit",				title: getFullTarget( that.target ),				summary: summary,				text: newWikitextLines.join( CHAR.NL )			} ); } );	};

Import.prototype.toggleStopped = => { this.stopped = !this.stopped; return this.setStopped( this.stopped ); };

/**	 * Move this import to another file. */	Import.prototype.move = ( newTarget ) => { if ( this.target === newTarget ) return; const old = new Import( this.page, this.wiki, this.url, this.target, this.stopped ); this.target = newTarget; return $.when( old.forget, this.import ); };

function getAllTargetWikitexts { return $.getJSON(			mw.util.wikiScript( "api" ),			{				format: "json",				action: "query",				prop: "revisions",				rvprop: "content",				rvslots: "main",				titles: SKIN.map( getFullTarget ).join( CHAR.pipe )			}		).then( ( data ) => {			if ( data && data.query && data.query.pages ) {				let result = {};					prefixLength = mw.config.get( "wgUserName" ).length + 6;				Object.values( data.query.pages ).forEach( ( moreData ) => { const nameWithoutExtension = new mw.Title( moreData.title ).getNameText; const targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( CHAR.slash ) + 1 ); result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main[CHAR.asterisk] : null; } );				return result;			}		} ); }

function buildImportList { return getAllTargetWikitexts.then( ( wikitexts ) => {			Object.keys( wikitexts ).forEach( ( targetName ) => { let targetImports = []; if ( wikitexts[ targetName ] ) { let lines = wikitexts[ targetName ].split( CHAR.NL ); let currImport; for ( let i = 0; i < lines.length; i++ ) { currImport = Import.fromJs( lines[i], targetName ); if ( currImport ) { targetImports.push( currImport ); scriptCount++; if ( currImport.type === 0 ) { if ( !localScriptsByName[ currImport.page ] ) localScriptsByName[ currImport.page ] = []; localScriptsByName[ currImport.page ].push( currImport.target ); }						}					}				}				scripts[ targetName ] = targetImports; } );		} );	}

/*	 * "Normalizes" (standardizes the format of) lines in the given * config page. */	function normalize( target ) { return getWikitext( getFullTarget( target ) ).then( ( wikitext ) => {			let lines = wikitext.split( CHAR.NL ),				newLines = Array( lines.length ),				currImport;			for ( let i = 0; i < lines.length; i++ ) {				currImport = Import.fromJs( lines[i], target );				if ( currImport ) {					newLines[i] = currImport.toJs;				} else {					newLines[i] = lines[i];				}			}			return api.postWithEditToken( { action: "edit", title: getFullTarget( target ), summary: TEXT.action.normalize.summary, text: newLines.join( CHAR.NL ) } );		} );	}

function conditionalReload( openPanel ) { if ( window.xScriptInstallerAutoReload ) { if ( openPanel ) document.cookie = "open_script_installer=yes"; window.location.reload( true ); }	}

/********************************************	 *	 * UI code *	 ********************************************/	function makePanel { let list = $( TAG.div ).attr( "id", "xsi-panel" ) .append( $( TAG.header ).text( TEXT.panelHeader ) ); let container = $( TAG.div ).addClass( "xsi-container" ).appendTo( list );

// Container for checkboxes container.append( $( TAG.div )			.attr( "class", "xsi-checkbox-container" )			.append( $( TAG.input ) .attr( { id: "xsi-toggle-normalize", type: "checkbox" } ) .click( => {						$( ".xsi-toggle-normalize-wrapper" ).toggle( 0 );					} ), $( TAG.label ) .attr( "for", "xsi-toggle-normalize" ) .text( TEXT.action.toggle.normalize ), $( TAG.input ) .attr( { id: "xsi-toggle-move", type: "checkbox" } ) .click( => {						$( ".xsi-toggle-move-wrapper" ).toggle( 0 );					} ), $( TAG.label ) .attr( "for", "xsi-toggle-move" ) .text( TEXT.action.toggle.move ) ) ); if ( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) { container.append( $( TAG.div )				.attr( "class", "xsi-filter-container" )				.append( $( TAG.label ) .attr( "for", "xsi-toggle-filter" ) .text( TEXT.quickFilter ), $( TAG.input ) .attr( { id: "xsi-toggle-filter", type: "text" } ) .on( "input", => {							let filterString = $( this ).val;							if ( filterString ) {								let selector = "#xsi-panel li[name*='" +										$.escapeSelector( $( this ).val ) + "']";								$( "#xsi-panel li.script" ).toggle( false );								$( selector ).toggle( true );							} else {								$( "#xsi-panel li.script" ).toggle( true );							}						} ) ) );

// Now, get the checkboxes out of the way container.find( ".xsi-checkbox-container" ) .css( "float", "right" ); }		$.each( scripts, ( targetName, targetImports ) => {			let fmtTargetName = ( targetName === "common" ? "common (applies to all skins)" : targetName );				if ( targetImports.length ) {				container.append( $( TAG.h2 ).append(						fmtTargetName,						$( TAG.span )						.addClass( "xsi-toggle-normalize-wrapper" )						.append( " (",							$( TAG.a )								.text( "normalize" )								.click( => { normalize( targetName ).done( => {										conditionalReload( true );									} ); } ),							")" )							.hide ), $( TAG.ul ).append(							targetImports.map( ( anImport ) => { return $( TAG.li ) .addClass( "xsi-script" ) .attr( "name", anImport.getDescription ) .append(										$( TAG.a )											.text( anImport.getDescription )											.addClass( "xsi-script" )											.attr( "href", anImport.getHumanUrl ),										" (", $( TAG.a ) .text( TEXT.action.forget.verb ) .click( => {												$( this ).text( TEXT.action.forget.acting );												anImport.forget.done(  => { conditionalReload( true ); } );											} ),										PREFAB.pipe, $( TAG.a ) .text( anImport.stopped ? TEXT.action.start.verb : TEXT.action.stop.verb ) .click( => {												$( this ).text( anImport.stopped ? TEXT.action.start.acting : TEXT.action.stop.acting );												anImport.toggleStopped.done( => { $( this ).toggleClass( "xsi-stopped" ); conditionalReload( true ); } );											} ),										$( TAG.span ) .addClass( "xsi-toggle-move-wrapper" ) .append(											PREFAB.pipe,											$( TAG.a )												.text( TEXT.action.move.verb )												.click( => { let dest = null; let PROMPT = TEXT.prompt.move + CHAR.space + SKIN.join( ", " ); do { dest = ( window.prompt( PROMPT ) || STR.empty ).toLowerCase; } while ( dest && SKIN.indexOf( dest ) < 0 ); if ( !dest ) return; $( this ).text( TEXT.action.move.acting ); anImport.move( dest ).done( => {														conditionalReload( true );													} ); } )											)											.hide, ")" )								.toggleClass( "xsi-stopped", anImport.stopped ); } ) ) );				}		} );		return list; }

function buildCurrentPageInstallElement { let addingInstallLink = false; // will we be adding a legitimate install link? let importElement = $( TAG.span ); // only used if addingInstallLink is set to true

let namespaceNumber = mw.config.get( "wgNamespaceNumber" ); let pageName = mw.config.get( "wgPageName" );

// Namespace 2 is User if ( namespaceNumber === 2 &&				pageName.indexOf( CHAR.slash ) > 0 ) { let contentModel = mw.config.get( "wgPageContentModel" ); if ( contentModel === "javascript" ) { let prefixLength = mw.config.get( "wgUserName" ).length + 6; if ( pageName.indexOf( USERSPACE_LOCAL_NAME + CHAR.colon + mw.config.get( "wgUserName" ) ) === 0 ) { let skinIndex = SKIN.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) ); if ( skinIndex >= 0 ) { return $( TAG.abbr ).text( TEXT.error.import.base) .attr( "title", TEXT.error.import.builtin ); }				}				addingInstallLink = true; } else { return $( TAG.abbr ).text( TEXT.error.import.base + " (" + TEXT.error.import.notJavaScript + ")" ) .attr( "title", TEXT.error.import.contentModel.replace( "$1", contentModel ) ); }		}

// Namespace 8 is MediaWiki if ( namespaceNumber === 8 ) { return $( TAG.a ).text( TEXT.action.controlInPrefs.verb ) .attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" ); }

let editRestriction = mw.config.get( "wgRestrictionEdit" ) || []; if ( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&			( editRestriction.indexOf( "sysop" ) >= 0 || editRestriction.indexOf( "editprotected" ) >= 0 ) ) { importElement.append( CHAR.space,				$( TAG.abbr ).append( $( TAG.img ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ), TEXT.warning.insecure )				.attr( "title", TEXT.warning.namespace ) ); addingInstallLink = true; }

if ( addingInstallLink ) { let fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, CHAR.space ); importElement.prepend( $( TAG.a )					.attr( "id", "script-installer-main-install" )					.text( localScriptsByName[ fixedPageName ] ? TEXT.action.forget.verb : TEXT.action.import.verb )					.click( makeLocalInstallClickHandler( fixedPageName ) ) );

// If the script is installed but stopped, allow the user to enable it			let allScriptsInTarget = scripts[ localScriptsByName[ fixedPageName ] ]; let importObj = allScriptsInTarget && allScriptsInTarget.find( ( anImport ) => { return anImport.page === fixedPageName; } ); if ( importObj && importObj.stopped ) { importElement.append( PREFAB.pipe,					$( TAG.a )						.attr( "id", "script-installer-main-enable" )						.text( TEXT.action.start.verb )						.click( => { $( this ).text( TEXT.action.start.acting ); importObj.setStopped( false ).done( => {								conditionalReload( false );							} ); } ) );			}			return importElement; }

return $( TAG.abbr ).text( TEXT.error.import.base + CHAR.space + TEXT.warning.insecure ) .attr( "title", TEXT.error.namespace ); }

function showUi { let fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, CHAR.space ); $( "#firstHeading" ).append( $( TAG.span )			.attr( "id", "script-installer-top-container" )			.append( buildCurrentPageInstallElement, PREFAB.pipe, $( TAG.a ) .text( TEXT.action.manage.verb ).click( => {						if ( !document.getElementById( "xsi-panel" ) ) {							$( "#mw-content-text" ).before( makePanel );						} else {							$( "#xsi-panel" ).remove;						}					 } ) ) ); }

function attachInstallLinks { // At the end of each transclusion, there is		//  $( "span.scriptInstallerLink" ).each( => {			let scriptName = this.id;			$( this ).append( PREFAB.pipe, $( TAG.a ) .text( localScriptsByName[ scriptName ] ? TEXT.action.forget.verb : TEXT.action.import.verb ) .click( makeLocalInstallClickHandler( scriptName ) ) );		} );

$( "table.infobox-user-script" ).each( => {			let scriptName = $( this ).find( "th:contains('Source')" ).next.text ||					mw.config.get( "wgPageName" );			scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];			$( this ).children( "tbody" ).append( $( TAG.tr ).append( $( TAG.td )					.attr( "colspan", "2" )					.addClass( "script-installer-ibx" )					.append( $( TAG.button ) .addClass( "mw-ui-button mw-ui-progressive mw-ui-big" ) .text( localScriptsByName[ scriptName ] ? TEXT.action.forget.verb : TEXT.action.import.verb ) .click( makeLocalInstallClickHandler( scriptName ) ) ) ) );		} ); }

function makeLocalInstallClickHandler( scriptName ) { return => { let $this = $( this ); if ( $this.text === TEXT.action.import.verb ) { let warning = TEXT.warning.risk; if ( scriptName.indexOf( CHAR.slash ) >= 0 ) { warning = warning.replace( '$1', TEXT.warning.trustAuthor.replace( '$1', scriptName.substring( 0, scriptName.indexOf( CHAR.slash ) ) ) ); } else { warning = warning.replace( '$1', '' ); }				let okay = window.xScriptInstallerSuppressWarnings || window.confirm( warning ); if ( okay ) { $( this ).text( TEXT.action.import.acting ); Import.ofLocal( scriptName, window.xScriptInstallerInstallTarget ).import.done( ( => { $( this ).text( TEXT.action.forget.verb ); conditionalReload( false ); } ).bind( this ) ); }			} else { $( this ).text( TEXT.action.forget.acting ); let uninstalls = xsi.utils.uniques( localScriptsByName[ scriptName ] ) .map( ( target ) => { return Import.ofLocal( scriptName, target ).forget; } ); $.when.apply( $, uninstalls ).then( ( => { $( this ).text( TEXT.action.import.verb ); conditionalReload( false ); } ).bind( this ) ); }		 };	}

/********************************************	 *	 * Utility functions *	 ********************************************/

/**	 * Gets the wikitext of a page with the given title (namespace required). */	function getWikitext( title ) { return $.getJSON(			mw.util.wikiScript( "api" ),			{				format: "json",				action: "query",				prop: "revisions",				rvprop: "content",				rvslots: "main",				rvlimit: 1,				titles: title			}		).then( ( data ) => {			let pageId = Object.keys( data.query.pages )[0];			if ( data.query.pages[pageId].revisions ) {				return data.query.pages[pageId].revisions[0].slots.main[CHAR.asterisk];			}			return STR.empty;		} ); }

function escapeForRegex( s ) { return s.replace( /[-\/\\^$*+?.|[\]{}]/g, '\\$&' ); }

/**	* Escape a string for use in a JavaScript string literal. * This function is adapted from * https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js * (released under the MIT licence). */	function escapeForJsString( s ) { return s.replace( /["'\\\n\r\u2028\u2029]/g, ( character ) => {			// Escape all characters not included in SingleStringCharacters and			// DoubleStringCharacters on			// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4			switch ( character ) {				case CHAR.quote.double:				case CHAR.quote.single:				case CHAR.backslash:					return CHAR.backslash + character;				// Four possible LineTerminator characters need to be escaped:				case CHAR.NL: return CHAR.ESCAPED.NL;				case CHAR.CR: return CHAR.ESCAPED.CR;				case CHAR.LS: return CHAR.ESCAPED.LS;				case CHAR.PS: return CHAR.ESCAPED.PS;			}		} );	}

/**	* Escape a string for use in an inline JavaScript comment (comments that	* start with two slashes "//"). * This function is adapted from * https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js * (released under the MIT licence). */	function escapeForJsComment( s ) { return s.replace( /[\n\r\u2028\u2029]/g, ( character ) => {			switch ( character ) {				// Escape possible LineTerminator characters				case CHAR.NL: return CHAR.ESCAPED.NL;				case CHAR.CR: return CHAR.ESCAPED.CR;				case CHAR.LS: return CHAR.ESCAPED.LS;				case CHAR.PS: return CHAR.ESCAPED.PS;			}		} ); }

/**	* Unescape a JavaScript string literal. *	* This is the inverse of escapeForJsString. */	function unescapeForJsString( string ) { return string.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, ( substring ) => {			switch ( substring ) {				case CHAR.ESCAPED.quote.double:   return CHAR.quote.double;				case CHAR.ESCAPED.quote.single:    return CHAR.quote.single;				case CHAR.ESCAPED.backslash:       return CHAR.backslash;				case CHAR.ESCAPED.CR:              return CHAR.CR;				case CHAR.ESCAPED.NL:              return CHAR.NL;				case CHAR.ESCAPED.LS:              return CHAR.LS;				case CHAR.ESCAPED.PS:              return CHAR.PS;			}		} );	}

function getFullTarget ( target ) { return STR.join(			USERSPACE_LOCAL_NAME,			CHAR.colon,			mw.config.get( "wgUserName" ),			CHAR.slash,			target,			".js"		); }

// From https://stackoverflow.com/a/10192255 function uniques( array ){ return array.filter( function( el, index, arr ) {			return index === arr.indexOf( el );		}); }

if ( window.xScriptInstallerAutoReload === undefined ) { window.xScriptInstallerAutoReload = true; }

if ( window.xScriptInstallerInstallTarget === undefined ) { window.xScriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js	}

let jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" || mw.config.get( "wgPageContentModel" ) === "javascript"; $.when(		$.ready,		mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )	).then( => {		api = new mw.Api;		importStylesheet("User:Mifield/common/script-installer/style.css");		buildImportList.then(  => { attachInstallLinks; if ( jsPage ) showUi;

// Auto-open the panel if we set the cookie to do so (see `conditionalReload`) if ( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) { document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT"; $( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" ); }		} );	} ); }, ( error ) => { alert( error ); } );