https://testwiki.wiki/api.php?action=feedcontributions&user=Syunsyunminmin&feedformat=atom
Test Wiki - User contributions [en]
2024-03-29T12:45:17Z
User contributions
MediaWiki 1.41.0
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=27722
User:Syunsyunminmin/common.js
2023-06-20T14:26:07Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');<br />
importScript('User:Syunsyunminmin/script-test.js'); // Backlink: [[User:Syunsyunminmin/script-test.js]]<br />
mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Tks4Fish/massBlock.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('http://localhost/script/test-2.js');</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Main_page/ja&diff=27545
MediaWiki:Main page/ja
2023-06-08T14:44:29Z
<p>Syunsyunminmin: Created page with "メインページ"</p>
<hr />
<div>メインページ</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Request_permissions/ja&diff=27544
MediaWiki:Request permissions/ja
2023-06-08T14:42:37Z
<p>Syunsyunminmin: Created page with "権限申請"</p>
<hr />
<div>権限申請</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-translateadmin-member/ja&diff=27543
MediaWiki:Group-translateadmin-member/ja
2023-06-08T14:40:51Z
<p>Syunsyunminmin: Created page with "翻訳管理者"</p>
<hr />
<div>翻訳管理者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-translateadmin/ja&diff=27542
MediaWiki:Group-translateadmin/ja
2023-06-08T14:40:43Z
<p>Syunsyunminmin: Created page with "翻訳管理者"</p>
<hr />
<div>翻訳管理者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-sysadmin-member/ja&diff=27541
MediaWiki:Group-sysadmin-member/ja
2023-06-08T14:40:14Z
<p>Syunsyunminmin: Created page with "システム管理者"</p>
<hr />
<div>システム管理者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-sysadmin/ja&diff=27540
MediaWiki:Group-sysadmin/ja
2023-06-08T14:40:06Z
<p>Syunsyunminmin: Created page with "システム管理者"</p>
<hr />
<div>システム管理者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-patroller-member/ja&diff=27537
MediaWiki:Group-patroller-member/ja
2023-06-08T14:38:40Z
<p>Syunsyunminmin: Created page with "巡回者"</p>
<hr />
<div>巡回者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-patroller/ja&diff=27536
MediaWiki:Group-patroller/ja
2023-06-08T14:38:32Z
<p>Syunsyunminmin: Created page with "巡回者"</p>
<hr />
<div>巡回者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-exampleuser-member/ja&diff=27535
MediaWiki:Group-exampleuser-member/ja
2023-06-08T14:35:30Z
<p>Syunsyunminmin: Created page with "実験用の利用者"</p>
<hr />
<div>実験用の利用者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-exampleuser/ja&diff=27534
MediaWiki:Group-exampleuser/ja
2023-06-08T14:35:20Z
<p>Syunsyunminmin: Created page with "実験用の利用者"</p>
<hr />
<div>実験用の利用者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-confirmed-member/ja&diff=27533
MediaWiki:Group-confirmed-member/ja
2023-06-08T14:32:00Z
<p>Syunsyunminmin: Created page with "承認された利用者"</p>
<hr />
<div>承認された利用者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-confirmed/ja&diff=27532
MediaWiki:Group-confirmed/ja
2023-06-08T14:31:50Z
<p>Syunsyunminmin: Created page with "承認された利用者"</p>
<hr />
<div>承認された利用者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-autopatrol-member/ja&diff=27531
MediaWiki:Group-autopatrol-member/ja
2023-06-08T14:25:13Z
<p>Syunsyunminmin: Created page with "自動巡回者"</p>
<hr />
<div>自動巡回者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-autopatrol/ja&diff=27530
MediaWiki:Group-autopatrol/ja
2023-06-08T14:25:03Z
<p>Syunsyunminmin: Created page with "自動巡回者"</p>
<hr />
<div>自動巡回者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-interwiki-admin-member/ja&diff=27529
MediaWiki:Group-interwiki-admin-member/ja
2023-06-08T14:24:26Z
<p>Syunsyunminmin: re</p>
<hr />
<div>インターウィキ管理者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-interwiki-admin/ja&diff=27528
MediaWiki:Group-interwiki-admin/ja
2023-06-08T14:24:14Z
<p>Syunsyunminmin: Created page with "インターウィキ管理者"</p>
<hr />
<div>インターウィキ管理者</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-steward-member/ja&diff=27527
MediaWiki:Group-steward-member/ja
2023-06-08T14:22:38Z
<p>Syunsyunminmin: Created page with "スチュワード"</p>
<hr />
<div>スチュワード</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=MediaWiki:Group-steward/ja&diff=27526
MediaWiki:Group-steward/ja
2023-06-08T14:22:03Z
<p>Syunsyunminmin: tr</p>
<hr />
<div>スチュワード</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=26603
User:Syunsyunminmin/common.js
2023-04-09T13:50:00Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');<br />
importScript('User:Syunsyunminmin/script-test.js'); // Backlink: [[User:Syunsyunminmin/script-test.js]]<br />
mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Tks4Fish/massBlock.js&action=raw&ctype=text/javascript');</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=26107
User:Syunsyunminmin/common.js
2023-03-04T14:00:00Z
<p>Syunsyunminmin: User:Syunsyunminmin/script-test.jsをインストール (script-installer)</p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');<br />
importScript('User:Syunsyunminmin/script-test.js'); // Backlink: [[User:Syunsyunminmin/script-test.js]]</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25872
User:Syunsyunminmin/script-installer-core.js
2023-02-04T14:21:03Z
<p>Syunsyunminmin: </p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 1;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "$1をインストール",<br />
installLinkText: "インストール",<br />
installProgressMsg: "インストール中...",<br />
uninstallSummary: "$1をアンインストール",<br />
uninstallLinkText: "アンインストール",<br />
uninstallProgressMsg: "アンインストール中...",<br />
disableSummary: "$1を無効化",<br />
disableLinkText: "無効化",<br />
disableProgressMsg: "無効化中...",<br />
enableSummary: "$1を有効化",<br />
enableLinkText: "有効化",<br />
enableProgressMsg: "有効化中...",<br />
moveLinkText: "移動",<br />
moveProgressMsg: "移動中...",<br />
movePrompt: "移動先は? 次の中から一つ入力してください:", // followed by the names of skins<br />
normalizeSummary: "スクリプトのインストールを標準化",<br />
remoteUrlDesc: "$1, $2からの読み込み",<br />
panelHeader: "現在、以下のスクリプトがインストールされています。",<br />
cannotInstall: "インストール出来ません",<br />
cannotInstallSkin: "このページはあなたの利用者カスタムページの1つで、すでにページの読み込み時に実行されているかもしれません(common.jsの場合は実行されています)。",<br />
cannotInstallContentModel: "ページのコンテンツモデルは$1です。'javascript'ではありません。",<br />
insecure: "(危険)", // used at the end of some messages<br />
notJavaScript: "JavaScriptではない",<br />
installViaPreferences: "個人設定からインストール",<br />
showNormalizeLinks: '"標準化"リンクを表示しますか?',<br />
showMoveLinks: '"移動"リンクを表示しますか?',<br />
quickFilter: "簡易フィルター:",<br />
tempWarning: "利用者サブページまたはMediaWiki名前空間以外で保護されているページからのインストールは一時的なものであり、いつか削除される可能性があります。",<br />
badPageError: "ページは User: または MediaWiki: ではなく、保護されていません",<br />
manageUserScripts: "ユーザースクリプトの管理",<br />
bigSecurityWarning: "警告!$1すべてのユーザースクリプトには、あなたのアカウントを侵害する悪質な内容が含まれている可能性があります。スクリプトをインストールするということは、他の人によってアカウントが乗っ取られる可能性があるということです。インストールする前に作者を信頼できるか確認してください。スクリプトが安全かどうかわからない場合は、プロジェクト:ウィキ技術部で確認してください。このスクリプトをインストールしますか? (次からこのダイアログを非表示にするにはcommon.jsに sciNoConfirm=true; を記述してください)",<br />
securityWarningSection: " $1を信頼しますか?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (全ての外装に適用)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
// </nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25871
User:Syunsyunminmin/script-installer-core.js
2023-02-04T14:18:50Z
<p>Syunsyunminmin: </p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 1;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "$1をインストール",<br />
installLinkText: "インストール",<br />
installProgressMsg: "インストール中...",<br />
uninstallSummary: "$1をアンインストール",<br />
uninstallLinkText: "アンインストール",<br />
uninstallProgressMsg: "アンインストール中...",<br />
disableSummary: "$1を無効化",<br />
disableLinkText: "無効化",<br />
disableProgressMsg: "無効化中...",<br />
enableSummary: "$1を有効化",<br />
enableLinkText: "有効化",<br />
enableProgressMsg: "有効化中...",<br />
moveLinkText: "移動",<br />
moveProgressMsg: "移動中...",<br />
movePrompt: "移動先は? 次の中から一つ入力してください:", // followed by the names of skins<br />
normalizeSummary: "スクリプトのインストールを標準化",<br />
remoteUrlDesc: "$1, $2からの読み込み",<br />
panelHeader: "現在、以下のスクリプトがインストールされています。",<br />
cannotInstall: "インストール出来ません",<br />
cannotInstallSkin: "このページはあなたの利用者カスタムページの1つで、すでにページの読み込み時に実行されているかもしれません(common.jsの場合は実行されています)。",<br />
cannotInstallContentModel: "ページのコンテンツモデルは$1です。'javascript'ではありません。",<br />
insecure: "(危険)", // used at the end of some messages<br />
notJavaScript: "JavaScriptではない",<br />
installViaPreferences: "個人設定からインストール",<br />
showNormalizeLinks: '"標準化"リンクを表示しますか?',<br />
showMoveLinks: '"移動"リンクを表示しますか?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "利用者サブページまたはMediaWiki名前空間以外で保護されているページからのインストールは一時的なものであり、いつか削除される可能性があります。",<br />
badPageError: "ページは User: または MediaWiki: ではなく、保護されていません",<br />
manageUserScripts: "ユーザースクリプトの管理",<br />
bigSecurityWarning: "警告!$1すべてのユーザースクリプトには、あなたのアカウントを侵害する悪質な内容が含まれている可能性があります。スクリプトをインストールするということは、他の人によってアカウントが乗っ取られる可能性があるということです。インストールする前に作者を信頼できるか確認してください。スクリプトが安全かどうかわからない場合は、プロジェクト:ウィキ技術部で確認してください。このスクリプトをインストールしますか? (次からこのダイアログを非表示にするにはcommon.jsに sciNoConfirm=true; を記述してください)",<br />
securityWarningSection: " $1を信頼しますか?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (全ての外装に適用)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
// </nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25870
User:Syunsyunminmin/script-installer-core.js
2023-02-04T14:18:40Z
<p>Syunsyunminmin: </p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 5;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "$1をインストール",<br />
installLinkText: "インストール",<br />
installProgressMsg: "インストール中...",<br />
uninstallSummary: "$1をアンインストール",<br />
uninstallLinkText: "アンインストール",<br />
uninstallProgressMsg: "アンインストール中...",<br />
disableSummary: "$1を無効化",<br />
disableLinkText: "無効化",<br />
disableProgressMsg: "無効化中...",<br />
enableSummary: "$1を有効化",<br />
enableLinkText: "有効化",<br />
enableProgressMsg: "有効化中...",<br />
moveLinkText: "移動",<br />
moveProgressMsg: "移動中...",<br />
movePrompt: "移動先は? 次の中から一つ入力してください:", // followed by the names of skins<br />
normalizeSummary: "スクリプトのインストールを標準化",<br />
remoteUrlDesc: "$1, $2からの読み込み",<br />
panelHeader: "現在、以下のスクリプトがインストールされています。",<br />
cannotInstall: "インストール出来ません",<br />
cannotInstallSkin: "このページはあなたの利用者カスタムページの1つで、すでにページの読み込み時に実行されているかもしれません(common.jsの場合は実行されています)。",<br />
cannotInstallContentModel: "ページのコンテンツモデルは$1です。'javascript'ではありません。",<br />
insecure: "(危険)", // used at the end of some messages<br />
notJavaScript: "JavaScriptではない",<br />
installViaPreferences: "個人設定からインストール",<br />
showNormalizeLinks: '"標準化"リンクを表示しますか?',<br />
showMoveLinks: '"移動"リンクを表示しますか?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "利用者サブページまたはMediaWiki名前空間以外で保護されているページからのインストールは一時的なものであり、いつか削除される可能性があります。",<br />
badPageError: "ページは User: または MediaWiki: ではなく、保護されていません",<br />
manageUserScripts: "ユーザースクリプトの管理",<br />
bigSecurityWarning: "警告!$1すべてのユーザースクリプトには、あなたのアカウントを侵害する悪質な内容が含まれている可能性があります。スクリプトをインストールするということは、他の人によってアカウントが乗っ取られる可能性があるということです。インストールする前に作者を信頼できるか確認してください。スクリプトが安全かどうかわからない場合は、プロジェクト:ウィキ技術部で確認してください。このスクリプトをインストールしますか? (次からこのダイアログを非表示にするにはcommon.jsに sciNoConfirm=true; を記述してください)",<br />
securityWarningSection: " $1を信頼しますか?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (全ての外装に適用)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
// </nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25869
User:Syunsyunminmin/common.js
2023-02-04T14:18:35Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25868
User:Syunsyunminmin/common.js
2023-02-04T14:17:37Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%あ.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%い.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%うdev.js&action=raw&ctype=text/javascript');</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25860
User:Syunsyunminmin/script-installer-core.js
2023-02-04T13:55:23Z
<p>Syunsyunminmin: </p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 5;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "$1をインストール",<br />
installLinkText: "インストール",<br />
installProgressMsg: "インストール中...",<br />
uninstallSummary: "$1をアンインストール",<br />
uninstallLinkText: "アンインストール",<br />
uninstallProgressMsg: "アンインストール中...",<br />
disableSummary: "$1を無効化",<br />
disableLinkText: "無効化",<br />
disableProgressMsg: "無効化中...",<br />
enableSummary: "$1を有効化",<br />
enableLinkText: "有効化",<br />
enableProgressMsg: "有効化中...",<br />
moveLinkText: "移動",<br />
moveProgressMsg: "移動中...",<br />
movePrompt: "移動先は? 次の中から一つ入力してください:", // followed by the names of skins<br />
normalizeSummary: "スクリプトのインストールを標準化",<br />
remoteUrlDesc: "$1, $2からの読み込み",<br />
panelHeader: "現在、以下のスクリプトがインストールされています。",<br />
cannotInstall: "インストール出来ません",<br />
cannotInstallSkin: "This page is one of your user customization pages, and may (will, if common.js) already run on each page load.",<br />
cannotInstallContentModel: "ページのコンテンツモデルは$1です。'javascript'ではありません。",<br />
insecure: "(insecure)", // used at the end of some messages<br />
notJavaScript: "not JavaScript",<br />
installViaPreferences: "Install via preferences",<br />
showNormalizeLinks: '"標準化"リンクを表示しますか?',<br />
showMoveLinks: '"移動"リンクを表示しますか?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "利用者サブページまたはMediaWiki名前空間以外で保護されているページからのインストールは一時的なものであり、いつか削除される可能性があります。",<br />
badPageError: "ページは User: または MediaWiki: ではなく、保護されていません",<br />
manageUserScripts: "ユーザースクリプトの管理",<br />
bigSecurityWarning: "警告!$1すべてのユーザースクリプトには、あなたのアカウントを侵害する悪質な内容が含まれている可能性があります。スクリプトをインストールするということは、他の人によってアカウントが乗っ取られる可能性があるということです。インストールする前に作者を信頼できるか確認してください。スクリプトが安全かどうかわからない場合は、プロジェクト:ウィキ技術部で確認してください。このスクリプトをインストールしますか? (次からこのダイアログを非表示にするにはcommon.jsに sciNoConfirm=true; を記述してください)",<br />
securityWarningSection: " $1を信頼しますか?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (全ての外装に適用)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
// </nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25859
User:Syunsyunminmin/script-installer-core.js
2023-02-04T13:54:36Z
<p>Syunsyunminmin: </p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 5;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "$1をインストール",<br />
installLinkText: "インストール",<br />
installProgressMsg: "インストール中...",<br />
uninstallSummary: "$1をアンインストール",<br />
uninstallLinkText: "アンインストール",<br />
uninstallProgressMsg: "アンインストール中...",<br />
disableSummary: "$1を無効化",<br />
disableLinkText: "無効化",<br />
disableProgressMsg: "無効化中...",<br />
enableSummary: "$1を有効化",<br />
enableLinkText: "有効化",<br />
enableProgressMsg: "有効化中...",<br />
moveLinkText: "移動",<br />
moveProgressMsg: "移動中...",<br />
movePrompt: "移動先は? 次の中から一つ入力してください:", // followed by the names of skins<br />
normalizeSummary: "スクリプトのインストールを標準化",<br />
remoteUrlDesc: "$1, $2からの読み込み",<br />
panelHeader: "現在、以下のスクリプトがインストールされています。",<br />
cannotInstall: "インストール出来ません",<br />
cannotInstallSkin: "This page is one of your user customization pages, and may (will, if common.js) already run on each page load.",<br />
cannotInstallContentModel: "ページのコンテンツモデルは$1です。'javascript'ではありません。",<br />
insecure: "(insecure)", // used at the end of some messages<br />
notJavaScript: "not JavaScript",<br />
installViaPreferences: "Install via preferences",<br />
showNormalizeLinks: '"標準化"リンクを表示しますか?',<br />
showMoveLinks: '"移動"リンクを表示しますか?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "利用者サブページまたはMediaWiki名前空間以外で保護されているページからのインストールは一時的なものであり、いつか削除される可能性があります。",<br />
badPageError: "ページは User: または MediaWiki: ではなく、保護されていません",<br />
manageUserScripts: "ユーザースクリプトの管理",<br />
bigSecurityWarning: "警告!$1すべてのユーザースクリプトには、あなたのアカウントを侵害する悪質な内容が含まれている可能性があります。スクリプトをインストールするということは、他の人によってアカウントが乗っ取られる可能性があるということです。インストールする前に作者を信頼できるか確認してください。スクリプトが安全かどうかわからない場合は、プロジェクト:ウィキ技術部で確認してください。このスクリプトをインストールしますか? (次からこのダイアログを非表示にするにはcommon.jsに sciNoConfirm=true; を記述してください)",<br />
securityWarningSection: " $1を信頼しますか?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (applies to all skins)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
// </nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25858
User:Syunsyunminmin/script-installer-core.js
2023-02-04T13:52:34Z
<p>Syunsyunminmin: Undo revision 25857 by Syunsyunminmin (talk)</p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 5;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "$1をインストール",<br />
installLinkText: "インストール",<br />
installProgressMsg: "インストール中...",<br />
uninstallSummary: "$1をアンインストール",<br />
uninstallLinkText: "アンインストール",<br />
uninstallProgressMsg: "アンインストール中...",<br />
disableSummary: "$1を無効化",<br />
disableLinkText: "無効化",<br />
disableProgressMsg: "無効化中...",<br />
enableSummary: "$1を有効化",<br />
enableLinkText: "有効化",<br />
enableProgressMsg: "有効化中...",<br />
moveLinkText: "移動",<br />
moveProgressMsg: "移動中...",<br />
movePrompt: "移動先は? 次の中から一つ入力してください:", // followed by the names of skins<br />
normalizeSummary: "スクリプトのインストールを標準化",<br />
remoteUrlDesc: "$1, $2からの読み込み",<br />
panelHeader: "現在、以下のスクリプトがインストールされています。",<br />
cannotInstall: "インストール出来ません",<br />
cannotInstallSkin: "This page is one of your user customization pages, and may (will, if common.js) already run on each page load.",<br />
cannotInstallContentModel: "ページのコンテンツモデルは$1です。'javascript'ではありません。",<br />
insecure: "(insecure)", // used at the end of some messages<br />
notJavaScript: "not JavaScript",<br />
installViaPreferences: "Install via preferences",<br />
showNormalizeLinks: '"標準化"リンクを表示しますか?',<br />
showMoveLinks: '"移動"リンクを表示しますか?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "利用者サブページまたはMediaWiki名前空間以外で保護されているページからのインストールは一時的なものであり、いつか削除される可能性があります。",<br />
badPageError: "ページは User: または MediaWiki: ではなく、保護されていません",<br />
manageUserScripts: "ユーザースクリプトの管理",<br />
bigSecurityWarning: "警告!$1すべてのユーザースクリプトには、あなたのアカウントを侵害する悪質な内容が含まれている可能性があります。スクリプトをインストールするということは、他の人によってアカウントが乗っ取られる可能性があるということです。インストールする前に作者を信頼できるか確認してください。スクリプトが安全かどうかわからない場合は、プロジェクト:ウィキ技術部で確認してください。このスクリプトをインストールしますか? (次からこのダイアログを非表示にするにはcommon.jsに sciNoConfirm=true; を記述してください)",<br />
securityWarningSection: " $1を信頼しますか?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (applies to all skins)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
</nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25857
User:Syunsyunminmin/script-installer-core.js
2023-02-04T13:52:10Z
<p>Syunsyunminmin: Syunsyunminmin (会話)さんによる第25847版を復元: (Twinkle使用)</p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 5;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "Installing $1",<br />
installLinkText: "Install",<br />
installProgressMsg: "Installing...",<br />
uninstallSummary: "Uninstalling $1",<br />
uninstallLinkText: "Uninstall",<br />
uninstallProgressMsg: "Uninstalling...",<br />
disableSummary: "Disabling $1",<br />
disableLinkText: "Disable",<br />
disableProgressMsg: "Disabling...",<br />
enableSummary: "Enabling $1",<br />
enableLinkText: "Enable",<br />
enableProgressMsg: "Enabling...",<br />
moveLinkText: "Move",<br />
moveProgressMsg: "Moving...",<br />
movePrompt: "Destination? Enter one of:", // followed by the names of skins<br />
normalizeSummary: "Normalizing script installs",<br />
remoteUrlDesc: "$1, loaded from $2",<br />
panelHeader: "You currently have the following scripts installed",<br />
cannotInstall: "Cannot install",<br />
cannotInstallSkin: "This page is one of your user customization pages, and may (will, if common.js) already run on each page load.",<br />
cannotInstallContentModel: "Page content model is $1, not 'javascript'",<br />
insecure: "(insecure)", // used at the end of some messages<br />
notJavaScript: "not JavaScript",<br />
installViaPreferences: "Install via preferences",<br />
showNormalizeLinks: 'Show "normalize" links?',<br />
showMoveLinks: 'Show "move" links?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "Installation of non-User, non-MediaWiki protected pages is temporary and may be removed in the future.",<br />
badPageError: "Page is not User: or MediaWiki: and is unprotected",<br />
manageUserScripts: "Manage user scripts",<br />
bigSecurityWarning: "Warning!$1 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.)",<br />
securityWarningSection: " Do you trust $1?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (applies to all skins)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
</nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&diff=25856
User:Syunsyunminmin/script-installer.js
2023-02-04T13:50:58Z
<p>Syunsyunminmin: </p>
<hr />
<div>/**<br />
* script-installer loader<br />
*/<br />
<br />
if( mw.config.get( "wgNamespaceNumber" ) > 0 ) {<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
if( jsPage || document.getElementsByClassName( "scriptInstallerLink" ).length ||<br />
document.querySelector( "table.infobox-user-script" ) ) {<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&action=raw&ctype=text/javascript');<br />
}<br />
}</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25855
User:Syunsyunminmin/script-installer-core.js
2023-02-04T13:49:48Z
<p>Syunsyunminmin: </p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 5;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "$1をインストール",<br />
installLinkText: "インストール",<br />
installProgressMsg: "インストール中...",<br />
uninstallSummary: "$1をアンインストール",<br />
uninstallLinkText: "アンインストール",<br />
uninstallProgressMsg: "アンインストール中...",<br />
disableSummary: "$1を無効化",<br />
disableLinkText: "無効化",<br />
disableProgressMsg: "無効化中...",<br />
enableSummary: "$1を有効化",<br />
enableLinkText: "有効化",<br />
enableProgressMsg: "有効化中...",<br />
moveLinkText: "移動",<br />
moveProgressMsg: "移動中...",<br />
movePrompt: "移動先は? 次の中から一つ入力してください:", // followed by the names of skins<br />
normalizeSummary: "スクリプトのインストールを標準化",<br />
remoteUrlDesc: "$1, $2からの読み込み",<br />
panelHeader: "現在、以下のスクリプトがインストールされています。",<br />
cannotInstall: "インストール出来ません",<br />
cannotInstallSkin: "This page is one of your user customization pages, and may (will, if common.js) already run on each page load.",<br />
cannotInstallContentModel: "ページのコンテンツモデルは$1です。'javascript'ではありません。",<br />
insecure: "(insecure)", // used at the end of some messages<br />
notJavaScript: "not JavaScript",<br />
installViaPreferences: "Install via preferences",<br />
showNormalizeLinks: '"標準化"リンクを表示しますか?',<br />
showMoveLinks: '"移動"リンクを表示しますか?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "利用者サブページまたはMediaWiki名前空間以外で保護されているページからのインストールは一時的なものであり、いつか削除される可能性があります。",<br />
badPageError: "ページは User: または MediaWiki: ではなく、保護されていません",<br />
manageUserScripts: "ユーザースクリプトの管理",<br />
bigSecurityWarning: "警告!$1すべてのユーザースクリプトには、あなたのアカウントを侵害する悪質な内容が含まれている可能性があります。スクリプトをインストールするということは、他の人によってアカウントが乗っ取られる可能性があるということです。インストールする前に作者を信頼できるか確認してください。スクリプトが安全かどうかわからない場合は、プロジェクト:ウィキ技術部で確認してください。このスクリプトをインストールしますか? (次からこのダイアログを非表示にするにはcommon.jsに sciNoConfirm=true; を記述してください)",<br />
securityWarningSection: " $1を信頼しますか?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (applies to all skins)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
</nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25854
User:Syunsyunminmin/common.js
2023-02-04T13:42:18Z
<p>Syunsyunminmin: Uninstalling User:Syunsyunminmin/Twinkle-dev.js (script-installer)</p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25853
User:Syunsyunminmin/common.js
2023-02-04T13:31:50Z
<p>Syunsyunminmin: Enabling User:Syunsyunminmin/Twinkle-dev.js, loaded from ja.wikipedia (script-installer)</p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');<br />
importScript('User:Syunsyunminmin/Twinkle-dev.js'); // Backlink: [[User:Syunsyunminmin/Twinkle-dev.js]]</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25852
User:Syunsyunminmin/common.js
2023-02-04T13:31:19Z
<p>Syunsyunminmin: Disabling User:Syunsyunminmin/Twinkle-dev.js, loaded from ja.wikipedia (script-installer)</p>
<hr />
<div>//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');<br />
importScript('User:Syunsyunminmin/Twinkle-dev.js'); // Backlink: [[User:Syunsyunminmin/Twinkle-dev.js]]</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25851
User:Syunsyunminmin/common.js
2023-02-04T13:31:13Z
<p>Syunsyunminmin: Installing User:Syunsyunminmin/Twinkle-dev.js (script-installer)</p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');<br />
importScript('User:Syunsyunminmin/Twinkle-dev.js'); // Backlink: [[User:Syunsyunminmin/Twinkle-dev.js]]</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25850
User:Syunsyunminmin/common.js
2023-02-04T13:28:22Z
<p>Syunsyunminmin: Normalizing script installs</p>
<hr />
<div>mw.loader.load('//ja.wikipedia.org/w/index.php?title=User%3ASyunsyunminmin%2FTwinkle-dev.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript');</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25849
User:Syunsyunminmin/common.js
2023-02-04T13:27:36Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.load("//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle-dev.js&action=raw&ctype=text/javascript");<br />
mw.loader.load("https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&action=raw&ctype=text/javascript");</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer.js&diff=25848
User:Syunsyunminmin/script-installer.js
2023-02-04T13:26:40Z
<p>Syunsyunminmin: :wikipedia:Special:permalink/886840674</p>
<hr />
<div>/**<br />
* script-installer loader<br />
*/<br />
<br />
if( mw.config.get( "wgNamespaceNumber" ) > 0 ) {<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
if( jsPage || document.getElementsByClassName( "scriptInstallerLink" ).length ||<br />
document.querySelector( "table.infobox-user-script" ) ) {<br />
mw.loader.load('https://en.wikipedia.org/w/index.php?title=MediaWiki:Gadget-script-installer-core.js&action=raw&ctype=text/javascript');<br />
}<br />
}</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/script-installer-core.js&diff=25847
User:Syunsyunminmin/script-installer-core.js
2023-02-04T13:25:51Z
<p>Syunsyunminmin: from :wikipedia:Special:Permalink/1104768383</p>
<hr />
<div>// <nowiki><br />
( function () {<br />
// An mw.Api object<br />
var api;<br />
<br />
// Keep "common" at beginning<br />
var SKINS = [ "common", "monobook", "minerva", "vector", "vector-2022", "timeless" ];<br />
<br />
// How many scripts do we need before we show the quick filter?<br />
var NUM_SCRIPTS_FOR_SEARCH = 5;<br />
<br />
// The master import list, keyed by target. (A "target" is a user JS subpage<br />
// where the script is imported, like "common" or "vector".) Set in buildImportList<br />
var imports = {};<br />
<br />
// Local scripts, keyed on name; value will be the target. Set in buildImportList.<br />
var localScriptsByName = {};<br />
<br />
// How many scripts are installed?<br />
var scriptCount = 0;<br />
<br />
// Goes on the end of edit summaries<br />
var ADVERT = " ([[User:Enterprisey/script-installer|script-installer]])";<br />
<br />
/**<br />
* Strings, for translation<br />
*/<br />
var STRINGS = {<br />
installSummary: "Installing $1",<br />
installLinkText: "Install",<br />
installProgressMsg: "Installing...",<br />
uninstallSummary: "Uninstalling $1",<br />
uninstallLinkText: "Uninstall",<br />
uninstallProgressMsg: "Uninstalling...",<br />
disableSummary: "Disabling $1",<br />
disableLinkText: "Disable",<br />
disableProgressMsg: "Disabling...",<br />
enableSummary: "Enabling $1",<br />
enableLinkText: "Enable",<br />
enableProgressMsg: "Enabling...",<br />
moveLinkText: "Move",<br />
moveProgressMsg: "Moving...",<br />
movePrompt: "Destination? Enter one of:", // followed by the names of skins<br />
normalizeSummary: "Normalizing script installs",<br />
remoteUrlDesc: "$1, loaded from $2",<br />
panelHeader: "You currently have the following scripts installed",<br />
cannotInstall: "Cannot install",<br />
cannotInstallSkin: "This page is one of your user customization pages, and may (will, if common.js) already run on each page load.",<br />
cannotInstallContentModel: "Page content model is $1, not 'javascript'",<br />
insecure: "(insecure)", // used at the end of some messages<br />
notJavaScript: "not JavaScript",<br />
installViaPreferences: "Install via preferences",<br />
showNormalizeLinks: 'Show "normalize" links?',<br />
showMoveLinks: 'Show "move" links?',<br />
quickFilter: "Quick filter:",<br />
tempWarning: "Installation of non-User, non-MediaWiki protected pages is temporary and may be removed in the future.",<br />
badPageError: "Page is not User: or MediaWiki: and is unprotected",<br />
manageUserScripts: "Manage user scripts",<br />
bigSecurityWarning: "Warning!$1 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.)",<br />
securityWarningSection: " Do you trust $1?"<br />
};<br />
<br />
var USER_NAMESPACE_NAME = mw.config.get( "wgFormattedNamespaces" )[2];<br />
<br />
/**<br />
* Constructs an Import. An Import is a line in a JS file that imports a<br />
* user script. Properties:<br />
*<br />
* - "page" is a page name, such as "User:Foo/Bar.js".<br />
* - "wiki" is a wiki from which the script is loaded, such as<br />
* "en.wikipedia". If null, the script is local, on the user's<br />
* wiki.<br />
* - "url" is a URL that can be passed into mw.loader.load.<br />
* - "target" is the title of the user subpage where the script is,<br />
* without the .js ending: for example, "common".<br />
* - "disabled" is whether this import is commented out.<br />
* - "type" is 0 if local, 1 if remotely loaded, and 2 if URL.<br />
*<br />
* EXACTLY one of "page" or "url" are null for every Import. This<br />
* constructor should not be used directly; use the factory<br />
* functions (Import.ofLocal, Import.ofUrl, Import.fromJs) instead.<br />
*/<br />
function Import( page, wiki, url, target, disabled ) {<br />
this.page = page;<br />
this.wiki = wiki;<br />
this.url = url;<br />
this.target = target;<br />
this.disabled = disabled;<br />
this.type = this.url ? 2 : ( this.wiki ? 1 : 0 );<br />
}<br />
<br />
Import.ofLocal = function ( page, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
return new Import( page, null, null, target, disabled );<br />
}<br />
<br />
/** URL to Import. Assumes wgScriptPath is "/w" */<br />
Import.ofUrl = function ( url, target, disabled ) {<br />
if( disabled === undefined ) disabled = false;<br />
var URL_RGX = /^(?:https?:)?\/\/(.+?)\.org\/w\/index\.php\?.*?title=(.+?(?:&|$))/;<br />
var match;<br />
if( match = URL_RGX.exec( url ) ) {<br />
var title = decodeURIComponent( match[2].replace( /&$/, "" ) ),<br />
wiki = decodeURIComponent( match[1] );<br />
return new Import( title, wiki, null, target, disabled );<br />
}<br />
return new Import( null, null, url, target, disabled );<br />
}<br />
<br />
Import.fromJs = function ( line, target ) {<br />
var IMPORT_RGX = /^\s*(\/\/)?\s*importScript\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
var match;<br />
if( match = IMPORT_RGX.exec( line ) ) {<br />
return Import.ofLocal( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
<br />
var LOADER_RGX = /^\s*(\/\/)?\s*mw\.loader\.load\s*\(\s*(?:"|')(.+?)(?:"|')\s*\)/;<br />
if( match = LOADER_RGX.exec( line ) ) {<br />
return Import.ofUrl( unescapeForJsString( match[2] ), target, !!match[1] );<br />
}<br />
}<br />
<br />
Import.prototype.getDescription = function ( useWikitext ) {<br />
switch( this.type ) {<br />
case 0: return useWikitext ? ( "[[" + this.page + "]]" ) : this.page;<br />
case 1: return STRINGS.remoteUrlDesc.replace( "$1", this.page ).replace( "$2", this.wiki );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
/**<br />
* Human-readable (NOT necessarily suitable for ResourceLoader) URL.<br />
*/<br />
Import.prototype.getHumanUrl = function () {<br />
switch( this.type ) {<br />
case 0: return "/wiki/" + encodeURI( this.page );<br />
case 1: return "//" + this.wiki + ".org/wiki/" + encodeURI( this.page );<br />
case 2: return this.url;<br />
}<br />
}<br />
<br />
Import.prototype.toJs = function () {<br />
var dis = this.disabled ? "//" : "",<br />
url = this.url;<br />
switch( this.type ) {<br />
case 0: return dis + "importScript('" + escapeForJsString( this.page ) + "'); // Backlink: [[" + escapeForJsComment( this.page ) + "]]";<br />
case 1: url = "//" + encodeURIComponent( this.wiki ) + ".org/w/index.php?title=" +<br />
encodeURIComponent( this.page ) + "&action=raw&ctype=text/javascript"; <br />
/* FALL THROUGH */<br />
case 2: return dis + "mw.loader.load('" + escapeForJsString( url ) + "');";<br />
}<br />
}<br />
<br />
/**<br />
* Installs the import.<br />
*/<br />
Import.prototype.install = function () {<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( this.target ),<br />
summary: STRINGS.installSummary.replace( "$1", this.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
appendtext: "\n" + this.toJs()<br />
} );<br />
}<br />
<br />
/**<br />
* Get all line numbers from the target page that mention<br />
* the specified script.<br />
*/<br />
Import.prototype.getLineNums = function ( targetWikitext ) {<br />
function quoted( s ) {<br />
return new RegExp( "(['\"])" + escapeForRegex( s ) + "\\1" );<br />
}<br />
var toFind;<br />
switch( this.type ) {<br />
case 0: toFind = quoted( escapeForJsString( this.page ) ); break;<br />
case 1: toFind = new RegExp( escapeForRegex( encodeURIComponent( this.wiki ) ) + ".*?" +<br />
escapeForRegex( encodeURIComponent( this.page ) ) ); break;<br />
case 2: toFind = quoted( escapeForJsString( this.url ) ); break;<br />
}<br />
var lineNums = [], lines = targetWikitext.split( "\n" );<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( toFind.test( lines[i] ) ) {<br />
lineNums.push( i );<br />
}<br />
}<br />
return lineNums;<br />
}<br />
<br />
/**<br />
* Uninstalls the given import. That is, delete all lines from the<br />
* target page that import the specified script.<br />
*/<br />
Import.prototype.uninstall = function () {<br />
var that = this;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitext = wikitext.split( "\n" ).filter( function ( _, idx ) {<br />
return lineNums.indexOf( idx ) < 0;<br />
} ).join( "\n" );<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: STRINGS.uninstallSummary.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT,<br />
text: newWikitext<br />
} );<br />
} );<br />
}<br />
<br />
/**<br />
* Sets whether the given import is disabled, based on the provided<br />
* boolean value.<br />
*/<br />
Import.prototype.setDisabled = function ( disabled ) {<br />
var that = this;<br />
this.disabled = disabled;<br />
return getWikitext( getFullTarget( this.target ) ).then( function ( wikitext ) {<br />
var lineNums = that.getLineNums( wikitext ),<br />
newWikitextLines = wikitext.split( "\n" );<br />
<br />
if( disabled ) {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) != 0 ) {<br />
newWikitextLines[lineNum] = "//" + newWikitextLines[lineNum].trim();<br />
}<br />
} );<br />
} else {<br />
lineNums.forEach( function ( lineNum ) {<br />
if( newWikitextLines[lineNum].trim().indexOf( "//" ) == 0 ) {<br />
newWikitextLines[lineNum] = newWikitextLines[lineNum].replace( /^\s*\/\/\s*/, "" );<br />
}<br />
} );<br />
}<br />
<br />
var summary = ( disabled ? STRINGS.disableSummary : STRINGS.enableSummary )<br />
.replace( "$1", that.getDescription( /* useWikitext */ true ) ) + ADVERT;<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( that.target ),<br />
summary: summary,<br />
text: newWikitextLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
Import.prototype.toggleDisabled = function () {<br />
this.disabled = !this.disabled;<br />
return this.setDisabled( this.disabled );<br />
}<br />
<br />
/**<br />
* Move this import to another file.<br />
*/<br />
Import.prototype.move = function ( newTarget ) {<br />
if( this.target === newTarget ) return;<br />
var old = new Import( this.page, this.wiki, this.url, this.target, this.disabled );<br />
this.target = newTarget;<br />
return $.when( old.uninstall(), this.install() );<br />
}<br />
<br />
function getAllTargetWikitexts() {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
titles: SKINS.map( getFullTarget ).join( "|" )<br />
}<br />
).then( function ( data ) {<br />
if( data && data.query && data.query.pages ) {<br />
var result = {};<br />
prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
Object.values( data.query.pages ).forEach( function ( moreData ) {<br />
var nameWithoutExtension = new mw.Title( moreData.title ).getNameText();<br />
var targetName = nameWithoutExtension.substring( nameWithoutExtension.indexOf( "/" ) + 1 );<br />
result[targetName] = moreData.revisions ? moreData.revisions[0].slots.main["*"] : null;<br />
} );<br />
return result;<br />
}<br />
} );<br />
}<br />
<br />
function buildImportList() {<br />
return getAllTargetWikitexts().then( function ( wikitexts ) {<br />
Object.keys( wikitexts ).forEach( function ( targetName ) {<br />
var targetImports = [];<br />
if( wikitexts[ targetName ] ) {<br />
var lines = wikitexts[ targetName ].split( "\n" );<br />
var currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], targetName ) ) {<br />
targetImports.push( currImport );<br />
scriptCount++;<br />
if( currImport.type === 0 ) {<br />
if( !localScriptsByName[ currImport.page ] )<br />
localScriptsByName[ currImport.page ] = [];<br />
localScriptsByName[ currImport.page ].push( currImport.target );<br />
}<br />
}<br />
}<br />
}<br />
imports[ targetName ] = targetImports;<br />
} );<br />
} );<br />
}<br />
<br />
<br />
/*<br />
* "Normalizes" (standardizes the format of) lines in the given<br />
* config page.<br />
*/<br />
function normalize( target ) {<br />
return getWikitext( getFullTarget( target ) ).then( function ( wikitext ) {<br />
var lines = wikitext.split( "\n" ),<br />
newLines = Array( lines.length ),<br />
currImport;<br />
for( var i = 0; i < lines.length; i++ ) {<br />
if( currImport = Import.fromJs( lines[i], target ) ) {<br />
newLines[i] = currImport.toJs();<br />
} else {<br />
newLines[i] = lines[i];<br />
}<br />
}<br />
return api.postWithEditToken( {<br />
action: "edit",<br />
title: getFullTarget( target ),<br />
summary: STRINGS.normalizeSummary,<br />
text: newLines.join( "\n" )<br />
} );<br />
} );<br />
}<br />
<br />
function conditionalReload( openPanel ) {<br />
if( window.scriptInstallerAutoReload ) {<br />
if( openPanel ) document.cookie = "open_script_installer=yes";<br />
window.location.reload( true );<br />
}<br />
}<br />
<br />
/********************************************<br />
*<br />
* UI code<br />
*<br />
********************************************/<br />
function makePanel() {<br />
var list = $( "<div>" ).attr( "id", "script-installer-panel" )<br />
.append( $( "<header>" ).text( STRINGS.panelHeader ) );<br />
var container = $( "<div>" ).addClass( "container" ).appendTo( list );<br />
<br />
// Container for checkboxes<br />
container.append( $( "<div>" )<br />
.attr( "class", "checkbox-container" )<br />
.append(<br />
$( "<input>" )<br />
.attr( { "id": "siNormalize", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".normalize-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siNormalize" )<br />
.text( STRINGS.showNormalizeLinks ),<br />
$( "<input>" )<br />
.attr( { "id": "siMove", "type": "checkbox" } )<br />
.click( function () {<br />
$( ".move-wrapper" ).toggle( 0 )<br />
} ),<br />
$( "<label>" )<br />
.attr( "for", "siMove" )<br />
.text( STRINGS.showMoveLinks ) ) );<br />
if( scriptCount > NUM_SCRIPTS_FOR_SEARCH ) {<br />
container.append( $( "<div>" )<br />
.attr( "class", "filter-container" )<br />
.append(<br />
$( "<label>" )<br />
.attr( "for", "siQuickFilter" )<br />
.text( STRINGS.quickFilter ),<br />
$( "<input>" )<br />
.attr( { "id": "siQuickFilter", "type": "text" } )<br />
.on( "input", function () {<br />
var filterString = $( this ).val();<br />
if( filterString ) {<br />
var sel = "#script-installer-panel li[name*='" +<br />
$.escapeSelector( $( this ).val() ) + "']";<br />
$( "#script-installer-panel li.script" ).toggle( false );<br />
$( sel ).toggle( true );<br />
} else {<br />
$( "#script-installer-panel li.script" ).toggle( true );<br />
}<br />
} )<br />
) );<br />
<br />
// Now, get the checkboxes out of the way<br />
container.find( ".checkbox-container" )<br />
.css( "float", "right" );<br />
}<br />
$.each( imports, function ( targetName, targetImports ) {<br />
var fmtTargetName = ( targetName === "common"<br />
? "common (applies to all skins)"<br />
: targetName );<br />
if( targetImports.length ) {<br />
container.append(<br />
$( "<h2>" ).append(<br />
fmtTargetName,<br />
$( "<span>" )<br />
.addClass( "normalize-wrapper" )<br />
.append( <br />
" (",<br />
$( "<a>" )<br />
.text( "normalize" )<br />
.click( function () {<br />
normalize( targetName ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
")" )<br />
.hide() ),<br />
$( "<ul>" ).append(<br />
targetImports.map( function ( anImport ) {<br />
return $( "<li>" )<br />
.addClass( "script" )<br />
.attr( "name", anImport.getDescription() )<br />
.append(<br />
$( "<a>" )<br />
.text( anImport.getDescription() )<br />
.addClass( "script" )<br />
.attr( "href", anImport.getHumanUrl() ),<br />
" (",<br />
$( "<a>" )<br />
.text( STRINGS.uninstallLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.uninstallProgressMsg );<br />
anImport.uninstall().done( function () {<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
" | ",<br />
$( "<a>" )<br />
.text( anImport.disabled ? STRINGS.enableLinkText : STRINGS.disableLinkText )<br />
.click( function () {<br />
$( this ).text( anImport.disabled ? STRINGS.enableProgressMsg : STRINGS.disableProgressMsg );<br />
anImport.toggleDisabled().done( function () {<br />
$( this ).toggleClass( "disabled" );<br />
conditionalReload( true );<br />
} );<br />
} ),<br />
$( "<span>" )<br />
.addClass( "move-wrapper" )<br />
.append(<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.moveLinkText )<br />
.click( function () {<br />
var dest = null;<br />
var PROMPT = STRINGS.movePrompt + " " + SKINS.join( ", " );<br />
do {<br />
dest = ( window.prompt( PROMPT ) || "" ).toLowerCase();<br />
} while( dest && SKINS.indexOf( dest ) < 0 )<br />
if( !dest ) return;<br />
$( this ).text( STRINGS.moveProgressMsg );<br />
anImport.move( dest ).done( function () {<br />
conditionalReload( true );<br />
} );<br />
} )<br />
)<br />
.hide(),<br />
")" )<br />
.toggleClass( "disabled", anImport.disabled );<br />
} ) ) );<br />
}<br />
} );<br />
return list;<br />
}<br />
<br />
function buildCurrentPageInstallElement() {<br />
var addingInstallLink = false; // will we be adding a legitimate install link?<br />
var installElement = $( "<span>" ); // only used if addingInstallLink is set to true<br />
<br />
var namespaceNumber = mw.config.get( "wgNamespaceNumber" );<br />
var pageName = mw.config.get( "wgPageName" );<br />
<br />
// Namespace 2 is User<br />
if( namespaceNumber === 2 &&<br />
pageName.indexOf( "/" ) > 0 ) {<br />
var contentModel = mw.config.get( "wgPageContentModel" );<br />
if( contentModel === "javascript" ) {<br />
var prefixLength = mw.config.get( "wgUserName" ).length + 6;<br />
if( pageName.indexOf( USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) ) === 0 ) {<br />
var skinIndex = SKINS.indexOf( pageName.substring( prefixLength ).slice( 0, -3 ) );<br />
if( skinIndex >= 0 ) {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall )<br />
.attr( "title", STRINGS.cannotInstallSkin );<br />
}<br />
}<br />
addingInstallLink = true;<br />
} else {<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " (" + STRINGS.notJavaScript + ")" )<br />
.attr( "title", STRINGS.cannotInstallContentModel.replace( "$1", contentModel ) );<br />
}<br />
}<br />
<br />
// Namespace 8 is MediaWiki<br />
if( namespaceNumber === 8 ) {<br />
return $( "<a>" ).text( STRINGS.installViaPreferences )<br />
.attr( "href", mw.util.getUrl( "Special:Preferences" ) + "#mw-prefsection-gadgets" );<br />
}<br />
<br />
var editRestriction = mw.config.get( "wgRestrictionEdit" ) || [];<br />
if( ( namespaceNumber !== 2 && namespaceNumber !== 8 ) &&<br />
( editRestriction.indexOf( "sysop" ) >= 0 ||<br />
editRestriction.indexOf( "editprotected" ) >= 0 ) ) {<br />
installElement.append( " ",<br />
$( "<abbr>" ).append(<br />
$( "<img>" ).attr( "src", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Achtung-yellow.svg/20px-Achtung-yellow.svg.png" ).addClass( "warning" ),<br />
STRINGS.insecure )<br />
.attr( "title", STRINGS.tempWarning ) );<br />
addingInstallLink = true;<br />
}<br />
<br />
if( addingInstallLink ) {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
installElement.prepend( $( "<a>" )<br />
.attr( "id", "script-installer-main-install" )<br />
.text( localScriptsByName[ fixedPageName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( fixedPageName ) ) );<br />
<br />
// If the script is installed but disabled, allow the user to enable it<br />
var allScriptsInTarget = imports[ localScriptsByName[ fixedPageName ] ];<br />
var importObj = allScriptsInTarget && allScriptsInTarget.find( function ( anImport ) { return anImport.page === fixedPageName; } );<br />
if( importObj && importObj.disabled ) {<br />
installElement.append( " | ",<br />
$( "<a>" )<br />
.attr( "id", "script-installer-main-enable" )<br />
.text( STRINGS.enableLinkText )<br />
.click( function () {<br />
$( this ).text( STRINGS.enableProgressMsg );<br />
importObj.setDisabled( false ).done( function () {<br />
conditionalReload( false );<br />
} );<br />
} ) );<br />
}<br />
return installElement;<br />
}<br />
<br />
return $( "<abbr>" ).text( STRINGS.cannotInstall + " " + STRINGS.insecure )<br />
.attr( "title", STRINGS.badPageError );<br />
}<br />
<br />
function showUi() {<br />
var fixedPageName = mw.config.get( "wgPageName" ).replace( /_/g, " " );<br />
$( "#firstHeading" ).append( $( "<span>" )<br />
.attr( "id", "script-installer-top-container" )<br />
.append(<br />
buildCurrentPageInstallElement(),<br />
" | ",<br />
$( "<a>" )<br />
.text( STRINGS.manageUserScripts ).click( function () {<br />
if( !document.getElementById( "script-installer-panel" ) ) {<br />
$( "#mw-content-text" ).before( makePanel() );<br />
} else {<br />
$( "#script-installer-panel" ).remove();<br />
}<br />
} ) ) );<br />
}<br />
<br />
function attachInstallLinks() {<br />
// At the end of each {{Userscript}} transclusion, there is<br />
// <span id='User:Foo/Bar.js' class='scriptInstallerLink'></span><br />
$( "span.scriptInstallerLink" ).each( function () {<br />
var scriptName = this.id;<br />
$( this ).append( " | ", $( "<a>" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) );<br />
} );<br />
<br />
$( "table.infobox-user-script" ).each( function () {<br />
var scriptName = $( this ).find( "th:contains('Source')" ).next().text() ||<br />
mw.config.get( "wgPageName" );<br />
scriptName = /user:.+?\/.+?.js/i.exec( scriptName )[0];<br />
$( this ).children( "tbody" ).append( $( "<tr>" ).append( $( "<td>" )<br />
.attr( "colspan", "2" )<br />
.addClass( "script-installer-ibx" )<br />
.append( $( "<button>" )<br />
.addClass( "mw-ui-button mw-ui-progressive mw-ui-big" )<br />
.text( localScriptsByName[ scriptName ] ? STRINGS.uninstallLinkText : STRINGS.installLinkText )<br />
.click( makeLocalInstallClickHandler( scriptName ) ) ) ) );<br />
} );<br />
}<br />
<br />
function makeLocalInstallClickHandler( scriptName ) {<br />
return function () {<br />
var $this = $( this );<br />
if( $this.text() === STRINGS.installLinkText ) {<br />
var bigSecurityWarning = STRINGS.bigSecurityWarning;<br />
if( scriptName.indexOf( '/' ) >= 0 ) {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', STRINGS.securityWarningSection.replace( '$1', scriptName.substring( 0, scriptName.indexOf( '/' ) ) ) );<br />
} else {<br />
bigSecurityWarning = bigSecurityWarning.replace( '$1', '' );<br />
}<br />
var okay = window.sciNoConfirm || window.confirm( bigSecurityWarning );<br />
if( okay ) {<br />
$( this ).text( STRINGS.installProgressMsg )<br />
Import.ofLocal( scriptName, window.scriptInstallerInstallTarget ).install().done( function () {<br />
$( this ).text( STRINGS.uninstallLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
} else {<br />
$( this ).text( STRINGS.uninstallProgressMsg )<br />
var uninstalls = uniques( localScriptsByName[ scriptName ] )<br />
.map( function ( target ) { return Import.ofLocal( scriptName, target ).uninstall(); } )<br />
$.when.apply( $, uninstalls ).then( function () {<br />
$( this ).text( STRINGS.installLinkText );<br />
conditionalReload( false );<br />
}.bind( this ) );<br />
}<br />
};<br />
}<br />
<br />
function addCss() {<br />
mw.util.addCSS(<br />
"#script-installer-panel li.disabled a.script { "+<br />
"text-decoration: line-through; font-style: italic; }"+<br />
"#script-installer-panel { width:60%; border:solid lightgray 1px; "+<br />
"padding:0; margin-left: auto; "+<br />
"margin-right: auto; margin-bottom: 15px; overflow: auto; "+<br />
"box-shadow: 5px 5px 5px #999; background-color: #fff; z-index:50; }"+<br />
"#script-installer-panel header { background-color:#CAE1FF; display:block;"+<br />
"padding:5px; font-size:1.1em; font-weight:bold; text-align:left; }"+<br />
"#script-installer-panel .checkbox-container input { margin-left: 1.5em; }"+<br />
"#script-installer-panel .filter-container { margin-bottom: -0.75em; }"+<br />
"#script-installer-panel .filter-container label { margin-right: 0.35em; }"+<br />
"#script-installer-panel .container { padding: 0.75em; }"+<br />
"#script-installer-panel .container h2 { margin-top: 0.75em; }"+<br />
"#script-installer-panel a { cursor: pointer; }"+<br />
"#script-installer-main-install { font-weight: bold; }"+<br />
"#script-installer-top-container { bottom: 5px; font-size: 70%; margin-left: 1em }"+<br />
"body.skin-modern #script-installer-top-container a { color: inherit; cursor: pointer }"+<br />
"body.skin-timeless #script-installer-top-container a,body.skin-cologneblue #script-installer-top-container a { cursor: pointer }"+<br />
"#script-installer-top-container img.warning { position: relative; top: -2px; margin-right: 3px }"+<br />
"td.script-installer-ibx { text-align: center }"<br />
);<br />
}<br />
<br />
/********************************************<br />
*<br />
* Utility functions<br />
*<br />
********************************************/<br />
<br />
/**<br />
* Gets the wikitext of a page with the given title (namespace required).<br />
*/<br />
function getWikitext( title ) {<br />
return $.getJSON(<br />
mw.util.wikiScript( "api" ),<br />
{<br />
format: "json",<br />
action: "query",<br />
prop: "revisions",<br />
rvprop: "content",<br />
rvslots: "main",<br />
rvlimit: 1,<br />
titles: title<br />
}<br />
).then( function ( data ) {<br />
var pageId = Object.keys( data.query.pages )[0];<br />
if( data.query.pages[pageId].revisions ) {<br />
return data.query.pages[pageId].revisions[0].slots.main["*"];<br />
}<br />
return "";<br />
} );<br />
}<br />
<br />
function escapeForRegex( s ) {<br />
return s.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );<br />
}<br />
<br />
/**<br />
* Escape a string for use in a JavaScript string literal.<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsString( s ) {<br />
return s.replace( /["'\\\n\r\u2028\u2029]/g, function ( character ) {<br />
// Escape all characters not included in SingleStringCharacters and<br />
// DoubleStringCharacters on<br />
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4<br />
switch ( character ) {<br />
case '"':<br />
case "'":<br />
case '\\':<br />
return '\\' + character;<br />
// Four possible LineTerminator characters need to be escaped:<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Escape a string for use in an inline JavaScript comment (comments that<br />
* start with two slashes "//").<br />
* This function is adapted from<br />
* https://github.com/joliss/js-string-escape/blob/6887a69003555edf5c6caaa75f2592228558c595/index.js<br />
* (released under the MIT licence).<br />
*/<br />
function escapeForJsComment( s ) {<br />
return s.replace( /[\n\r\u2028\u2029]/g, function ( character ) {<br />
switch ( character ) {<br />
// Escape possible LineTerminator characters<br />
case '\n':<br />
return '\\n';<br />
case '\r':<br />
return '\\r';<br />
case '\u2028':<br />
return '\\u2028';<br />
case '\u2029':<br />
return '\\u2029';<br />
}<br />
} );<br />
}<br />
<br />
/**<br />
* Unescape a JavaScript string literal.<br />
*<br />
* This is the inverse of escapeForJsString.<br />
*/<br />
function unescapeForJsString( s ) {<br />
return s.replace( /\\"|\\'|\\\\|\\n|\\r|\\u2028|\\u2029/g, function ( substring ) {<br />
switch ( substring ) {<br />
case '\\"':<br />
return '"';<br />
case "\\'":<br />
return "'";<br />
case "\\\\":<br />
return "\\";<br />
case "\\r":<br />
return "\r";<br />
case "\\n":<br />
return "\n";<br />
case "\\u2028":<br />
return "\u2028";<br />
case "\\u2029":<br />
return "\u2029";<br />
}<br />
} );<br />
}<br />
<br />
function getFullTarget ( target ) {<br />
return USER_NAMESPACE_NAME + ":" + mw.config.get( "wgUserName" ) + "/" + <br />
target + ".js";<br />
}<br />
<br />
// From https://stackoverflow.com/a/10192255<br />
function uniques( array ){<br />
return array.filter( function( el, index, arr ) {<br />
return index === arr.indexOf( el );<br />
});<br />
}<br />
<br />
if( window.scriptInstallerAutoReload === undefined ) {<br />
window.scriptInstallerAutoReload = true;<br />
}<br />
<br />
if( window.scriptInstallerInstallTarget === undefined ) {<br />
window.scriptInstallerInstallTarget = "common"; // by default, install things to the user's common.js<br />
}<br />
<br />
var jsPage = mw.config.get( "wgPageName" ).slice( -3 ) === ".js" ||<br />
mw.config.get( "wgPageContentModel" ) === "javascript";<br />
$.when(<br />
$.ready,<br />
mw.loader.using( [ "mediawiki.api", "mediawiki.util" ] )<br />
).then( function () {<br />
api = new mw.Api();<br />
addCss();<br />
buildImportList().then( function () {<br />
attachInstallLinks();<br />
if( jsPage ) showUi();<br />
<br />
// Auto-open the panel if we set the cookie to do so (see `conditionalReload()`)<br />
if( document.cookie.indexOf( "open_script_installer=yes" ) >= 0 ) {<br />
document.cookie = "open_script_installer=; expires=Thu, 01 Jan 1970 00:00:01 GMT";<br />
$( "#script-installer-top-container a:contains('Manage')" ).trigger( "click" );<br />
}<br />
} );<br />
} );<br />
} )();<br />
</nowiki></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25800
User:Syunsyunminmin/common.js
2023-01-27T14:45:23Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.load("//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle-dev.js&action=raw&ctype=text/javascript");</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25792
User:Syunsyunminmin/common.js
2023-01-17T10:29:39Z
<p>Syunsyunminmin: Syunsyunminmin (会話)さんによる第25619版を復元: (Twinkle使用)</p>
<hr />
<div><br />
importScript('User:Syunsyunminmin/Twinkle-dev.js'); // Backlink: [[User:Syunsyunminmin/Twinkle-dev.js]]</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/common.js&diff=25791
User:Syunsyunminmin/common.js
2023-01-17T10:28:35Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.load("//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/script/AFDstarter.js&action=raw&ctype=text/javascript");<br />
importScript('User:Syunsyunminmin/Twinkle-dev.js'); // Backlink: [[User:Syunsyunminmin/Twinkle-dev.js]]</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/Twinkle-dev.js&diff=25753
User:Syunsyunminmin/Twinkle-dev.js
2023-01-12T14:08:32Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.using( [ 'mediawiki.api', 'mediawiki.util', 'jquery.ui' ] )<br />
.then(<br />
function () {<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/select2.min.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/morebits/morebits.css&action=raw&ctype=text/css', 'text/css');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/Twinkle-pagestyles.css&action=raw&ctype=text/css', 'text/css');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/Twinkle.css&action=raw&ctype=text/css', 'text/css');<br />
mw.loader.getScript('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/morebits/morebits.js&action=raw&ctype=text/javascript')<br />
.then (<br />
function () {<br />
mw.loader.getScript('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/Twinkle.js&action=raw&ctype=text/javascript')<br />
.then (<br />
function () {<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleconfig.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlyshared.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklespeedy.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklediff.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleunlink.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklefluff.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklewarn.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleprotect.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklearv.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlytag.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkledeprod.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklebatchdelete.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklebatchprotect.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklebatchundelete.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleblock.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlywelcome.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlytalkback.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleprod.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('http://localhost/scripts/twinklexfd.js');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleimage.js&action=raw&ctype=text/javascript');<br />
}<br />
);<br />
}<br />
);<br />
});</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=User:Syunsyunminmin/Twinkle-dev.js&diff=25752
User:Syunsyunminmin/Twinkle-dev.js
2023-01-12T14:07:52Z
<p>Syunsyunminmin: </p>
<hr />
<div>mw.loader.using( [ 'mediawiki.api', 'mediawiki.util', 'jquery.ui' ] )<br />
.then(<br />
function () {<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/select2.min.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/morebits/morebits.css&action=raw&ctype=text/css', 'text/css');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/Twinkle-pagestyles.css&action=raw&ctype=text/css', 'text/css');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/Twinkle.css&action=raw&ctype=text/css', 'text/css');<br />
mw.loader.getScript('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/morebits/morebits.js&action=raw&ctype=text/javascript')<br />
.then (<br />
function () {<br />
mw.loader.getScript('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/Twinkle.js&action=raw&ctype=text/javascript')<br />
.then (<br />
function () {<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleconfig.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlyshared.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklespeedy.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklediff.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleunlink.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklefluff.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklewarn.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleprotect.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklearv.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlytag.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkledeprod.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklebatchdelete.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklebatchprotect.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinklebatchundelete.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleblock.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlywelcome.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/friendlytalkback.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleprod.js&action=raw&ctype=text/javascript');<br />
mw.loader.load('//testwiki.wiki/index.php?title=User:Syunsyunminmin/Twinkle/twinklexfd.js&action=raw&ctype=text/javascript');<br />
//mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Syunsyunminmin/Twinkle/twinkleimage.js&action=raw&ctype=text/javascript');<br />
}<br />
);<br />
}<br />
);<br />
});</div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=Sandbox&diff=25750
Sandbox
2023-01-11T16:11:25Z
<p>Syunsyunminmin: Reverted edits by Syunsyunminmin (talk) to last revision by Q8j</p>
<hr />
<div>{{Test Wiki Sandbox}}<br />
<!-- Please leave this line above alone and do not remove the sandbox header. This notes all users that this page is a sandbox. Removing it is unhelpful. --><br />
<!-- Welcome to the Sandbox! Freely test your changes below this line, and do not change anything above --></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=Sandbox&diff=25749
Sandbox
2023-01-11T16:11:08Z
<p>Syunsyunminmin: "File:Wintzer Heinrich 1942.jpgファイルの使用をコメントアウトする: test (Twinkle使用)</p>
<hr />
<div>{{Test Wiki Sandbox}}<br />
<!-- Please leave this line above alone and do not remove the sandbox header. This notes all users that this page is a sandbox. Removing it is unhelpful. --><br />
<!-- Welcome to the Sandbox! Freely test your changes below this line, and do not change anything above --><br />
[[testing]]<br />
<br />
<br />
<br />
{{Testtemp<br />
|画像 = Testimage.jpg<br />
}}<br />
<br />
<gallery><br />
</gallery></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=Sandbox&diff=25746
Sandbox
2023-01-11T16:09:12Z
<p>Syunsyunminmin: "File:Testimage.jpgファイルの使用をコメントアウトする: test (Twinkle使用)</p>
<hr />
<div>{{Test Wiki Sandbox}}<br />
<!-- Please leave this line above alone and do not remove the sandbox header. This notes all users that this page is a sandbox. Removing it is unhelpful. --><br />
<!-- Welcome to the Sandbox! Freely test your changes below this line, and do not change anything above --><br />
[[testing]]<br />
<br />
<br />
<br />
{{Testtemp<br />
|画像 = Testimage.jpg<br />
}}<br />
<br />
<gallery><br />
Image:Wintzer_Heinrich_1942.jpg<br />
</gallery></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=Sandbox&diff=25745
Sandbox
2023-01-11T16:09:03Z
<p>Syunsyunminmin: Syunsyunminmin (会話)さんによる第25742版を復元: (Twinkle使用)</p>
<hr />
<div>{{Test Wiki Sandbox}}<br />
<!-- Please leave this line above alone and do not remove the sandbox header. This notes all users that this page is a sandbox. Removing it is unhelpful. --><br />
<!-- Welcome to the Sandbox! Freely test your changes below this line, and do not change anything above --><br />
[[testing]]<br />
<br />
[[File:testimage.jpg]]<br />
<br />
[[File:testimage.jpg|test]]<br />
<br />
{{Testtemp<br />
|画像 = Testimage.jpg<br />
}}<br />
<br />
<gallery><br />
Image:testimage.jpg|てすとてすと<br />
Image:Wintzer_Heinrich_1942.jpg<br />
</gallery></div>
Syunsyunminmin
https://testwiki.wiki/index.php?title=Sandbox&diff=25743
Sandbox
2023-01-11T16:07:21Z
<p>Syunsyunminmin: "File:Testimage.jpgファイルの使用をコメントアウトする: test (Twinkle使用)</p>
<hr />
<div>{{Test Wiki Sandbox}}<br />
<!-- Please leave this line above alone and do not remove the sandbox header. This notes all users that this page is a sandbox. Removing it is unhelpful. --><br />
<!-- Welcome to the Sandbox! Freely test your changes below this line, and do not change anything above --><br />
[[testing]]<br />
<br />
<br />
<br />
<br />
<br />
{{Testtemp<br />
|画像 = Testimage.jpg<br />
}}<br />
<br />
<gallery><br />
Image:Wintzer_Heinrich_1942.jpg<br />
</gallery></div>
Syunsyunminmin