查看“MediaWiki:Gadget-SettingsManager.js”的源代码
←
MediaWiki:Gadget-SettingsManager.js
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
此页面为本wiki上的软件提供界面文本,并受到保护以防止滥用。 如欲修改所有wiki的翻译,请访问
translatewiki.net
上的MediaWiki本地化项目。
您无权编辑此JavaScript页面,因为编辑此页面可能会影响所有访问者。
您可以查看和复制此页面的源代码。
/** * Localization required. [[User:Liangent]] 2013年4月8日 (一) 10:20 (UTC) * * [[:commons:MediaWiki:Gadget-SettingsManager.js]] * Managing user preferences of scripts * Managing gadgets and gadget preferences * * Use it for good, not for evil. * * @author Rillke, 2012 * @license GPL v.3 * <nowiki> */ // List the global variables for jsHint-Validation. // Scheme: globalVariable:allowOverwriting[, globalVariable:allowOverwriting][, globalVariable:allowOverwriting] /*global jQuery:false, mediaWiki:false*/ // Set jsHint-options. /*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:false, browser:true*/ ( function ( $, mw, undefined ) { "use strict"; /** * Refresh edit token * * @example * refreshToken( function() { doGoodStuff.retry(); } ); * * @param cb {Function} Callback function. The first argument supplied is whether the operation succeeded. * @context {closure} private function * @return {Object} a jQuery deferred-object-queue */ var refreshToken = function(cb) { var mwa = new mw.Api(), apiDef = mwa.get( { action: 'query', meta: 'tokens', type: 'csrf' } ); apiDef.done(function(result) { if (!result.query || !result.query.tokens) return cb( false, 'wrong-response' ); mw.user.tokens.set( 'csrfToken', result.query.tokens.csrftoken ); cb( true ); }); apiDef.fail(function(code, result) { if (!result.query || !result.query.tokens) return cb( false, code ); }); return apiDef; }; var firstItem = function(o) { for (var i in o) { if (o.hasOwnProperty( i )) { return o[i]; } } }; var valByString = function(identifier) { var arr = identifier.split( '.' ), lenArr = arr.length, i, elemArr, objCurrent = window; for (i = 0; i < lenArr; i++) { elemArr = arr[i]; objCurrent = objCurrent[elemArr]; } return objCurrent; }; var sm = { version: '0.1.0.1', errorPrefix: "SettingsWizard encountered a problem. We regret the inconvenience. ", /** * Constructor-method. Returns an option-object you should perform the actions on. * * @example * var opt = mw.libs.settingsManager.option( { optionName: 'CatALotOptions', value: { watchCopy: false, watchRemove: false } } ); * // Save the options we've set before and make the script triggering events on the document, you can listen to * opt.save( $(document), 'CatALotSaveProgress' ); * // or, use the deferred-object returned: * opt.save().done(function(msg, status, jsFile) { * alert( msg ); * }).status(function(msg, status, jsFile) { * console.log( 'settings progress>' + msg ); * }); * * @param specsIn {Object} specifications passed in. List of defaults (cf. specs) follows. * @context {mw.libs.settingsManager} * @return {Object} option-object you can use for performing actions on. */ option: function(specsIn) { // List of defaults: var specs = { // The global name the option will be saved under // This can be also something like "mw.settingsOfToolX" optionName: '', // The position where to save them. By default options are // saved at the user's common.js or <skin>.js (e.g. vector.js) // specify other locations like "settingsOfToolX" --> "User:Example/prefs/settingsOfToolX.js" saveAt: false, // By default the option is not enclosed in a comment-block // Comment-blocks are recommended for larger configurations // specifies the signature that will be used for the enclosing comments // blockSettingsOfToolX --> //blockSettingsOfToolX/////////////////////// // (ignored when saveAt is set) encloseSignature: false, // Specify additional block comments added below the signature // Should be something that explains what the following JSON does or is good for // Recommended line-length for consistent alignment: 48 chars encloseBlock: false, // If no own location for saving the option is used (options that must be // available while loading the script should not be saved to a separate file while // complex options should), if the RegExp will have a match on either the common.js // or the <skin>.js, and this option is not saved yet to another .js, the option // will be saved to this js-file. In case the option is not saved yet and there is // no RegExp supplied or it did not match, the option will be saved to the larger // JavaScript triggerSaveAt: false, // Should the new content be insered in front of the match by triggerSaveAt // If none of the following options is specified, the new content // will be appended to the script insertBeforeTrigger: false, insertAfterTrigger: false, replaceTrigger: false, // Finally the option's value. Objects are possible. They will be automatically // transformed into a JSON-string value: undefined, // Edit summary to use while saving the JavaScript editSummary: "" }; if (!specsIn) throw new Error(sm.errorPrefix + "Data to save or retrieve was not supplied by the script using SettingsWizard."); if (!specsIn.optionName && !specsIn.saveAt) throw new Error(sm.errorPrefix + "The options\'s name was not supplied by the script using SettingsWizard."); $.extend( true, specs, specsIn ); // Prepare variables we need later var nsUser = mw.config.get('wgFormattedNamespaces')[2], skin = mw.config.get('skin'), user = mw.config.get('wgUserName'), skinJS = [nsUser, ':', user, '/', skin, '.js'].join(''), commonJS = [nsUser, ':', user, '/','common', '.js'].join(''); // Event-handler system var $el, evt, jsFiles, process, $progress = new $.Deferred(), customJS; var triggerEvt = function(any) { return (evt && $el && $el instanceof jQuery && $el.triggerHandler( evt, Array.prototype.slice.call( arguments, 0 ) )); }; process = { updateVars: function() { // Reset variables that could be polluted jsFiles = []; $progress = new $.Deferred(); customJS = [nsUser, ':', user, '/prefs/', specs.saveAt, '.js'].join(''); }, start: function() { this.updateVars(); // Subscribe to any event: We want to know everything :-) $progress.then( triggerEvt, triggerEvt, triggerEvt ); // Always async setTimeout( $.proxy( this.getScripts, this ), 1 ); return $progress; }, getScripts: function() { var i, len; $progress.notify( "Preparing", 1 ); // First, we need something to work on/ edit token, etc. - request the JavaScript(s) if (specs.saveAt) { jsFiles.push( sm.script( customJS ) ); } else { jsFiles.push( sm.script( skinJS ) ); jsFiles.push( sm.script( commonJS ) ); } len = jsFiles.length; for (i = 0; i < len; i++) { var jsFile = jsFiles[i]; jsFile.fetchText( process.gotJS, process.gotJSErr ); $progress.notify( "Requesting " + jsFile.getSource(), Math.round( (i+1)*(9/len) ) + 1, jsFile ); } return $progress; }, gotJS: function(jsFile, r){ jsFile.gotContent = true; var i, len = jsFiles.length, pendings = 0; for (i = 0; i < len; i++) { if (!jsFiles[i].gotContent) { pendings++; } } $progress.notify( "Got " + jsFile.getSource() + '. File length: ' + jsFile.get().length + ' characters.' , Math.round( (len - pendings)*(9/len) ) + 10, jsFile ); if (pendings) return; process.process(); }, gotJSErr: function(jsFile) { $progress.reject( "Failed. Could not retrieve " + jsFile.getSource(), -1, jsFile ); }, getStartBlock: function(sig) { // String concat is sloooow return '//' + sig + new Array(48 - 2 - sig.length + 1).join('/'); }, getEndBlock: function(sig) { return new Array(48 - 2 - 3 - sig.length + 1).join('/') + sig + 'End' + '//'; }, getBlockRegExp: function(sig) { var escSig = process.escapeRE(sig); return new RegExp('\\n?\\n?\\/\\/' + escSig + '(?:.|\\n)*' + escSig + 'End\\/\\/', 'g'); }, escapeRE: function(string) { string = $.escapeRE(string); var specials = ['t', 'n', 'v', '0', 'f']; $.each(specials, function(i, s) { var rx = new RegExp('\\'+s, 'g'); string = string.replace(rx, '\\'+s); }); return string; }, getVariableRegExp: function(varName) { var escVar = process.escapeRE(varName); return { varRE: new RegExp('\\s*(?:var\\s+|window\\.)?' + escVar + '\\s*=.+', 'g'), // Throw a warning if the last char of the line is a "+" , "{", "(" or "," varWarnRE: new RegExp('\\s*(?:var\\s+|window\\.)?' + escVar + '\\s*=.+(?:\\n?\\s*[\\,\\+\\{\\(])\\s*\\n') }; }, process: function() { var JSONVal = $.toJSON( specs.value ), sig = specs.encloseSignature, tsa = specs.triggerSaveAt, opn = specs.optionName, jsFile, i, len = jsFiles.length, plainJSON = !opn && !!jsFile, oldText, newText, hadMatch; if (opn) { // No semicolon for valid JSON! JSONVal = 'window.' + opn + ' = ' + JSONVal + ';'; } if (!plainJSON) JSONVal = ((specs.encloseBlock && ('\n' + specs.encloseBlock)) || '') + JSONVal; if (sig && !plainJSON) JSONVal = process.getStartBlock( sig ) + JSONVal + '\n' + process.getEndBlock( sig ); JSONVal = '\n\n' + JSONVal; // Fine, we've constructed everything we'll need. Now look up where to insert. // Looking for signature if (sig) { var reBl = process.getBlockRegExp( sig ); for (i = 0; i < len; i++) { jsFile = jsFiles[i]; oldText = jsFile.get(); newText = oldText.replace( reBl, JSONVal ); if (reBl.test( oldText )) { $progress.notify( "Replacing text enclosed by signature " + jsFile.getSource(), 25, jsFile ); process.save( jsFile.set( newText ) ); hadMatch = true; } } } if (hadMatch) return; // Looking for variable-name if (opn) { var vre = process.getVariableRegExp( opn ), warnFile; for (i = 0; i < len; i++) { jsFile = jsFiles[i]; oldText = jsFile.get(); if (vre.varWarnRE.test(oldText)) { // WARNING!!! $progress.notify( "Unable to remove config from " + jsFile.getSource(), -2, jsFile ); warnFile = jsFile; } else { newText = oldText.replace( vre.varRE, JSONVal ); if (vre.varRE.test( oldText )) { $progress.notify( "Replacing variable " + jsFile.getSource(), 25, jsFile ); process.save( jsFile.set( newText ) ); hadMatch = true; } } // Only append in case of warning if it was not added to another file if (warnFile && !hadMatch) { $progress.notify( "Appending variable after warning to " + jsFile.getSource(), 25, jsFile ); process.save( warnFile.set( oldText + JSONVal ) ); hadMatch = true; } } } if (hadMatch) return; // If it's just JSON, replace the whole thingy if (!opn && specs.saveAt) { jsFile = jsFiles[0]; $progress.notify( "Replacing whole content of " + jsFile.getSource(), 25, jsFile ); process.save( jsFile.set( JSONVal ) ); hadMatch = true; } if (hadMatch) return; // Looking whether supplied RegExp can find something if (tsa) { var searchMatch, triggerLen = 0; for (i = 0; i < len; i++) { jsFile = jsFiles[i]; oldText = jsFile.get(); searchMatch = oldText.search( tsa ); if (-1 !== searchMatch) { if (specs.insertBeforeTrigger) { $progress.notify( "Inserting before pattern in " + jsFile.getSource(), 25, jsFile ); jsFile.set( oldText.slice( 0, searchMatch ) + JSONVal + oldText.slice( searchMatch ) ); } else if (specs.insertAfterTrigger) { triggerLen = oldText.match( tsa )[0].length; $progress.notify( "Inserting after pattern in " + jsFile.getSource(), 25, jsFile ); jsFile.set( oldText.slice( 0, searchMatch + triggerLen ) + JSONVal + oldText.slice( searchMatch + triggerLen ) ); } else if (specs.replaceTrigger) { $progress.notify( "Replacing pattern with new content in " + jsFile.getSource(), 25, jsFile ); jsFile.set( oldText.replace( tsa, JSONVal ) ); } else { $progress.notify( "Found pattern, appending to " + jsFile.getSource(), 25, jsFile ); jsFile.set( oldText + '\n//<nowiki>' + JSONVal + '\n//<\/nowiki>' ); } process.save( jsFile ); hadMatch = true; break; } } } if (hadMatch) return; // Finally compare file size var biggest = { size: 0, jsFile: null }; for (i = 0; i < len; i++) { jsFile = jsFiles[i]; oldText = jsFile.get(); var oldTextLen = oldText.length; if (oldTextLen >= biggest.size) biggest = { size: oldTextLen, jsFile: jsFile }; } $progress.notify( "Appending to bigger file: " + biggest.jsFile.getSource(), 25, biggest.jsFile ); biggest.jsFile.set( biggest.jsFile.get() + '\n//<nowiki>' + JSONVal + '\n//<\/nowiki>' ); process.save( biggest.jsFile ); }, save: function(jsFile) { jsFile.saving = true; $progress.notify( "Saving " + jsFile.getSource(), 30, jsFile ); jsFile.save( process.saved, process.savedErr, "[[MediaWiki:Gadget-SettingsManager.js|SettingsManager]]: " + specs.editSummary ); }, saved: function(jsFile) { var i, len = jsFiles.length, jsf, waitingFor = []; jsFile.saving = false; for (i = 0; i < len; i++) { jsf = jsFiles[i]; if (jsf.saving) { waitingFor.push(jsf.getSource()); } } $progress.notify( "Saved " + jsFile.getSource() + ". Waiting for " + (waitingFor.join(', ') || '-'), Math.round( (len - waitingFor.length)*(20/len) ) + 50, jsFile ); if (waitingFor.length) return; $progress.resolve( "Success!", 100, jsFile ); }, savedErr: function(jsFile, code, errObj) { $progress.reject( "Error saving " + jsFile.getSource() + ". Code is " + code + ".\n", -1, errObj ); } }; return { getSpecs: function() { return specs; }, setSpecs: function(specsIn) { specs = specsIn; return this; }, // Warning: If you specified a different save-position ("saveAt") // and also an optionName, the script has to be fetched and evaluated // We recommend omitting setting "optionName" when using "saveAt" fetchValue: function(cb, errCb) { process.updateVars(); if (specs.saveAt) { var s = sm.script( customJS ); if (specs.optionName) { s.fetchText(function() { s.doEval(); cb( valByString( specs.optionName ) ); }, errCb); } else { s.fetchJSON(function(scriptObj, JSON) { cb( JSON ); }, errCb); } return this; } cb( valByString( specs.optionName ) ); return this; }, getValue: function() { return specs.value; }, setValue: function(val) { specs.value = val; return this; }, save: function($elem, event) { // We won't check whether the value is undefined. This is your task. $el = $elem; evt = event; return process.start(); }, getProgress: function() { return $progress; } }; }, /** * Constructor-method. Returns a script-object you should perform the actions on. * * @example * var commonJS = mw.libs.settingsManager.script( 'User:Example/common.js' ); * commonJS.set( '// empty!' ).setSummary( 'Removing Content' ).save( function() { console.log( 'Successfully removed content from ' + commonJS.getSource() ) } ) * * // Enable a gadget and load it: * mw.libs.settingsManager.gadget( 'Slideshow' ).load().enable(); * * @param source {String} The name of the JavaScript file with namespace. * @context {mw.libs.settingsManager} * @return {Object} script-object you can use for performing actions on. */ script: function(source) { var content, page, summary = "Changing configuration using [[:commons:MediaWiki:Gadget-SettingsManager.js]]", minor = 1, exists, fetch, save; fetch = function() { var mwa = new mw.Api(); return mwa.get( { prop: 'info|revisions', titles: source, rvprop: 'timestamp|content', intoken: 'edit' } ); }; save = function() { var mwa = new mw.Api(), edit = { action: 'edit', title: source, text: 'object' === typeof content ? $.toJSON(content) : content, summary: summary, watchlist: 'nochange', recreate: 1 }; if (minor) edit.minor = 1; if (exists) { edit.basetimestamp = page.revisions[0].timestamp; } else { edit.starttimestamp = page.starttimestamp; } edit.token = page.edittoken; return mwa.post( edit ); }; return { get: function() { return content; }, getSource: function() { return source; }, doEval: function() { /*jshint evil:true */ return eval(content); }, parseJSON: function() { // jquery.json - plugin required return ('string' === typeof content && '' !== content) ? $.secureEvalJSON( content ) : ''; }, // Supplied callback called with a string as second argument fetchText: function(cb, errCb) { var pgs, pg, scriptObj = this; fetch().done( function(result) { pgs = result.query.pages; page = firstItem( pgs ); exists = !!(page.revisions && page.revisions[0]); content = (exists && page.revisions[0]['*']) || ''; cb( scriptObj, content ); } ).fail( function( status, errObj ) { errCb( scriptObj, status, errObj ); } ); return this; }, // Supplied callback called with parsed JSON-data as second argument fetchJSON: function(cb, errCb) { this.fetchText( function(scriptObj, content) { cb( scriptObj, scriptObj.parseJSON() ); }, function(scriptObj, status, errObj) { errCb( scriptObj, status, errObj ); } ); return this; }, set: function(newContent) { content = newContent; return this; }, setMinor: function(newMinor) { minor = !!newMinor; }, setSummary: function(newSummary) { summary = newSummary; }, save: function(cb, errCb, newSummary, newContent, newMinor) { var scriptObj = this; if (newContent !== undefined) content = newContent; if (newSummary !== undefined) summary = newSummary; if (newMinor !== undefined) minor = !!newMinor; save().done( function(result) { cb( scriptObj, result ); } ).fail( function(status, errObj) { errCb( scriptObj, status, errObj ); } ); return this; } }; }, /** * Switch a user preference using Ajax! * * @example * mw.libs.settingsManager.switchPref( 'myOption', 'new value' ); * * @param prefName {String} The name of the preference. * @param prefName {String} The new value the preference should set to. * @param cb {Function} Callback in case of success. * @param cb {Function} Callback in case of an error. * @context {mw.libs.settingsManager} * @return {Object} a jQuery deferred-object-queue. Don't use it for error-handling - Done by this method. */ switchPref: function(prefName, prefVal, cb, errCb) { var mwa = new mw.Api(), args = arguments, apiDef = mwa.post( { action: 'options', token: mw.user.tokens.get('csrfToken'), optionname: prefName, optionvalue: prefVal || 0 } ); // If we changed a preference successfully, update user.options reflecting the change apiDef.done( function() { mw.user.options.set( prefName, prefVal ); } ); if (cb) apiDef.done( cb ); // Catch badtoken and some other common errors apiDef.fail( function(code, result) { switch (code) { case 'badtoken': refreshToken(function (gotANewToken) { if (gotANewToken) return sm.switchPref.apply( sm, Array.prototype.slice.call( args, 0 ) ); } ); // Stop the propagation of return false; case 'http': case 'ok-but-empty': setTimeout( function() { return sm.switchPref.apply( sm, Array.prototype.slice.call(args, 0) ); }, 1000 ); return false; default: return (errCb && errCb(code, result) && false); } } ); return apiDef; }, /** * Constructor-method. Returns an option-object you should perform the actions on. * * @example * var slideshowGadget = mw.libs.settingsManager.gadget( 'Slideshow' ); * if (slideshowGadget.isEnabled()) { slideshowGadget.disable( myCallback ) } * * // Enable a gadget and load it: * mw.libs.settingsManager.gadget( 'Slideshow' ).load().enable(); * * @param gadgetName {Object} The name of the gadget. (Not the script file; without Gadget- prefix or other decoration) * @context {mw.libs.settingsManager} * @return {Object} gadget-object you can use for performing actions on. */ gadget: function(gadgetName) { var optGadget = 'gadget-' + gadgetName, rlGadget = 'ext.gadget.' + gadgetName; return { getName: function() { return gadgetName; }, isDefault: function() { var opt = mw.user.options.get( optGadget ); return ('number' === typeof opt || '' === opt); }, isEnabled: function() { var opt = mw.user.options.get( optGadget ); return !!opt; }, getState: function() { return mw.loader.getState( rlGadget ); }, isLoaded: function() { return ('ready' === this.getState()); }, load: function(cb, errCb) { // Always async if (this.isLoaded && cb) return setTimeout( function() { cb( gadgetName, true ); }, 1 ); mw.loader.using( rlGadget, cb ? function() { cb( gadgetName ); } : undefined, errCb ? function() { errCb( gadgetName ); } : undefined ); return this; }, enable: function(cb, errCb) { // Type wouldn't matter due to URL-encoding but we also want to update // the user.options object sm.switchPref( optGadget, this.isDefault() ? 1 : '1', cb, errCb ); return this; }, disable: function(cb, errCb) { sm.switchPref( optGadget, this.isDefault() ? '' : null, cb, errCb ); return this; } }; } }; mw.libs.settingsManager = sm; // TODO add to gadget-def // mw.loader.load(['jquery.json', 'mediawiki.user', 'user.options']); }( jQuery, mediaWiki )); // </nowiki>
返回
MediaWiki:Gadget-SettingsManager.js
。
导航菜单
个人工具
登录
命名空间
消息
讨论
English
查看
阅读
查看源代码
查看历史
更多
搜索
导航
首页
最近更改
随机页面
学习园地
新人须知
教程整合
论坛百科
SS分区介绍
论坛设定
论坛文化
传奇殿堂
SS大事年表
SS大学图书馆
维基事务
基本规则
新手指南——技术
新手指南——内容
wiki语法手册
规范全集
工具
链入页面
相关更改
特殊页面
页面信息