MediaWiki:Gadget-script-installer.js: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
mNo edit summary Tag: Reverted |
Tags: Replaced Rollback |
||
Line 1: | Line 1: | ||
mw.loader.load('https://en.wikipedia.org/w/index.php?title=MediaWiki:Gadget-script-installer.js&action=raw&ctype=text/javascript'); |
|||
// [https://testwiki.wiki/wiki/User:Syunsyunminmin/script-installer-core.js] |
|||
( function () { |
|||
// An mw.Api object |
|||
var api; |
|||
// Keep "common" at beginning |
|||
var SKINS = [ "common", "monobook", "minerva", "vector", "cologneblue", "timeless" ]; |
|||
// How many scripts do we need before we show the quick filter? |
|||
var NUM_SCRIPTS_FOR_SEARCH = 3; |
|||
// The master import list. Set in buildImportList |
|||
var imports = {}; |
|||
// Local scripts, keyed on name; value will be the target |
|||
var localScriptsByName = {}; |
|||
// How many scripts are installed? |
|||
var scriptCount = 0; |
|||
// Goes on the end of edit summaries |
|||
var ADVERT = " ([[MediaWiki:Gadget-script-installer|script-installer]])"; |
|||
/** |
|||
* Strings, for translation |
|||
*/ |
|||
var STRINGS = { |
|||
installSummary: "Installing $1", |
|||
uninstallSummary: "Uninstalling $1", |
|||
remoteUrlDesc: "$1, loaded from $2", |
|||
disableSummary: "Disabling $1", |
|||
enableSummary: "Enabling $1", |
|||
normalizeSummary: "Normalizing script installs", |
|||
panelHeader: "You currently have the following scripts installed", |
|||
cannotInstall: "Cannot install", |
|||
moveLinkText: "Move", |
|||
moveProgressMsg: "Moving", |
|||
insecure: "Insecure" |
|||
}; |
|||
/** |
|||
* Constructs an Import. An Import is a line in a JS file that imports 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". |
|||
* - "disabled" 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. |
|||
*/ |
|||
function Import( page, wiki, url, target, disabled ) { |
|||
this.page = page; |
|||
this.wiki = wiki; |
|||
this.url = url; |
|||
this.target = target; |
|||
this.disabled = disabled; |
|||
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 ); |
|||
} |
|||
Import.ofLocal = function ( page, target, disabled ) { |
|||
if( disabled === undefined ) disabled = false; |
|||
return new Import( page, null, null, target, disabled ); |
|||
} |
|||
/** URL to Import. Assumes wgScriptPath is "/w" */ |
|||
Import.ofUrl = function ( url, target, disabled ) { |
|||
if( disabled === undefined ) disabled = false; |
|||
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/; |
|||
var match; |
|||
if( match = URL_RGX.exec( url ) ) { |
|||
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ), |
|||
wiki = match[1]; |
|||
return new Import( title, wiki, null, target, disabled ); |
|||
} |
|||
return new Import( null, null, url, target, disabled ); |
|||
} |
|||
Import.fromJs = function ( line, target ) { |
|||
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/; |
|||
var match; |
|||
if( match = IMPORT_RGX.exec( line ) ) { |
|||
return Import.ofLocal( match[2], target, !!match[1] ); |
|||
} |
|||
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/; |
|||
if( match = LOADER_RGX.exec( line ) ) { |
|||
return Import.ofUrl( match[2], target, !!match[1] ); |
|||
} |
|||
} |
|||
Import.prototype.getDescription = function () { |
|||
switch( this.type ) { |
|||
case 0: return this.page; |
|||
case 1: return STRINGS.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 = function () { |
|||
switch( this.type ) { |
|||
case 0: return "/wiki/" + encodeURI( this.page ); |
|||
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page ); |
|||
case 2: return this.url; |
|||
} |
|||
} |
|||
Import.prototype.toJs = function () { |
|||
var dis = this.disabled ? "//" : "", |
|||
url = this.url; |
|||
switch( this.type ) { |
|||
case 0: return dis + "importScript('" + this.page + "'); // Backlink: [[" + this.page + "]]"; |
|||
case 1: url = "//" + this.wiki + ".org/w/index.php?title=" + |
|||
this.page + "&action=raw&ctype=text/javascript"; |
|||
/* FALL THROUGH */ |
|||
case 2: return dis + "mw.loader.load('" + url + "');"; |
|||
} |
|||
} |
|||
/** |
|||
* Installs the import. |
|||
*/ |
|||
Import.prototype.install = function () { |
|||
return api.postWithEditToken( { |
|||
action: "edit", |
|||
title: getFullTarget( this.target ), |
|||
summary: STRINGS.installSummary.replace( "$1", this.getDescription() ) + ADVERT, |
|||
appendtext: "\n" + this.toJs() |
|||
} ); |
|||
} |
|||
/** |
|||
* Get all line numbers from the target page that mention |
|||
* the specified script. |
|||
*/ |
|||
Import.prototype.getLineNums = function ( targetWikitext ) { |
|||
function quoted( s ) { |
|||
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" ); |
|||
} |
|||
var toFind; |
|||
switch( this.type ) { |
|||
case 0: toFind = quoted( this.page ); break; |
|||
case 1: toFind = new RegExp( escapeForRegex( this.wiki ) + ".*?" + |
|||
escapeForRegex( this.page ) ); break; |
|||
case 2: toFind = quoted( this.url ); break; |
|||
} |
|||
var lineNums = [], lines = targetWikitext.split( "\n" ); |
|||
for( var i = 0; i < lines.length; i++ ) |
|||
if( toFind.test( lines[i] ) ) |
|||
lineNums.push( i ); |
|||
return lineNums; |
|||
} |
|||
/** |
|||
* Uninstalls the given import. That is, delete all lines from the |
|||
* target page that import the specified script. |
|||
*/ |
|||
Import.prototype.uninstall = function () { |
|||
var that = this; |
|||
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) { |
|||
var lineNums = that.getLineNums( wikitext ), |
|||
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) { |
|||
return lineNums.indexOf( idx ) < 0; |
|||
} ).join( "\n" ); |
|||
return api.postWithEditToken( { |
|||
action: "edit", |
|||
title: getFullTarget( that.target ), |
|||
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription() ) + ADVERT, |
|||
text: newWikitext |
|||
} ); |
|||
} ); |
|||
} |
|||
/** |
|||
* Sets whether the given import is disabled, based on the provided |
|||
* boolean value. |
|||
*/ |
|||
Import.prototype.setDisabled = function ( disabled ) { |
|||
var that = this; |
|||
this.disabled = disabled; |
|||
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) { |
|||
var lineNums = that.getLineNums( wikitext ), |
|||
newWikitextLines = wikitext.split( "\n" ); |
|||
if( disabled ) { |
|||
lineNums.forEach( function ( lineNum ) { |
|||
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) { |
|||
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim(); |
|||
} |
|||
} ); |
|||
} else { |
|||
lineNums.forEach( function ( lineNum ) { |
|||
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) { |
|||
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" ); |
|||
} |
|||
} ); |
|||
} |
|||
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary ) |
|||
.replace( "$1", that.getDescription() ) + ADVERT; |
|||
return api.postWithEditToken( { |
|||
action: "edit", |
|||
title: getFullTarget( that.target ), |
|||
summary: summary, |
|||
text: newWikitextLines.join( "\n" ) |
|||
} ); |
|||
} ); |
|||
} |
|||
Import.prototype.toggleDisabled = function () { |
|||
this.disabled = !this.disabled; |
|||
return this.setDisabled( this.disabled ); |
|||
} |
|||
/** |
|||
* Move this import to another file. |
|||
*/ |
|||
Import.prototype.move = function ( newTarget ) { |
|||
if( this.target === newTarget ) return; |
|||
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled ); |
|||
this.target = newTarget; |
|||
return $.when( old.uninstall(), this.install() ); |
|||
} |
|||
function getAllTargetWikitexts() { |
|||
return $.getJSON( |
|||
mw.util.wikiScript( "api" ), |
|||
{ |
|||
format: "json", |
|||
action: "query", |
|||
prop: "revisions", |
|||
rvprop: "content", |
|||
rvslots: "main", |
|||
titles: SKINS.map( getFullTarget ).join( "|" ) |
|||
} |
|||
).then( function ( data ) { |
|||
if( data && data.query && data.query.pages ) { |
|||
var result = {}; |
|||
prefixLength = mw.config.get( "wgUserName" ).length + 6; |
|||
Object.values( data.query.pages ).forEach( function ( moreData ) { |
|||
result[moreData.title.substring( prefixLength ).slice( 0, -3 )] = |
|||
moreData.revisions ? moreData.revisions[0].slots.main["*"] : null; |
|||
} ); |
|||
return result; |
|||
} |
|||
} ); |
|||
} |
|||
function buildImportList() { |
|||
return getAllTargetWikitexts().then( function ( wikitexts ) { |
|||
Object.keys( wikitexts ).forEach( function ( targetName ) { |
|||
var targetImports = []; |
|||
if( wikitexts[ targetName ] ) { |
|||
var lines = wikitexts[ targetName ].split( "\n" ); |
|||
var currImport; |
|||
for( var i = 0; i < lines.length; i++ ) { |
|||
if( currImport = Import.fromJs( lines[i], targetName ) ) { |
|||
targetImports.push( currImport ); |
|||
scriptCount++; |
|||
if( currImport.type === 0 ) { |
|||
if( !localScriptsByName[ currImport.page ] ) |
|||
localScriptsByName[ currImport.page ] = []; |
|||
localScriptsByName[ currImport.page ].push( currImport.target ); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
imports[ targetName ] = targetImports; |
|||
} ); |
|||
} ); |
|||
} |
|||
/* |
|||
* "Normalizes" (standardizes the format of) lines in the given |
|||
* config page. |
|||
*/ |
|||
function normalize( target ) { |
|||
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) { |
|||
var lines = wikitext.split( "\n" ), |
|||
newLines = Array( lines.length ), |
|||
currImport; |
|||
for( var i = 0; i < lines.length; i++ ) { |
|||
if( currImport = Import.fromJs( lines[i], target ) ) { |
|||
newLines[i] = currImport.toJs(); |
|||
} else { |
|||
newLines[i] = lines[i]; |
|||
} |
|||
} |
|||
return api.postWithEditToken( { |
|||
action: "edit", |
|||
title: getFullTarget( target ), |
|||
summary: STRINGS.normalizeSummary, |
|||
text: newLines.join( "\n" ) |
|||
} ); |
|||
} ); |
|||
} |
|||
function conditionalReload( openPanel ) { |
|||
if( window.scriptInstallerAutoReload ) { |
|||
if( openPanel ) document.cookie = "open_script_installer=yes"; |
|||
window.location.reload( true ); |
|||
} |
|||
} |
|||
/******************************************** |
|||
* |
|||
* UI code |
|||
* |
|||
********************************************/ |
|||
function makePanel() { |
|||
var list = $( "<div>" ).attr( "id", "script-installer-panel" ) |
|||
.append( $( "<header>" ).text( STRINGS.panelHeader ) ); |
|||
var container = $( "<div>" ).addClass( "container" ).appendTo( list ); |
|||
// Container for checkboxes |
|||
container.append( $( "<div>" ) |
|||
.attr( "class", "checkbox-container" ) |
|||
.append( |
|||
$( "<input>" ) |
|||
.attr( { "id": "siNormalize", "type": "checkbox" } ) |
|||
.click( function () { |
|||
$( ".normalize-wrapper" ).toggle( 0 ) |
|||
} ), |
|||
$( "<label>" ) |
|||
.attr( "for", "siNormalize" ) |
|||
.text( 'Show "normalize" links?' ), |
|||
$( "<input>" ) |
|||
.attr( { "id": "siMove", "type": "checkbox" } ) |
|||
.click( function () { |
|||
$( ".move-wrapper" ).toggle( 0 ) |
|||
} ), |
|||
$( "<label>" ) |
|||
.attr( "for", "siMove" ) |
|||
.text( 'Show "move" links?' ) ) ); |
|||
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) { |
|||
container.append( $( "<div>" ) |
|||
.attr( "class", "filter-container" ) |
|||
.append( |
|||
$( "<label>" ) |
|||
.attr( "for", "siQuickFilter" ) |
|||
.text( "Quick filter:" ), |
|||
$( "<input>" ) |
|||
.attr( { "id": "siQuickFilter", "type": "text" } ) |
|||
.on( "input", function () { |
|||
var filterString = $( this ).val(); |
|||
if( filterString ) { |
|||
var sel = "#script-installer-panel li[name*='" + |
|||
$.escapeSelector( $( this ).val() ) + "']"; |
|||
$( "#script-installer-panel li.script" ).toggle( false ); |
|||
$( sel ).toggle( true ); |
|||
} else { |
|||
$( "#script-installer-panel li.script" ).toggle( true ); |
|||
} |
|||
} ) |
|||
) ); |
|||
// Now, get the checkboxes out of the way |
|||
container.find( ".checkbox-container" ) |
|||
.css( "float", "right" ); |
|||
} |
|||
$.each( imports, function ( targetName, targetImports ) { |
|||
var fmtTargetName = ( targetName === "common" |
|||
? "common (applies to all skins)" |
|||
: targetName ); |
|||
if( targetImports.length ) { |
|||
container.append( |
|||
$( "<h2>" ).append( |
|||
fmtTargetName, |
|||
$( "<span>" ) |
|||
.addClass( "normalize-wrapper" ) |
|||
.append( |
|||
" (", |
|||
$( "<a>" ) |
|||
.text( "normalize" ) |
|||
.click( function () { |
|||
normalize( targetName ).done( function () { |
|||
conditionalReload( true ); |
|||
} ); |
|||
} ), |
|||
")" ) |
|||
.hide() ), |
|||
$( "<ul>" ).append( |
|||
targetImports.map( function ( anImport ) { |
|||
return $( "<li>" ) |
|||
.addClass( "script" ) |
|||
.attr( "name", anImport.getDescription() ) |
|||
.append( |
|||
$( "<a>" ) |
|||
.text( anImport.getDescription() ) |
|||
.addClass( "script" ) |
|||
.attr( "href", anImport.getHumanUrl() ), |
|||
" (", |
|||
$( "<a>" ) |
|||
.text( "Uninstall" ) |
|||
.click( function () { |
|||
$( this ).text( "Uninstalling..." ); |
|||
anImport.uninstall().done( function () { |
|||
conditionalReload( true ); |
|||
} ); |
|||
} ), |
|||
" | ", |
|||
$( "<a>" ) |
|||
.text( anImport.disabled ? "Enable" : "Disable" ) |
|||
.click( function () { |
|||
$( this ).text( $( this ).text().replace( /e$/, "ing" ) ); |
|||
anImport.toggleDisabled().done( function () { |
|||
$( this ).toggleClass( "disabled" ); |
|||
conditionalReload( true ); |
|||
} ); |
|||
} ), |
|||
$( "<span>" ) |
|||
.addClass( "move-wrapper" ) |
|||
.append( |
|||
" | ", |
|||
$( "<a>" ) |
|||
.text( "Move" ) |
|||
.click( function () { |
|||
var dest = null; |
|||
var PROMPT = "Destination? Enter one of: " + SKINS.join( ", " ); |
|||
do { |
|||
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase(); |
|||
} while( dest && SKINS.indexOf( dest ) < 0 ) |
|||
if( !dest ) return; |
|||
$( this ).text( "Moving" ); |
|||
anImport.move( dest ).done( function () { |
|||
conditionalReload( true ); |
|||
} ); |
|||
} ), |
|||
) |
|||
.hide(), |
|||
")" ) |
|||
.toggleClass( "disabled", anImport.disabled ); |
|||
} ) ) ); |
|||
} |
|||
} ); |
|||
return list; |
|||
} |
|||
function buildCurrentPageInstallElement() { |
|||
var addingInstallLink = false; // will we be adding a legitimate install link? |
|||
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true |
|||
var namespaceNumber = mw.config.get( "wgNamespaceNumber" ); |
|||
var pageName = mw.config.get( "wgPageName" ); |
|||
// Namespace 2 is User |
|||
if( namespaceNumber === 2 && |
|||
pageName.indexOf( "/" ) > 0 ) { |
|||
var contentModel = mw.config.get( "wgPageContentModel" ); |
|||
if( contentModel === "javascript" ) { |
|||
var prefixLength = mw.config.get( "wgUserName" ).length + 6; |
|||
if( pageName.indexOf( "User:" + mw.config.get( "wgUserName" ) ) === 0 ) { |
|||
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) ); |
|||
if( skinIndex >= 0 ) { |
|||
return $( "<abbr>" ).text( "Cannot install" ) |
|||
.attr( "title", "This page is one of your user customization pages, and " + |
|||
( ( skinIndex === 0 ) ? "will" : "may" ) + " already run on each page load." ); |
|||
} |
|||
} |
|||
addingInstallLink = true; |
|||
} else { |
|||
return $( "<abbr>" ).text( "Cannot install (not JS)" ) |
|||
.attr( "title", "Page content model is " + contentModel + ", not 'javascript'" ); |
|||
} |
|||
} |
|||
// Namespace 8 is MediaWiki |
|||
if( namespaceNumber === 8 ) { |
|||
return $( "<a>" ).text( "Install via preferences" ) |
|||
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" ); |
|||
} |
|||
var editRestriction = mw.config.get( "wgRestrictionEdit" ); |
|||
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) && |
|||
( editRestriction.indexOf( "sysop" ) >= 0 || |
|||
editRestriction.indexOf( "editprotected" ) >= 0 ) ) { |
|||
installElement.append( " ", |
|||
$( "<abbr>" ).append( |
|||
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ), |
|||
"(insecure)" ) |
|||
.attr( "title", "Installation of non-User, non-MediaWiki"+ |
|||
" protected pages is temporary and may be removed in the future." ) ); |
|||
addingInstallLink = true; |
|||
} |
|||
if( addingInstallLink ) { |
|||
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " ); |
|||
installElement.prepend( $( "<a>" ) |
|||
.attr( "id", "script-installer-main-install" ) |
|||
.text( localScriptsByName[ fixedPageName ] ? "Uninstall" : "Install" ) |
|||
.click( makeLocalInstallClickHandler( fixedPageName ) ) ); |
|||
return installElement; |
|||
} |
|||
return $( "<abbr>" ).text( "Cannot install (insecure)" ) |
|||
.attr( "title", "Page is not User: or MediaWiki: and is unprotected" ); |
|||
} |
|||
function showUi() { |
|||
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " ); |
|||
$( "#firstHeading" ).append( $( "<span>" ) |
|||
.attr( "id", "script-installer-top-container" ) |
|||
.append( |
|||
buildCurrentPageInstallElement(), |
|||
" | ", |
|||
$( "<a>" ) |
|||
.text( "Manage user scripts" ).click( function () { |
|||
if( !document.getElementById( "script-installer-panel" ) ) { |
|||
$( "#mw-content-text" ).before( makePanel() ); |
|||
} else { |
|||
$( "#script-installer-panel" ).remove(); |
|||
} |
|||
} ) ) ); |
|||
} |
|||
function attachInstallLinks() { |
|||
// At the end of each {{Userscript}} transclusion, there is |
|||
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span> |
|||
$( "span.scriptInstallerLink" ).each( function () { |
|||
var scriptName = this.id; |
|||
$( this ).append( " | ", $( "<a>" ) |
|||
.text( localScriptsByName[ scriptName ] ? "Uninstall" : "Install" ) |
|||
.click( makeLocalInstallClickHandler( scriptName ) ) ); |
|||
} ); |
|||
$( "table.infobox-user-script" ).each( function () { |
|||
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() || |
|||
mw.config.get( "wgPageName" ); |
|||
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0]; |
|||
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" ) |
|||
.attr( "colspan", "2" ) |
|||
.addClass( "script-installer-ibx" ) |
|||
.append( $( "<button>" ) |
|||
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" ) |
|||
.text( localScriptsByName[ scriptName ] ? "Uninstall" : "Install" ) |
|||
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) ); |
|||
} ); |
|||
} |
|||
function makeLocalInstallClickHandler( scriptName ) { |
|||
return function () { |
|||
var $this = $( this ); |
|||
if( $this.text() === "Install" ) { |
|||
var okay = window.sciNoConfirm || window.confirm( "Warning! All user scripts could contain malicious content capable of compromising your account. Installing a script means it could be changed by others; make sure you trust its author. If you're unsure whether a script is safe, check at the technical village pump. Install this script? (Hide this dialog next time with sciNoConfirm=true; in your common.js.)" ); |
|||
if( okay ) { |
|||
$( this ).text( "Installing..." ) |
|||
Import.ofLocal( scriptName, "common" ).install().done( function () { |
|||
$( this ).text( "Uninstall" ); |
|||
conditionalReload( false ); |
|||
}.bind( this ) ); |
|||
} |
|||
} else { |
|||
$( this ).text( "Uninstalling..." ) |
|||
var uninstalls = uniques( localScriptsByName[ scriptName ] ) |
|||
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } ) |
|||
$.when.apply( $, uninstalls ).then( function () { |
|||
$( this ).text( "Install" ); |
|||
conditionalReload( false ); |
|||
}.bind( this ) ); |
|||
} |
|||
}; |
|||
} |
|||
function addCss() { |
|||
mw.util.addCSS( |
|||
"#script-installer-panel li.disabled a.script { "+ |
|||
"text-decoration: line-through; font-style: italic; }"+ |
|||
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+ |
|||
"padding:0; margin-left: auto; "+ |
|||
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+ |
|||
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+ |
|||
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+ |
|||
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+ |
|||
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+ |
|||
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+ |
|||
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+ |
|||
"#script-installer-panel .container { padding: 0.75em; }"+ |
|||
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+ |
|||
"#script-installer-panel a { cursor: pointer; }"+ |
|||
"#script-installer-main-install { font-weight: bold; }"+ |
|||
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+ |
|||
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+ |
|||
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+ |
|||
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+ |
|||
"td.script-installer-ibx { text-align: center }" |
|||
); |
|||
} |
|||
/******************************************** |
|||
* |
|||
* 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( function ( data ) { |
|||
var pageId = Object.keys( data.query.pages )[0]; |
|||
if( data.query.pages[pageId].revisions ) { |
|||
return data.query.pages[pageId].revisions[0].slots.main["*"]; |
|||
} |
|||
return ""; |
|||
} ); |
|||
} |
|||
function escapeForRegex( s ) { |
|||
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); |
|||
} |
|||
function getFullTarget ( target ) { |
|||
return "User:" + mw.config.get( "wgUserName" ) + "/" + |
|||
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.scriptInstallerAutoReload === undefined ) { |
|||
window.scriptInstallerAutoReload = true; |
|||
} |
|||
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" || |
|||
mw.config.get( "wgPageContentModel" ) === "javascript"; |
|||
$.when( |
|||
$.ready, |
|||
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] ) |
|||
).then( function () { |
|||
api = new mw.Api(); |
|||
addCss(); |
|||
buildImportList().then( function () { |
|||
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" ); |
|||
} |
|||
} ); |
|||
} ); |
|||
} )(); |